policies/expression: add support for dynamic variables

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt 2023-09-29 02:15:23 +02:00
parent efb2823391
commit 5945b36200
No known key found for this signature in database
GPG Key ID: 9C3FA22FABF1AA8D
31 changed files with 1247 additions and 33 deletions

1
.gitignore vendored
View File

@ -194,6 +194,7 @@ pip-selfcheck.json
# End of https://www.gitignore.io/api/python,django # End of https://www.gitignore.io/api/python,django
/static/ /static/
local.env.yml local.env.yml
/variables/
media/ media/
*mmdb *mmdb

View File

@ -106,6 +106,7 @@ default_token_length: 60
impersonation: true impersonation: true
blueprints_dir: /blueprints blueprints_dir: /blueprints
variables_discovery_dir: /data/variables
web: web:
# No default here as it's set dynamically # No default here as it's set dynamically

View File

@ -1,10 +1,32 @@
"""Expression Policy API""" """Expression Policy API"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.expression.evaluator import PolicyEvaluator from authentik.policies.expression.evaluator import PolicyEvaluator
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy, ExpressionVariable
class ExpressionVariableSerializer(ModelSerializer):
"""Expression Variable Serializer"""
class Meta:
model = ExpressionVariable
fields = "__all__"
extra_kwargs = {
"managed": {"read_only": True},
}
class ExpressionVariableViewSet(UsedByMixin, ModelViewSet):
"""Expression Variable Viewset"""
queryset = ExpressionVariable.objects.all()
serializer_class = ExpressionVariableSerializer
filterset_fields = "__all__"
ordering = ["name"]
search_fields = ["name"]
class ExpressionPolicySerializer(PolicySerializer): class ExpressionPolicySerializer(PolicySerializer):
@ -18,7 +40,7 @@ class ExpressionPolicySerializer(PolicySerializer):
class Meta: class Meta:
model = ExpressionPolicy model = ExpressionPolicy
fields = PolicySerializer.Meta.fields + ["expression"] fields = PolicySerializer.Meta.fields + ["expression", "variables"]
class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet): class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet):

View File

@ -13,7 +13,7 @@ from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger() LOGGER = get_logger()
if TYPE_CHECKING: if TYPE_CHECKING:
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy, ExpressionVariable
class PolicyEvaluator(BaseEvaluator): class PolicyEvaluator(BaseEvaluator):
@ -30,6 +30,7 @@ class PolicyEvaluator(BaseEvaluator):
# update website/docs/expressions/_functions.md # update website/docs/expressions/_functions.md
self._context["ak_message"] = self.expr_func_message self._context["ak_message"] = self.expr_func_message
self._context["ak_user_has_authenticator"] = self.expr_func_user_has_authenticator self._context["ak_user_has_authenticator"] = self.expr_func_user_has_authenticator
self._context["ak_variables"] = {}
def expr_func_message(self, message: str): def expr_func_message(self, message: str):
"""Wrapper to append to messages list, which is returned with PolicyResult""" """Wrapper to append to messages list, which is returned with PolicyResult"""
@ -52,6 +53,12 @@ class PolicyEvaluator(BaseEvaluator):
self._context["ak_client_ip"] = ip_address(get_client_ip(request)) self._context["ak_client_ip"] = ip_address(get_client_ip(request))
self._context["http_request"] = request self._context["http_request"] = request
def set_variables(self, variables: list["ExpressionVariable"]):
"""Update context base on expression policy variables"""
for variable in variables:
variable.reload()
self._context["ak_variables"][variable.name] = variable.value
def handle_error(self, exc: Exception, expression_source: str): def handle_error(self, exc: Exception, expression_source: str):
"""Exception Handler""" """Exception Handler"""
raise PolicyException(exc) raise PolicyException(exc)

View File

@ -0,0 +1,48 @@
# Generated by Django 4.2.5 on 2023-09-29 00:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_expression", "0004_expressionpolicy_authentik_p_policy__fb6feb_idx"),
]
operations = [
migrations.CreateModel(
name="ExpressionVariable",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"managed",
models.TextField(
default=None,
help_text="Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
("name", models.TextField(unique=True)),
("value", models.TextField()),
],
options={
"verbose_name": "Expression Variable",
"verbose_name_plural": "Expression Variables",
},
),
migrations.AddField(
model_name="expressionpolicy",
name="variables",
field=models.ManyToManyField(
blank=True, to="authentik_policies_expression.expressionvariable"
),
),
]

View File

@ -1,18 +1,66 @@
"""authentik expression Policy Models""" """authentik expression Policy Models"""
from pathlib import Path
from django.db import models from django.db import models
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer from rest_framework.serializers import BaseSerializer
from structlog.stdlib import get_logger
from authentik.blueprints.models import ManagedModel
from authentik.lib.config import CONFIG
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
from authentik.policies.expression.evaluator import PolicyEvaluator from authentik.policies.expression.evaluator import PolicyEvaluator
from authentik.policies.models import Policy from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
MANAGED_DISCOVERED = "goauthentik.io/variables/discovered/%s"
class ExpressionVariable(SerializerModel, ManagedModel, CreatedUpdatedModel):
"""Variable that can be given to expression policies"""
name = models.TextField(unique=True)
value = models.TextField()
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.policies.expression.api import ExpressionVariableSerializer
return ExpressionVariableSerializer
def reload(self):
"""Reload a variable from disk if it's managed"""
if self.managed != MANAGED_DISCOVERED % self.name:
return
path = Path(CONFIG.get("variables_discovery_dir")) / Path(self.name)
try:
with open(path, "r", encoding="utf-8") as _file:
body = _file.read()
if body != self.value:
self.value = body
self.save()
except (OSError, ValueError) as exc:
LOGGER.warning(
"Failed to reload variable, continuing anyway",
exc=exc,
file=path,
variable=self.name,
)
class Meta:
verbose_name = _("Expression Variable")
verbose_name_plural = _("Expression Variables")
class ExpressionPolicy(Policy): class ExpressionPolicy(Policy):
"""Execute arbitrary Python code to implement custom checks and validation.""" """Execute arbitrary Python code to implement custom checks and validation."""
expression = models.TextField() expression = models.TextField()
variables = models.ManyToManyField(ExpressionVariable, blank=True)
@property @property
def serializer(self) -> type[BaseSerializer]: def serializer(self) -> type[BaseSerializer]:
from authentik.policies.expression.api import ExpressionPolicySerializer from authentik.policies.expression.api import ExpressionPolicySerializer
@ -28,6 +76,7 @@ class ExpressionPolicy(Policy):
evaluator = PolicyEvaluator(self.name) evaluator = PolicyEvaluator(self.name)
evaluator.policy = self evaluator.policy = self
evaluator.set_policy_request(request) evaluator.set_policy_request(request)
evaluator.set_variables(self.variables)
return evaluator.evaluate(self.expression) return evaluator.evaluate(self.expression)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View File

