Merge branch 'master' into vagrant_appmgr
diff --git a/app_mgr/__init__.py b/app_mgr/__init__.py
index e69de29..f47b747 100644
--- a/app_mgr/__init__.py
+++ b/app_mgr/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'app_mgr.apps.AppMgrConfig'
diff --git a/app_mgr/admin.py b/app_mgr/admin.py
index 9fc9ecb..f09e67d 100644
--- a/app_mgr/admin.py
+++ b/app_mgr/admin.py
@@ -23,7 +23,7 @@
 
 class MembershipAdmin(admin.ModelAdmin):
     search_fields = ['join_date']
-    list_display = ['user', 'org', 'join_date']
+    list_display = ['user', 'org', 'is_admin', 'join_date']
 
 class ApplicationInline(GenericTabularInline):
     model = Application
@@ -33,6 +33,7 @@
 class ApplicationAdmin(GuardedModelAdmin):
     inlines = [ApplicationInline]
     search_fields = ['name']
+    list_display = ['id', 'name', 'isPublic']
 
 class AppVersionAdmin(admin.ModelAdmin):
     model = AppVersion
diff --git a/app_mgr/apps.py b/app_mgr/apps.py
new file mode 100644
index 0000000..fba4f88
--- /dev/null
+++ b/app_mgr/apps.py
@@ -0,0 +1,9 @@
+from django.apps import AppConfig
+
+class AppMgrConfig(AppConfig):
+    name = 'app_mgr'
+    verbose_name = "Application Manager"
+
+    def ready(self):
+        print 'Loading', self.verbose_name
+        import app_mgr.signals.handlers
diff --git a/app_mgr/migrations/0002_membership_is_admin.py b/app_mgr/migrations/0002_membership_is_admin.py
new file mode 100644
index 0000000..b4b9139
--- /dev/null
+++ b/app_mgr/migrations/0002_membership_is_admin.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-06-28 04:39
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('app_mgr', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='membership',
+            name='is_admin',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/app_mgr/migrations/0003_auto_20160629_0112.py b/app_mgr/migrations/0003_auto_20160629_0112.py
new file mode 100644
index 0000000..cbe8910
--- /dev/null
+++ b/app_mgr/migrations/0003_auto_20160629_0112.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-06-29 05:12
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0007_alter_validators_add_error_messages'),
+        ('app_mgr', '0002_membership_is_admin'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='organization',
+            name='admin_group',
+            field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='admins_of', to='auth.Group'),
+        ),
+        migrations.AddField(
+            model_name='organization',
+            name='member_group',
+            field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='members_of', to='auth.Group'),
+        ),
+    ]
diff --git a/app_mgr/migrations/0004_auto_20160629_0252.py b/app_mgr/migrations/0004_auto_20160629_0252.py
new file mode 100644
index 0000000..00c719b
--- /dev/null
+++ b/app_mgr/migrations/0004_auto_20160629_0252.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-06-29 06:52
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('app_mgr', '0003_auto_20160629_0112'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='organization',
+            name='admin_group',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='admins_of', to='auth.Group'),
+        ),
+        migrations.AlterField(
+            model_name='organization',
+            name='member_group',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='members_of', to='auth.Group'),
+        ),
+    ]
diff --git a/app_mgr/models.py b/app_mgr/models.py
index 93fd37f..ca7d735 100644
--- a/app_mgr/models.py
+++ b/app_mgr/models.py
@@ -6,21 +6,12 @@
 from django.contrib.contenttypes.fields import GenericForeignKey
 
 from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
 
 from custom_user.models import AbstractEmailUser
 
 from guardian.mixins import GuardianUserMixin
 
-from django.db.models.signals import post_save
-from django.dispatch import receiver
-from rest_framework.authtoken.models import Token
-
-# Define signals here
-@receiver(post_save, sender=settings.AUTH_USER_MODEL)
-def create_auth_token(sender, instance=None, created=False, **kwargs):
-    if created:
-        Token.objects.create(user=instance)
-
 # Create your models here.
 class UserProfile(AbstractEmailUser, GuardianUserMixin):
 
