CORD-1316: Implement new credentials system

Change-Id: Iaf869cc275fe78a3dd871dc45ef5bba55cde2027
diff --git a/README.md b/README.md
index 0b93d3f..dc673ad 100644
--- a/README.md
+++ b/README.md
@@ -21,3 +21,4 @@
 version is configured with a service graph that includes
 `ExampleService`, which is a good platform for understanding how to
 build and use XOS.
+
diff --git a/xos/api/utility/sliceplus.py b/xos/api/utility/sliceplus.py
index 591160f..6d6339c 100644
--- a/xos/api/utility/sliceplus.py
+++ b/xos/api/utility/sliceplus.py
@@ -9,7 +9,7 @@
 from core.xoslib.objects.sliceplus import SlicePlus
 from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
 import json
-from core.models import Slice, SlicePrivilege, SliceRole, Instance, Site, Node, User
+from core.models import Slice, Privilege, SliceRole, Instance, Site, Node, User
 from operator import itemgetter, attrgetter
 from api.xosapi_helpers import PlusObjectMixin, PlusModelSerializer
 
@@ -67,10 +67,16 @@
                     ready_sites[site.name] = ready_sites.get(site.name, 0) + 1
 
             users = {}
-            for priv in SlicePrivilege.objects.filter(slice=self):
-                if not (priv.user.id in users.keys()):
-                    users[priv.user.id] = {"name": priv.user.email, "id": priv.user.id, "roles": []}
-                users[priv.user.id]["roles"].append(priv.role.role)
+            for priv in Privilege.objects.filter(object_id=self.id, accessor_type='User', object_type='Slice'):
+                if not (priv.accessor_id in users.keys()):
+                    accessor_user = User.objects.get(pk = priv.accessor_id)
+                    users[priv.accessor_id] = {"name": accessor_user.email, "id": priv.accessor_id, "roles": []}
+
+                permission = priv.permission
+                if permission.startswith('role:'):
+                    accessor_role = permission[5:]
+                    accessor_role.lstrip()
+                    users[priv.accessor_id]["roles"].append(accessor_role)
 
             # XXX this assumes there is only one network that can have ports bound
             # to it for a given slice. This is intended for the tenant view, which
@@ -159,7 +165,7 @@
         if user.is_admin:
             qs = SlicePlus.objects.all()
         else:
-            slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user)]
+            slice_ids = [sp.object_id for sp in Privilege.objects.filter(accessor_id=user.id, accessor_type='User', object_type='Slice')]
             qs = SlicePlus.objects.filter(id__in=slice_ids)
         return qs
 
@@ -272,12 +278,12 @@
         except:
             default_role = SliceRole.objects.get(role="default")
 
-        slice_privs = self.sliceprivileges.all()
-        slice_user_ids = [priv.user.id for priv in slice_privs]
+        slice_privs = Privilege.objects.filter(object_id = self.id, object_type = 'Slice', accessor_type='User')
+        slice_user_ids = [priv.accessor_id for priv in slice_privs]
 
         for user_id in new_users:
             if (user_id not in slice_user_ids):
-                priv = SlicePrivilege(slice=self, user=User.objects.get(id=user_id), role=default_role)
+                priv = Privilege(object_id=self.id, accessor_id=user_id, accessor_type = 'User', permission='role:%s'%default_role.role, object_type = 'Slice')
                 priv.caller = self.caller
                 if (not noAct):
                     priv.save()
diff --git a/xos/core/models/attic/controller_model.py b/xos/core/models/attic/controller_model.py
index 1e07807..9e76572 100644
--- a/xos/core/models/attic/controller_model.py
+++ b/xos/core/models/attic/controller_model.py
@@ -1,10 +1,9 @@
 @staticmethod
 def select_by_user(user):
-
     if user.is_admin:
         qs = Controller.objects.all()
     else:
-        from core.models.deploymentprivilege import DeploymentPrivilege
-        deployments = [dp.deployment for dp in DeploymentPrivilege.objects.filter(user=user, role__role__in=['Admin', 'admin'])]
+        from core.models.privilege import Privilege
+        deployments = [dp.deployment for dp in Privilege.objects.filter(accessor_id=user_id, accessor_type='User', permission__in=['role:Admin', 'role:admin'])]
         qs = Controller.objects.filter(deployment__in=deployments)
     return qs