@ -0,0 +1,86 @@
"""Expression tasks"""
from glob import glob
from pathlib import Path
from django.utils.translation import gettext_lazy as _
from structlog.stdlib import get_logger
from watchdog.events import (
FileCreatedEvent,
FileModifiedEvent,
FileSystemEvent,
FileSystemEventHandler,
)
from watchdog.observers import Observer
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.lib.config import CONFIG
from authentik.policies.expression.models import MANAGED_DISCOVERED, ExpressionVariable
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()
_file_watcher_started = False
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task
def variable_discovery(self: MonitoredTask):
"""Discover, import and update variables from the filesystem"""
variables = {}
discovered = 0
base_path = Path(CONFIG.get("variables_discovery_dir")).absolute()
for file in glob(str(base_path) + "/**", recursive=True):
path = Path(file)
if not path.exists():
continue
if path.is_dir():
continue
try:
with open(path, "r", encoding="utf-8") as _file:
body = _file.read()
variables[str(path.relative_to(base_path))] = body
discovered += 1
except (OSError, ValueError) as exc:
LOGGER.warning("Failed to open file", exc=exc, file=path)
for name, value in variables.items():
variable = ExpressionVariable.objects.filter(managed=MANAGED_DISCOVERED % name).first()
if not variable:
variable = ExpressionVariable(name=name, managed=MANAGED_DISCOVERED % name)
if variable.value != value:
variable.value = value
variable.save()
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL,
messages=[_("Successfully imported %(count)d files." % {"count": discovered})],
)
)
class VariableEventHandler(FileSystemEventHandler):
"""Event handler for variable events"""
def on_any_event(self, event: FileSystemEvent):
if not isinstance(event, (FileCreatedEvent, FileModifiedEvent)):
return
if event.is_directory:
return
LOGGER.debug("variable file changed, starting discovery", file=event.src_path)
variable_discovery.delay()
def start_variables_watcher():
"""Start variables watcher, if it's not running already."""
# This function might be called twice since it's called on celery startup
# pylint: disable=global-statement
global _file_watcher_started
if _file_watcher_started:
return
observer = Observer()
observer.schedule(VariableEventHandler(), CONFIG.get("variables_discovery_dir"), recursive=True)
observer.start()
_file_watcher_started = True