@@ -39,6 +30,11 @@
 
     members = models.ManyToManyField(UserProfile, through='Membership')
 
+    member_group = models.OneToOneField(Group, null=True, blank=True,
+                                        related_name='members_of')
+    admin_group = models.OneToOneField(Group, null=True, blank=True,
+                                       related_name='admins_of')
+
     class Meta:
         permissions = (
             ("view_organization", "view organization information"),
@@ -51,6 +47,7 @@
     user = models.ForeignKey(UserProfile,  null=True, blank=False)
     org  = models.ForeignKey(Organization, null=True, blank=False)
     join_date = models.DateTimeField()
+    is_admin = models.BooleanField(default=False)
     
 class Application(models.Model):
     name = models.CharField(max_length=255)
diff --git a/app_mgr/permissions.py b/app_mgr/permissions.py
index daf8d9d..334a662 100644
--- a/app_mgr/permissions.py
+++ b/app_mgr/permissions.py
@@ -3,6 +3,7 @@
 from guardian.shortcuts import get_perms, get_perms_for_model, get_users_with_perms
 
 SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
+
 class ViewControlObjectPermissions(DjangoObjectPermissions):
     """ same as base object level permissions, plus read permission """
     perms_map = {
@@ -15,6 +16,18 @@
         'DELETE': ['%(app_label)s.delete_%(model_name)s'],
     }
 
+class ApplicationObjectPermissions(DjangoObjectPermissions):
+    """ same as base object level permissions, plus read permission """
+    perms_map = {
+        'GET': ['%(app_label)s.view_%(model_name)s'],
+        'OPTIONS': [],
+        'HEAD': [],
+        'POST': ['%(app_label)s.add_%(model_name)s'],
+        'PUT': ['%(app_label)s.change_%(model_name)s'],
+        'PATCH': ['%(app_label)s.change_%(model_name)s'],
+        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
+    }
+
     def has_object_permission(self, request, view, obj):
         if hasattr(view, 'get_queryset'):
             queryset = view.get_queryset()
@@ -31,6 +44,9 @@
 
         perms = self.get_required_object_permissions(request.method, model_cls)
 
+        if obj.isPublic and request.method == 'GET':
+            perms = []
+
         #print "-----------"
         #print request.method, perms
         #print obj.id, obj
@@ -42,7 +58,7 @@
         #print get_perms(request.user, obj)
         #print get_perms_for_model(model_cls)
         #print get_users_with_perms(obj)
-        #print "-----------"
+        #print "~~~~~~~~~~~"
 
         if not user.has_perms(perms, obj):
             # If the user does not have permissions we need to determine if
diff --git a/app_mgr/serializers.py b/app_mgr/serializers.py
index e551137..a8f4a74 100644
--- a/app_mgr/serializers.py
+++ b/app_mgr/serializers.py
@@ -9,7 +9,7 @@
     org = serializers.PrimaryKeyRelatedField(queryset=Organization.objects.all())
     class Meta:
         model = Membership
-        fields = ('org', 'user', 'join_date')
+        fields = ('org', 'user', 'is_admin', 'join_date')
 
 class UserProfileSerializer(serializers.ModelSerializer):
     memberships = MembershipSerializer(source='membership_set', many=True)
diff --git a/app_mgr/signals/__init__.py b/app_mgr/signals/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app_mgr/signals/__init__.py
diff --git a/app_mgr/signals/handlers.py b/app_mgr/signals/handlers.py
new file mode 100644
index 0000000..06fb660
--- /dev/null
+++ b/app_mgr/signals/handlers.py
@@ -0,0 +1,116 @@
+from django.contrib.auth.models import Group
+
+from django.conf import settings
+
+from django.contrib.contenttypes.models import ContentType
+
+from django.apps import apps
+
+from guardian.shortcuts import assign_perm, get_user_perms, get_users_with_perms, remove_perm, get_perms_for_model
+
+from django.db.models.signals import pre_save, post_save, pre_delete
+from django.dispatch import receiver
+from rest_framework.authtoken.models import Token
+
+# Define signals here
+
+### UserProfile Signals
+@receiver(post_save, sender=settings.AUTH_USER_MODEL)
+def set_user_perms(sender, instance=None, created=False, **kwargs):
+    perms = get_perms_for_model(apps.get_model('app_mgr', 'UserProfile'))
+    for perm in perms:
+        assign_perm(perm.codename, instance, instance)
+
+@receiver(post_save, sender=settings.AUTH_USER_MODEL)
+def create_auth_token(sender, instance=None, created=False, **kwargs):
+    if created:
+        Token.objects.create(user=instance)
+
+### Organization Signals
+@receiver(pre_delete, sender='app_mgr.Organization')
+def delete_org_groups(sender, instance=None, **kwargs):
+    if instance.member_group != None:
+        instance.member_group.delete()
+    if instance.admin_group != None:
+        instance.admin_group.delete()
+
+@receiver(pre_save, sender='app_mgr.Organization')
+def create_org_groups(sender, instance=None, created=False, **kwargs):
+    if instance.member_group == None:
+        g_name = '%s members' % (instance.name)
+        instance.member_group = Group.objects.create(name=g_name)
+    if instance.admin_group == None:
+        g_name = '%s admins' % (instance.name)
+        instance.admin_group = Group.objects.create(name=g_name)
+
+@receiver(post_save, sender='app_mgr.Organization')
+def set_org_perms(sender, instance=None, created=False, **kwargs):
+    perms = get_perms_for_model(apps.get_model('app_mgr', 'Organization'))
+    
+    if not created:
+        old_members = get_users_with_perms(instance)
+        for member in old_members:
+            instance.member_group.user_set.remove(member)
+            instance.admin_group.user_set.remove(member)
+            for perm in perms:
+                remove_perm(perm.codename, member, instance)
+    
+    new_members = instance.members.all()
+
+    for member in (m for m in new_members if m.is_admin):
+        instance.admin_group.user_set.add(member)
+        instance.member_group.user_set.add(member)
+        for perm in perms:
+            assign_perm(perm.codename, member.user, instance)
+
+    for member in (m for m in new_members if not m.is_admin):
+        instance.member_group.user_set.add(member)
+        assign_perm('view_organization', member.user, instance)
+
+### Membership Signals
+@receiver(post_save, sender='app_mgr.Membership')
+def set_member_perms(sender, instance=None, created=False, **kwargs):
+    perms = get_perms_for_model(apps.get_model('app_mgr', 'Organization'))
+
+    was_admin  = len(get_user_perms(instance.user, instance.org)) > 1
+
+    if instance.is_admin and not was_admin:
+        instance.org.admin_group.user_set.add(instance.user)
+        for perm in perms:
+            assign_perm(perm.codename, instance.user, instance.org)
+
+    if not instance.is_admin and was_admin:
+        instance.org.admin_group.user_set.remove(instance.user)
+        for perm in perms:
+            remove_perm(perm.codename, instance.user, instance.org)
+
+    instance.org.member_group.user_set.add(instance.user)
+    assign_perm('view_organization', instance.user, instance.org)
+
+@receiver(pre_delete, sender='app_mgr.Membership')
+def rm_org_perms(sender, instance=None, **kwargs):
+    perms = get_perms_for_model(apps.get_model('app_mgr', 'Organization'))
+
+    instance.org.admin_group.user_set.remove(instance.user)
+    instance.org.member_group.user_set.remove(instance.user)
+    for perm in perms:
+        remove_perm(perm.codename, instance.user, instance.org)
+
+### Application Signals
+@receiver(post_save, sender='app_mgr.Application')
+def set_app_perms(sender, instance=None, created=False, **kwargs):
+    perms = get_perms_for_model(apps.get_model('app_mgr', 'Application'))
+
+    if not created:
+        old_members = get_users_with_perms(instance)
+        for member in old_members:
+            for perm in perms:
+                remove_perm(perm.codename, member, instance)
+
+    if instance.content_type.name == u'user profile':
+        for perm in perms:
+            assign_perm(perm.codename, instance.content_object, instance)
+    
+    if instance.content_type.name == u'organization':
+        for perm in perms:
+            assign_perm(perm.codename, instance.content_object.member_group, instance)
diff --git a/app_mgr/urls.py b/app_mgr/urls.py
index 496992d..827f2e3 100644
--- a/app_mgr/urls.py
+++ b/app_mgr/urls.py
@@ -33,7 +33,7 @@
     url(r'^apps/$', views.ApplicationListView.as_view(), name='app-list'),
 
     url(r'^user/(?P<pk>[\d]+)/$', views.UserProfileInstanceView.as_view(), name='user-instance'),
-    url(r'^user/(?P<pk>current)/$', views.UserProfileInstanceView.as_view(), name='user-instance'),
-    url(r'^org/(?P<pk>[\d]+)/$', views.OrganizationInstanceView.as_view(), name='user-instance'),
-    url(r'^app/(?P<pk>[\d]+)/$', views.ApplicationInstanceView.as_view(), name='user-instance'),
+    url(r'^user/(?P<pk>current)/$', views.UserProfileInstanceView.as_view(), name='user-current'),
+    url(r'^org/(?P<pk>[\d]+)/$', views.OrganizationInstanceView.as_view(), name='org-instance'),
+    url(r'^app/(?P<pk>[\d]+)/$', views.ApplicationInstanceView.as_view(), name='app-instance'),
 ]
diff --git a/app_mgr/views.py b/app_mgr/views.py
index 86b6a72..2d4ae9a 100644
--- a/app_mgr/views.py
+++ b/app_mgr/views.py
@@ -9,6 +9,7 @@
 from django.conf import settings
 
 from django.db import IntegrityError
+from django.db.models import Q
 
 from django.views.generic.base import RedirectView
 
@@ -19,9 +20,10 @@
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
-from guardian.shortcuts import assign_perm
+from guardian.shortcuts import assign_perm, get_objects_for_user
 
 from app_mgr.permissions import ViewControlObjectPermissions
+from app_mgr.permissions import ApplicationObjectPermissions
 from app_mgr.models import UserProfile, Organization, Application, AppVersion
 from app_mgr.serializers import UserProfileSerializer, OrganizationSerializer, ApplicationSerializer
 
@@ -42,20 +44,46 @@
     queryset = UserProfile.objects.all()
     serializer_class = UserProfileSerializer
 
+    def get_queryset(self):
+        # only used for list
+        return get_objects_for_user(self.request.user, "view_userprofile", 
+                                    UserProfile.objects.all())
+
 class OrganizationListView(generics.ListCreateAPIView):
     """
     Returns a list of all organizations.
     """
+    authentication_classes = (TokenAuthentication,)
+    permission_classes = (IsAuthenticated,)
+
     queryset = Organization.objects.all()
     serializer_class = OrganizationSerializer
 
