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