View File

@ -1,4 +1,7 @@
"""API URLs""" """API URLs"""
from authentik.policies.expression.api import ExpressionPolicyViewSet from authentik.policies.expression.api import ExpressionPolicyViewSet, ExpressionVariableViewSet
api_urlpatterns = [("policies/expression", ExpressionPolicyViewSet)] api_urlpatterns = [
("policies/expression/variables", ExpressionVariableViewSet),
("policies/expression", ExpressionPolicyViewSet),
]

View File

@ -105,8 +105,10 @@ def worker_ready_hook(*args, **kwargs):
except ProgrammingError as exc: except ProgrammingError as exc:
LOGGER.warning("Startup task failed", task=task, exc=exc) LOGGER.warning("Startup task failed", task=task, exc=exc)
from authentik.blueprints.v1.tasks import start_blueprint_watcher from authentik.blueprints.v1.tasks import start_blueprint_watcher
from authentik.policies.expression.tasks import start_variables_watcher
start_blueprint_watcher() start_blueprint_watcher()
start_variables_watcher()
class LivenessProbe(bootsteps.StartStopStep): class LivenessProbe(bootsteps.StartStopStep):

View File

@ -559,6 +559,43 @@
} }
} }
}, },
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_policies_expression.expressionvariable"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created",
"must_created"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"attrs": {
"$ref": "#/$defs/model_authentik_policies_expression.expressionvariable"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_policies_expression.expressionvariable"
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -3426,6 +3463,7 @@
"authentik_policies_dummy.dummypolicy", "authentik_policies_dummy.dummypolicy",
"authentik_policies_event_matcher.eventmatcherpolicy", "authentik_policies_event_matcher.eventmatcherpolicy",
"authentik_policies_expiry.passwordexpirypolicy", "authentik_policies_expiry.passwordexpirypolicy",
"authentik_policies_expression.expressionvariable",
"authentik_policies_expression.expressionpolicy", "authentik_policies_expression.expressionpolicy",
"authentik_policies_password.passwordpolicy", "authentik_policies_password.passwordpolicy",
"authentik_policies_reputation.reputationpolicy", "authentik_policies_reputation.reputationpolicy",
@ -3517,6 +3555,22 @@
}, },
"required": [] "required": []
}, },
"model_authentik_policies_expression.expressionvariable": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"value": {
"type": "string",
"minLength": 1,
"title": "Value"
}
},
"required": []
},
"model_authentik_policies_expression.expressionpolicy": { "model_authentik_policies_expression.expressionpolicy": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3534,6 +3588,13 @@
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
"title": "Expression" "title": "Expression"
},
"variables": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Variables"
} }
}, },
"required": [] "required": []

View File

@ -74,6 +74,7 @@ services:
- ./media:/media - ./media:/media
- ./certs:/certs - ./certs:/certs
- ./custom-templates:/templates - ./custom-templates:/templates
- ./data:/data
env_file: env_file:
- .env - .env
depends_on: depends_on:

View File

