policies/expression: add Expression based policy
This commit is contained in:
parent
f31cd7dec6
commit
9f00843441
|
@ -5,9 +5,9 @@ from time import sleep
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""Passbook passbook expression policy Admin"""
|
||||||
|
|
||||||
|
from passbook.lib.admin import admin_autoregister
|
||||||
|
|
||||||
|
admin_autoregister("passbook_policies_expression")
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""Expression Policy API"""
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from passbook.policies.expression.models import ExpressionPolicy
|
||||||
|
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||||
|
|
||||||
|
|
||||||
|
class ExpressionPolicySerializer(ModelSerializer):
|
||||||
|
"""Group Membership Policy Serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExpressionPolicy
|
||||||
|
fields = GENERAL_SERIALIZER_FIELDS + ["expression"]
|
||||||
|
|
||||||
|
|
||||||
|
class ExpressionPolicyViewSet(ModelViewSet):
|
||||||
|
"""Source Viewset"""
|
||||||
|
|
||||||
|
queryset = ExpressionPolicy.objects.all()
|
||||||
|
serializer_class = ExpressionPolicySerializer
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""Passbook policy_expression app config"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookPolicyExpressionConfig(AppConfig):
|
||||||
|
"""Passbook policy_expression app config"""
|
||||||
|
|
||||||
|
name = "passbook.policies.expression"
|
||||||
|
label = "passbook_policies_expression"
|
||||||
|
verbose_name = "passbook Policies.Expression"
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""passbook Expression Policy forms"""
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from passbook.policies.expression.models import ExpressionPolicy
|
||||||
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
|
|
||||||
|
|
||||||
|
class ExpressionPolicyForm(forms.ModelForm):
|
||||||
|
"""ExpressionPolicy Form"""
|
||||||
|
|
||||||
|
template_name = "policy/expression/form.html"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = ExpressionPolicy
|
||||||
|
fields = GENERAL_FIELDS + [
|
||||||
|
"expression",
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"name": forms.TextInput(),
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-02-18 14:00
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_core", "0007_auto_20200217_1934"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ExpressionPolicy",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"policy_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="passbook_core.Policy",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("expression", models.TextField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Expression Policy",
|
||||||
|
"verbose_name_plural": "Expression Policies",
|
||||||
|
},
|
||||||
|
bases=("passbook_core.policy",),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,49 @@
|
||||||
|
"""passbook expression Policy Models"""
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from jinja2.exceptions import TemplateSyntaxError, UndefinedError
|
||||||
|
from jinja2.nativetypes import NativeEnvironment
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
|
from passbook.core.models import Policy
|
||||||
|
from passbook.policies.struct import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
NATIVE_ENVIRONMENT = NativeEnvironment()
|
||||||
|
|
||||||
|
|
||||||
|
class ExpressionPolicy(Policy):
|
||||||
|
"""Jinja2-based Expression policy that allows Admins to write their own logic"""
|
||||||
|
|
||||||
|
expression = models.TextField()
|
||||||
|
|
||||||
|
form = "passbook.policies.expression.forms.ExpressionPolicyForm"
|
||||||
|
|
||||||
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
|
"""Evaluate and render expression. Returns PolicyResult(false) on error."""
|
||||||
|
try:
|
||||||
|
expression = NATIVE_ENVIRONMENT.from_string(self.expression)
|
||||||
|
except TemplateSyntaxError as exc:
|
||||||
|
return PolicyResult(False, str(exc))
|
||||||
|
try:
|
||||||
|
result = expression.render(request=request)
|
||||||
|
if isinstance(result, list) and len(result) == 2:
|
||||||
|
return PolicyResult(*result)
|
||||||
|
if result:
|
||||||
|
return PolicyResult(result)
|
||||||
|
return PolicyResult(False)
|
||||||
|
except UndefinedError as exc:
|
||||||
|
return PolicyResult(False, str(exc))
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
NATIVE_ENVIRONMENT.from_string(self.expression)
|
||||||
|
except TemplateSyntaxError as exc:
|
||||||
|
raise ValidationError("Expression Syntax Error") from exc
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
verbose_name = _("Expression Policy")
|
||||||
|
verbose_name_plural = _("Expression Policies")
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "generic/form.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block beneath_form %}
|
||||||
|
<div class="form-group ">
|
||||||
|
<label class="col-sm-2 control-label" for="friendly_name-2">
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<p>
|
||||||
|
Expression using <a href="https://jinja.palletsprojects.com/en/2.11.x/templates/">Jinja</a>. Following variables are available:
|
||||||
|
<ul>
|
||||||
|
<li><code>request.user</code>: Passbook User Object (<a href="https://beryju.github.io/passbook/reference/property-mappings/user-object/">Reference</a>)</li>
|
||||||
|
<li><code>request.http_request</code>: Django HTTP Request Object (<a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects">Reference</a>) </li>
|
||||||
|
<li><code>request.obj</code>: Model the Policy is run against. </li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -98,6 +98,7 @@ INSTALLED_APPS = [
|
||||||
"passbook.policies.password.apps.PassbookPoliciesPasswordConfig",
|
"passbook.policies.password.apps.PassbookPoliciesPasswordConfig",
|
||||||
"passbook.policies.sso.apps.PassbookPoliciesSSOConfig",
|
"passbook.policies.sso.apps.PassbookPoliciesSSOConfig",
|
||||||
"passbook.policies.webhook.apps.PassbookPoliciesWebhookConfig",
|
"passbook.policies.webhook.apps.PassbookPoliciesWebhookConfig",
|
||||||
|
"passbook.policies.expression.apps.PassbookPolicyExpressionConfig",
|
||||||
]
|
]
|
||||||
|
|
||||||
GUARDIAN_MONKEY_PATCH = False
|
GUARDIAN_MONKEY_PATCH = False
|
||||||
|
|
Reference in New Issue