392 lines
15 KiB
Python
392 lines
15 KiB
Python
'''
|
|
VERIDIC - Towards a centralized access control system
|
|
|
|
Copyright (C) 2011 Mikael Ates
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
'''
|
|
|
|
|
|
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
from django.utils.translation import ugettext as _
|
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib.contenttypes import generic
|
|
|
|
from acs.abac.models import AbacRule
|
|
|
|
|
|
class Namespace(models.Model):
|
|
'''Namespace
|
|
Allow objects of same name
|
|
if they are in different namespace'''
|
|
name = models.CharField(max_length = 40, unique = True)
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class UserAlias(models.Model):
|
|
alias = models.CharField(max_length = 40)
|
|
namespace = models.ForeignKey(Namespace, verbose_name = _('Namespace'))
|
|
user = models.ForeignKey(User, null=True, blank=True)
|
|
self_admin = models.BooleanField(default=False)
|
|
delegated_permissions = models.ManyToManyField('AcsPermission',
|
|
verbose_name=_('delegated permissions list'),
|
|
blank=True, related_name = 'permissions_list')
|
|
|
|
class Meta:
|
|
verbose_name = _('alias')
|
|
verbose_name_plural = _('aliases')
|
|
unique_together = ("alias", "namespace")
|
|
|
|
def __unicode__(self):
|
|
if self.user:
|
|
return '%s is %s in %s' \
|
|
%(self.alias, self.user, self.namespace)
|
|
return '%s in %s (alias only)' \
|
|
%(self.alias, self.namespace)
|
|
|
|
|
|
class Role(models.Model):
|
|
'''Role'''
|
|
name = models.CharField(max_length = 40)
|
|
namespace = models.ForeignKey(Namespace, verbose_name = _('Namespace'),
|
|
default = '1')
|
|
|
|
users = models.ManyToManyField('UserAlias',
|
|
verbose_name=_('users'), blank=True)
|
|
roles = models.ManyToManyField('Role', symmetrical=False,
|
|
verbose_name=_('roles'), blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('role')
|
|
verbose_name_plural = _('roles')
|
|
unique_together = ("name", "namespace")
|
|
|
|
def __unicode__(self):
|
|
return '%s in %s' %(self.name, self.namespace.name)
|
|
|
|
|
|
class Action(models.Model):
|
|
'''Action'''
|
|
name = models.CharField(max_length = 40)
|
|
namespace = models.ForeignKey(Namespace, verbose_name = _('Namespace'),
|
|
default = '1')
|
|
|
|
class Meta:
|
|
verbose_name = _('action')
|
|
verbose_name_plural = _('actions')
|
|
unique_together = ("name", "namespace")
|
|
|
|
def __unicode__(self):
|
|
return '%s in %s' %(self.name, self.namespace.name)
|
|
|
|
|
|
class Activity(models.Model):
|
|
'''Activity'''
|
|
name = models.CharField(max_length = 40)
|
|
namespace = models.ForeignKey(Namespace, verbose_name = _('Namespace'),
|
|
default = '1')
|
|
|
|
actions = models.ManyToManyField(Action, verbose_name=_('actions'),
|
|
blank=True)
|
|
activities = models.ManyToManyField('self', symmetrical=False,
|
|
verbose_name=_('activities'), blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('activity')
|
|
verbose_name_plural = _('activities')
|
|
unique_together = ("name", "namespace")
|
|
|
|
def __unicode__(self):
|
|
return '%s in %s' %(self.name, self.namespace.name)
|
|
|
|
|
|
class AcsObject(models.Model):
|
|
'''
|
|
Object
|
|
This object is used to determine objects of an application policy
|
|
The object of the policy of administration of ACS are the policy
|
|
entities.
|
|
Then, none of this type of object should be created in the default
|
|
namespace or in the global admin view which identifies/point at
|
|
objects in the policy of the ACS system
|
|
'''
|
|
name = models.CharField(max_length = 40)
|
|
namespace = models.ForeignKey(Namespace, verbose_name = _('Namespace'),
|
|
default = '1')
|
|
regex = models.CharField(max_length = 100,
|
|
verbose_name = _('Regular expression'), null=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('object')
|
|
verbose_name_plural = _('objects')
|
|
unique_together = ("name", "namespace")
|
|
|
|
def __unicode__(self):
|
|
if self.regex:
|
|
return '%s in %s with regex %s' \
|
|
% (self.name, self.namespace.name, self.regex)
|
|
return '%s in %s' %(self.name, self.namespace.name)
|
|
|
|
|
|
class View(models.Model):
|
|
'''
|
|
View
|
|
|
|
A view is a container for objects.
|
|
Everything is object
|
|
'''
|
|
name = models.CharField(max_length = 40)
|
|
namespace = models.ForeignKey(Namespace, verbose_name = _('Namespace'),
|
|
default = '1')
|
|
|
|
acs_objects = models.ManyToManyField(AcsObject,
|
|
verbose_name=_('acs objects'), blank=True)
|
|
views = models.ManyToManyField('self', symmetrical=False,
|
|
verbose_name=_('views'), blank=True)
|
|
users = models.ManyToManyField(UserAlias,
|
|
verbose_name=_('users'), blank=True)
|
|
roles = models.ManyToManyField(Role, verbose_name=_('roles'), blank=True)
|
|
actions = models.ManyToManyField(Action, verbose_name=_('actions'),
|
|
blank=True)
|
|
activities = models.ManyToManyField(Activity,
|
|
verbose_name=_('activities'), blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('view')
|
|
verbose_name_plural = _('views')
|
|
unique_together = ("name", "namespace")
|
|
|
|
def __unicode__(self):
|
|
return '%s in %s' %(self.name, self.namespace.name)
|
|
|
|
|
|
class AcsPermission(models.Model):
|
|
content_type_who = models.ForeignKey(ContentType,
|
|
related_name = "who_rbac")
|
|
content_type_what = models.ForeignKey(ContentType,
|
|
related_name = "what_rbac")
|
|
content_type_how = models.ForeignKey(ContentType,
|
|
related_name = "how_rbac")
|
|
object_id_who = models.PositiveIntegerField()
|
|
object_id_what = models.PositiveIntegerField()
|
|
object_id_how = models.PositiveIntegerField()
|
|
who = generic.GenericForeignKey(ct_field='content_type_who',
|
|
fk_field='object_id_who')
|
|
what = generic.GenericForeignKey(ct_field='content_type_what',
|
|
fk_field='object_id_what')
|
|
how = generic.GenericForeignKey(ct_field='content_type_how',
|
|
fk_field='object_id_how')
|
|
'''
|
|
If set it means that the permission is obtained by delegation
|
|
'''
|
|
permission_source = models.ForeignKey('AcsPermission',
|
|
blank=True, null=True, related_name = 'source_permission')
|
|
'''
|
|
Means that a permission can be delegated
|
|
Also that a permission delegated can be delegated
|
|
'''
|
|
delegable = models.BooleanField(default=False)
|
|
'''
|
|
Expiration of the permission
|
|
That means also expiration of the delegation
|
|
'''
|
|
expiration_date = models.DateTimeField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('permission')
|
|
verbose_name_plural = _('permissions')
|
|
unique_together = (("content_type_who", "object_id_who",
|
|
"content_type_what", "object_id_what",
|
|
"content_type_how", "object_id_how"))
|
|
|
|
def __unicode__(self):
|
|
s = _('Permission of %s on %s to perform %s') \
|
|
%(self.who, self.what, self.how.name)
|
|
if self.permission_source:
|
|
s += _(' (delegated)')
|
|
if self.delegable:
|
|
s += _(' (delegable)')
|
|
if self.expiration_date:
|
|
s += _(' (expires on %s)' %self.expiration_date)
|
|
return s
|
|
|
|
def save(self, *args, **kwargs):
|
|
p = None
|
|
t_who = ContentType.objects.get_for_model(self.who)
|
|
t_what = ContentType.objects.get_for_model(self.what)
|
|
t_how = ContentType.objects.get_for_model(self.how)
|
|
'''We check here the unicity, not redundant with unique_together'''
|
|
try:
|
|
p = AcsPermission.objects.get(content_type_who__pk=t_who.id,
|
|
object_id_who=self.who.id,
|
|
content_type_what__pk=t_what.id,
|
|
object_id_what=self.what.id,
|
|
content_type_how__pk=t_how.id,
|
|
object_id_how=self.how.id,
|
|
)
|
|
except MultipleObjectsReturned:
|
|
raise Exception('This should never happen')
|
|
except ObjectDoesNotExist:
|
|
pass
|
|
if p:
|
|
raise Exception('This permission already exists')
|
|
if not isinstance(self.who, (UserAlias, Role)):
|
|
raise Exception('Who is %s, not a User or a Role') \
|
|
%ContentType.objects.get_for_model(self.who)
|
|
if not isinstance(self.how, (Action, Activity)):
|
|
raise Exception('How is %s, not an Action or an Activity') \
|
|
%ContentType.objects.get_for_model(self.how)
|
|
self.content_type_who = ContentType.objects.get_for_model(self.who)
|
|
self.object_id_who = self.who.id
|
|
self.content_type_what = ContentType.objects.get_for_model(self.what)
|
|
self.object_id_what = self.what.id
|
|
self.content_type_how = ContentType.objects.get_for_model(self.how)
|
|
self.object_id_how = self.how.id
|
|
super(AcsPermission, self).save(*args, **kwargs)
|
|
|
|
|
|
class AcsAbacPermission(models.Model):
|
|
'''
|
|
Who is only a User
|
|
|
|
When a request is treated:
|
|
If who is a role. If there is a matching RBAC rule, the decision is
|
|
True.
|
|
If there is an ABAC rule set on anybody with a unique predicate role
|
|
on that role, the decission is True.
|
|
Else a new rule is returned made of permissions set on anybody.
|
|
|
|
Axiom:
|
|
p_RBAC(role, Y, Z)
|
|
equiv.
|
|
p_ABAC(Anybody, Y, Z, rule: PredicateRole(role))
|
|
|
|
p_ABAC(user, Y, Z, rule: PredicateRole(role)) means that user may be
|
|
granted the access if it has the role.
|
|
More powerful than using p_RBAC
|
|
|
|
Should depreciate AcsPermission and RBAC only processing.
|
|
'''
|
|
who = models.ForeignKey(UserAlias, related_name = "who_abac",
|
|
blank=True, null=True)
|
|
content_type_what = models.ForeignKey(ContentType,
|
|
related_name = "what_abac")
|
|
content_type_how = models.ForeignKey(ContentType,
|
|
related_name = "how_abac")
|
|
object_id_what = models.PositiveIntegerField()
|
|
object_id_how = models.PositiveIntegerField()
|
|
what = generic.GenericForeignKey(ct_field='content_type_what',
|
|
fk_field='object_id_what')
|
|
how = generic.GenericForeignKey(ct_field='content_type_how',
|
|
fk_field='object_id_how')
|
|
rule = models.ForeignKey(AbacRule, verbose_name = _('AbacRule'))
|
|
'''
|
|
Expiration of the permission
|
|
'''
|
|
expiration_date = models.DateTimeField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('abac permission')
|
|
verbose_name_plural = _('abac permissions')
|
|
unique_together = (("rule",
|
|
"content_type_what", "object_id_what",
|
|
"content_type_how", "object_id_how"))
|
|
|
|
def __unicode__(self):
|
|
if self.who:
|
|
s = _('Permission of %s on %s to perform %s if "%s"') \
|
|
% (self.who, self.what, self.how.name, self.rule)
|
|
else:
|
|
s = _('Permission of anyone on %s to perform %s if "%s"') \
|
|
% (self.what, self.how.name, self.rule)
|
|
if self.expiration_date:
|
|
s += _(' (expires on %s)' %self.expiration_date)
|
|
return s
|
|
|
|
def save(self, *args, **kwargs):
|
|
p = None
|
|
t_what = ContentType.objects.get_for_model(self.what)
|
|
t_how = ContentType.objects.get_for_model(self.how)
|
|
'''We check here the unicity, not redundant with unique_together'''
|
|
try:
|
|
if self.who:
|
|
p = AcsAbacPermission.objects.get(rule=self.rule,
|
|
who=self.who,
|
|
content_type_what__pk=t_what.id,
|
|
object_id_what=self.what.id,
|
|
content_type_how__pk=t_how.id,
|
|
object_id_how=self.how.id,
|
|
)
|
|
else:
|
|
p = AcsAbacPermission.objects.get(rule=self.rule,
|
|
content_type_what__pk=t_what.id,
|
|
object_id_what=self.what.id,
|
|
content_type_how__pk=t_how.id,
|
|
object_id_how=self.how.id,
|
|
)
|
|
except MultipleObjectsReturned:
|
|
raise Exception('This should never happen')
|
|
except ObjectDoesNotExist:
|
|
pass
|
|
if p:
|
|
raise Exception('This permission already exists')
|
|
if not isinstance(self.how, (Action, Activity)):
|
|
raise Exception('How is %s, not an Action or an Activity') \
|
|
%ContentType.objects.get_for_model(self.how)
|
|
self.content_type_what = ContentType.objects.get_for_model(self.what)
|
|
self.object_id_what = self.what.id
|
|
self.content_type_how = ContentType.objects.get_for_model(self.how)
|
|
self.object_id_how = self.how.id
|
|
super(AcsAbacPermission, self).save(*args, **kwargs)
|
|
|
|
|
|
class Policy(models.Model):
|
|
name = models.CharField(max_length = 40, unique=True, null=False)
|
|
namespace = models.ForeignKey(Namespace,
|
|
verbose_name = _('Namespace of the policy'))
|
|
admin_view = models.ForeignKey(View,
|
|
verbose_name = _('Admin view of the policy'))
|
|
admin_role = models.ForeignKey(Role,
|
|
verbose_name = _('Admin role of the policy'),
|
|
related_name = 'admin_role')
|
|
user_admin_role = models.ForeignKey(Role,
|
|
verbose_name = _('User admin role of the policy'),
|
|
related_name = 'user_admin_role')
|
|
abac_admin_role = models.ForeignKey(Role,
|
|
verbose_name = _('ABAC admin role of the policy'),
|
|
related_name = 'abac_admin_role')
|
|
object_creator_role = models.ForeignKey(Role,
|
|
verbose_name = _('Object and View creator role of the policy'),
|
|
related_name = 'object_creator_role', null=True)
|
|
action_creator_role = models.ForeignKey(Role,
|
|
verbose_name = _('Action and Activity creator role of the policy'),
|
|
related_name = 'action_creator_role', null=True)
|
|
admin_roles = models.ManyToManyField(Role,
|
|
verbose_name=_('admin_roles'),
|
|
blank=True, related_name = 'admin_roles')
|
|
admin_views = models.ManyToManyField(View,
|
|
verbose_name=_('admin_views'),
|
|
blank=True, related_name = 'admin_views')
|
|
|
|
class Meta:
|
|
verbose_name = _('policy')
|
|
verbose_name_plural = _('policies')
|
|
|
|
def __unicode__(self):
|
|
return '%s with namespace %s' %(self.name, self.namespace)
|