diff --git a/xos/core/models/attic/controllersiteprivilege_model.py b/xos/core/models/attic/controllersiteprivilege_model.py
index b9d8af4..11ba22d 100644
--- a/xos/core/models/attic/controllersiteprivilege_model.py
+++ b/xos/core/models/attic/controllersiteprivilege_model.py
@@ -3,17 +3,18 @@
         return False
     if user.is_admin:
         return True
-    cprivs = ControllerSitePrivilege.objects.filter(site_privilege__user=user)
-    for cpriv in dprivs:
-        if cpriv.site_privilege.role.role == ['admin', 'Admin']:
+
+    cprivs = ControllerPrivilege.objects.filter(privilege__accessor_id=user.id, privilege__object_type='Site')
+    for cpriv in cprivs:
+        if cpriv.privilege.permission in ['role:admin', 'role:Admin']:
             return True
     return False
 
 @staticmethod
 def select_by_user(user):
     if user.is_admin:
-        qs = ControllerSitePrivilege.objects.all()
+        qs = ControllerPrivilege.objects.filter(privilege__object_type='Site')
     else:
-        cpriv_ids = [cp.id for cp in ControllerSitePrivilege.objects.filter(site_privilege__user=user)]
-        qs = ControllerSitePrivilege.objects.filter(id__in=cpriv_ids)
+        cpriv_ids = [cp.id for cp in ControllerPrivilege.objects.filter(privilege__accessor_id=user.id, privilege__object_type='Site')]
+        qs = ControllerPrivilege.objects.filter(id__in=cpriv_ids, privilege__object_type='Site')
     return qs
diff --git a/xos/core/models/attic/controllersliceprivilege_model.py b/xos/core/models/attic/controllersliceprivilege_model.py
index aaf6053..ab64569 100644
--- a/xos/core/models/attic/controllersliceprivilege_model.py
+++ b/xos/core/models/attic/controllersliceprivilege_model.py
@@ -3,17 +3,17 @@
         return False
     if user.is_admin:
         return True
-    cprivs = ControllerSlicePrivilege.objects.filter(slice_privilege__user=user)
-    for cpriv in dprivs:
-        if cpriv.role.role == ['admin', 'Admin']:
+    cprivs = ControllerPrivilege.objects.filter(privilege__accessor_id=user.id, privilege__object_type='Slice')
+    for cpriv in cprivs:
+        if cpriv.privilege.permission in ['role:admin', 'role:Admin']:
             return True
     return False
 
 @staticmethod
 def select_by_user(user):
     if user.is_admin:
-        qs = ControllerSlicePrivilege.objects.all()
+        qs = ControllerPrivilege.objects.filter(privilege__object_type='Slice')
     else:
-        cpriv_ids = [cp.id for cp in ControllerSlicePrivilege.objects.filter(slice_privilege__user=user)]
-        qs = ControllerSlicePrivilege.objects.filter(id__in=cpriv_ids)
+        cpriv_ids = [cp.id for cp in ControllerPrivilege.objects.filter(privilege__accessor_id=user.id, privilege__object_type='Slice')]
+        qs = ControllerPrivilege.objects.filter(id__in=cpriv_ids, privilege__object_type='Slice')
     return qs
diff --git a/xos/core/models/attic/instance_model.py b/xos/core/models/attic/instance_model.py
index d2e7b89..a86e53d 100644
--- a/xos/core/models/attic/instance_model.py
+++ b/xos/core/models/attic/instance_model.py
@@ -32,10 +32,10 @@
         raise ValidationError("Parent field can only be set on Container-vm instances")
 
     if (self.slice.creator != self.creator):
-        from core.models.sliceprivilege import SlicePrivilege
+        from core.models.privilege import Privilege
         # Check to make sure there's a slice_privilege for the user. If there
         # isn't, then keystone will throw an exception inside the observer.
-        slice_privs = SlicePrivilege.objects.filter(slice=self.slice, user=self.creator)
+        slice_privs = Privilege.objects.filter(object_id=self.slice.id, accessor_id=self.creator.id, object_type='Slice')
         if not slice_privs:
             raise ValidationError('instance creator has no privileges on slice')
 
@@ -108,9 +108,10 @@
         return 'ssh -o "ProxyCommand ssh -q %s@%s" ubuntu@%s' % (self.instance_id, self.node.name, self.instance_name)
 
 def get_public_keys(self):
-    from core.models.sliceprivilege import SlicePrivilege
-    slice_memberships = SlicePrivilege.objects.filter(slice=self.slice)
-    pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.public_key])
+    from core.models.sliceprivilege import Privilege
+    slice_privileges = Privilege.objects.filter(object_id=self.slice.id, object_type='Slice', accessor_type='User')
+    slice_users = [User.objects.get(pk = priv.accessor_id) for priv in slice_privileges]
+    pubkeys = set([u.public_key for u in slice_users if u.public_key])
 
     if self.creator.public_key:
         pubkeys.add(self.creator.public_key)
diff --git a/xos/core/models/attic/service_model.py b/xos/core/models/attic/service_model.py
index d7175b6..9cb2d4d 100644
--- a/xos/core/models/attic/service_model.py
+++ b/xos/core/models/attic/service_model.py
@@ -10,9 +10,9 @@
     if user.is_admin:
         return cls.objects.all()
     else:
-        from core.models.serviceprivilege import ServicePrivilege
+        from core.models.privilege import Privilege
         service_ids = [
-            sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
+            sp.object_id for sp in Privilege.objects.filter(accessor_id=user.id, accessor_type='User', object_type='Service')]
         return cls.objects.filter(id__in=service_ids)
 
 @property
diff --git a/xos/core/models/attic/slice_model.py b/xos/core/models/attic/slice_model.py
index db53e11..7154c73 100644
--- a/xos/core/models/attic/slice_model.py
+++ b/xos/core/models/attic/slice_model.py
@@ -51,13 +51,14 @@
     if user.is_admin:
         qs = Slice.objects.all()
     else:
-        from core.models.sliceprivilege import SlicePrivilege 
-        from core.models.siteprivilege import SitePrivilege
+        from core.models.privilege import Privilege 
         # users can see slices they belong to 
-        slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user)]
+        slice_ids = [sp.object_id for sp in Privilege.objects.filter(accessor_id=user.id, accessor_type='User', object_type='Slice')]
         # pis and admins can see slices at their sites
-        sites = [sp.site for sp in SitePrivilege.objects.filter(user=user)\
-                    if (sp.role.role == 'pi') or (sp.role.role == 'admin')]
+        site_ids = [sp.object_id for sp in Privilege.objects.filter(accessor_id=user.id, accessor_type='User', object_type='Site')\
+                    if (sp.permission in ['role:pi', 'role:admin'])]
+        sites = [Site.objects.get(pk = site_id) for site_id in site_ids]
+
         slice_ids.extend([s.id for s in Slice.objects.filter(site__in=sites)])
         qs = Slice.objects.filter(id__in=slice_ids)
     return qs
diff --git a/xos/core/models/attic/tenantroot_model.py b/xos/core/models/attic/tenantroot_model.py
index bb09b1e..86208cd 100644
--- a/xos/core/models/attic/tenantroot_model.py
+++ b/xos/core/models/attic/tenantroot_model.py
@@ -23,9 +23,9 @@
     if user.is_admin:
         return cls.objects.all()
     else:
-        from core.models.tenantrootprivilege import TenantRootPrivilege
+        from core.models.privilege import Privilege
         tr_ids = [
-            trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
+            trp.object_id for trp in Privilege.objects.filter(accessor_id=user.id, accessor_type='User', object_type='TenantRoot')]
         return cls.objects.filter(id__in=tr_ids)
 
 # helper function to be used in subclasses that want to ensure
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 587769d..adb5842 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -57,6 +57,16 @@
      optional string policy_status = 32 [default = "0 - Policy in process", max_length = 1024];
 }
 
+message Privilege (XOSBase) {
+     required int32 accessor_id = 1 [null = False];
+     required string accessor_type = 2 [null = False, max_length=1024];
+     required int32 object_id = 3 [null = False];  
+     required string object_type = 4 [null = False, max_length=1024];
+     required string permission = 5 [null = False, default = "all", max_length=1024];
+     required string granted = 6 [content_type = "date", auto_now_add = True, max_length=1024];
+     required string expires = 7 [content_type = "date", null = True, max_length=1024];
+}
+
 message AddressPool (XOSBase) {
      required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
      optional string addresses = 2 [db_index = False, null = True, blank = True, varchar = True];
@@ -124,6 +134,11 @@
      optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = True];
 }
 
+message ControllerPrivilege (XOSBase) {
+     required manytoone controller->Controller:controllerprivileges = 1 [db_index = True, null = False, blank = False];
+     required manytoone privilege->Privilege:controllerprivileges = 2 [db_index = True, null = False, blank = False];
+     optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
+}
 
 message ControllerSitePrivilege (XOSBase) {
      required manytoone controller->Controller:controllersiteprivileges = 1 [db_index = True, null = False, blank = False, unique_with = "site_privilege"];
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 0d721e2..3add183 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -322,17 +322,18 @@
         msg.send()
 
     def can_update(self, user):
-        from core.models.siteprivilege import SitePrivilege
+        from core.models.privilege import Privilege
         _cant_update_fieldName = None
         if user.can_update_root():
             return True
 
         # site pis can update
-        site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
+        site_privs = Privilege.objects.filter(accessor_id=user.id, object_id=self.site.id, object_type='Site', accessor_type='User')
         for site_priv in site_privs:
-            if site_priv.role.role == 'admin':
+            if site_priv.permission == 'role:admin':
                 return True
-            if site_priv.role.role == 'pi':
+
+            if site_priv.permission == 'role:pi':
                 for fieldName in self.diff.keys():
                     if fieldName in self.PI_FORBIDDEN_FIELDS:
                         _cant_update_fieldName = fieldName
@@ -359,28 +360,28 @@
         return False
 
     def can_update_deployment(self, deployment):
-        from core.models.deploymentprivilege import DeploymentPrivilege
+        from core.models.privilege import Privilege
         if self.can_update_root():
             return True
 
-        if DeploymentPrivilege.objects.filter(
-                deployment=deployment,
-                user=self,
-                role__role__in=['admin', 'Admin']):
+        if Privilege.objects.filter(
+                object_id=deployment.id,
+                accessor_id=self.id,
+                permission__in=['role:admin', 'role:Admin']):
             return True
         return False
 
     def can_update_site(self, site, allow=[]):
-        from core.models.siteprivilege import SitePrivilege
+        from core.models.privilege import Privilege
         if self.can_update_root():
             return True
-        if SitePrivilege.objects.filter(
-                site=site, user=self, role__role__in=['admin', 'Admin'] + allow):
+        if Privilege.objects.filter(
+                object_id=site.id, accessor_id=self.id, accessor_type='User', permission__in=['role:admin', 'role:Admin'] + ['role:'+opt for opt in allow]):
             return True
         return False
 
     def can_update_slice(self, slice):
-        from core.models.sliceprivilege import SlicePrivilege
+        from core.models.privilege import Privilege
         if self.can_update_root():
             return True
         if self == slice.creator:
@@ -388,37 +389,37 @@
         if self.can_update_site(slice.site, allow=['pi']):
             return True
 
-        if SlicePrivilege.objects.filter(
-                slice=slice, user=self, role__role__in=['admin', 'Admin']):
+        if Privilege.objects.filter(
+                object_id=slice.id, accessor_id=self.id, permission__in=['role:admin', 'role:Admin'], accessor_type='User', object_type='Slice'):
             return True
         return False
 
     def can_update_service(self, service, allow=[]):
-        from core.models.serviceprivilege import ServicePrivilege
+        from core.models.privilege import Privilege
         if self.can_update_root():
             return True
-        if ServicePrivilege.objects.filter(
-                service=service, user=self, role__role__in=['admin', 'Admin'] + allow):
+        if Privilege.objects.filter(
+                object_id=service.id, accessor_id=self.id, accessor_type='User', permission__in=['role:admin', 'role:Admin'] + ['role:'+opt for opt in allow]):
             return True
         return False
 
     def can_update_tenant_root(self, tenant_root, allow=[]):
         from core.models.tenantroot import TenantRoot
-        from core.models.tenantrootprivilege import TenantRootPrivilege
+        from core.models.privilege import Privilege
         if self.can_update_root():
             return True
-        if TenantRootPrivilege.objects.filter(
-                tenant_root=tenant_root, user=self, role__role__in=['admin', 'Admin'] + allow):
+        if Privilege.objects.filter(
+                object_id=tenant_root.id, accessor_type='User',accessor_id=self.id, permission__in=['role:admin', 'role:Admin'] + ['role:'+opt for opt in allow]):
             return True
         return False
 
     def can_update_tenant(self, tenant, allow=[]):
         from core.models.tenant import Tenant
-        from core.models.tenantprivilege import TenantPrivilege
+        from core.models.privilege import Privilege
         if self.can_update_root():
             return True
-        if TenantPrivilege.objects.filter(
-                tenant=tenant, user=self, role__role__in=['admin', 'Admin'] + allow):
+        if Privilege.objects.filter(
+                object_id=tenant.id, accessor_type='User',accessor_id=self.id, permission__in=['role:admin', 'role:Admin'] + ['role:'+opt for opt in allow]):
             return True
         return False
 
@@ -428,132 +429,22 @@
     def can_update_tenant_privilege(self, tenant_privilege, allow=[]):
         return self.can_update_tenant(tenant_privilege.tenant, allow)
 
-    def get_readable_objects(self, filter_by=None):
-        """ Returns a list of objects that the user is allowed to read. """
-        from core.models import Deployment, Flavor, Image, Network, NetworkTemplate, Node, PlModelMixIn, Site, Slice, SliceTag, Instance, Tag, User, DeploymentPrivilege, SitePrivilege, SlicePrivilege
-        models = []
-        if filter_by and isinstance(filter_by, list):
-            models = [m for m in filter_by if issubclass(m, PlModelMixIn)]
-        if not models:
-            models = [Deployment, Network, Site,
-                      Slice, SliceTag, Instance, Tag, User]
-        readable_objects = []
-        for model in models:
-            readable_objects.extend(model.select_by_user(self))
-        return readable_objects
-
-    def get_permissions(self, filter_by=None):
-        """ Return a list of objects for which the user has read or read/write
-        access. The object will be an instance of a django model object.
-        Permissions will be either 'r' or 'rw'.
-
-        e.g.
-        [{'object': django_object_instance, 'permissions': 'rw'}, ...]
-
-        Returns:
-          list of dicts
-
-        """
-        from core.models import Deployment, Flavor, Image, Network, NetworkTemplate, Node, PlModelMixIn, Site, Slice, SliceTag, Instance, Tag, User, DeploymentPrivilege, SitePrivilege, SlicePrivilege
-        READ = 'r'
-        READWRITE = 'rw'
-        models = []
-        if filter_by and isinstance(filter_by, list):
-            models = [m for m in filter_by if issubclass(m, PlModelMixIn)]
-
-        deployment_priv_objs = [Image, NetworkTemplate, Flavor]
-        site_priv_objs = [Node, Slice, User]
-        slice_priv_objs = [Instance, Network]
-
-        # maps the set of objects a paticular role has write access
-        write_map = {
-            DeploymentPrivilege: {
-                'admin': deployment_priv_objects,
-            },
-            SitePrivilege: {
-                'admin': site_priv_objs,
-                'pi': [Slice, User],
-                'tech': [Node],
-            },
-            SlicePrivilege: {
-                'admin': slice_priv_objs,
-            },
-        }
-
-        privilege_map = {
-            DeploymentPrivilege: (Deployment, deployment_priv_objs),
-            SitePrivilege: (Site, site_priv_objs),
-            SlicePrivilege: (Slice, slice_priv_objs)
-        }
-        permissions = []
-        permission_dict = lambda x, y: {'object': x, 'permission': y}
-        for privilege_model, (model, affected_models) in privileg_map.items():
-            if models and model not in models:
-                continue
-
-            # get the objects affected by this privilege model
-            affected_objects = []
-            for affected_model in affected_models:
-                affected_objects.extend(affected_model.select_by_user(self))
-
-            if self.is_admin:
-                # assume admin users have read/write access to all objects
-                for affected_object in affected_objects:
-                    permissions.append(permission_dict(
-                        affected_object, READWRITE))
-            else:
-                # create a dict of the user's per object privileges
-                # ex:  {princeton_tmack : ['admin']
-                privileges = privilege_model.objects.filter(user=self)
-                for privilege in privileges:
-                    object_roles = defaultdict(list)
-                    obj = None
-                    roles = []
-                    for field in dir(privilege):
-                        if field == model.__name__.lower():
-                            obj = getattr(privilege, field)
-                    if obj:
-                        object_roles[obj].append(privilege.role.role)
-
-                # loop through all objects the user has access to and determine
-                # if they also have write access
-                for affected_object in affected_objects:
-                    if affected_object not in objects_roles:
-                        permissions.append(
-                            permission_dict(affected_object, READ))
-                    else:
-                        has_write_permission = False
-                        for write_role, models in write_dict.items():
-                            if affected_object._meta.model in models and \
-                                    write_role in object_roles[affected_object]:
-                                has_write_permission = True
-                                break
-                        if has_write_permission:
-                            permissions.append(
-                                permission_dict(affected_object, WRITE))
-                        else:
-                            permissions.append(
-                                permission_dict(affected_object, READ))
-
-        return permissions
-
-    def get_tenant_permissions(self):
-        from core.models import Site, Slice
-        return self.get_object_permissions(filter_by=[Site, Slice])
-
     @staticmethod
     def select_by_user(user):
         if user.is_admin:
             qs = User.objects.all()
         else:
             # can see all users at any site where this user has pi role
-            from core.models.siteprivilege import SitePrivilege
-            site_privs = SitePrivilege.objects.filter(user=user)
-            sites = [sp.site for sp in site_privs if sp.role.role in [
-                'Admin', 'admin', 'pi']]
+            from core.models.privilege import Privilege
+            site_privs = Privilege.objects.filter(accessor_type='User', accessor_id=user.id, object_type='Site')
+            site_ids = [sp.object_id for sp in site_privs if sp.permission in [
+                'role:Admin', 'role:admin', 'role:pi']]
+            sites = [Site.objects.get(pk=sid) for sid in site_ids]
+
             # get site privs of users at these sites
-            site_privs = SitePrivilege.objects.filter(site__in=sites)
-            user_ids = [sp.user.id for sp in site_privs] + [user.id]
+            site_privs = Privilege.objects.filter(object_id__in=site_ids, object_type='Site', accessor_type='User')
+
+            user_ids = [sp.accessor_id for sp in site_privs] + [user.id]
             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
         return qs
 
diff --git a/xos/core/xoslib/objects/sliceplus.py b/xos/core/xoslib/objects/sliceplus.py
index 9d2868f..5086f79 100644
--- a/xos/core/xoslib/objects/sliceplus.py
+++ b/xos/core/xoslib/objects/sliceplus.py
@@ -1,4 +1,4 @@
-from core.models import Slice, SlicePrivilege, SliceRole, Instance, Site, Node, User
+from core.models import Slice, Privilege, SliceRole, Instance, Site, Node, User
 from plus import PlusObjectMixin
 from operator import itemgetter, attrgetter
 from rest_framework.exceptions import APIException
@@ -38,10 +38,11 @@
                     ready_sites[site.name] = ready_sites.get(site.name, 0) + 1
 
             users = {}
-            for priv in SlicePrivilege.objects.filter(slice=self):
-                if not (priv.user.id in users.keys()):
-                    users[priv.user.id] = {"name": priv.user.email, "id": priv.user.id, "roles": []}
-                users[priv.user.id]["roles"].append(priv.role.role)
+            for priv in Privilege.objects.filter(object_id=self.id, object_type='Slice', accessor_type='User'):
+                if not (priv.accessor_id in users.keys()):
+                    user = User.objects.get(pk=priv.accessor_id)
+                    users[priv.accessor_id] = {"name": user.email, "id": user.id, "roles": []}
+                users[priv.accessor_id]["roles"].append(priv.permission)
 
             # XXX this assumes there is only one network that can have ports bound
             # to it for a given slice. This is intended for the tenant view, which
@@ -118,7 +119,7 @@
         if user.is_admin:
             qs = SlicePlus.objects.all()
         else:
-            slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user)]
+            slice_ids = [sp.slice.id for sp in Privilege.objects.filter(accessor_type='User',accessor_id=user.id, object_type='Slice')]
             qs = SlicePlus.objects.filter(id__in=slice_ids)
         return qs
 
@@ -231,12 +232,12 @@
         except:
             default_role = SliceRole.objects.get(role="default")
 
-        slice_privs = self.sliceprivileges.all()
-        slice_user_ids = [priv.user.id for priv in slice_privs]
+        slice_privs = Privilege.objects.filter(object_id=self.id, object_type='Slice', accessor_type='User')
+        slice_user_ids = [priv.accessor_id for priv in slice_privs]
 
         for user_id in new_users:
             if (user_id not in slice_user_ids):
-                priv = SlicePrivilege(slice=self, user=User.objects.get(id=user_id), role=default_role)
+                priv = Privilege(object_id=self.id, accessor_id=user_id, permission='role:'+default_role, accessor_type='User', object_type='Slice')
                 priv.caller = self.caller
                 if (not noAct):
                     priv.save()
diff --git a/xos/synchronizers/model_policy.py b/xos/synchronizers/model_policy.py
index d79ed81..6455111 100644
--- a/xos/synchronizers/model_policy.py
+++ b/xos/synchronizers/model_policy.py
@@ -147,7 +147,7 @@
 
 def run_policy_once():
         from core.models import Instance,Slice,Controller,Network,User,SlicePrivilege,Site,SitePrivilege,Image,ControllerSlice,ControllerUser,ControllerSite
-        models = [Controller, Site, SitePrivilege, Image, ControllerSlice, ControllerSite, ControllerUser, User, Slice, Network, Instance, SlicePrivilege]
+        models = [Controller, Site, SitePrivilege, Image, ControllerSlice, ControllerSite, ControllerUser, User, Slice, Network, Instance, SlicePrivilege, Privilege]
         objects = []
         deleted_objects = []
 
diff --git a/xos/tools/cleanup_unique.py b/xos/tools/cleanup_unique.py
index 97710ec..e8358ce 100644
--- a/xos/tools/cleanup_unique.py
+++ b/xos/tools/cleanup_unique.py
@@ -71,6 +71,15 @@
              conflict.delete(purge=True)
 
 seen=[]
+for obj in Privilege.objects.all():
+     seen.append(obj.id)
+     conflicts = Privilege.objects.filter(accessor_id=obj.accessor_id, object_id=obj.object_id, permission=obj.permission, accessor_type=obj.accessor_type, object_type=obj.object_type)
+     for conflict in conflicts:
+         if conflict.id not in seen:
+             print "Purging", conflict, conflict.id, "due to duplicate of", obj.id
+             conflict.delete(purge=True)
+
+seen=[]
 for obj in SiteDeployment.objects.all():
      seen.append(obj.id)
      conflicts = SiteDeployment.objects.filter(site=obj.site, deployment=obj.deployment, controller=obj.controller)
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index e37aaf1..58e3a81 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -1004,6 +1004,44 @@
                 required: false
                 description: gateway mac address
 
+    tosca.nodes.Privilege:
+        derived_from: tosca.nodes.Root
+        description: >
+            A permission granted to an accessor (a user, slice etc.) towards some type of access
+        capabilities:
+            privilege:
+                type: tosca.capabilities.xos.privilege
+        properties:
+            xos_base_props
+            accessor_id:
+                type: integer
+                required: true
+                description: id of the object representing the accessor
+            accessor_type:
+                type: string
+                required: true
+                description: name of the model representing the accessor
+            object_id:
+                type: integer
+                required: true
+                description: id of the object being accessed
+            object_type:
+                type: string
+                required: true
+                description: name of the model representing the object being accessed
+            permission:
+                type: string
+                required: true
+                description: a custom name that defines the type of access
+            granted:
+                type: string
+                required: false
+                description: time at which the permission was granted
+            expires:
+                type: string
+                required: false
+                description: time at which the permission is set to expire
+
     tosca.nodes.Image:
         derived_from: tosca.nodes.Root
         description: >
@@ -1348,6 +1386,14 @@
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Image ]
 
+    tosca.relationships.HasPrivilege:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabilities.xos.Privilege ]
+
+    tosca.relationships.SupportsPrivilege:
+        derived_from: tosca.relationships.Root
+        valid_target_types: [ tosca.capabilities.xos.Privilege ]
+
     tosca.relationships.ConnectsToSlice:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.Slice ]
@@ -1516,3 +1562,7 @@
     tosca.capabilities.xos.AddressPool:
         derived_from: tosca.capabilities.Root
         description: An XOS AddressPool
+
+    tosca.capabilities.xos.Privilege:
+        derived_from: tosca.capabilities.Root
+        description: An XOS Privilege
diff --git a/xos/tosca/resources/deployment.py b/xos/tosca/resources/deployment.py
index ec53849..bb11e0d 100644
--- a/xos/tosca/resources/deployment.py
+++ b/xos/tosca/resources/deployment.py
@@ -15,7 +15,7 @@
         # Note: support for Flavors and Images is dropped
 
         rolemap = ( ("tosca.relationships.AdminPrivilege", "admin"), )
-        self.postprocess_privileges(DeploymentRole, DeploymentPrivilege, rolemap, obj, "deployment")
+        self.postprocess_privileges(DeploymentRole, 'Deployment', rolemap, obj)
 
     def delete(self, obj):
         if obj.sites.exists():
diff --git a/xos/tosca/resources/slice.py b/xos/tosca/resources/slice.py
index df9fcc9..1a633ba 100644
--- a/xos/tosca/resources/slice.py
+++ b/xos/tosca/resources/slice.py
@@ -45,7 +45,7 @@
 
         rolemap = ( ("tosca.relationships.AdminPrivilege", "admin"), ("tosca.relationships.AccessPrivilege", "access"),
                     ("tosca.relationships.PIPrivilege", "pi"), ("tosca.relationships.TechPrivilege", "tech") )
-        self.postprocess_privileges(SliceRole, SlicePrivilege, rolemap, obj, "slice")
+        self.postprocess_privileges(SliceRole, 'Slice', rolemap, obj)
 
     def delete(self, obj):
         if obj.instances.exists():
diff --git a/xos/tosca/resources/user.py b/xos/tosca/resources/user.py
index f1ff044..87dbaab 100644
--- a/xos/tosca/resources/user.py
+++ b/xos/tosca/resources/user.py
@@ -27,14 +27,14 @@
                 dest = self.engine.name_to_xos_model(self.user, obj_name)
                 if dest.__class__.__name__ == "Slice":
                     role_obj = self.get_xos_object(SliceRole, role=role)
-                    if not SlicePrivilege.objects.filter(user=user, role=role_obj, slice=dest):
-                        sp = SlicePrivilege(user=obj, role=role_obj, slice=dest)
+                    if not Privilege.objects.filter(accessor_id=obj.id, permission='role:'+role_obj.role, object_id=dest.id, accessor_type='User', object_type='Slice'):
+                        sp = Privilege(accessor_id=obj.id, permission='role:'+role_obj.role, object_id=dest.id, accessor_type='User', object_type='Slice')
                         sp.save()
                         self.info("Added slice privilege on %s role %s for %s" % (str(dest), str(role), str(obj)))
                 elif dest.__class__.__name__ == "Site":
                     role_obj = self.get_xos_object(SiteRole, role=role)
-                    if not SitePrivilege.objects.filter(user=obj, role=role_obj, site=dest):
-                        sp = SitePrivilege(user=obj, role=role_obj, site=dest)
+                    if not Privilege.objects.filter(accessor_id=obj.id, permission='role:'+role_obj.role, object_id=dest.id, accessor_type='User', object_type='Site'):
+                        sp = SitePrivilege(accessor_id=obj.id, permission='role:'+role_obj.role, object_id=dest.id, accessor_type='User', object_type='Site')
                         sp.save()
                         self.info("Added site privilege on %s role %s for %s" % (str(dest), str(role), str(obj)))
 
diff --git a/xos/tosca/resources/xosresource.py b/xos/tosca/resources/xosresource.py
index bc33991..d6dd482 100644
--- a/xos/tosca/resources/xosresource.py
+++ b/xos/tosca/resources/xosresource.py
@@ -3,7 +3,7 @@
 import subprocess
 import sys
 
-from core.models import User
+from core.models import User, Privilege
 
 class XOSResource(object):
     xos_base_class = "XOSResource"
@@ -148,7 +148,7 @@
             return False
         return True
 
-    def postprocess_privileges(self, roleclass, privclass, rolemap, obj, toFieldName):
+    def postprocess_privileges(self, roleclass, modelname, rolemap, obj):
         for (rel, role) in rolemap:
             for email in self.get_requirements(rel):
                 role_obj = self.get_xos_object(roleclass, throw_exception=False, role=role)
@@ -159,8 +159,8 @@
                     role_obj.save()
 
                 user = self.get_xos_object(User, email=email)
-                if not privclass.objects.filter(user=user, role=role_obj, **{toFieldName: obj}):
-                    sp = privclass(user=user, role=role_obj, **{toFieldName: obj})
+                if not Privilege.objects.filter(accessor_id=user.id, accessor_type='User', object_type=modelname, permission='role:'+role_obj.role, object_id=obj.id):
+                    sp = Privilege(accessor_id=user.id, accessor_type='User', object_type=modelname, permission='role:'+role_obj.role, object_id=obj.id)
                     sp.save()
                     self.info("Added privilege on %s role %s for %s" % (str(obj), str(role), str(user)))
 
diff --git a/xos/xos_client/xosapi/convenience/privilege.py b/xos/xos_client/xosapi/convenience/privilege.py
new file mode 100644
index 0000000..d8292cf
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/privilege.py
@@ -0,0 +1,6 @@
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+
+class ORMWrapperPrivilege(ORMWrapper):
+    pass
+    
+register_convenience_wrapper("Privilege", ORMWrapperPrivilege)
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index ce815b4..84a326f 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -503,6 +503,7 @@
     return cls(wrapped_class, *args, **kwargs)
 
 import convenience.addresspool
+import convenience.privilege
 import convenience.instance
 import convenience.cordsubscriberroot
 import convenience.volttenant