CORD-1570: Re-implementation of XOS Security via xproto at the API boundary
Change-Id: I9cb6380b0798a5f4af2f0459c5decd0b9edbb317
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py b/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
index 0c8513a..4552d59 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
@@ -422,7 +422,7 @@
if not tag:
tag = gen_random_string()
- policy_function_name_template = 'policy_%s_' + '%(random_string)s' % {'random_string': tag}
+ policy_function_name_template = '%s_' + '%(random_string)s' % {'random_string': tag}
policy_function_name = policy_function_name_template % policy_name
self.verdict_next()
@@ -636,7 +636,7 @@
if fol_reduced in ['True','False'] and fol != fol_reduced:
raise TrivialPolicy("Policy %(name)s trivially reduces to %(reduced)s. If this is what you want, replace its contents with %(reduced)s"%{'name':policy, 'reduced':fol_reduced})
- a = f2p.gen_test_function(fol_reduced, policy, tag='enforcer')
+ a = f2p.gen_test_function(fol_reduced, policy, tag='security_check')
return astunparse.unparse(a)
diff --git a/lib/xos-genx/xosgenx/targets/django-security.xtarget b/lib/xos-genx/xosgenx/targets/django-security.xtarget
new file mode 100644
index 0000000..d970cea
--- /dev/null
+++ b/lib/xos-genx/xosgenx/targets/django-security.xtarget
@@ -0,0 +1,9 @@
+from privilege import Privilege
+from django.db.models import Q
+
+{% for m in proto.messages %}
+{% if m.policy %}
+{{ xproto_fol_to_python_test(m.policy, proto.policies[m.policy], m) }}
+{% endif %}
+
+{% endfor %}
diff --git a/lib/xos-genx/xosgenx/targets/django-split.xtarget b/lib/xos-genx/xosgenx/targets/django-split.xtarget
index 5260e85..60cf9de 100644
--- a/lib/xos-genx/xosgenx/targets/django-split.xtarget
+++ b/lib/xos-genx/xosgenx/targets/django-split.xtarget
@@ -4,13 +4,17 @@
{%- for l in m.links %}
{% if l.peer.name != m.name %}
-from core.models.{{ l.peer.name | lower }} import {{ l.peer.name }}
+from {{ l.peer.name | lower }} import {{ l.peer.name }}
{% endif %}
{%- endfor %}
+{% if m.name!='XOSBase' and 'Mixin' not in m.name %}
+import security
+from privilege import Privilege
+{% endif %}
{% for b in m.bases %}
{% if b.name!='XOSBase' and 'Mixin' not in b.name %}
-from core.models.{{b.name | lower}} import {{ b.name }}
+from {{b.name | lower}} import {{ b.name }}
{% endif %}
{% endfor %}
@@ -40,9 +44,9 @@
unique_together = {{ xproto_tuplify(uniques) }}
{%- endif %}
{% if file_exists(m.name|lower + '_model.py') -%}{{ include_file(m.name|lower + '_model.py') | indent(width=2)}}{%- endif %}
+ pass
{% if m.name!='XOSBase' and 'Mixin' not in m.name %}
-
# Generated methods
def save(self, *args, **kwds):
if not self.leaf_model_name:
@@ -58,7 +62,17 @@
{% endfor %}
super({{ m.name }}, self).save(*args, **kwds)
+ def can_access(self, ctx):
+ {% if m.policy %}
+ verdict = security.{{m.policy}}_security_check(self, ctx)
+ return verdict,"{{ m.policy }}"
+ {% else %}
+ verdict = XOS_GLOBAL_DEFAULT_SECURITY_POLICY
+ return verdict,"xos_default_policy"
+ {% endif %}
+
{% endif %}
+
{% if file_exists(xproto_base_name(m.name)|lower+'_bottom.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_bottom.py') }}{% endif %}
+++ {{m.name|lower}}.py
{% endif %}{% endfor %}
diff --git a/lib/xos-genx/xosgenx/targets/django.xtarget b/lib/xos-genx/xosgenx/targets/django.xtarget
index e9a56ec..1c8ce93 100644
--- a/lib/xos-genx/xosgenx/targets/django.xtarget
+++ b/lib/xos-genx/xosgenx/targets/django.xtarget
@@ -4,13 +4,19 @@
{%- for l in m.links %}
{% if l.peer.name != m.name %}
-from core.models.{{ l.peer.name | lower }} import {{ l.peer.name }}
+from {{ l.peer.name | lower }} import {{ l.peer.name }}
{% endif %}
{%- endfor %}
+{% if m.name!='XOSBase' and 'Mixin' not in m.name %}
+import security
+{% if m.name!='Privilege' %}
+from privilege import Privilege
+{% endif %}
+{% endif %}
{% for b in m.bases %}
{% if b.name!='XOSBase' and 'Mixin' not in b.name %}
-from core.models.{{b.name | lower}} import {{ b.name }}
+from {{b.name | lower}} import {{ b.name }}
{% endif %}
{% endfor %}
@@ -57,6 +63,16 @@
policy_{{policy}}_validator(self, None)
{% endfor %}
super({{ m.name }}, self).save(*args, **kwds)
+
+ def can_access(self, ctx):
+ {% if m.policy %}
+ verdict = security.{{m.policy}}_security_check(self, ctx)
+ return verdict,"{{ m.policy }}"
+ {% else %}
+ verdict = XOS_GLOBAL_DEFAULT_SECURITY_POLICY
+ return verdict,"xos_default_policy"
+ {% endif %}
+
{% endif %}
{% if file_exists(xproto_base_name(m.name)|lower+'_bottom.py') -%}{{ include_file(xproto_base_name(m.name)|lower+'_bottom.py') }}{% endif %}
diff --git a/lib/xos-genx/xosgenx/targets/grpc_api.xtarget b/lib/xos-genx/xosgenx/targets/grpc_api.xtarget
index a0373a3..4d05870 100644
--- a/lib/xos-genx/xosgenx/targets/grpc_api.xtarget
+++ b/lib/xos-genx/xosgenx/targets/grpc_api.xtarget
@@ -21,19 +21,19 @@
def List{{ object.name }}(self, request, context):
user=self.authenticate(context)
model=self.get_model("{{ object.name }}")
- return self.querysetToProto(model, model.objects.all())
+ return self.list(model, user)
@translate_exceptions
def Filter{{ object.name }}(self, request, context):
user=self.authenticate(context)
model=self.get_model("{{ object.name }}")
- return self.filter(model, request)
+ return self.filter(model, user, request)
@translate_exceptions
def Get{{ object.name }}(self, request, context):
user=self.authenticate(context)
model=self.get_model("{{ object.name }}")
- return self.get(model, request.id)
+ return self.get(model, user, request.id)
@translate_exceptions
def Create{{ object.name }}(self, request, context):
diff --git a/lib/xos-genx/xosgenx/targets/service.xtarget b/lib/xos-genx/xosgenx/targets/service.xtarget
index 5336f82..bf14b83 100644
--- a/lib/xos-genx/xosgenx/targets/service.xtarget
+++ b/lib/xos-genx/xosgenx/targets/service.xtarget
@@ -73,6 +73,15 @@
policy_{{policy}}_validator(self, None)
{% endfor %}
super({{ m.name }}{{ legacy_tag }}, self).save(*args, **kwds)
+
+ def can_access(self, ctx):
+ {% if m.policy %}
+ verdict = security.{{m.policy}}_security_check(self, ctx)
+ return verdict,"{{ m.policy }}"
+ {% else %}
+ verdict = True
+ return verdict,"xos_default_policy"
+ {% endif %}
{% if file_exists(m.name|lower+'_bottom.py') -%}{{ include_file(m.name|lower+'_bottom.py') }}{% endif %}
{% endfor %}
diff --git a/xos/api/xosapi_helpers.py b/xos/api/xosapi_helpers.py
index 90163e6..ae27d82 100644
--- a/xos/api/xosapi_helpers.py
+++ b/xos/api/xosapi_helpers.py
@@ -65,8 +65,6 @@
user = self.context['request'].user
if user.__class__.__name__=="AnonymousUser":
raise XOSPermissionDenied()
- if not instance.can_update(user):
- raise XOSPermissionDenied()
for k in validated_data:
if k in property_fields:
@@ -142,8 +140,6 @@
user = self.request.user
if user.__class__.__name__=="AnonymousUser":
raise XOSPermissionDenied()
- if not obj.can_update(user):
- raise XOSPermissionDenied()
return obj
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 152ef1c..72d56dc 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -144,16 +144,16 @@
obj.caller = request.user
# update openstack connection to use this site/tenant
- obj.save_by_user(request.user)
+ obj.save()
def delete_model(self, request, obj):
- obj.delete_by_user(request.user)
+ obj.delete()
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
instance.caller = request.user
- instance.save_by_user(request.user)
+ instance.save()
# BUG in django 1.7? Objects are not deleted by formset.save if
# commit is False. So let's delete them ourselves.
@@ -830,10 +830,10 @@
def save_model(self, request, obj, form, change):
# update openstack connection to use this site/tenant
- obj.save_by_user(request.user)
+ obj.save()
def delete_model(self, request, obj):
- obj.delete_by_user(request.user)
+ obj.delete()
def queryset(self, request):
return Controller.select_by_user(request.user)
@@ -1047,10 +1047,10 @@
def save_model(self, request, obj, form, change):
# update openstack connection to use this site/tenant
- obj.save_by_user(request.user)
+ obj.save()
def delete_model(self, request, obj):
- obj.delete_by_user(request.user)
+ obj.delete()
class SitePrivilegeAdmin(XOSBaseAdmin):
diff --git a/xos/core/models/attic/controller_model.py b/xos/core/models/attic/controller_model.py
index 9e76572..8b13789 100644
--- a/xos/core/models/attic/controller_model.py
+++ b/xos/core/models/attic/controller_model.py
@@ -1,9 +1 @@
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = Controller.objects.all()
- else:
- 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/controllernetwork_model.py b/xos/core/models/attic/controllernetwork_model.py
index 43bec3d..5879050 100644
--- a/xos/core/models/attic/controllernetwork_model.py
+++ b/xos/core/models/attic/controllernetwork_model.py
@@ -6,15 +6,3 @@
except:
pass
return d
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = ControllerNetwork.objects.all()
- else:
- from core.models.slice import Slice
- slices = Slice.select_by_user(user)
- networks = Network.objects.filter(owner__in=slices)
- qs = ControllerNetwork.objects.filter(network__in=networks)
- return qs
-
diff --git a/xos/core/models/attic/controllersiteprivilege_model.py b/xos/core/models/attic/controllersiteprivilege_model.py
deleted file mode 100644
index 11ba22d..0000000
--- a/xos/core/models/attic/controllersiteprivilege_model.py
+++ /dev/null
@@ -1,20 +0,0 @@
-def can_update(self, user):
- if user.is_readonly:
- return False
- if user.is_admin:
- return True
-
- 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 = ControllerPrivilege.objects.filter(privilege__object_type='Site')
- else:
- 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/controllerslice_model.py b/xos/core/models/attic/controllerslice_model.py
index d8f4b4d..9e4a3d0 100644
--- a/xos/core/models/attic/controllerslice_model.py
+++ b/xos/core/models/attic/controllerslice_model.py
@@ -6,13 +6,3 @@
except:
pass
return d
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = ControllerSlice.objects.all()
- else:
- slices = Slice.select_by_user(user)
- qs = ControllerSlice.objects.filter(slice__in=slices)
- return qs
-
diff --git a/xos/core/models/attic/controllersliceprivilege_model.py b/xos/core/models/attic/controllersliceprivilege_model.py
deleted file mode 100644
index ab64569..0000000
--- a/xos/core/models/attic/controllersliceprivilege_model.py
+++ /dev/null
@@ -1,19 +0,0 @@
-def can_update(self, user):
- if user.is_readonly:
- return False
- if user.is_admin:
- return True
- 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 = ControllerPrivilege.objects.filter(privilege__object_type='Slice')
- else:
- 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/controlleruser_model.py b/xos/core/models/attic/controlleruser_model.py
deleted file mode 100644
index ae2f3ac..0000000
--- a/xos/core/models/attic/controlleruser_model.py
+++ /dev/null
@@ -1,12 +0,0 @@
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = ControllerUser.objects.all()
- else:
- users = User.select_by_user(user)
- qs = ControllerUser.objects.filter(user__in=users)
- return qs
-
-def can_update(self, user):
- return user.can_update_root()
-
diff --git a/xos/core/models/attic/deployment_model.py b/xos/core/models/attic/deployment_model.py
index d9da903..0448f02 100644
--- a/xos/core/models/attic/deployment_model.py
+++ b/xos/core/models/attic/deployment_model.py
@@ -29,7 +29,3 @@
ids.append(deployment.id)
return Deployment.objects.filter(id__in=ids)
-
-def can_update(self, user):
- return user.can_update_deployment(self)
-
diff --git a/xos/core/models/attic/deploymentprivilege_model.py b/xos/core/models/attic/deploymentprivilege_model.py
deleted file mode 100644
index 8fc40d4..0000000
--- a/xos/core/models/attic/deploymentprivilege_model.py
+++ /dev/null
@@ -1,12 +0,0 @@
-def can_update(self, user):
- return user.can_update_deployment(self)
-
-@staticmethod
-def select_by_user(user):
- from core.models.deploymentprivilege import DeploymentPrivilege
- if user.is_admin:
- qs = DeploymentPrivilege.objects.all()
- else:
- dpriv_ids = [dp.id for dp in DeploymentPrivilege.objects.filter(user=user)]
- qs = DeploymentPrivilege.objects.filter(id__in=dpriv_ids)
- return qs
diff --git a/xos/core/models/attic/header.py b/xos/core/models/attic/header.py
index a03046a..b6b8d31 100644
--- a/xos/core/models/attic/header.py
+++ b/xos/core/models/attic/header.py
@@ -5,7 +5,6 @@
import operator
from operator import attrgetter
from core.models.xosbase import *
-from core.models.privilege import *
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.utils.timezone import now
diff --git a/xos/core/models/attic/imagedeployments_model.py b/xos/core/models/attic/imagedeployments_model.py
deleted file mode 100644
index ad51017..0000000
--- a/xos/core/models/attic/imagedeployments_model.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def can_update(self, user):
- return user.can_update_deployment(self.deployment)
diff --git a/xos/core/models/attic/instance_model.py b/xos/core/models/attic/instance_model.py
index 3ea46fb..2ce52f7 100644
--- a/xos/core/models/attic/instance_model.py
+++ b/xos/core/models/attic/instance_model.py
@@ -16,9 +16,6 @@
if not self.creator and hasattr(self, 'caller'):
self.creator = self.caller
-def can_update(self, user):
- return user.can_update_slice(self.slice)
-
def all_ips(self):
ips={}
for ns in self.ports.all():
@@ -63,15 +60,6 @@
# if all else fails, look for nat-net (for OpenCloud?)
return self.get_network_ip("nat")
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = Instance.objects.all()
- else:
- slices = Slice.select_by_user(user)
- qs = Instance.objects.filter(slice__in=slices)
- return qs
-
def get_ssh_command(self):
if (not self.instance_id) or (not self.node) or (not self.instance_name):
return None
diff --git a/xos/core/models/attic/network_model.py b/xos/core/models/attic/network_model.py
deleted file mode 100644
index c58639a..0000000
--- a/xos/core/models/attic/network_model.py
+++ /dev/null
@@ -1,14 +0,0 @@
-def can_update(self, user):
- return user.can_update_slice(self.owner)
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = Network.objects.all()
- else:
- slices = Slice.select_by_user(user)
- #slice_ids = [s.id for s in Slice.select_by_user(user)]
- qs = Network.objects.filter(owner__in=slices)
- return qs
-
-
diff --git a/xos/core/models/attic/networkslice_model.py b/xos/core/models/attic/networkslice_model.py
deleted file mode 100644
index 1f33390..0000000
--- a/xos/core/models/attic/networkslice_model.py
+++ /dev/null
@@ -1,13 +0,0 @@
-def can_update(self, user):
- return user.can_update_slice(self.slice)
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = NetworkSlice.objects.all()
- else:
- slice_ids = [s.id for s in Slice.select_by_user(user)]
- network_ids = [network.id for network in Network.select_by_user(user)]
- qs = NetworkSlice.objects.filter(Q(slice__in=slice_ids) | Q(network__in=network_ids))
- return qs
-
diff --git a/xos/core/models/attic/node_model.py b/xos/core/models/attic/node_model.py
deleted file mode 100644
index 763f480..0000000
--- a/xos/core/models/attic/node_model.py
+++ /dev/null
@@ -1,3 +0,0 @@
-def can_update(self, user):
- return user.can_update_site(self.site_deployment.site, allow=['tech'])
-
diff --git a/xos/core/models/attic/port_model.py b/xos/core/models/attic/port_model.py
deleted file mode 100644
index 57617c6..0000000
--- a/xos/core/models/attic/port_model.py
+++ /dev/null
@@ -1,19 +0,0 @@
-def can_update(self, user):
- if self.instance:
- return user.can_update_slice(self.instance.slice)
- if self.network:
- return user.can_update_slice(self.network.owner)
- return False
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = Port.objects.all()
- else:
- instances = Instance.select_by_user(user)
- instance_ids = [instance.id for instance in instances]
- networks = Network.select_by_user(user)
- network_ids = [network.id for network in networks]
- qs = Port.objects.filter(Q(instance__in=instance_ids) | Q(network__in=network_ids))
- return qs
-
diff --git a/xos/core/models/attic/service_model.py b/xos/core/models/attic/service_model.py
index 9cb2d4d..5b305cd 100644
--- a/xos/core/models/attic/service_model.py
+++ b/xos/core/models/attic/service_model.py
@@ -5,16 +5,6 @@
self._meta.get_field("kind").default = self.KIND
super(Service, self).__init__(*args, **kwargs)
-@classmethod
-def select_by_user(cls, user):
- if user.is_admin:
- return cls.objects.all()
- else:
- from core.models.privilege import Privilege
- service_ids = [
- 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
def serviceattribute_dict(self):
attrs = {}
@@ -22,9 +12,6 @@
attrs[attr.name] = attr.value
return attrs
-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.
diff --git a/xos/core/models/attic/serviceprivilege_model.py b/xos/core/models/attic/serviceprivilege_model.py
deleted file mode 100644
index bac83cc..0000000
--- a/xos/core/models/attic/serviceprivilege_model.py
+++ /dev/null
@@ -1,7 +0,0 @@
-@classmethod
-def select_by_user(cls, user):
- if user.is_admin:
- qs = cls.objects.all()
- else:
- qs = cls.objects.filter(user=user)
- return qs
diff --git a/xos/core/models/attic/site_model.py b/xos/core/models/attic/site_model.py
deleted file mode 100644
index f16b1a8..0000000
--- a/xos/core/models/attic/site_model.py
+++ /dev/null
@@ -1,4 +0,0 @@
-def can_update(self, user):
- return user.can_update_site(self, allow=['pi'])
-
-
diff --git a/xos/core/models/attic/siteprivilege_model.py b/xos/core/models/attic/siteprivilege_model.py
deleted file mode 100644
index 0a05b5f..0000000
--- a/xos/core/models/attic/siteprivilege_model.py
+++ /dev/null
@@ -1,12 +0,0 @@
-def can_update(self, user):
- return user.can_update_site(self, allow=['pi'])
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = SitePrivilege.objects.all()
- else:
- sp_ids = [sp.id for sp in SitePrivilege.objects.filter(user=user)]
- qs = SitePrivilege.objects.filter(id__in=sp_ids)
- return qs
-
diff --git a/xos/core/models/attic/slice_model.py b/xos/core/models/attic/slice_model.py
index 37ec321..eaa5b9d 100644
--- a/xos/core/models/attic/slice_model.py
+++ b/xos/core/models/attic/slice_model.py
@@ -26,26 +26,3 @@
# "Private Only" was the default from the old Tenant View
self.network=None
self.enforce_choices(self.network, self.NETWORK_CHOICES)
-
-def can_update(self, user):
- return user.can_update_slice(self)
-
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = Slice.objects.all()
- else:
- from core.models.privilege import Privilege
- # users can see slices they belong to
- 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
- 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/sliceprivilege_model.py b/xos/core/models/attic/sliceprivilege_model.py
deleted file mode 100644
index e2599a3..0000000
--- a/xos/core/models/attic/sliceprivilege_model.py
+++ /dev/null
@@ -1,23 +0,0 @@
-def can_update(self, user):
- return user.can_update_slice(self.slice)
-
-@staticmethod
-def select_by_user(user):
- if user.is_admin:
- qs = SlicePrivilege.objects.all()
- else:
- # You can see your own SlicePrivileges
- sp_ids = [sp.id for sp in SlicePrivilege.objects.filter(user=user)]
-
- from core.models.siteprivilege import SitePrivilege
- # A site pi or site admin can see the SlicePrivileges for all slices in his Site
- for priv in SitePrivilege.objects.filter(user=user):
- if priv.role.role in ['pi', 'admin']:
- sp_ids.extend( [sp.id for sp in SlicePrivilege.objects.filter(slice__site = priv.site)] )
-
- # A slice admin can see the SlicePrivileges for his Slice
- for priv in SlicePrivilege.objects.filter(user=user, role__role="admin"):
- sp_ids.extend( [sp.id for sp in SlicePrivilege.objects.filter(slice=priv.slice)] )
-
- qs = SlicePrivilege.objects.filter(id__in=sp_ids)
- return qs
diff --git a/xos/core/models/attic/tag_model.py b/xos/core/models/attic/tag_model.py
deleted file mode 100644
index 364baa2..0000000
--- a/xos/core/models/attic/tag_model.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def can_update(self, user):
- return user.can_update_root()
-
-@staticmethod
-def select_by_user(user):
- return Tag.objects.all()
diff --git a/xos/core/models/attic/tenantroot_model.py b/xos/core/models/attic/tenantroot_model.py
index 86208cd..a31a3cc 100644
--- a/xos/core/models/attic/tenantroot_model.py
+++ b/xos/core/models/attic/tenantroot_model.py
@@ -5,9 +5,6 @@
self._meta.get_field("kind").default = self.KIND
super(TenantRoot, self).__init__(*args, **kwargs)
-def can_update(self, user):
- return user.can_update_tenant_root(self, allow=['admin'])
-
def get_subscribed_tenants(self, tenant_class):
ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
return tenant_class.objects.filter(id__in=ids)
@@ -18,16 +15,6 @@
return None
return sorted(st, key=attrgetter('id'))[0]
-@classmethod
-def select_by_user(cls, user):
- if user.is_admin:
- return cls.objects.all()
- else:
- from core.models.privilege import Privilege
- tr_ids = [
- 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
# service_specific_id is unique
def validate_unique_service_specific_id(self, none_okay=False):
diff --git a/xos/core/models/attic/xosbase_header.py b/xos/core/models/attic/xosbase_header.py
index 5848900..93df83d 100644
--- a/xos/core/models/attic/xosbase_header.py
+++ b/xos/core/models/attic/xosbase_header.py
@@ -19,6 +19,8 @@
import redis
from redis import ConnectionError
+XOS_GLOBAL_DEFAULT_SECURITY_POLICY = True
+
def date_handler(obj):
if isinstance(obj, pytz.tzfile.DstTzInfo):
# json can't serialize DstTzInfo
diff --git a/xos/core/models/attic/xosbase_model.py b/xos/core/models/attic/xosbase_model.py
index 98746c6..dc7e5ed 100644
--- a/xos/core/models/attic/xosbase_model.py
+++ b/xos/core/models/attic/xosbase_model.py
@@ -15,9 +15,6 @@
def get_controller(self):
return self.controller
-def can_update(self, user):
- return user.can_update_root()
-
def delete(self, *args, **kwds):
# so we have something to give the observer
purge = kwds.get('purge',False)
@@ -131,26 +128,6 @@
self._initial = self._dict
-def save_by_user(self, user, *args, **kwds):
- if not self.can_update(user):
- if getattr(self, "_cant_update_fieldName", None) is not None:
- raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
- else:
- raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
-
- self.save(*args, **kwds)
-
-def delete_by_user(self, user, *args, **kwds):
- if not self.can_update(user):
- raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
- self.delete(*args, **kwds)
-
-@classmethod
-def select_by_user(cls, user):
- # This should be overridden by descendant classes that want to perform
- # filtering of visible objects by user.
- return cls.objects.all()
-
def tologdict(self):
try:
d = {'model_name':self.__class__.__name__, 'pk': self.pk}
diff --git a/xos/core/models/core.xproto b/xos/core/models/core.xproto
index 524a62c..b0c6a31 100644
--- a/xos/core/models/core.xproto
+++ b/xos/core/models/core.xproto
@@ -20,7 +20,18 @@
required string leaf_model_name = 15 [null = False, max_length = 1024, help_text = "The most specialized model in this chain of inheritance, often defined by a service developer"];
}
-message User (AbstractBaseUser,PlModelMixIn) {
+// The calling user represents the user being accessed, or is a site admin.
+policy user_policy <
+ ctx.user.is_admin
+ | ctx.user.id = obj.id
+ | (exists Privilege:
+ Privilege.accessor_id = ctx.user.id
+ & Privilege.accessor_type = "User"
+ & Privilege.permission = "role:admin"
+ & Privilege.object_type = "Site"
+ & Privilege.object_id = ctx.user.site.id) >
+
+message User::user_policy (AbstractBaseUser,PlModelMixIn) {
option skip_django = True;
option tosca_description = "An XOS User";
@@ -58,14 +69,23 @@
optional string policy_status = 32 [default = "0 - Policy in process", max_length = 1024];
}
-message Privilege (XOSBase) {
+// A user may give a permission that he has to another user
+policy grant_policy < ctx.user.is_admin
+ | exists Privilege:Privilege.object_type = obj.object_type
+ & Privilege.object_id = obj.object_id
+ & Privilege.accessor_type = "User"
+ & Privilege.accessor_id = ctx.user.id
+ & Privilege.permission = "role:admin" >
+
+message Privilege::grant_policy (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];
+ required int32 controller_id = 3 [null = True];
+ required int32 object_id = 4 [null = False];
+ required string object_type = 5 [null = False, max_length=1024];
+ required string permission = 6 [null = False, default = "all", max_length=1024];
+ required string granted = 7 [content_type = "date", auto_now_add = True, max_length=1024];
+ required string expires = 8 [content_type = "date", null = True, max_length=1024];
}
message AddressPool (XOSBase) {
@@ -78,8 +98,16 @@
optional manytoone service->Service:addresspools = 7 [db_index = True, null = True, blank = True];
}
+// Admins at a deployment have access to controllers at those deployments
+policy controller_policy
+ < ctx.user.is_admin
+ | exists Privilege:
+ Privilege.accessor_id = ctx.user.id
+ & Privilege.object_type = "Deployment"
+ & Privilege.permission = "role:admin"
+ & Privilege.object_id = obj.id >
-message Controller (XOSBase) {
+message Controller::controller_policy (XOSBase) {
required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Controller", null = False, db_index = False];
required string backend_type = 2 [max_length = 200, content_type = "stripped", blank = False, help_text = "Type of compute controller, e.g. EC2, OpenStack, or OpenStack version", null = False, db_index = False];
required string version = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Controller version", null = False, db_index = False];
@@ -109,8 +137,47 @@
optional string glance_image_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Glance image id", null = True, db_index = False];
}
+// Everyone has read access
+// For write access, you have to be a site_admin
-message ControllerNetwork (XOSBase) {
+policy site_policy <
+ ctx.user.is_admin
+ | (ctx.write_access -> exists Privilege: Privilege.object_type = "Site" & Privilege.object_id = obj.id & Privilege.accessor_id = ctx.user.id & Privilege.permission = "role:admin") >
+
+// If you can access (read or write) the site, you can also access its slices
+// Otherwise, you need an explicit privilege on the Slice (admin for write access)
+// or admin privilege on the associated site.
+policy slice_policy <
+ ctx.user.is_admin
+ | (*site_policy(site)
+ & (ctx.user.id = obj.creator.id
+ | (exists Privilege:
+ Privilege.accessor_id = ctx.user.id
+ & Privilege.accessor_type = "User"
+ & Privilege.object_type = "Slice"
+ & Privilege.object_id = obj.id
+ & (ctx.write_access -> Privilege.permission = "role:admin"))
+ )
+ |
+ (exists Privilege:
+ Privilege.accessor_id = ctx.user.id
+ & Privilege.accessor_type = "User"
+ & Privilege.object_type = "Slice"
+ & Privilege.object_id = obj.id)
+ | (exists Privilege:
+ Privilege.accessor_id = ctx.user.id
+ & Privilege.accessor_type = "User"
+ & Privilege.object_type = "Site"
+ & Privilege.object_id = obj.site.id
+ & Privilege.permission = "role:admin")
+ ) >
+
+policy controller_network_policy <
+ ctx.user.is_admin
+ | *slice_policy(network.owner) >
+
+
+message ControllerNetwork::controller_network_policy (XOSBase) {
required manytoone network->Network:controllernetworks = 1 [db_index = True, null = False, blank = False, unique_with = "controller"];
required manytoone controller->Controller:controllernetworks = 2 [db_index = True, null = False, blank = False];
required string subnet = 3 [db_index = False, max_length = 32, null = False, blank = True];
@@ -147,8 +214,11 @@
optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
}
+policy controller_slice_policy <
+ ctx.user.is_admin
+ | *slice_policy(slice) >
-message ControllerSlice (XOSBase) {
+message ControllerSlice::controller_slice_policy (XOSBase) {
required manytoone controller->Controller:controllerslices = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
required manytoone slice->Slice:controllerslices = 2 [db_index = True, null = False, blank = False];
optional string tenant_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone tenant id", null = True, db_index = False];
@@ -161,8 +231,11 @@
optional string role_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone id", null = True, db_index = True];
}
+policy controller_user_policy <
+ ctx.user.is_admin
+ | (ctx.read_access & *user_policy(user)) >
-message ControllerUser (XOSBase) {
+message ControllerUser::controller_user_policy (XOSBase) {
required manytoone user->User:controllerusers = 1 [db_index = True, null = False, blank = False];
required manytoone controller->Controller:controllersusers = 2 [db_index = True, null = False, blank = False, unique_with = "user"];
optional string kuser_id = 3 [max_length = 200, content_type = "stripped", blank = True, help_text = "Keystone user id", null = True, db_index = False];
@@ -179,8 +252,13 @@
required manytomany deployments->Deployment/DashboardView_deployments:dashboardviews = 7 [help_text = "Deployments that should be included in this view", null = False, db_index = False, blank = True];
}
+// Everyone has read access
+// For write access you need admin privileges at that deployment
+policy deployment_policy <
+ ctx.user.is_admin
+ | (ctx.write_access -> exists Privilege: Privilege.object_type = "Deployment" & Privilege.object_id = obj.id & Privilege.accessor_id = ctx.user.id & Privilege.permission = "role:admin") >
-message Deployment (XOSBase) {
+message Deployment::deployment_policy (XOSBase) {
required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Deployment", null = False, db_index = False];
required string accessControl = 2 [default = "allow all", max_length = 200, blank = False, help_text = "Access control list that specifies which sites/users may use nodes in this deployment", null = False, db_index = False, varchar = True];
}
@@ -219,6 +297,9 @@
optional string tag = 6 [max_length = 256, content_type = "stripped", blank = True, help_text = "For Docker Images, tag of image", null = True, db_index = False];
}
+policy image_deployment_policy <
+ *deployment_policy(deployment)
+>
message ImageDeployments (XOSBase) {
required manytoone image->Image:imagedeployments = 1 [db_index = True, null = False, blank = False, unique_with = "deployment"];
@@ -232,9 +313,10 @@
policy instance_isolation_vm < (obj.isolation = "vm") -> (obj.image.kind = "vm") >
policy instance_creator_privilege < not (obj.slice.creator = obj.creator) -> exists Privilege:Privilege.object_id = obj.slice.id & Privilege.accessor_id = obj.creator.id & Privilege.object_type = "Slice" >
-message Instance (XOSBase) {
- option validators = "instance_creator:Instance has no creator, instance_isolation: Container instance { obj.name } must use container image, instance_isolation_container_vm_parent:Container-vm instance {obj.name} must have a parent, instance_parent_isolation_container_vm:Parent field can only be set on Container-vm instances ({ obj.name }), instance_isolation_vm: VM Instance { obj.name } must use VM image, instance_creator_privilege: instance creator has no privileges on slice";
-
+policy instance_policy < *slice_policy(slice) >
+
+message Instance::instance_policy (XOSBase) {
+ option validators = "instance_creator:Instance has no creator, instance_isolation: Container instance {obj.name} must use container image, instance_isolation_container_vm_parent:Container-vm instance {obj.name} must have a parent, instance_parent_isolation_container_vm:Parent field can only be set on Container-vm instances ({obj.name}), instance_isolation_vm: VM Instance {obj.name} must use VM image, instance_creator_privilege: instance creator has no privileges on slice";
optional string instance_id = 1 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance id", null = True, db_index = False];
optional string instance_uuid = 2 [max_length = 200, content_type = "stripped", blank = True, help_text = "Nova instance uuid", null = True, db_index = False];
required string name = 3 [max_length = 200, content_type = "stripped", blank = False, help_text = "Instance name", null = False, db_index = False];
@@ -252,7 +334,11 @@
optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
}
-message Network (XOSBase) {
+
+
+policy network_policy < *slice_policy(owner) >
+
+message Network::network_policy (XOSBase) {
required string name = 1 [db_index = False, max_length = 32, null = False, blank = False];
required manytoone template->NetworkTemplate:network = 2 [db_index = True, null = False, blank = False];
required string subnet = 3 [db_index = False, max_length = 32, null = False, blank = True];
@@ -283,8 +369,9 @@
}
policy network_slice_validator < (obj.slice in obj.network.permitted_slices.all()) | (obj.slice = obj.network.owner) | obj.network.permit_all_slices >
+policy network_slice_policy < *slice_policy(slice) & *network_policy(network) >
-message NetworkSlice (XOSBase) {
+message NetworkSlice::network_slice_policy (XOSBase) {
option validators = "network_slice_validator:Slice { obj.slice.name } is not allowed to connect to networks { obj.network }";
required manytoone network->Network:networkslices = 1 [db_index = True, null = False, blank = False, unique_with = "slice"];
required manytoone slice->Slice:networkslices = 2 [db_index = True, null = False, blank = False];
@@ -302,7 +389,10 @@
optional string controller_kind = 10 [blank = True, max_length = 30, null = True, db_index = False, choices = "((None, 'None'), ('onos', 'ONOS'), ('custom', 'Custom'))"];
optional string vtn_kind = 11 [default = "PRIVATE", choices = "(('PRIVATE', 'Private'), ('PUBLIC', 'Public'), ('MANAGEMENT_LOCAL', 'Management Local'), ('MANAGEMENT_HOST', 'Management Host'), ('VSG', 'VSG'), ('ACCESS_AGENT', 'Access Agent'))", max_length = 30, blank = True, null = True, db_index = False];
}
-message Node (XOSBase) {
+
+policy node_policy < *site_policy(site_deployment.site) >
+
+message Node::node_policy (XOSBase) {
required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Node", null = False, db_index = False];
required manytoone site_deployment->SiteDeployment:nodes = 2 [db_index = True, null = False, blank = False];
}
@@ -312,8 +402,9 @@
}
policy port_validator < (obj.instance.slice in obj.network.permitted_slices.all()) | (obj.instance.slice = obj.network.owner) | obj.network.permit_all_slices >
+policy port_policy < *instance_policy(instance) & *network_policy(network) >
-message Port (XOSBase) {
+message Port::port_policy (XOSBase) {
option validators = "port_validator:Slice is not allowed to connect to network";
required manytoone network->Network:links = 1 [db_index = True, null = False, blank = False, unique_with = "instance"];
optional manytoone instance->Instance:ports = 2 [db_index = True, null = True, blank = True];
@@ -330,6 +421,7 @@
required string description = 3 [db_index = False, max_length = 120, null = False, content_type = "stripped", blank = False];
}
+policy service_policy <ctx.user.is_admin | exists Privilege: Privilege.accessor_id = ctx.user.id & Privilege.accessor_type = "User" & Privilege.object_type = "Service" & Privilege.object_id = obj.id >
message Service (XOSBase,AttributeMixin) {
optional string description = 1 [help_text = "Description of Service", max_length = 254, null = True, db_index = False, blank = True, varchar = True];
@@ -380,7 +472,8 @@
}
-message Site (XOSBase) {
+
+message Site::site_policy (XOSBase) {
required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name for this Site", null = False, db_index = False];
optional string site_url = 2 [max_length = 512, content_type = "url", blank = True, help_text = "Site's Home URL Page", null = True, db_index = False];
required bool enabled = 3 [help_text = "Status for this Site", default = True, null = False, db_index = False, blank = True];
@@ -418,8 +511,10 @@
policy slice_name_length_and_no_spaces < {{ len(obj.site.login_base) + 1 < len(obj.name) and ' ' not in obj.name }} >
policy slice_has_creator < obj.creator >
-message Slice (XOSBase) {
- option validators = "slice_name:Slice name ({ obj.name}) must begin with site login_base ({ obj.site.login_base}), slice_name_length_and_no_spaces:Slice name too short or contains spaces, slice_has_creator:Slice has no creator";
+
+
+message Slice::slice_policy (XOSBase) {
+ option validators = "slice_name:Slice name ({obj.name}) must begin with site login_base ({ obj.site.login_base}), slice_name_length_and_no_spaces:Slice name too short or contains spaces, slice_has_creator:Slice has no creator";
option plural = "Slices";
required string name = 1 [max_length = 80, content_type = "stripped", blank = False, help_text = "The Name of the Slice", null = False, db_index = False];
@@ -451,8 +546,9 @@
required string role = 1 [choices = "(('admin', 'Admin'), ('default', 'Default'))", max_length = 30, content_type = "stripped", blank = False, null = False, db_index = False];
}
+policy tag_policy < ctx.user.is_admin >
-message Tag (XOSBase) {
+message Tag::tag_policy (XOSBase) {
required manytoone service->Service:tags = 1 [help_text = "The Service this Tag is associated with", null = False, db_index = True, blank = False];
required string name = 2 [help_text = "The name of this tag", max_length = 128, null = False, db_index = True, blank = False];
required string value = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The value of this tag", null = False, db_index = False];
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 3add183..dd7ae84 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -7,7 +7,8 @@
from operator import attrgetter, itemgetter
from core.middleware import get_request
-from core.models import DashboardView, XOSBase, PlModelMixIn, Site, ModelLink
+from xosbase import XOSBase,PlModelMixIn
+import dashboardview
from core.models.xosbase import StrippedCharField, XOSCollector
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.core.exceptions import PermissionDenied
@@ -21,6 +22,10 @@
from timezones.fields import TimeZoneField
from django.contrib.contenttypes.models import ContentType
+# The following manual import is needed because we do not
+# currently generate the User models.
+import security
+
import redis
from redis import ConnectionError
@@ -114,7 +119,7 @@
phone = StrippedCharField(null=True, blank=True,
help_text="phone number contact", max_length=100)
user_url = models.URLField(null=True, blank=True)
- site = models.ForeignKey(Site, related_name='users',
+ site = models.ForeignKey('Site', related_name='users',
help_text="Site this user will be homed too")
public_key = models.TextField(
null=True, blank=True, max_length=1024, help_text="Public key string")
@@ -143,8 +148,6 @@
no_sync = models.BooleanField(default=False) # prevent object sync
no_policy = models.BooleanField(default=False) # prevent model_policy run
- #xos_links = [ModelLink(Site,via='site')]
-
timezone = TimeZoneField()
dashboards = models.ManyToManyField(
@@ -207,7 +210,7 @@
if (not dashboards) and (not self.is_appuser):
for dashboardName in DEFAULT_DASHBOARDS:
- dbv = DashboardView.objects.filter(name=dashboardName)
+ dbv = dashboardview.DashboardView.objects.filter(name=dashboardName)
if dbv:
dashboards.append(dbv[0])
@@ -321,150 +324,6 @@
msg.attach_alternative(html_content, "text/html")
msg.send()
- def can_update(self, user):
- from core.models.privilege import Privilege
- _cant_update_fieldName = None
- if user.can_update_root():
- return True
-
- # site pis can update
- 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.permission == 'role:admin':
- return True
-
- if site_priv.permission == 'role:pi':
- for fieldName in self.diff.keys():
- if fieldName in self.PI_FORBIDDEN_FIELDS:
- _cant_update_fieldName = fieldName
- return False
- return True
- if (user.id == self.id):
- for fieldName in self.diff.keys():
- if fieldName in self.USER_FORBIDDEN_FIELDS:
- _cant_update_fieldName = fieldName
- return False
- return True
-
- return False
-
- def can_update_root(self):
- """
- Return True if user has root (global) write access.
- """
- if self.is_readonly:
- return False
- if self.is_admin:
- return True
-
- return False
-
- def can_update_deployment(self, deployment):
- from core.models.privilege import Privilege
- if self.can_update_root():
- return True
-
- 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.privilege import Privilege
- if self.can_update_root():
- return True
- 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.privilege import Privilege
- if self.can_update_root():
- return True
- if self == slice.creator:
- return True
- if self.can_update_site(slice.site, allow=['pi']):
- return True
-
- 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.privilege import Privilege
- if self.can_update_root():
- return True
- 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.privilege import Privilege
- if self.can_update_root():
- return True
- 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.privilege import Privilege
- if self.can_update_root():
- return True
- 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
-
- 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)
-
- @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.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 = 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
-
- def save_by_user(self, user, *args, **kwds):
- if not self.can_update(user):
- if getattr(self, "_cant_update_fieldName", None) is not None:
- raise PermissionDenied("You do not have permission to update field %s on object %s" % (
- self._cant_update_fieldName, self.__class__.__name__))
- else:
- raise PermissionDenied(
- "You do not have permission to update %s objects" % self.__class__.__name__)
-
- self.save(*args, **kwds)
-
- def delete_by_user(self, user, *args, **kwds):
- if not self.can_update(user):
- raise PermissionDenied(
- "You do not have permission to delete %s objects" % self.__class__.__name__)
- self.delete(*args, **kwds)
-
def apply_profile(self, profile):
if profile == "regular":
self.is_appuser = False
@@ -491,11 +350,14 @@
cls = ct.model_class()
return cls.objects.get(id=object_id)
+ ''' This function is hardcoded here because we do not yet
+ generate the User class'''
+ def can_access(self, ctx):
+ return security.user_policy_security_check(self, ctx), "user_policy"
class UserDashboardView(XOSBase):
user = models.ForeignKey(User, related_name='userdashboardviews')
dashboardView = models.ForeignKey(
- DashboardView, related_name='userdashboardviews')
+ dashboardview.DashboardView, related_name='userdashboardviews')
order = models.IntegerField(default=0)
- xos_links = [ModelLink(User, via='user'), ModelLink(DashboardView,via='userdashboardviews')]
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index de7ee19..5d21c74 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -15,6 +15,12 @@
from importlib import import_module
from django.conf import settings
+
+class XOSDefaultSecurityContext(object):
+ grant_access = True
+ write_access = True
+ read_access = True
+
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
def translate_exceptions(function):
@@ -261,24 +267,64 @@
obj = djangoClass.objects.get(id=id)
return obj
- def get(self, djangoClass, id):
+ def xos_security_gate(self, obj, user, **access_types):
+ sec_ctx = XOSDefaultSecurityContext()
+ sec_ctx.user = user
+
+ for k,v in access_types.items():
+ setattr(sec_ctx, k, v)
+
+ obj_ctx = obj
+
+ verdict, policy_name = obj.can_access(ctx = sec_ctx)
+
+ # FIXME: This is the central point of enforcement for security policies
+ # Implement Auditing here.
+ # logging.info( ... )
+
+ if not verdict:
+ # logging.critical( ... )
+ if object_id:
+ object_descriptor = 'object %d'%object_id
+ else:
+ object_descriptor = 'new object'
+
+ raise XOSPermissionDenied("User %(user_email)s cannot access %(django_class_name)s %(descriptor)s due to policy %(policy_name)s"%{'user_email':user.email, 'django_class_name':obj.__class__.__name__, 'policy_name': policy_name, 'descriptor': object_descriptor})
+
+ def xos_security_check(self, obj, user, **access_types):
+ sec_ctx = XOSDefaultSecurityContext()
+ sec_ctx.user = user
+ for k,v in access_types.items():
+ setattr(sec_ctx, k, v)
+
+ obj_ctx = obj
+
+ verdict, _ = obj.can_access(ctx = sec_ctx)
+ return verdict
+
+ def get(self, djangoClass, user, id):
obj = self.get_live_or_deleted_object(djangoClass, id)
+
+ self.xos_security_gate(obj, user, read_access=True)
+
return self.objToProto(obj)
def create(self, djangoClass, user, request):
args = self.protoToArgs(djangoClass, request)
new_obj = djangoClass(**args)
new_obj.caller = user
- if (not user) or (not new_obj.can_update(user)):
- raise XOSPermissionDenied()
+
+ self.xos_security_gate(new_obj, user, write_access=True)
+
new_obj.save()
return self.objToProto(new_obj)
def update(self, djangoClass, user, id, message, context):
obj = self.get_live_or_deleted_object(djangoClass, id)
obj.caller = user
- if (not user) or (not obj.can_update(user)):
- raise XOSPermissionDenied()
+
+ self.xos_security_gate(obj, user, write_access=True)
+
args = self.protoToArgs(djangoClass, message)
for (k,v) in args.iteritems():
setattr(obj, k, v)
@@ -297,8 +343,9 @@
def delete(self, djangoClass, user, id):
obj = djangoClass.objects.get(id=id)
- if (not user) or (not obj.can_update(user)):
- raise XOSPermissionDenied()
+
+ self.xos_security_gate(obj, user, write_access=True)
+
obj.delete()
return Empty()
@@ -329,7 +376,16 @@
return q
- def filter(self, djangoClass, request):
+ def list(self, djangoClass, user):
+ queryset = djangoClass.objects.all()
+ filtered_queryset = (elt for elt in queryset if self.xos_security_check(elt, user, read_access=True))
+
+ # FIXME: Implement auditing here
+ # logging.info("User requested x objects, y objects were filtered out by policy z")
+
+ return self.querysetToProto(djangoClass, filtered_queryset)
+
+ def filter(self, djangoClass, user, request):
query = None
if request.kind == request.DEFAULT:
for element in request.elements:
@@ -352,7 +408,12 @@
elif request.kind == request.ALL:
queryset = djangoClass.objects.all()
- return self.querysetToProto(djangoClass, queryset)
+ filtered_queryset = (elt for elt in queryset if self.xos_security_check(elt, user, read_access=True))
+
+ # FIXME: Implement auditing here
+ # logging.info("User requested x objects, y objects were filtered out by policy z")
+
+ return self.querysetToProto(djangoClass, filtered_queryset)
def authenticate(self, context, required=False):
for (k, v) in context.invocation_metadata():
diff --git a/xos/tests/permissiontest.py b/xos/tests/permissiontest.py
index 191eae6..0b3c88f 100644
--- a/xos/tests/permissiontest.py
+++ b/xos/tests/permissiontest.py
@@ -112,7 +112,7 @@
def test_deployment(self):
for user in [self.user_admin, self.user_deployment_admin]:
self.assertEqual(
- self.deployment.save_by_user(user), None)
+ self.deployment.save(), None)
for user in [self.user_read_only, self.user_default, self.user_site_admin,
self.user_site_pi, self.user_site_tech, self.user_slice_admin,
self.user_slice_access]:
diff --git a/xos/tools/corebuilder/corebuilder.py b/xos/tools/corebuilder/corebuilder.py
index 845e7c7..26820da 100644
--- a/xos/tools/corebuilder/corebuilder.py
+++ b/xos/tools/corebuilder/corebuilder.py
@@ -316,6 +316,16 @@
XOSGenerator.generate(args)
+ # Generate security checks
+ class SecurityArgs:
+ output = build_dest_fn
+ target = 'django-security.xtarget'
+ dest_file = 'security.py'
+ write_to_file = 'single'
+ files = file_list
+
+ XOSGenerator.generate(SecurityArgs())
+
# Generate __init__.py
if service_name == "core":
class InitArgs:
@@ -324,6 +334,7 @@
dest_file = '__init__.py'
write_to_file = 'single'
files = file_list
+
XOSGenerator.generate(InitArgs())
except Exception, e:
diff --git a/xos/xos/apibase.py b/xos/xos/apibase.py
index 8d80f38..e22128b 100644
--- a/xos/xos/apibase.py
+++ b/xos/xos/apibase.py
@@ -32,8 +32,6 @@
for attr, value in serializer.validated_data.items():
setattr(obj, attr, value)
obj.caller = request.user
- if not obj.can_update(request.user):
- raise XOSPermissionDenied()
self.perform_update(serializer)
@@ -42,11 +40,8 @@
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
obj.caller = request.user
- if obj.can_update(request.user):
- self.perform_destroy(obj)
- return Response(status=status.HTTP_204_NO_CONTENT)
- else:
- return Response(status=status.HTTP_400_BAD_REQUEST)
+ self.perform_destroy(obj)
+ return Response(status=status.HTTP_204_NO_CONTENT)
def handle_exception(self, exc):
# REST API drops the string attached to Django's PermissionDenied
@@ -70,9 +65,6 @@
# now do XOS can_update permission checking
obj = serializer.Meta.model(**serializer.validated_data)
obj.caller = request.user
- if not obj.can_update(request.user):
- raise XOSPermissionDenied()
-
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)