+    def get_queryset(self):
+        # only used for list
+        return get_objects_for_user(self.request.user, "view_organization", 
+                                    Organization.objects.all())
+
 class ApplicationListView(generics.ListCreateAPIView):
     """
     Returns a list of all applications.
     """
+    authentication_classes = (TokenAuthentication,)
+    permission_classes = (IsAuthenticated,)
+
     queryset = Application.objects.all()
     serializer_class = ApplicationSerializer
 
+    def get_queryset(self):
+        # only used for list
+        owned = get_objects_for_user(self.request.user, "view_application", 
+                                     Application.objects.all())
+        public = Application.objects.filter(isPublic=True)
+
+        viewable = list(set(list(owned) + list(public)))
+       
+        return viewable
+
 # SINGLE RETRIEVE/UPDATE/DESTROY
 class UserProfileInstanceView(generics.RetrieveUpdateDestroyAPIView):
     """
@@ -92,6 +120,10 @@
     """
     Returns a single org.
     """
+    authentication_classes = (TokenAuthentication,)
+    permission_classes = (ViewControlObjectPermissions,)
+    _ignore_model_permissions = True
+
     queryset = Organization.objects.all()
     serializer_class = OrganizationSerializer
 
@@ -99,6 +131,10 @@
     """
     Returns a single app.
     """
+    authentication_classes = (TokenAuthentication,)
+    permission_classes = (ApplicationObjectPermissions,)
+    _ignore_model_permissions = True
+
     queryset = Application.objects.all()
     serializer_class = ApplicationSerializer