core: add basic model against which rules can be checked

This commit is contained in:
Jens Langhammer 2018-11-22 13:12:59 +01:00
parent 849f9c9251
commit 095a5c0268
7 changed files with 31 additions and 15 deletions

View file

@ -20,12 +20,24 @@ class User(AbstractUser):
@reversion.register() @reversion.register()
class Provider(models.Model): class Provider(models.Model):
"""Application-independant Provider instance. For example SAML2 Remote, OAuth2 Application""" """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
# This class defines no field for easier inheritance # This class defines no field for easier inheritance
class RuleModel(UUIDModel, CreatedUpdatedModel):
"""Base model which can have rules applied to it"""
rules = models.ManyToManyField('Rule')
def passes(self, user: User) -> bool:
"""Return true if user passes, otherwise False or raise Exception"""
for rule in self.rules:
if not rule.passes(user):
return False
return True
@reversion.register() @reversion.register()
class Application(UUIDModel, CreatedUpdatedModel): class Application(RuleModel):
"""Every Application which uses passbook for authentication/identification/authorization """Every Application which uses passbook for authentication/identification/authorization
needs an Application record. Other authentication types can subclass this Model to needs an Application record. Other authentication types can subclass this Model to
add custom fields and other properties""" add custom fields and other properties"""
@ -45,7 +57,7 @@ class Application(UUIDModel, CreatedUpdatedModel):
return self.name return self.name
@reversion.register() @reversion.register()
class Source(UUIDModel, CreatedUpdatedModel): class Source(RuleModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
name = models.TextField() name = models.TextField()
@ -82,7 +94,6 @@ class Rule(UUIDModel, CreatedUpdatedModel):
) )
name = models.TextField(blank=True, null=True) name = models.TextField(blank=True, null=True)
application = models.ForeignKey(Application, on_delete=models.CASCADE)
action = models.CharField(max_length=20, choices=ACTIONS) action = models.CharField(max_length=20, choices=ACTIONS)
negate = models.BooleanField(default=False) negate = models.BooleanField(default=False)
@ -91,9 +102,9 @@ class Rule(UUIDModel, CreatedUpdatedModel):
def __str__(self): def __str__(self):
if self.name: if self.name:
return self.name return self.name
return "%s action %s" % (self.application, self.action) return "%s action %s" % (self.name, self.action)
def user_passes(self, user: User) -> bool: def passes(self, user: User) -> bool:
"""Check if user instance passes this rule""" """Check if user instance passes this rule"""
raise NotImplementedError() raise NotImplementedError()
@ -120,13 +131,13 @@ class FieldMatcherRule(Rule):
value = models.TextField() value = models.TextField()
def __str__(self): def __str__(self):
description = "app %s, user.%s %s '%s'" % (self.application, self.user_field, description = "%s, user.%s %s '%s'" % (self.name, self.user_field,
self.match_action, self.value) self.match_action, self.value)
if self.name: if self.name:
description = "%s: %s" % (self.name, description) description = "%s: %s" % (self.name, description)
return description return description
def user_passes(self, user: User) -> bool: def passes(self, user: User) -> bool:
"""Check if user instance passes this role""" """Check if user instance passes this role"""
if not hasattr(user, self.user_field): if not hasattr(user, self.user_field):
raise ValueError("Field does not exist") raise ValueError("Field does not exist")

View file

@ -1,8 +1,9 @@
django>=2.0 django>=2.0
django-reversion django-reversion
django-model-utils
django-crispy-forms
djangorestframework
PyYAML PyYAML
raven raven
djangorestframework
markdown markdown
django-model-utils
colorlog colorlog

View file

@ -54,8 +54,10 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'reversion', 'reversion',
'rest_framework', 'rest_framework',
'crispy_forms',
'passbook.core', 'passbook.core',
'passbook.admin', 'passbook.admin',
'passbook.audit',
'passbook.lib', 'passbook.lib',
'passbook.ldap', 'passbook.ldap',
'passbook.oauth_client', 'passbook.oauth_client',

View file

@ -53,8 +53,8 @@
</ul> </ul>
</li> </li>
</ul> </ul>
<ul class="nav navbar-nav navbar-primary"> {% is_active_app 'passbook_admin' as is_admin %}
{# FIXME: Detect active application #} <ul class="nav navbar-nav navbar-primary {% if is_admin == 'active' %}persistent-secondary{% endif %}">
<li class="{% is_active_app 'passbook_core' %}"> <li class="{% is_active_app 'passbook_core' %}">
<a href="{% url 'passbook_core:overview' %}">{% trans 'Overview' %}</a> <a href="{% url 'passbook_core:overview' %}">{% trans 'Overview' %}</a>
</li> </li>

View file

@ -1,6 +1,7 @@
{% extends "overview/base.html" %} {% extends "overview/base.html" %}
{% block content %} {% block content %}
<div class="container">
<div class="row row-cards-pf"> <div class="row row-cards-pf">
<!-- Important: if you need to nest additional .row within a .row.row-cards-pf, do *not* use .row-cards-pf on the nested .row --> <!-- Important: if you need to nest additional .row within a .row.row-cards-pf, do *not* use .row-cards-pf on the nested .row -->
<div class="col-xs-12 col-sm-6 col-md-3"> <div class="col-xs-12 col-sm-6 col-md-3">
@ -58,4 +59,5 @@
</div> </div>
</div> </div>
</div><!-- /row --> </div><!-- /row -->
</div>
{% endblock %} {% endblock %}

View file

@ -26,7 +26,7 @@ urlpatterns = [
for _passbook_app in get_apps(): for _passbook_app in get_apps():
if hasattr(_passbook_app, 'mountpoint'): if hasattr(_passbook_app, 'mountpoint'):
_path = path(_passbook_app.mountpoint, include((_passbook_app.name+'.urls', _path = path(_passbook_app.mountpoint, include((_passbook_app.name+'.urls',
_passbook_app.name), _passbook_app.label),
namespace=_passbook_app.label)) namespace=_passbook_app.label))
urlpatterns.append(_path) urlpatterns.append(_path)
LOGGER.debug("Loaded %s's URLs", _passbook_app.name) LOGGER.debug("Loaded %s's URLs", _passbook_app.name)

View file

@ -11,5 +11,5 @@ class OverviewView(LoginRequiredMixin, TemplateView):
template_name = 'overview/index.html' template_name = 'overview/index.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['applications'] = self.request.user.applications.objects.all() kwargs['applications'] = self.request.user.applications.all()
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)