@ -1,4 +1,5 @@
#!/bin/bash -e #!/usr/bin/env bash
set -e
MODE_FILE="${TMPDIR}/authentik-mode" MODE_FILE="${TMPDIR}/authentik-mode"
function log { function log {

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-15 09:51+0000\n" "POT-Creation-Date: 2023-09-29 00:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -70,6 +70,7 @@ msgid "authentik Export - %(date)s"
msgstr "" msgstr ""
#: authentik/blueprints/v1/tasks.py:150 authentik/crypto/tasks.py:93 #: authentik/blueprints/v1/tasks.py:150 authentik/crypto/tasks.py:93
#: authentik/policies/expression/tasks.py:59
#, python-format #, python-format
msgid "Successfully imported %(count)d files." msgid "Successfully imported %(count)d files."
msgstr "" msgstr ""
@ -724,11 +725,19 @@ msgstr ""
msgid "Password Expiry Policies" msgid "Password Expiry Policies"
msgstr "" msgstr ""
#: authentik/policies/expression/models.py:40 #: authentik/policies/expression/models.py:53
msgid "Expression Variable"
msgstr ""
#: authentik/policies/expression/models.py:54
msgid "Expression Variables"
msgstr ""
#: authentik/policies/expression/models.py:89
msgid "Expression Policy" msgid "Expression Policy"
msgstr "" msgstr ""
#: authentik/policies/expression/models.py:41 #: authentik/policies/expression/models.py:90
msgid "Expression Policies" msgid "Expression Policies"
msgstr "" msgstr ""

View File

@ -11748,6 +11748,14 @@ paths:
description: A search term. description: A search term.
schema: schema:
type: string type: string
- in: query
name: variables
schema:
type: array
items:
type: integer
explode: true
style: form
tags: tags:
- policies - policies
security: security:
@ -11984,6 +11992,288 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/policies/expression/variables/:
get:
operationId: policies_expression_variables_list
description: Expression Variable Viewset
parameters:
- in: query
name: created
schema:
type: string
format: date-time
- in: query
name: last_updated
schema:
type: string
format: date-time
- in: query
name: managed
schema:
type: string
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: value
schema:
type: string
tags:
- policies
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedExpressionVariableList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: policies_expression_variables_create
description: Expression Variable Viewset
tags:
- policies
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ExpressionVariableRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/ExpressionVariable'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/policies/expression/variables/{id}/:
get:
operationId: policies_expression_variables_retrieve
description: Expression Variable Viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Expression Variable.
required: true
tags:
- policies
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExpressionVariable'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: policies_expression_variables_update
description: Expression Variable Viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Expression Variable.
required: true
tags:
- policies
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ExpressionVariableRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExpressionVariable'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: policies_expression_variables_partial_update
description: Expression Variable Viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Expression Variable.
required: true
tags:
- policies
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedExpressionVariableRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExpressionVariable'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: policies_expression_variables_destroy
description: Expression Variable Viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Expression Variable.
required: true
tags:
- policies
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/policies/expression/variables/{id}/used_by/:
get:
operationId: policies_expression_variables_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Expression Variable.
required: true
tags:
- policies
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/policies/password/: /policies/password/:
get: get:
operationId: policies_password_list operationId: policies_password_list
@ -29556,6 +29846,7 @@ components:
* `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_dummy.dummypolicy` - Dummy Policy
* `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy
* `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy
* `authentik_policies_expression.expressionvariable` - Expression Variable
* `authentik_policies_expression.expressionpolicy` - Expression Policy * `authentik_policies_expression.expressionpolicy` - Expression Policy
* `authentik_policies_password.passwordpolicy` - Password Policy * `authentik_policies_password.passwordpolicy` - Password Policy
* `authentik_policies_reputation.reputationpolicy` - Reputation Policy * `authentik_policies_reputation.reputationpolicy` - Reputation Policy
@ -29749,6 +30040,7 @@ components:
* `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_dummy.dummypolicy` - Dummy Policy
* `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy
* `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy
* `authentik_policies_expression.expressionvariable` - Expression Variable
* `authentik_policies_expression.expressionpolicy` - Expression Policy * `authentik_policies_expression.expressionpolicy` - Expression Policy
* `authentik_policies_password.passwordpolicy` - Password Policy * `authentik_policies_password.passwordpolicy` - Password Policy
* `authentik_policies_reputation.reputationpolicy` - Reputation Policy * `authentik_policies_reputation.reputationpolicy` - Reputation Policy
@ -29918,6 +30210,10 @@ components:
readOnly: true readOnly: true
expression: expression:
type: string type: string
variables:
type: array
items:
type: integer
required: required:
- bound_to - bound_to
- component - component
@ -29941,9 +30237,61 @@ components:
expression: expression:
type: string type: string
minLength: 1 minLength: 1
variables:
type: array
items:
type: integer
required: required:
- expression - expression
- name - name
ExpressionVariable:
type: object
description: Expression Variable Serializer
properties:
id:
type: integer
readOnly: true
created:
type: string
format: date-time
readOnly: true
last_updated:
type: string
format: date-time
readOnly: true
managed:
type: string
readOnly: true
nullable: true
title: Managed by authentik
description: Objects that are managed by authentik. These objects are created
and updated automatically. This flag only indicates that an object can
be overwritten by migrations. You can still modify the objects via the
API, but expect changes to be overwritten in a later update.
name:
type: string
value:
type: string
required:
- created
- id
- last_updated
- managed
- name
- value
ExpressionVariableRequest:
type: object
description: Expression Variable Serializer
properties:
name:
type: string
minLength: 1
value:
type: string
minLength: 1
required:
- name
- value
FilePathRequest: FilePathRequest:
type: object type: object
description: Serializer to upload file description: Serializer to upload file
@ -31909,6 +32257,7 @@ components:
- authentik_policies_dummy.dummypolicy - authentik_policies_dummy.dummypolicy
- authentik_policies_event_matcher.eventmatcherpolicy - authentik_policies_event_matcher.eventmatcherpolicy
- authentik_policies_expiry.passwordexpirypolicy - authentik_policies_expiry.passwordexpirypolicy
- authentik_policies_expression.expressionvariable
- authentik_policies_expression.expressionpolicy - authentik_policies_expression.expressionpolicy
- authentik_policies_password.passwordpolicy - authentik_policies_password.passwordpolicy
- authentik_policies_reputation.reputationpolicy - authentik_policies_reputation.reputationpolicy
@ -31983,6 +32332,7 @@ components:
* `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_dummy.dummypolicy` - Dummy Policy
* `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy
* `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy
* `authentik_policies_expression.expressionvariable` - Expression Variable
* `authentik_policies_expression.expressionpolicy` - Expression Policy * `authentik_policies_expression.expressionpolicy` - Expression Policy
* `authentik_policies_password.passwordpolicy` - Password Policy * `authentik_policies_password.passwordpolicy` - Password Policy
* `authentik_policies_reputation.reputationpolicy` - Reputation Policy * `authentik_policies_reputation.reputationpolicy` - Reputation Policy
@ -33288,6 +33638,18 @@ components:
required: required:
- pagination - pagination
- results - results
PaginatedExpressionVariableList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/ExpressionVariable'
required:
- pagination
- results
PaginatedFlowList: PaginatedFlowList:
type: object type: object
properties: properties:
@ -34973,6 +35335,7 @@ components:
* `authentik_policies_dummy.dummypolicy` - Dummy Policy * `authentik_policies_dummy.dummypolicy` - Dummy Policy
* `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy
* `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy
* `authentik_policies_expression.expressionvariable` - Expression Variable
* `authentik_policies_expression.expressionpolicy` - Expression Policy * `authentik_policies_expression.expressionpolicy` - Expression Policy
* `authentik_policies_password.passwordpolicy` - Password Policy * `authentik_policies_password.passwordpolicy` - Password Policy
* `authentik_policies_reputation.reputationpolicy` - Reputation Policy * `authentik_policies_reputation.reputationpolicy` - Reputation Policy
@ -35070,6 +35433,20 @@ components:
expression: expression:
type: string type: string
minLength: 1 minLength: 1
variables:
type: array
items:
type: integer
PatchedExpressionVariableRequest:
type: object
description: Expression Variable Serializer
properties:
name:
type: string
minLength: 1
value:
type: string
minLength: 1
PatchedFlowRequest: PatchedFlowRequest:
type: object type: object
description: Flow Serializer description: Flow Serializer

View File

@ -17,6 +17,7 @@ with open("local.env.yml", "w", encoding="utf-8") as _config:
}, },
"blueprints_dir": "./blueprints", "blueprints_dir": "./blueprints",
"cert_discovery_dir": "./certs", "cert_discovery_dir": "./certs",
"variables_discovery_dir": "./variables",
"geoip": "tests/GeoLite2-City-Test.mmdb", "geoip": "tests/GeoLite2-City-Test.mmdb",
}, },
_config, _config,

View File

@ -201,6 +201,7 @@ export class AdminInterface extends Interface {
["/events/transports", msg("Notification Transports")]]], ["/events/transports", msg("Notification Transports")]]],
[null, msg("Customisation"), null, [ [null, msg("Customisation"), null, [
["/policy/policies", msg("Policies")], ["/policy/policies", msg("Policies")],
["/policy/expression/variables", msg("Variables")],
["/core/property-mappings", msg("Property Mappings")], ["/core/property-mappings", msg("Property Mappings")],
["/blueprints/instances", msg("Blueprints")], ["/blueprints/instances", msg("Blueprints")],
["/policy/reputation", msg("Reputation scores")]]], ["/policy/reputation", msg("Reputation scores")]]],

View File

@ -60,6 +60,10 @@ export const ROUTES: Route[] = [
await import("@goauthentik/admin/policies/PolicyListPage"); await import("@goauthentik/admin/policies/PolicyListPage");
return html`<ak-policy-list></ak-policy-list>`; return html`<ak-policy-list></ak-policy-list>`;
}), }),
new Route(new RegExp("^/policy/expression/variables"), async () => {
await import("@goauthentik/admin/policies/expression/ExpressionVariableListPage");
return html`<ak-expression-variable-list></ak-expression-variable-list>`;
}),
new Route(new RegExp("^/policy/reputation$"), async () => { new Route(new RegExp("^/policy/reputation$"), async () => {
await import("@goauthentik/admin/policies/reputation/ReputationListPage"); await import("@goauthentik/admin/policies/reputation/ReputationListPage");
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`; return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;

View File

@ -11,7 +11,7 @@ import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { ExpressionPolicy, PoliciesApi } from "@goauthentik/api"; import { ExpressionPolicy, PaginatedExpressionVariableList, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-expression-form") @customElement("ak-policy-expression-form")
export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> { export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
@ -21,6 +21,14 @@ export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
}); });
} }
async load(): Promise<void> {
this.variables = await new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesList({
ordering: "name",
});
}
variables?: PaginatedExpressionVariableList;
getSuccessMessage(): string { getSuccessMessage(): string {
if (this.instance) { if (this.instance) {
return msg("Successfully updated policy."); return msg("Successfully updated policy.");
@ -100,6 +108,35 @@ export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
</a> </a>
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Variables")}
?required=${true}
name="variables"
>
<select class="pf-c-form-control" multiple>
${this.variables?.results.map((variable) => {
const selected = Array.from(this.instance?.variables || []).some(
(va) => {
return va == variable.id;
},
);
return html`<option
value=${ifDefined(variable.id)}
?selected=${selected}
>
${variable.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"Select variables that will be made available to this expression.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div> </div>
</ak-form-group> </ak-form-group>
</form>`; </form>`;

View File

@ -0,0 +1,62 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { ExpressionVariable, PoliciesApi } from "@goauthentik/api";
@customElement("ak-expression-variable-form")
export class ExpressionVariableForm extends ModelForm<ExpressionVariable, number> {
loadInstance(pk: number): Promise<ExpressionVariable> {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesRetrieve({
id: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return msg("Successfully updated variable.");
} else {
return msg("Successfully created variable.");
}
}
async send(data: ExpressionVariable): Promise<ExpressionVariable> {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesUpdate({
id: this.instance.id || 0,
expressionVariableRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesCreate({
expressionVariableRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<span> ${msg("Variable that can be passed to an expression policy")} </span>
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Value")} ?required=${true} name="value">
<input
type="text"
value="${ifDefined(this.instance?.value || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -0,0 +1,106 @@
import "@goauthentik/admin/policies/expression/ExpressionVariableForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/forms/ConfirmationForm";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ExpressionVariable, PoliciesApi } from "@goauthentik/api";
@customElement("ak-expression-variable-list")
export class ExpressionVariableListPage extends TablePage<ExpressionVariable> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return msg("Variables");
}
pageDescription(): string {
return msg("Variables that can be passed on to expressions.");
}
pageIcon(): string {
// TODO: ask Jens what to put here
return "pf-icon pf-icon-infrastructure";
}
checkbox = true;
@property()
order = "name";
async apiEndpoint(page: number): Promise<PaginatedResponse<ExpressionVariable>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesList({
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [new TableColumn(msg("Name"), "name"), new TableColumn(msg("Actions"))];
}
row(item: ExpressionVariable): TemplateResult[] {
let managedSubText = msg("Managed by authentik");
if (item.managed && item.managed.startsWith("goauthentik.io/variables/discovered")) {
managedSubText = msg("Managed by authentik (Discovered)");
}
return [
html`<div>${item.name}</div>
${item.managed ? html`<small>${managedSubText}</small>` : html``}`,
html` <ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update Variable")} </span>
<ak-expression-variable-form slot="form" .instancePk=${item.id}>
</ak-expression-variable-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit"></i>
</pf-tooltip>
</button>
</ak-forms-modal>`,
];
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Variable / Variables")}
.objects=${this.selectedElements}
.usedBy=${(item: ExpressionVariable) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesUsedByList({
id: item.id,
});
}}
.delete=${(item: ExpressionVariable) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionVariablesDestroy({
id: item.id,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
renderObjectCreate(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Variable")} </span>
<ak-expression-variable-form slot="form"> </ak-expression-variable-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
</ak-forms-modal>
`;
}
}

View File

@ -5925,6 +5925,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6239,6 +6239,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5833,6 +5833,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5941,6 +5941,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6072,6 +6072,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6174,6 +6174,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5826,6 +5826,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2"> <?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext"> <file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body> <body>
<trans-unit id="s4caed5b7a7e5d89b"> <trans-unit id="s4caed5b7a7e5d89b">
@ -613,9 +613,9 @@
</trans-unit> </trans-unit>
<trans-unit id="saa0e2675da69651b"> <trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source> <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>未找到 URL &quot; <target>未找到 URL "
<x id="0" equiv-text="${this.url}"/>&quot;。</target> <x id="0" equiv-text="${this.url}"/>"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s58cd9c2fe836d9c6"> <trans-unit id="s58cd9c2fe836d9c6">
@ -1067,8 +1067,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa8384c9c26731f83"> <trans-unit id="sa8384c9c26731f83">
<source>To allow any redirect URI, set this value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source> <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
<target>要允许任何重定向 URI请将此值设置为 &quot;.*&quot;。请注意这可能带来的安全影响。</target> <target>要允许任何重定向 URI请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
</trans-unit> </trans-unit>
<trans-unit id="s55787f4dfcdce52b"> <trans-unit id="s55787f4dfcdce52b">
@ -1814,8 +1814,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa90b7809586c35ce"> <trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source> <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 &quot;fa-test&quot;。</target> <target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s0410779cb47de312"> <trans-unit id="s0410779cb47de312">
@ -3238,8 +3238,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s76768bebabb7d543"> <trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>包含组成员的字段。请注意,如果使用 &quot;memberUid&quot; 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target> <target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
</trans-unit> </trans-unit>
<trans-unit id="s026555347e589f0e"> <trans-unit id="s026555347e589f0e">
@ -4031,8 +4031,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s7b1fba26d245cb1c"> <trans-unit id="s7b1fba26d245cb1c">
<source>When using an external logging solution for archiving, this can be set to &quot;minutes=5&quot;.</source> <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 &quot;minutes=5&quot;。</target> <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s44536d20bb5c8257"> <trans-unit id="s44536d20bb5c8257">
@ -4041,8 +4041,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s3bb51cabb02b997e"> <trans-unit id="s3bb51cabb02b997e">
<source>Format: &quot;weeks=3;days=2;hours=3,seconds=2&quot;.</source> <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
<target>格式:&quot;weeks=3;days=2;hours=3,seconds=2&quot;。</target> <target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s04bfd02201db5ab8"> <trans-unit id="s04bfd02201db5ab8">
@ -4238,10 +4238,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sa95a538bfbb86111"> <trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source> <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>您确定要更新 <target>您确定要更新
<x id="0" equiv-text="${this.objectLabel}"/>&quot; <x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>&quot; 吗?</target> <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
</trans-unit> </trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6"> <trans-unit id="sc92d7cfb6ee1fec6">
@ -5342,7 +5342,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sdf1d8edef27236f0"> <trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source> <source>A "roaming" authenticator, like a YubiKey</source>
<target>像 YubiKey 这样的“漫游”身份验证器</target> <target>像 YubiKey 这样的“漫游”身份验证器</target>
</trans-unit> </trans-unit>
@ -5677,10 +5677,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s2d5f69929bb7221d"> <trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${prompt.name}"/> (&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${prompt.type}"/>)</source> <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
<target> <target>
<x id="0" equiv-text="${prompt.name}"/>&quot; <x id="0" equiv-text="${prompt.name}"/>"
<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;,类型为 <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
<x id="2" equiv-text="${prompt.type}"/></target> <x id="2" equiv-text="${prompt.type}"/></target>
</trans-unit> </trans-unit>
@ -5729,7 +5729,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s1608b2f94fa0dbd4"> <trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source> <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target> <target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target>
</trans-unit> </trans-unit>
@ -7818,7 +7818,37 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
<target>浏览器不支持 WebAuthn。</target> <target>浏览器不支持 WebAuthn。</target>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -5878,6 +5878,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5877,6 +5877,36 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="se9e9e1d6799b86a5"> <trans-unit id="se9e9e1d6799b86a5">
<source>WebAuthn not supported by browser.</source> <source>WebAuthn not supported by browser.</source>
</trans-unit>
<trans-unit id="sb39940945f8f7122">
<source>Variables</source>
</trans-unit>
<trans-unit id="se1d0e029ea650f11">
<source>Select variables that will be made available to this expression.</source>
</trans-unit>
<trans-unit id="s97e9ea093d519435">
<source>Successfully updated variable.</source>
</trans-unit>
<trans-unit id="s9d8d42526c7f0f0e">
<source>Successfully created variable.</source>
</trans-unit>
<trans-unit id="sd483758478e36417">
<source>Variable that can be passed to an expression policy</source>
</trans-unit>
<trans-unit id="s41f7dca46d3f1b4a">
<source>Value</source>
</trans-unit>
<trans-unit id="se5363f539effce3b">
<source>Variables that can be passed on to expressions.</source>
</trans-unit>
<trans-unit id="s4bc8b951c8067ed4">
<source>Update Variable</source>
</trans-unit>
<trans-unit id="s5ab1f5073aa96219">
<source>Variable / Variables</source>
</trans-unit>
<trans-unit id="s52a2427b2afc939b">
<source>Create Variable</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -16,6 +16,39 @@ return False
to fail it. to fail it.
## Variables
You can create variables that will be made available to the policy under an `ak_variables` dictionary.
Creating a variable named `my_var` with a value `this is the value` and associating this variable to the expression will result in the following `ak_variables` dictionary being available to use in the expression:
```python
ak_variables["my_var"] # This is equal to `this_is_the_value`
```
### External variables
To use externally managed variables (for instance, to pass a secret as a file to authentik), you can use the discovery feature.
The docker-compose installation maps `data/variables` directory to `/data/variables`, you can simply use this directory to store your variables.
For Kubernetes, you can map custom configmaps/secrets/volumes under /data/variables.
You can also bind mount single files into the folder.
The name of the variable will be the full path from the `/data/variables/` directory. For instance:
```
data/variables/
├── baz
│ └── bar.baz # The variable will be named `baz/bar.baz`
└── foo.bar # The variable will be named `foo.bar`
```
Note that file contents are not stripped, and may contain an extra `\n` at the end.
External variables are reloaded from disk on every policy execution. If the reload fails, the previous value is used.
## Available Functions ## Available Functions
### `ak_message(message: str)` ### `ak_message(message: str)`
@ -65,6 +98,8 @@ import Objects from "../expressions/_objects.md";
See also [Python documentation](https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_address) See also [Python documentation](https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_address)
- `ak_variables`: dictionary of name, value obtained from the variables bound to the expression policy.
Additionally, when the policy is executed from a flow, every variable from the flow's current context is accessible under the `context` object. Additionally, when the policy is executed from a flow, every variable from the flow's current context is accessible under the `context` object.
This includes the following: This includes the following: