| @ -73,6 +73,11 @@ def auth_user_lookup(raw_header: bytes) -> User | None: | |||||||
|     if user: |     if user: | ||||||
|         CTX_AUTH_VIA.set("secret_key") |         CTX_AUTH_VIA.set("secret_key") | ||||||
|         return user |         return user | ||||||
|  |     # then try to auth via expression JWT | ||||||
|  |     user = token_expression_jwt(auth_credentials) | ||||||
|  |     if user: | ||||||
|  |         CTX_AUTH_VIA.set("expression_jwt") | ||||||
|  |         return user | ||||||
|     raise AuthenticationFailed("Token invalid/expired") |     raise AuthenticationFailed("Token invalid/expired") | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -90,6 +95,13 @@ def token_secret_key(value: str) -> User | None: | |||||||
|     return outpost.user |     return outpost.user | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def token_expression_jwt(value: str) -> User | None: | ||||||
|  |     """Authenticate API call made by Expressions""" | ||||||
|  |     from authentik.lib.expression.evaluator import authenticate_token | ||||||
|  |  | ||||||
|  |     return authenticate_token(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenAuthentication(BaseAuthentication): | class TokenAuthentication(BaseAuthentication): | ||||||
|     """Token-based authentication using HTTP Bearer authentication""" |     """Token-based authentication using HTTP Bearer authentication""" | ||||||
|  |  | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ class PropertyMappingEvaluator(BaseEvaluator): | |||||||
|             _filename = model.name |             _filename = model.name | ||||||
|         else: |         else: | ||||||
|             _filename = str(model) |             _filename = str(model) | ||||||
|         super().__init__(filename=_filename) |         super().__init__(user=user, filename=_filename) | ||||||
|         req = PolicyRequest(user=User()) |         req = PolicyRequest(user=User()) | ||||||
|         req.obj = model |         req.obj = model | ||||||
|         if user: |         if user: | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ | |||||||
| import re | import re | ||||||
| import socket | import socket | ||||||
| from collections.abc import Iterable | from collections.abc import Iterable | ||||||
|  | from datetime import timedelta | ||||||
|  | from functools import lru_cache | ||||||
| from ipaddress import ip_address, ip_network | from ipaddress import ip_address, ip_network | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from tempfile import gettempdir | from tempfile import gettempdir | ||||||
| @ -34,7 +36,9 @@ from authentik_client.configuration import Configuration | |||||||
| from cachetools import TLRUCache, cached | from cachetools import TLRUCache, cached | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.core.exceptions import FieldError | from django.core.exceptions import FieldError | ||||||
|  | from django.utils.timezone import now | ||||||
| from guardian.shortcuts import get_anonymous_user | from guardian.shortcuts import get_anonymous_user | ||||||
|  | from jwt import PyJWTError, decode, encode | ||||||
| from rest_framework.serializers import ValidationError | from rest_framework.serializers import ValidationError | ||||||
| from RestrictedPython import compile_restricted, limited_builtins, safe_builtins, utility_builtins | from RestrictedPython import compile_restricted, limited_builtins, safe_builtins, utility_builtins | ||||||
| from sentry_sdk.hub import Hub | from sentry_sdk.hub import Hub | ||||||
| @ -42,14 +46,12 @@ from sentry_sdk.tracing import Span | |||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik.core.models import ( | from authentik.core.models import ( | ||||||
|     USER_ATTRIBUTE_CHANGE_EMAIL, |  | ||||||
|     USER_ATTRIBUTE_CHANGE_NAME, |  | ||||||
|     USER_ATTRIBUTE_CHANGE_USERNAME, |  | ||||||
|     User, |     User, | ||||||
| ) | ) | ||||||
| from authentik.events.models import Event | from authentik.events.models import Event | ||||||
| from authentik.lib.config import CONFIG | from authentik.lib.config import CONFIG | ||||||
| from authentik.lib.utils.http import get_http_session | from authentik.lib.utils.errors import exception_to_string | ||||||
|  | from authentik.lib.utils.http import authentik_user_agent, get_http_session | ||||||
| from authentik.lib.utils.reflection import get_apps | from authentik.lib.utils.reflection import get_apps | ||||||
| from authentik.policies.models import Policy, PolicyBinding | from authentik.policies.models import Policy, PolicyBinding | ||||||
| from authentik.policies.process import PolicyProcess | from authentik.policies.process import PolicyProcess | ||||||
| @ -82,6 +84,23 @@ API_CLIENTS = { | |||||||
|     "TenantsApi": TenantsApi, |     "TenantsApi": TenantsApi, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | JWT_AUD = "goauthentik.io/api/expression" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @lru_cache | ||||||
|  | def get_api_token_secret(): | ||||||
|  |     return "foo" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def authenticate_token(raw_value: str): | ||||||
|  |     """Authenticate API call from evaluator token""" | ||||||
|  |     try: | ||||||
|  |         jwt = decode(raw_value, get_api_token_secret(), ["HS256"], audience=JWT_AUD) | ||||||
|  |         return User.objects.filter(uuid=jwt["sub"]).first() | ||||||
|  |     except PyJWTError as exc: | ||||||
|  |         LOGGER.debug("failed to auth", exc=exc) | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseEvaluator: | class BaseEvaluator: | ||||||
|     """Validate and evaluate python-based expressions""" |     """Validate and evaluate python-based expressions""" | ||||||
| @ -94,8 +113,14 @@ class BaseEvaluator: | |||||||
|     # Filename used for exec |     # Filename used for exec | ||||||
|     _filename: str |     _filename: str | ||||||
|  |  | ||||||
|     def __init__(self, filename: str | None = None): |     _user: User | ||||||
|  |  | ||||||
|  |     # Timeout in seconds, used for the expiration of the API key | ||||||
|  |     timeout = 30 | ||||||
|  |  | ||||||
|  |     def __init__(self, user: User, filename: str | None = None): | ||||||
|         self._filename = filename if filename else "BaseEvaluator" |         self._filename = filename if filename else "BaseEvaluator" | ||||||
|  |         self._user = user | ||||||
|         # update website/docs/expressions/_objects.md |         # update website/docs/expressions/_objects.md | ||||||
|         # update website/docs/expressions/_functions.md |         # update website/docs/expressions/_functions.md | ||||||
|         self._globals = { |         self._globals = { | ||||||
| @ -113,12 +138,6 @@ class BaseEvaluator: | |||||||
|             "requests": get_http_session(), |             "requests": get_http_session(), | ||||||
|             "resolve_dns": BaseEvaluator.expr_resolve_dns, |             "resolve_dns": BaseEvaluator.expr_resolve_dns, | ||||||
|             "reverse_dns": BaseEvaluator.expr_reverse_dns, |             "reverse_dns": BaseEvaluator.expr_reverse_dns, | ||||||
|             # Temporary addition of config until #7590 is through and this is not needed anymore |  | ||||||
|             "CONFIG": CONFIG, |  | ||||||
|             "USER_ATTRIBUTE_CHANGE_EMAIL": USER_ATTRIBUTE_CHANGE_EMAIL, |  | ||||||
|             "USER_ATTRIBUTE_CHANGE_NAME": USER_ATTRIBUTE_CHANGE_NAME, |  | ||||||
|             "USER_ATTRIBUTE_CHANGE_USERNAME": USER_ATTRIBUTE_CHANGE_USERNAME, |  | ||||||
|             "api": self.get_api_client(), |  | ||||||
|         } |         } | ||||||
|         for app in get_apps(): |         for app in get_apps(): | ||||||
|             # Load models from each app |             # Load models from each app | ||||||
| @ -127,18 +146,34 @@ class BaseEvaluator: | |||||||
|         self._globals.update(API_CLIENTS) |         self._globals.update(API_CLIENTS) | ||||||
|         self._context = {} |         self._context = {} | ||||||
|  |  | ||||||
|  |     def get_token(self) -> str: | ||||||
|  |         """Generate API token to be used by the API Client""" | ||||||
|  |         _now = now() | ||||||
|  |         return encode( | ||||||
|  |             { | ||||||
|  |                 "aud": JWT_AUD, | ||||||
|  |                 "iss": f"goauthentik.io/expression/{self._filename}", | ||||||
|  |                 "sub": str(self._user.uuid), | ||||||
|  |                 "iat": int(_now.timestamp()), | ||||||
|  |                 "exp": int((_now + timedelta(seconds=self.timeout)).timestamp()), | ||||||
|  |             }, | ||||||
|  |             get_api_token_secret(), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def get_api_client(self): |     def get_api_client(self): | ||||||
|         token = "" |         token = self.get_token() | ||||||
|         config = Configuration( |         config = Configuration( | ||||||
|             f"unix://{str(_tmp.joinpath('authentik-core.sock'))}", |             f"unix://{str(_tmp.joinpath('authentik-core.sock'))}/api/v3", | ||||||
|             api_key={ |             api_key={ | ||||||
|                 "authentik": token, |                 "authentik": token, | ||||||
|             }, |             }, | ||||||
|             api_key_prefix={"authentik": "Bearer "}, |             api_key_prefix={"authentik": "Bearer"}, | ||||||
|         ) |         ) | ||||||
|         if settings.DEBUG: |         if settings.DEBUG: | ||||||
|             config.host = "http://localhost:8000" |             config.host = "http://localhost:8000/api/v3" | ||||||
|         return ApiClient(config) |         client = ApiClient(config) | ||||||
|  |         client.user_agent = authentik_user_agent() | ||||||
|  |         return client | ||||||
|  |  | ||||||
|     @cached(cache=TLRUCache(maxsize=32, ttu=lambda key, value, now: now + 180)) |     @cached(cache=TLRUCache(maxsize=32, ttu=lambda key, value, now: now + 180)) | ||||||
|     @staticmethod |     @staticmethod | ||||||
| @ -296,6 +331,9 @@ class BaseEvaluator: | |||||||
|                         **utility_builtins, |                         **utility_builtins, | ||||||
|                     } |                     } | ||||||
|                 _locals = self._context |                 _locals = self._context | ||||||
|  |                 # We need to create the API Client later so that the token is valid | ||||||
|  |                 # from when the execution starts | ||||||
|  |                 self._globals["api"] = self.get_api_client() | ||||||
|                 # Yes this is an exec, yes it is potentially bad. Since we limit what variables are |                 # Yes this is an exec, yes it is potentially bad. Since we limit what variables are | ||||||
|                 # available here, and these policies can only be edited by admins, this is a risk |                 # available here, and these policies can only be edited by admins, this is a risk | ||||||
|                 # we're willing to take. |                 # we're willing to take. | ||||||
| @ -303,6 +341,7 @@ class BaseEvaluator: | |||||||
|                 exec(ast_obj, self._globals, _locals)  # nosec # noqa |                 exec(ast_obj, self._globals, _locals)  # nosec # noqa | ||||||
|                 result = _locals["result"] |                 result = _locals["result"] | ||||||
|             except Exception as exc: |             except Exception as exc: | ||||||
|  |                 print(exception_to_string(exc)) | ||||||
|                 # So, this is a bit questionable. Essentially, we are edit the stacktrace |                 # So, this is a bit questionable. Essentially, we are edit the stacktrace | ||||||
|                 # so the user only sees information relevant to them |                 # so the user only sees information relevant to them | ||||||
|                 # and none of our surrounding error handling |                 # and none of our surrounding error handling | ||||||
|  | |||||||
| @ -14,12 +14,12 @@ class ExpressionPolicySerializer(PolicySerializer): | |||||||
|     def validate_expression(self, expr: str) -> str: |     def validate_expression(self, expr: str) -> str: | ||||||
|         """validate the syntax of the expression""" |         """validate the syntax of the expression""" | ||||||
|         name = "temp-policy" if not self.instance else self.instance.name |         name = "temp-policy" if not self.instance else self.instance.name | ||||||
|         PolicyEvaluator(name).validate(expr) |         PolicyEvaluator(self.context["request"].user, name).validate(expr) | ||||||
|         return expr |         return expr | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = ExpressionPolicy |         model = ExpressionPolicy | ||||||
|         fields = PolicySerializer.Meta.fields + ["expression"] |         fields = PolicySerializer.Meta.fields + ["expression", "execution_user"] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet): | class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet): | ||||||
|  | |||||||
| @ -1,11 +1,12 @@ | |||||||
| """Authentik policy_expression app config""" | """Authentik policy_expression app config""" | ||||||
|  |  | ||||||
| from django.apps import AppConfig | from authentik.blueprints.apps import ManagedAppConfig | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthentikPolicyExpressionConfig(AppConfig): | class AuthentikPolicyExpressionConfig(ManagedAppConfig): | ||||||
|     """Authentik policy_expression app config""" |     """Authentik policy_expression app config""" | ||||||
|  |  | ||||||
|     name = "authentik.policies.expression" |     name = "authentik.policies.expression" | ||||||
|     label = "authentik_policies_expression" |     label = "authentik_policies_expression" | ||||||
|     verbose_name = "authentik Policies.Expression" |     verbose_name = "authentik Policies.Expression" | ||||||
|  |     default = True | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional | |||||||
| from django.http import HttpRequest | from django.http import HttpRequest | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.models import User | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_SSO | from authentik.flows.planner import PLAN_CONTEXT_SSO | ||||||
| from authentik.lib.expression.evaluator import BaseEvaluator | from authentik.lib.expression.evaluator import BaseEvaluator | ||||||
| from authentik.policies.exceptions import PolicyException | from authentik.policies.exceptions import PolicyException | ||||||
| @ -24,8 +25,8 @@ class PolicyEvaluator(BaseEvaluator): | |||||||
|  |  | ||||||
|     policy: Optional["ExpressionPolicy"] = None |     policy: Optional["ExpressionPolicy"] = None | ||||||
|  |  | ||||||
|     def __init__(self, policy_name: str | None = None): |     def __init__(self, user: User, policy_name: str | None = None): | ||||||
|         super().__init__(policy_name or "PolicyEvaluator") |         super().__init__(user, policy_name or "PolicyEvaluator") | ||||||
|         self._messages = [] |         self._messages = [] | ||||||
|         # update website/docs/expressions/_objects.md |         # update website/docs/expressions/_objects.md | ||||||
|         # update website/docs/expressions/_functions.md |         # update website/docs/expressions/_functions.md | ||||||
| @ -44,6 +45,8 @@ class PolicyEvaluator(BaseEvaluator): | |||||||
|         if request.http_request: |         if request.http_request: | ||||||
|             self.set_http_request(request.http_request) |             self.set_http_request(request.http_request) | ||||||
|         self._context["request"] = request |         self._context["request"] = request | ||||||
|  |         if not self._user: | ||||||
|  |             self._user = request.user | ||||||
|         self._context["context"] = request.context |         self._context["context"] = request.context | ||||||
|  |  | ||||||
|     def set_http_request(self, request: HttpRequest): |     def set_http_request(self, request: HttpRequest): | ||||||
|  | |||||||
| @ -0,0 +1,26 @@ | |||||||
|  | # Generated by Django 5.0.3 on 2024-03-20 12:14 | ||||||
|  |  | ||||||
|  | import django.db.models.deletion | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_policies_expression", "0004_expressionpolicy_authentik_p_policy__fb6feb_idx"), | ||||||
|  |         migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="expressionpolicy", | ||||||
|  |             name="execution_user", | ||||||
|  |             field=models.ForeignKey( | ||||||
|  |                 default=None, | ||||||
|  |                 null=True, | ||||||
|  |                 on_delete=django.db.models.deletion.SET_DEFAULT, | ||||||
|  |                 to=settings.AUTH_USER_MODEL, | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -12,6 +12,9 @@ from authentik.policies.types import PolicyRequest, PolicyResult | |||||||
| 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.""" | ||||||
|  |  | ||||||
|  |     execution_user = models.ForeignKey( | ||||||
|  |         "authentik_core.User", default=None, null=True, on_delete=models.SET_DEFAULT | ||||||
|  |     ) | ||||||
|     expression = models.TextField() |     expression = models.TextField() | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @ -26,17 +29,11 @@ class ExpressionPolicy(Policy): | |||||||
|  |  | ||||||
|     def passes(self, request: PolicyRequest) -> PolicyResult: |     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||||
|         """Evaluate and render expression. Returns PolicyResult(false) on error.""" |         """Evaluate and render expression. Returns PolicyResult(false) on error.""" | ||||||
|         evaluator = PolicyEvaluator(self.name) |         evaluator = PolicyEvaluator(self.execution_user, self.name) | ||||||
|         evaluator.policy = self |         evaluator.policy = self | ||||||
|         evaluator.set_policy_request(request) |         evaluator.set_policy_request(request) | ||||||
|         return evaluator.evaluate(self.expression) |         return evaluator.evaluate(self.expression) | ||||||
|  |  | ||||||
|     def save(self, *args, **kwargs): |  | ||||||
|         evaluator = PolicyEvaluator(self.name) |  | ||||||
|         evaluator.policy = self |  | ||||||
|         evaluator.validate(self.expression) |  | ||||||
|         return super().save(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     class Meta(Policy.PolicyMeta): |     class Meta(Policy.PolicyMeta): | ||||||
|         verbose_name = _("Expression Policy") |         verbose_name = _("Expression Policy") | ||||||
|         verbose_name_plural = _("Expression Policies") |         verbose_name_plural = _("Expression Policies") | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								authentik/policies/expression/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								authentik/policies/expression/signals.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | from django.db.models.signals import pre_save | ||||||
|  | from django.dispatch import receiver | ||||||
|  |  | ||||||
|  | from authentik.policies.expression.evaluator import PolicyEvaluator | ||||||
|  | from authentik.policies.expression.models import ExpressionPolicy | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @receiver(pre_save, sender=ExpressionPolicy) | ||||||
|  | def pre_save_expression_policy(sender: type[ExpressionPolicy], instance: ExpressionPolicy, **_): | ||||||
|  |     """Ensure policy is valid before saving""" | ||||||
|  |     evaluator = PolicyEvaluator(instance.execution_user, instance.name) | ||||||
|  |     evaluator.policy = instance | ||||||
|  |     evaluator.validate(instance.expression) | ||||||
| @ -3477,6 +3477,10 @@ | |||||||
|                     "type": "string", |                     "type": "string", | ||||||
|                     "minLength": 1, |                     "minLength": 1, | ||||||
|                     "title": "Expression" |                     "title": "Expression" | ||||||
|  |                 }, | ||||||
|  |                 "execution_user": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "title": "Execution user" | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "required": [] |             "required": [] | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								schema.yml
									
									
									
									
									
								
							| @ -11852,6 +11852,10 @@ paths: | |||||||
|         name: execution_logging |         name: execution_logging | ||||||
|         schema: |         schema: | ||||||
|           type: boolean |           type: boolean | ||||||
|  |       - in: query | ||||||
|  |         name: execution_user | ||||||
|  |         schema: | ||||||
|  |           type: integer | ||||||
|       - in: query |       - in: query | ||||||
|         name: expression |         name: expression | ||||||
|         schema: |         schema: | ||||||
| @ -33565,6 +33569,9 @@ components: | |||||||
|           readOnly: true |           readOnly: true | ||||||
|         expression: |         expression: | ||||||
|           type: string |           type: string | ||||||
|  |         execution_user: | ||||||
|  |           type: integer | ||||||
|  |           nullable: true | ||||||
|       required: |       required: | ||||||
|       - bound_to |       - bound_to | ||||||
|       - component |       - component | ||||||
| @ -33588,6 +33595,9 @@ components: | |||||||
|         expression: |         expression: | ||||||
|           type: string |           type: string | ||||||
|           minLength: 1 |           minLength: 1 | ||||||
|  |         execution_user: | ||||||
|  |           type: integer | ||||||
|  |           nullable: true | ||||||
|       required: |       required: | ||||||
|       - expression |       - expression | ||||||
|       - name |       - name | ||||||
| @ -38841,6 +38851,9 @@ components: | |||||||
|         expression: |         expression: | ||||||
|           type: string |           type: string | ||||||
|           minLength: 1 |           minLength: 1 | ||||||
|  |         execution_user: | ||||||
|  |           type: integer | ||||||
|  |           nullable: true | ||||||
|     PatchedFlowRequest: |     PatchedFlowRequest: | ||||||
|       type: object |       type: object | ||||||
|       description: Flow Serializer |       description: Flow Serializer | ||||||
|  | |||||||
| @ -12,7 +12,13 @@ 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 { | ||||||
|  |     CoreApi, | ||||||
|  |     CoreUsersListRequest, | ||||||
|  |     ExpressionPolicy, | ||||||
|  |     PoliciesApi, | ||||||
|  |     User, | ||||||
|  | } from "@goauthentik/api"; | ||||||
|  |  | ||||||
| @customElement("ak-policy-expression-form") | @customElement("ak-policy-expression-form") | ||||||
| export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> { | export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> { | ||||||
| @ -92,6 +98,39 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> { | |||||||
|                             </a> |                             </a> | ||||||
|                         </p> |                         </p> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|  |                     <ak-form-element-horizontal label=${msg("Execution user")} name="executionUser"> | ||||||
|  |                         <ak-search-select | ||||||
|  |                             .fetchObjects=${async (query?: string): Promise<User[]> => { | ||||||
|  |                                 const args: CoreUsersListRequest = { | ||||||
|  |                                     ordering: "username", | ||||||
|  |                                 }; | ||||||
|  |                                 if (query !== undefined) { | ||||||
|  |                                     args.search = query; | ||||||
|  |                                 } | ||||||
|  |                                 const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args); | ||||||
|  |                                 return users.results; | ||||||
|  |                             }} | ||||||
|  |                             .renderElement=${(user: User): string => { | ||||||
|  |                                 return user.username; | ||||||
|  |                             }} | ||||||
|  |                             .renderDescription=${(user: User): TemplateResult => { | ||||||
|  |                                 return html`${user.name}`; | ||||||
|  |                             }} | ||||||
|  |                             .value=${(user: User | undefined): number | undefined => { | ||||||
|  |                                 return user?.pk; | ||||||
|  |                             }} | ||||||
|  |                             .selected=${(user: User): boolean => { | ||||||
|  |                                 return this.instance?.executionUser === user.pk; | ||||||
|  |                             }} | ||||||
|  |                             blankable | ||||||
|  |                         > | ||||||
|  |                         </ak-search-select> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg( | ||||||
|  |                                 "Configure which user the bundled API client authenticates as. When left empty, the API client will inherit the permissions of the user triggering the policy execution.", | ||||||
|  |                             )} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|                 </div> |                 </div> | ||||||
|             </ak-form-group>`; |             </ak-form-group>`; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ export function docLink(path: string): string { | |||||||
|     const ak = globalAK(); |     const ak = globalAK(); | ||||||
|     // Default case or beta build which should always point to latest |     // Default case or beta build which should always point to latest | ||||||
|     if (!ak || ak.build !== "") { |     if (!ak || ak.build !== "") { | ||||||
|         return `https://goauthentik.io${path}`; |         return `https://docs.goauthentik.io${path}`; | ||||||
|     } |     } | ||||||
|     return `https://${ak.versionSubdomain}.goauthentik.io${path}`; |     return `https://${ak.versionSubdomain}.goauthentik.io${path}`; | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer