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 uuid import uuid4
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
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.sso.apps.PassbookPoliciesSSOConfig",
|
||||
"passbook.policies.webhook.apps.PassbookPoliciesWebhookConfig",
|
||||
"passbook.policies.expression.apps.PassbookPolicyExpressionConfig",
|
||||
]
|
||||
|
||||
GUARDIAN_MONKEY_PATCH = False
|
||||
|
|
Reference in New Issue