Merge pull request #85 from open-cloud/service_permissions

Service permissions
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 1ded815..fa9ca55 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -503,6 +503,24 @@
     def queryset(self, request):
         return SitePrivilege.select_by_user(request.user)
 
+
+class ServicePrivilegeInline(XOSTabularInline):
+    model = ServicePrivilege
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-serviceprivileges'
+    fields = ['backend_status_icon', 'user','service', 'role']
+    readonly_fields = ('backend_status_icon', )
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == 'service':
+            kwargs['queryset'] = Service.select_by_user(request.user)
+        if db_field.name == 'user':
+            kwargs['queryset'] = User.select_by_user(request.user)
+        return super(ServicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)         
+
+    def queryset(self, request):
+        return ServicePrivilege.select_by_user(request.user)
+
 class SiteDeploymentInline(XOSTabularInline):
     model = SiteDeployment
     extra = 0
@@ -783,7 +801,7 @@
     list_display_links = ('backend_status_icon', 'name', )
     fieldList = ["backend_status_text","name","kind","description","versionNumber","enabled","published","view_url","icon_url","public_key"]
     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
-    inlines = [ServiceAttrAsTabInline,SliceInline,ProviderTenantInline,SubscriberTenantInline]
+    inlines = [ServiceAttrAsTabInline,SliceInline,ProviderTenantInline,SubscriberTenantInline,ServicePrivilegeInline]
     readonly_fields = ('backend_status_text', )
 
     user_readonly_fields = fieldList
@@ -792,6 +810,7 @@
         ('slices','Slices'),
         ('serviceattrs','Additional Attributes'),
         ('servicetenants','Tenancy'),
+        ('serviceprivileges','Privileges') 
     )
 
 class SiteNodeInline(XOSTabularInline):
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index 81bf4cc..b13e449 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -1,7 +1,7 @@
 from .plcorebase import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,PlModelMixIn
 from .project import Project
 from .singletonmodel import SingletonModel
-from .service import Service, Tenant, CoarseTenant
+from .service import Service, Tenant, CoarseTenant, ServicePrivilege
 from .service import ServiceAttribute
 from .tag import Tag
 from .role import Role
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 8a10f37..d2cc302 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -29,6 +29,9 @@
 
     def __unicode__(self): return u'%s' % (self.name)
 
+    def can_update(self, user):
+        return user.can_update_service(self, allow=['admin'])
+     
     def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
         """
              Get a list of nodes that can be used to scale up a slice.
@@ -107,6 +110,45 @@
     value = StrippedCharField(help_text="Attribute Value", max_length=1024)
     service = models.ForeignKey(Service, related_name='serviceattributes', help_text="The Service this attribute is associated with")
 
+class ServiceRole(PlCoreBase):
+    ROLE_CHOICES = (('admin','Admin'),)
+    role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
+
+    def __unicode__(self):  return u'%s' % (self.role)
+
+class ServicePrivilege(PlCoreBase):
+    user = models.ForeignKey('User', related_name='serviceprivileges')
+    service = models.ForeignKey('Service', related_name='serviceprivileges')
+    role = models.ForeignKey('ServiceRole',related_name='serviceprivileges')
+
+    class Meta:
+        unique_together =  ('user', 'service', 'role')
+
+    def __unicode__(self):  return u'%s %s %s' % (self.service, self.user, self.role)
+
+    def can_update(self, user):
+        if not self.service.enabled:
+            raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
+        return self.service.can_update(user)
+
+    def save(self, *args, **kwds):
+        if not self.service.enabled:
+            raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
+        super(ServicePrivilege, self).save(*args, **kwds)
+
+    def delete(self, *args, **kwds):
+        if not self.service.enabled:
+            raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
+        super(ServicePrivilege, self).delete(*args, **kwds)                    
+    
+    @staticmethod
+    def select_by_user(user):
+        if user.is_admin:
+            qs = ServicePrivilege.objects.all()
+        else:
+            qs = SitePrivilege.objects.filter(user=user)
+        return qs        
+
 class Tenant(PlCoreBase):
     """ A tenant is a relationship between two entities, a subscriber and a
         provider.
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index e34abdb..e62d6db 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -314,6 +314,15 @@
             return True
         return False
 
+    def can_update_service(self, service, allow=[]):
+        from core.models.service import ServicePrivilege
+        if self.can_update_root():
+            return True
+        if ServicePrivilege.objects.filter(
+            service=service, user=self, role__role__in=['admin', 'Admin']+allow):
+            return True
+        return False           
+
     @staticmethod
     def select_by_user(user):
         if user.is_admin: