Add privileges for tenants
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 6448ed4..8ca8ff8 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -879,6 +879,16 @@
def queryset(self, request):
return TenantRootPrivilege.select_by_user(request.user)
+class TenantPrivilegeInline(XOSTabularInline):
+ model = TenantPrivilege
+ extra = 0
+ suit_classes = 'suit-tab suit-tab-tenantprivileges'
+ fields = ['backend_status_icon', 'user', 'role', 'tenant']
+ readonly_fields = ('backend_status_icon', )
+
+ def queryset(self, request):
+ return TenantPrivilege.select_by_user(request.user)
+
class TenantRootAdmin(XOSBaseAdmin):
model = TenantRoot
list_display = ('backend_status_icon', 'name', 'kind')
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index 2ee6b94..f203ba3 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, TenantWithContainer, CoarseTenant, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, Subscriber, Provider
+from .service import Service, Tenant, TenantWithContainer, CoarseTenant, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, TenantPrivilege, TenantRole, Subscriber, Provider
from .service import ServiceAttribute, TenantAttribute, ServiceRole
from .tag import Tag
from .role import Role
@@ -29,4 +29,3 @@
from .network import Network, NetworkParameterType, NetworkParameter, Port, NetworkTemplate, Router, NetworkSlice, ControllerNetwork, AddressPool
from .billing import Account, Invoice, Charge, UsableObject, Payment
from .program import Program
-
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index ee28cf6..97fa890 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -644,14 +644,14 @@
for slice in slices:
if slice.instances.all().count() > 0:
for instance in slice.instances.all():
- #Pick the first instance that has lesser than 5 tenants
+ #Pick the first instance that has lesser than 5 tenants
if self.count_of_tenants_of_an_instance(instance) < 5:
return instance
return None
- #TODO: Ideally the tenant count for an instance should be maintained using a
- #many-to-one relationship attribute, however this model being proxy, it does
- #not permit any new attributes to be defined. Find if any better solutions
+ #TODO: Ideally the tenant count for an instance should be maintained using a
+ #many-to-one relationship attribute, however this model being proxy, it does
+ #not permit any new attributes to be defined. Find if any better solutions
def count_of_tenants_of_an_instance(self, instance):
tenant_count = 0
for tenant in self.get_tenant_objects().all():
@@ -805,4 +805,41 @@
return cls.objects.filter(id__in=trp_ids)
+class TenantRole(PlCoreBase):
+ ROLE_CHOICES = (('admin','Admin'), ('access','Access'))
+ role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
+
+ def __unicode__(self): return u'%s' % (self.role)
+
+class TenantPrivilege(PlCoreBase):
+ user = models.ForeignKey('User', related_name="tenant_privileges")
+ tenant = models.ForeignKey('Tenant', related_name="tenant_privileges")
+ role = models.ForeignKey('TenantRole', related_name="tenant_privileges")
+
+ class Meta:
+ unique_together = ('user', 'tenant', 'role')
+
+ def __unicode__(self): return u'%s %s %s' % (self.tenant, self.user, self.role)
+
+ def save(self, *args, **kwds):
+ if not self.user.is_active:
+ raise PermissionDenied, "Cannot modify role(s) of a disabled user"
+ super(TenantPrivilege, self).save(*args, **kwds)
+
+ def can_update(self, user):
+ return user.can_update_tenant_privilege(self)
+
+ @classmethod
+ def select_by_user(cls, user):
+ if user.is_admin:
+ return cls.objects.all()
+ else:
+ # User can see his own privilege
+ trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
+
+ # A tenant admin can see the TenantPrivileges for their Tenants
+ for priv in cls.objects.filter(user=user, role__role="admin"):
+ trp_ids.extend( [trp.id for trp in cls.objects.filter(tenant=priv.tenant)] )
+
+ return cls.objects.filter(id__in=trp_ids)
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 0b8e3af..a8ed571 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -165,7 +165,7 @@
purge = purge or observer_disabled
except NameError:
pass
-
+
if (purge):
super(User, self).delete(*args, **kwds)
else:
@@ -219,7 +219,7 @@
# roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
# for slice_membership in slice_memberships:
# roles[slice_membership.role.role_type].append(slice_membership.slice.name)
-# return roles
+# return roles
def save(self, *args, **kwds):
if not self.id:
@@ -254,7 +254,7 @@
site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
for site_priv in site_privs:
if site_priv.role.role == 'admin':
- return True
+ return True
if site_priv.role.role == 'pi':
for fieldName in self.diff.keys():
if fieldName in self.PI_FORBIDDEN_FIELDS:
@@ -272,26 +272,26 @@
def can_update_root(self):
"""
- Return True if user has root (global) write access.
+ Return True if user has root (global) write access.
"""
if self.is_readonly:
return False
if self.is_admin:
return True
- return False
+ return False
def can_update_deployment(self, deployment):
from core.models.site import DeploymentPrivilege
if self.can_update_root():
- return True
-
+ return True
+
if DeploymentPrivilege.objects.filter(
deployment=deployment,
user=self,
role__role__in=['admin', 'Admin']):
return True
- return False
+ return False
def can_update_site(self, site, allow=[]):
from core.models.site import SitePrivilege
@@ -301,7 +301,7 @@
site=site, user=self, role__role__in=['admin', 'Admin']+allow):
return True
return False
-
+
def can_update_slice(self, slice):
from core.models.slice import SlicePrivilege
if self.can_update_root():
@@ -310,7 +310,7 @@
return True
if self.can_update_site(slice.site, allow=['pi']):
return True
-
+
if SlicePrivilege.objects.filter(
slice=slice, user=self, role__role__in=['admin', 'Admin']):
return True
@@ -334,9 +334,21 @@
return True
return False
+ def can_update_tenant(self, tenant, allow=[]):
+ from core.models.service import Tenant, TenantPrivilege
+ if self.can_update_root():
+ return True
+ if TenantPrivilege.objects.filter(
+ tenant=tenant, user=self, role__role__in=['admin', 'Admin']+allow):
+ return True
+ return False
+
def can_update_tenant_root_privilege(self, tenant_root_privilege, allow=[]):
return self.can_update_tenant_root(tenant_root_privilege.tenant_root, allow)
+ 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
@@ -351,18 +363,18 @@
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.
+ """ 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
-
+ list of dicts
+
"""
- from core.models import Deployment, Flavor, Image, Network, NetworkTemplate, Node, PlModelMixIn, Site, Slice, SliceTag, Instance, Tag, User, DeploymentPrivilege, SitePrivilege, SlicePrivilege
+ 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 = []
@@ -371,8 +383,8 @@
deployment_priv_objs = [Image, NetworkTemplate, Flavor]
site_priv_objs = [Node, Slice, User]
- slice_priv_objs = [Instance, Network]
-
+ slice_priv_objs = [Instance, Network]
+
# maps the set of objects a paticular role has write access
write_map = {
DeploymentPrivilege : {
@@ -382,12 +394,12 @@
'admin' : site_priv_objs,
'pi' : [Slice, User],
'tech': [Node],
- },
+ },
SlicePrivilege : {
- 'admin': slice_priv_objs,
- },
+ 'admin': slice_priv_objs,
+ },
}
-
+
privilege_map = {
DeploymentPrivilege : (Deployment, deployment_priv_objs),
SitePrivilege : (Site, site_priv_objs),
@@ -399,7 +411,7 @@
if models and model not in models:
continue
- # get the objects affected by this privilege model
+ # 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))
@@ -410,7 +422,7 @@
permissions.append(permission_dict(affected_object, READWRITE))
else:
# create a dict of the user's per object privileges
- # ex: {princeton_tmack : ['admin']
+ # ex: {princeton_tmack : ['admin']
privileges = privilege_model.objects.filter(user=self)
for privilege in privileges:
object_roles = defaultdict(list)
@@ -421,7 +433,7 @@
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:
@@ -438,15 +450,15 @@
permissions.append(permission_dict(affected_object, WRITE))
else:
permissions.append(permission_dict(affected_object, READ))
-
- return permissions
-
+
+ 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:
diff --git a/xos/core/xoslib/methods/vpnview.py b/xos/core/xoslib/methods/vpnview.py
index 7a101f5..0e2c376 100644
--- a/xos/core/xoslib/methods/vpnview.py
+++ b/xos/core/xoslib/methods/vpnview.py
@@ -62,6 +62,7 @@
def get_queryset(self):
queryset = VPNTenant.get_tenant_objects().all()
+ queryset = [ tenant for tenant in queryset if self.request.user.can_update_tenant(tenant, ['access','Access'])]
for tenant in queryset:
tenant.script_text = tenant.create_client_script()
return queryset
diff --git a/xos/services/vpn/admin.py b/xos/services/vpn/admin.py
index 35e1174..0e2362f 100644
--- a/xos/services/vpn/admin.py
+++ b/xos/services/vpn/admin.py
@@ -1,6 +1,6 @@
import time
-from core.admin import ReadOnlyAwareAdmin, SliceInline
+from core.admin import ReadOnlyAwareAdmin, SliceInline, TenantPrivilegeInline
from core.middleware import get_request
from core.models import User
from django import forms
@@ -126,12 +126,39 @@
'classes': ['suit-tab suit-tab-general']})]
readonly_fields = ('backend_status_text', 'instance')
form = VPNTenantForm
+ inlines = [TenantPrivilegeInline]
- suit_form_tabs = (('general', 'Details'),)
+ suit_form_tabs = (('general', 'Details'),
+ ('tenantprivileges','Privileges')
+ )
def queryset(self, request):
return VPNTenant.get_tenant_objects_by_user(request.user)
+ def certificate_name(self, tenant_privilege):
+ return str(tenant_privilege.user.email) + "-" + str(tenant_privilege.tenant.id)
+
+ def save_formset(self, request, form, formset, change):
+ super(VPNTenantAdmin, self).save_formset(request, form, formset, change)
+ for obj in formset.deleted_objects:
+ # If anything deleated was a TenantPrivilege then revoke the certificate
+ if type(obj) is TenantPrivilege:
+ certificate = self.certificate_name(obj)
+ # revoke the cert
+ pass
+ # TODO(jermowery): determine if this is necessary.
+ # if type(obj) is VPNTenant:
+ # if the tenant was deleted revoke all certs assoicated
+ # pass
+
+ for obj in formset.new_objects:
+ # If there were any new TenantPrivlege objects then create certs
+ if type(obj) is TenantPrivilege:
+ certificate = self.certificate_name(obj)
+ # create the cert
+ pass
+
+
# Associate the admin forms with the models.
admin.site.register(VPNService, VPNServiceAdmin)
admin.site.register(VPNTenant, VPNTenantAdmin)