diff --git a/authentik/admin/api/meta.py b/authentik/admin/api/meta.py index 17a83ec1e..25d944411 100644 --- a/authentik/admin/api/meta.py +++ b/authentik/admin/api/meta.py @@ -8,6 +8,7 @@ from rest_framework.viewsets import ViewSet from authentik.core.api.utils import PassiveSerializer from authentik.lib.utils.reflection import get_apps +from authentik.policies.event_matcher.models import model_choices class AppSerializer(PassiveSerializer): @@ -29,3 +30,17 @@ class AppsViewSet(ViewSet): for app in sorted(get_apps(), key=lambda app: app.name): data.append({"name": app.name, "label": app.verbose_name}) return Response(AppSerializer(data, many=True).data) + + +class ModelViewSet(ViewSet): + """Read-only view list all installed models""" + + permission_classes = [IsAdminUser] + + @extend_schema(responses={200: AppSerializer(many=True)}) + def list(self, request: Request) -> Response: + """Read-only view list all installed models""" + data = [] + for name, label in model_choices(): + data.append({"name": name, "label": label}) + return Response(AppSerializer(data, many=True).data) diff --git a/authentik/admin/tests/test_api.py b/authentik/admin/tests/test_api.py index e4e5b1347..8fcbfa286 100644 --- a/authentik/admin/tests/test_api.py +++ b/authentik/admin/tests/test_api.py @@ -94,6 +94,11 @@ class TestAdminAPI(TestCase): response = self.client.get(reverse("authentik_api:apps-list")) self.assertEqual(response.status_code, 200) + def test_models(self): + """Test models API""" + response = self.client.get(reverse("authentik_api:models-list")) + self.assertEqual(response.status_code, 200) + @reconcile_app("authentik_outposts") def test_system(self): """Test system API""" diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 25ad5885b..fec51f5f7 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -1,7 +1,7 @@ """API URLs""" from django.urls import path -from authentik.admin.api.meta import AppsViewSet +from authentik.admin.api.meta import AppsViewSet, ModelViewSet from authentik.admin.api.metrics import AdministrationMetricsViewSet from authentik.admin.api.system import SystemView from authentik.admin.api.tasks import TaskViewSet @@ -11,6 +11,7 @@ from authentik.admin.api.workers import WorkerView api_urlpatterns = [ ("admin/system_tasks", TaskViewSet, "admin_system_tasks"), ("admin/apps", AppsViewSet, "apps"), + ("admin/models", ModelViewSet, "models"), path( "admin/metrics/", AdministrationMetricsViewSet.as_view(), diff --git a/authentik/policies/event_matcher/api.py b/authentik/policies/event_matcher/api.py index 7a5ded3e3..4d8181915 100644 --- a/authentik/policies/event_matcher/api.py +++ b/authentik/policies/event_matcher/api.py @@ -6,7 +6,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.used_by import UsedByMixin from authentik.policies.api.policies import PolicySerializer -from authentik.policies.event_matcher.models import EventMatcherPolicy, app_choices +from authentik.policies.event_matcher.models import EventMatcherPolicy, app_choices, model_choices class EventMatcherPolicySerializer(PolicySerializer): @@ -21,9 +21,24 @@ class EventMatcherPolicySerializer(PolicySerializer): "all applications are matched." ), ) + model = ChoiceField( + choices=model_choices(), + required=False, + allow_blank=True, + help_text=_( + "Match events created by selected model. " + "When left empty, all models are matched. When an app is selected, " + "all the application's models are matched." + ), + ) def validate(self, attrs: dict) -> dict: - if attrs["action"] == "" and attrs["client_ip"] == "" and attrs["app"] == "": + if ( + attrs["action"] == "" + and attrs["client_ip"] == "" + and attrs["app"] == "" + and attrs["model"] == "" + ): raise ValidationError(_("At least one criteria must be set.")) return super().validate(attrs) @@ -33,6 +48,7 @@ class EventMatcherPolicySerializer(PolicySerializer): "action", "client_ip", "app", + "model", ] diff --git a/authentik/policies/event_matcher/migrations/0022_eventmatcherpolicy_model.py b/authentik/policies/event_matcher/migrations/0022_eventmatcherpolicy_model.py new file mode 100644 index 000000000..bfa8070c4 --- /dev/null +++ b/authentik/policies/event_matcher/migrations/0022_eventmatcherpolicy_model.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.7 on 2023-05-29 15:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_policies_event_matcher", "0021_alter_eventmatcherpolicy_app"), + ] + + operations = [ + migrations.AddField( + model_name="eventmatcherpolicy", + name="model", + field=models.TextField( + blank=True, + default="", + help_text="Match events created by selected model. When left empty, all models are matched. When an app is selected, all the application's models are matched.", + ), + ), + ] diff --git a/authentik/policies/event_matcher/models.py b/authentik/policies/event_matcher/models.py index cad2f261f..83c44f76c 100644 --- a/authentik/policies/event_matcher/models.py +++ b/authentik/policies/event_matcher/models.py @@ -1,13 +1,19 @@ """Event Matcher models""" +from itertools import chain + from django.apps import apps from django.db import models from django.utils.translation import gettext as _ from rest_framework.serializers import BaseSerializer +from structlog.stdlib import get_logger +from authentik.blueprints.v1.importer import is_model_allowed from authentik.events.models import Event, EventAction from authentik.policies.models import Policy from authentik.policies.types import PolicyRequest, PolicyResult +LOGGER = get_logger() + def app_choices() -> list[tuple[str, str]]: """Get a list of all installed applications that create events. @@ -19,6 +25,18 @@ def app_choices() -> list[tuple[str, str]]: return choices +def model_choices() -> list[tuple[str, str]]: + """Get a list of all installed models + Returns a list of tuples containing (dotted.model.path, name)""" + choices = [] + for model in apps.get_models(): + if not is_model_allowed(model): + continue + name = f"{model._meta.app_label}.{model._meta.model_name}" + choices.append((name, model._meta.verbose_name)) + return choices + + class EventMatcherPolicy(Policy): """Passes when Event matches selected criteria.""" @@ -38,6 +56,15 @@ class EventMatcherPolicy(Policy): "When left empty, all applications are matched." ), ) + model = models.TextField( + blank=True, + default="", + help_text=_( + "Match events created by selected model. " + "When left empty, all models are matched. When an app is selected, " + "all the application's models are matched." + ), + ) client_ip = models.TextField( blank=True, help_text=_( @@ -60,13 +87,55 @@ class EventMatcherPolicy(Policy): if "event" not in request.context: return PolicyResult(False) event: Event = request.context["event"] - if event.action == self.action: - return PolicyResult(True, "Action matched.") - if event.client_ip == self.client_ip: - return PolicyResult(True, "Client IP matched.") - if event.app == self.app: - return PolicyResult(True, "App matched.") - return PolicyResult(False) + matches: list[PolicyResult] = [] + messages = [] + checks = [ + self.passes_action, + self.passes_client_ip, + self.passes_app, + self.passes_model, + ] + for checker in checks: + result = checker(request, event) + if result is None: + continue + LOGGER.info( + "Event matcher check result", + checker=checker.__name__, + result=result, + ) + matches.append(result) + passing = any(x.passing for x in matches) + messages = chain(*[x.messages for x in matches]) + result = PolicyResult(passing, *messages) + result.source_results = matches + return result + + def passes_action(self, request: PolicyRequest, event: Event) -> PolicyResult | None: + """Check if `self.action` matches""" + if self.action == "": + return None + return PolicyResult(self.action == event.action, "Action matched.") + + def passes_client_ip(self, request: PolicyRequest, event: Event) -> PolicyResult | None: + """Check if `self.client_ip` matches""" + if self.client_ip == "": + return None + return PolicyResult(self.client_ip == event.client_ip, "Client IP matched.") + + def passes_app(self, request: PolicyRequest, event: Event) -> PolicyResult | None: + """Check if `self.app` matches""" + if self.app == "": + return None + return PolicyResult(self.app == event.app, "App matched.") + + def passes_model(self, request: PolicyRequest, event: Event) -> PolicyResult | None: + """Check if `self.model` is set, and pass if it matches the event's model""" + if self.model == "": + return None + event_model_info = event.context.get("model", {}) + event_model = f"{event_model_info.get('app')}.{event_model_info.get('model_name')}" + return PolicyResult(event_model == self.model, "Model matched.") class Meta(Policy.PolicyMeta): verbose_name = _("Event Matcher Policy") diff --git a/authentik/policies/event_matcher/tests.py b/authentik/policies/event_matcher/tests.py index 2c6347430..d703125e9 100644 --- a/authentik/policies/event_matcher/tests.py +++ b/authentik/policies/event_matcher/tests.py @@ -42,6 +42,22 @@ class TestEventMatcherPolicy(TestCase): self.assertTrue(response.passing) self.assertTupleEqual(response.messages, ("App matched.",)) + def test_match_model(self): + """Test match model""" + event = Event.new(EventAction.LOGIN) + event.context = { + "model": { + "app": "foo", + "model_name": "bar", + } + } + request = PolicyRequest(get_anonymous_user()) + request.context["event"] = event + policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(model="foo.bar") + response = policy.passes(request) + self.assertTrue(response.passing) + self.assertTupleEqual(response.messages, ("Model matched.",)) + def test_drop(self): """Test drop event""" event = Event.new(EventAction.LOGIN) @@ -52,6 +68,19 @@ class TestEventMatcherPolicy(TestCase): response = policy.passes(request) self.assertFalse(response.passing) + def test_drop_multiple(self): + """Test drop event""" + event = Event.new(EventAction.LOGIN) + event.app = "foo" + event.client_ip = "1.2.3.4" + request = PolicyRequest(get_anonymous_user()) + request.context["event"] = event + policy: EventMatcherPolicy = EventMatcherPolicy.objects.create( + client_ip="1.2.3.5", app="bar" + ) + response = policy.passes(request) + self.assertFalse(response.passing) + def test_invalid(self): """Test passing event""" request = PolicyRequest(get_anonymous_user()) diff --git a/blueprints/schema.json b/blueprints/schema.json index edbc7f81b..8f41c9449 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -3249,6 +3249,84 @@ ], "title": "App", "description": "Match events created by selected application. When left empty, all applications are matched." + }, + "model": { + "type": "string", + "enum": [ + "", + "authentik_crypto.certificatekeypair", + "authentik_events.event", + "authentik_events.notificationtransport", + "authentik_events.notification", + "authentik_events.notificationrule", + "authentik_events.notificationwebhookmapping", + "authentik_flows.flow", + "authentik_flows.flowstagebinding", + "authentik_outposts.dockerserviceconnection", + "authentik_outposts.kubernetesserviceconnection", + "authentik_outposts.outpost", + "authentik_policies_dummy.dummypolicy", + "authentik_policies_event_matcher.eventmatcherpolicy", + "authentik_policies_expiry.passwordexpirypolicy", + "authentik_policies_expression.expressionpolicy", + "authentik_policies_password.passwordpolicy", + "authentik_policies_reputation.reputationpolicy", + "authentik_policies_reputation.reputation", + "authentik_policies.policybinding", + "authentik_providers_ldap.ldapprovider", + "authentik_providers_oauth2.scopemapping", + "authentik_providers_oauth2.oauth2provider", + "authentik_providers_oauth2.authorizationcode", + "authentik_providers_oauth2.accesstoken", + "authentik_providers_oauth2.refreshtoken", + "authentik_providers_proxy.proxyprovider", + "authentik_providers_radius.radiusprovider", + "authentik_providers_saml.samlprovider", + "authentik_providers_saml.samlpropertymapping", + "authentik_providers_scim.scimprovider", + "authentik_providers_scim.scimmapping", + "authentik_sources_ldap.ldapsource", + "authentik_sources_ldap.ldappropertymapping", + "authentik_sources_oauth.oauthsource", + "authentik_sources_oauth.useroauthsourceconnection", + "authentik_sources_plex.plexsource", + "authentik_sources_plex.plexsourceconnection", + "authentik_sources_saml.samlsource", + "authentik_sources_saml.usersamlsourceconnection", + "authentik_stages_authenticator_duo.authenticatorduostage", + "authentik_stages_authenticator_duo.duodevice", + "authentik_stages_authenticator_sms.authenticatorsmsstage", + "authentik_stages_authenticator_sms.smsdevice", + "authentik_stages_authenticator_static.authenticatorstaticstage", + "authentik_stages_authenticator_totp.authenticatortotpstage", + "authentik_stages_authenticator_validate.authenticatorvalidatestage", + "authentik_stages_authenticator_webauthn.authenticatewebauthnstage", + "authentik_stages_authenticator_webauthn.webauthndevice", + "authentik_stages_captcha.captchastage", + "authentik_stages_consent.consentstage", + "authentik_stages_consent.userconsent", + "authentik_stages_deny.denystage", + "authentik_stages_dummy.dummystage", + "authentik_stages_email.emailstage", + "authentik_stages_identification.identificationstage", + "authentik_stages_invitation.invitationstage", + "authentik_stages_invitation.invitation", + "authentik_stages_password.passwordstage", + "authentik_stages_prompt.prompt", + "authentik_stages_prompt.promptstage", + "authentik_stages_user_delete.userdeletestage", + "authentik_stages_user_login.userloginstage", + "authentik_stages_user_logout.userlogoutstage", + "authentik_stages_user_write.userwritestage", + "authentik_tenants.tenant", + "authentik_blueprints.blueprintinstance", + "authentik_core.group", + "authentik_core.user", + "authentik_core.application", + "authentik_core.token" + ], + "title": "Model", + "description": "Match events created by selected model. When left empty, all models are matched. When an app is selected, all the application's models are matched." } }, "required": [] diff --git a/schema.yml b/schema.yml index 63065ceee..857e4d2a1 100644 --- a/schema.yml +++ b/schema.yml @@ -65,6 +65,35 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' + /admin/models/: + get: + operationId: admin_models_list + description: Read-only view list all installed models + tags: + - admin + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/App' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' /admin/system/: get: operationId: admin_system_retrieve @@ -10973,6 +11002,10 @@ paths: schema: type: string format: date-time + - in: query + name: model + schema: + type: string - in: query name: name schema: @@ -29155,6 +29188,82 @@ components: * `authentik.blueprints` - authentik Blueprints * `authentik.core` - authentik Core * `authentik.enterprise` - authentik Enterprise + model: + allOf: + - $ref: '#/components/schemas/ModelEnum' + description: |- + Match events created by selected model. When left empty, all models are matched. When an app is selected, all the application's models are matched. + + * `authentik_crypto.certificatekeypair` - Certificate-Key Pair + * `authentik_events.event` - Event + * `authentik_events.notificationtransport` - Notification Transport + * `authentik_events.notification` - Notification + * `authentik_events.notificationrule` - Notification Rule + * `authentik_events.notificationwebhookmapping` - Webhook Mapping + * `authentik_flows.flow` - Flow + * `authentik_flows.flowstagebinding` - Flow Stage Binding + * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection + * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection + * `authentik_outposts.outpost` - outpost + * `authentik_policies_dummy.dummypolicy` - Dummy Policy + * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy + * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy + * `authentik_policies_expression.expressionpolicy` - Expression Policy + * `authentik_policies_password.passwordpolicy` - Password Policy + * `authentik_policies_reputation.reputationpolicy` - Reputation Policy + * `authentik_policies_reputation.reputation` - reputation + * `authentik_policies.policybinding` - Policy Binding + * `authentik_providers_ldap.ldapprovider` - LDAP Provider + * `authentik_providers_oauth2.scopemapping` - Scope Mapping + * `authentik_providers_oauth2.oauth2provider` - OAuth2/OpenID Provider + * `authentik_providers_oauth2.authorizationcode` - Authorization Code + * `authentik_providers_oauth2.accesstoken` - OAuth2 Access Token + * `authentik_providers_oauth2.refreshtoken` - OAuth2 Refresh Token + * `authentik_providers_proxy.proxyprovider` - Proxy Provider + * `authentik_providers_radius.radiusprovider` - Radius Provider + * `authentik_providers_saml.samlprovider` - SAML Provider + * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping + * `authentik_providers_scim.scimprovider` - SCIM Provider + * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_sources_ldap.ldapsource` - LDAP Source + * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping + * `authentik_sources_oauth.oauthsource` - OAuth Source + * `authentik_sources_oauth.useroauthsourceconnection` - User OAuth Source Connection + * `authentik_sources_plex.plexsource` - Plex Source + * `authentik_sources_plex.plexsourceconnection` - User Plex Source Connection + * `authentik_sources_saml.samlsource` - SAML Source + * `authentik_sources_saml.usersamlsourceconnection` - User SAML Source Connection + * `authentik_stages_authenticator_duo.authenticatorduostage` - Duo Authenticator Setup Stage + * `authentik_stages_authenticator_duo.duodevice` - Duo Device + * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage + * `authentik_stages_authenticator_sms.smsdevice` - SMS Device + * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage + * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage + * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage + * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage + * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device + * `authentik_stages_captcha.captchastage` - Captcha Stage + * `authentik_stages_consent.consentstage` - Consent Stage + * `authentik_stages_consent.userconsent` - User Consent + * `authentik_stages_deny.denystage` - Deny Stage + * `authentik_stages_dummy.dummystage` - Dummy Stage + * `authentik_stages_email.emailstage` - Email Stage + * `authentik_stages_identification.identificationstage` - Identification Stage + * `authentik_stages_invitation.invitationstage` - Invitation Stage + * `authentik_stages_invitation.invitation` - Invitation + * `authentik_stages_password.passwordstage` - Password Stage + * `authentik_stages_prompt.prompt` - Prompt + * `authentik_stages_prompt.promptstage` - Prompt Stage + * `authentik_stages_user_delete.userdeletestage` - User Delete Stage + * `authentik_stages_user_login.userloginstage` - User Login Stage + * `authentik_stages_user_logout.userlogoutstage` - User Logout Stage + * `authentik_stages_user_write.userwritestage` - User Write Stage + * `authentik_tenants.tenant` - Tenant + * `authentik_blueprints.blueprintinstance` - Blueprint Instance + * `authentik_core.group` - group + * `authentik_core.user` - User + * `authentik_core.application` - Application + * `authentik_core.token` - Token required: - bound_to - component @@ -29265,6 +29374,82 @@ components: * `authentik.blueprints` - authentik Blueprints * `authentik.core` - authentik Core * `authentik.enterprise` - authentik Enterprise + model: + allOf: + - $ref: '#/components/schemas/ModelEnum' + description: |- + Match events created by selected model. When left empty, all models are matched. When an app is selected, all the application's models are matched. + + * `authentik_crypto.certificatekeypair` - Certificate-Key Pair + * `authentik_events.event` - Event + * `authentik_events.notificationtransport` - Notification Transport + * `authentik_events.notification` - Notification + * `authentik_events.notificationrule` - Notification Rule + * `authentik_events.notificationwebhookmapping` - Webhook Mapping + * `authentik_flows.flow` - Flow + * `authentik_flows.flowstagebinding` - Flow Stage Binding + * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection + * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection + * `authentik_outposts.outpost` - outpost + * `authentik_policies_dummy.dummypolicy` - Dummy Policy + * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy + * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy + * `authentik_policies_expression.expressionpolicy` - Expression Policy + * `authentik_policies_password.passwordpolicy` - Password Policy + * `authentik_policies_reputation.reputationpolicy` - Reputation Policy + * `authentik_policies_reputation.reputation` - reputation + * `authentik_policies.policybinding` - Policy Binding + * `authentik_providers_ldap.ldapprovider` - LDAP Provider + * `authentik_providers_oauth2.scopemapping` - Scope Mapping + * `authentik_providers_oauth2.oauth2provider` - OAuth2/OpenID Provider + * `authentik_providers_oauth2.authorizationcode` - Authorization Code + * `authentik_providers_oauth2.accesstoken` - OAuth2 Access Token + * `authentik_providers_oauth2.refreshtoken` - OAuth2 Refresh Token + * `authentik_providers_proxy.proxyprovider` - Proxy Provider + * `authentik_providers_radius.radiusprovider` - Radius Provider + * `authentik_providers_saml.samlprovider` - SAML Provider + * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping + * `authentik_providers_scim.scimprovider` - SCIM Provider + * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_sources_ldap.ldapsource` - LDAP Source + * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping + * `authentik_sources_oauth.oauthsource` - OAuth Source + * `authentik_sources_oauth.useroauthsourceconnection` - User OAuth Source Connection + * `authentik_sources_plex.plexsource` - Plex Source + * `authentik_sources_plex.plexsourceconnection` - User Plex Source Connection + * `authentik_sources_saml.samlsource` - SAML Source + * `authentik_sources_saml.usersamlsourceconnection` - User SAML Source Connection + * `authentik_stages_authenticator_duo.authenticatorduostage` - Duo Authenticator Setup Stage + * `authentik_stages_authenticator_duo.duodevice` - Duo Device + * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage + * `authentik_stages_authenticator_sms.smsdevice` - SMS Device + * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage + * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage + * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage + * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage + * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device + * `authentik_stages_captcha.captchastage` - Captcha Stage + * `authentik_stages_consent.consentstage` - Consent Stage + * `authentik_stages_consent.userconsent` - User Consent + * `authentik_stages_deny.denystage` - Deny Stage + * `authentik_stages_dummy.dummystage` - Dummy Stage + * `authentik_stages_email.emailstage` - Email Stage + * `authentik_stages_identification.identificationstage` - Identification Stage + * `authentik_stages_invitation.invitationstage` - Invitation Stage + * `authentik_stages_invitation.invitation` - Invitation + * `authentik_stages_password.passwordstage` - Password Stage + * `authentik_stages_prompt.prompt` - Prompt + * `authentik_stages_prompt.promptstage` - Prompt Stage + * `authentik_stages_user_delete.userdeletestage` - User Delete Stage + * `authentik_stages_user_login.userloginstage` - User Login Stage + * `authentik_stages_user_logout.userlogoutstage` - User Logout Stage + * `authentik_stages_user_write.userwritestage` - User Write Stage + * `authentik_tenants.tenant` - Tenant + * `authentik_blueprints.blueprintinstance` - Blueprint Instance + * `authentik_core.group` - group + * `authentik_core.user` - User + * `authentik_core.application` - Application + * `authentik_core.token` - Token required: - name EventRequest: @@ -31236,6 +31421,150 @@ components: required: - labels - name + ModelEnum: + enum: + - authentik_crypto.certificatekeypair + - authentik_events.event + - authentik_events.notificationtransport + - authentik_events.notification + - authentik_events.notificationrule + - authentik_events.notificationwebhookmapping + - authentik_flows.flow + - authentik_flows.flowstagebinding + - authentik_outposts.dockerserviceconnection + - authentik_outposts.kubernetesserviceconnection + - authentik_outposts.outpost + - authentik_policies_dummy.dummypolicy + - authentik_policies_event_matcher.eventmatcherpolicy + - authentik_policies_expiry.passwordexpirypolicy + - authentik_policies_expression.expressionpolicy + - authentik_policies_password.passwordpolicy + - authentik_policies_reputation.reputationpolicy + - authentik_policies_reputation.reputation + - authentik_policies.policybinding + - authentik_providers_ldap.ldapprovider + - authentik_providers_oauth2.scopemapping + - authentik_providers_oauth2.oauth2provider + - authentik_providers_oauth2.authorizationcode + - authentik_providers_oauth2.accesstoken + - authentik_providers_oauth2.refreshtoken + - authentik_providers_proxy.proxyprovider + - authentik_providers_radius.radiusprovider + - authentik_providers_saml.samlprovider + - authentik_providers_saml.samlpropertymapping + - authentik_providers_scim.scimprovider + - authentik_providers_scim.scimmapping + - authentik_sources_ldap.ldapsource + - authentik_sources_ldap.ldappropertymapping + - authentik_sources_oauth.oauthsource + - authentik_sources_oauth.useroauthsourceconnection + - authentik_sources_plex.plexsource + - authentik_sources_plex.plexsourceconnection + - authentik_sources_saml.samlsource + - authentik_sources_saml.usersamlsourceconnection + - authentik_stages_authenticator_duo.authenticatorduostage + - authentik_stages_authenticator_duo.duodevice + - authentik_stages_authenticator_sms.authenticatorsmsstage + - authentik_stages_authenticator_sms.smsdevice + - authentik_stages_authenticator_static.authenticatorstaticstage + - authentik_stages_authenticator_totp.authenticatortotpstage + - authentik_stages_authenticator_validate.authenticatorvalidatestage + - authentik_stages_authenticator_webauthn.authenticatewebauthnstage + - authentik_stages_authenticator_webauthn.webauthndevice + - authentik_stages_captcha.captchastage + - authentik_stages_consent.consentstage + - authentik_stages_consent.userconsent + - authentik_stages_deny.denystage + - authentik_stages_dummy.dummystage + - authentik_stages_email.emailstage + - authentik_stages_identification.identificationstage + - authentik_stages_invitation.invitationstage + - authentik_stages_invitation.invitation + - authentik_stages_password.passwordstage + - authentik_stages_prompt.prompt + - authentik_stages_prompt.promptstage + - authentik_stages_user_delete.userdeletestage + - authentik_stages_user_login.userloginstage + - authentik_stages_user_logout.userlogoutstage + - authentik_stages_user_write.userwritestage + - authentik_tenants.tenant + - authentik_blueprints.blueprintinstance + - authentik_core.group + - authentik_core.user + - authentik_core.application + - authentik_core.token + type: string + description: |- + * `authentik_crypto.certificatekeypair` - Certificate-Key Pair + * `authentik_events.event` - Event + * `authentik_events.notificationtransport` - Notification Transport + * `authentik_events.notification` - Notification + * `authentik_events.notificationrule` - Notification Rule + * `authentik_events.notificationwebhookmapping` - Webhook Mapping + * `authentik_flows.flow` - Flow + * `authentik_flows.flowstagebinding` - Flow Stage Binding + * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection + * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection + * `authentik_outposts.outpost` - outpost + * `authentik_policies_dummy.dummypolicy` - Dummy Policy + * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy + * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy + * `authentik_policies_expression.expressionpolicy` - Expression Policy + * `authentik_policies_password.passwordpolicy` - Password Policy + * `authentik_policies_reputation.reputationpolicy` - Reputation Policy + * `authentik_policies_reputation.reputation` - reputation + * `authentik_policies.policybinding` - Policy Binding + * `authentik_providers_ldap.ldapprovider` - LDAP Provider + * `authentik_providers_oauth2.scopemapping` - Scope Mapping + * `authentik_providers_oauth2.oauth2provider` - OAuth2/OpenID Provider + * `authentik_providers_oauth2.authorizationcode` - Authorization Code + * `authentik_providers_oauth2.accesstoken` - OAuth2 Access Token + * `authentik_providers_oauth2.refreshtoken` - OAuth2 Refresh Token + * `authentik_providers_proxy.proxyprovider` - Proxy Provider + * `authentik_providers_radius.radiusprovider` - Radius Provider + * `authentik_providers_saml.samlprovider` - SAML Provider + * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping + * `authentik_providers_scim.scimprovider` - SCIM Provider + * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_sources_ldap.ldapsource` - LDAP Source + * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping + * `authentik_sources_oauth.oauthsource` - OAuth Source + * `authentik_sources_oauth.useroauthsourceconnection` - User OAuth Source Connection + * `authentik_sources_plex.plexsource` - Plex Source + * `authentik_sources_plex.plexsourceconnection` - User Plex Source Connection + * `authentik_sources_saml.samlsource` - SAML Source + * `authentik_sources_saml.usersamlsourceconnection` - User SAML Source Connection + * `authentik_stages_authenticator_duo.authenticatorduostage` - Duo Authenticator Setup Stage + * `authentik_stages_authenticator_duo.duodevice` - Duo Device + * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage + * `authentik_stages_authenticator_sms.smsdevice` - SMS Device + * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage + * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage + * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage + * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage + * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device + * `authentik_stages_captcha.captchastage` - Captcha Stage + * `authentik_stages_consent.consentstage` - Consent Stage + * `authentik_stages_consent.userconsent` - User Consent + * `authentik_stages_deny.denystage` - Deny Stage + * `authentik_stages_dummy.dummystage` - Dummy Stage + * `authentik_stages_email.emailstage` - Email Stage + * `authentik_stages_identification.identificationstage` - Identification Stage + * `authentik_stages_invitation.invitationstage` - Invitation Stage + * `authentik_stages_invitation.invitation` - Invitation + * `authentik_stages_password.passwordstage` - Password Stage + * `authentik_stages_prompt.prompt` - Prompt + * `authentik_stages_prompt.promptstage` - Prompt Stage + * `authentik_stages_user_delete.userdeletestage` - User Delete Stage + * `authentik_stages_user_login.userloginstage` - User Login Stage + * `authentik_stages_user_logout.userlogoutstage` - User Logout Stage + * `authentik_stages_user_write.userwritestage` - User Write Stage + * `authentik_tenants.tenant` - Tenant + * `authentik_blueprints.blueprintinstance` - Blueprint Instance + * `authentik_core.group` - group + * `authentik_core.user` - User + * `authentik_core.application` - Application + * `authentik_core.token` - Token NameIdPolicyEnum: enum: - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress @@ -35988,6 +36317,82 @@ components: * `authentik.blueprints` - authentik Blueprints * `authentik.core` - authentik Core * `authentik.enterprise` - authentik Enterprise + model: + allOf: + - $ref: '#/components/schemas/ModelEnum' + description: |- + Match events created by selected model. When left empty, all models are matched. When an app is selected, all the application's models are matched. + + * `authentik_crypto.certificatekeypair` - Certificate-Key Pair + * `authentik_events.event` - Event + * `authentik_events.notificationtransport` - Notification Transport + * `authentik_events.notification` - Notification + * `authentik_events.notificationrule` - Notification Rule + * `authentik_events.notificationwebhookmapping` - Webhook Mapping + * `authentik_flows.flow` - Flow + * `authentik_flows.flowstagebinding` - Flow Stage Binding + * `authentik_outposts.dockerserviceconnection` - Docker Service-Connection + * `authentik_outposts.kubernetesserviceconnection` - Kubernetes Service-Connection + * `authentik_outposts.outpost` - outpost + * `authentik_policies_dummy.dummypolicy` - Dummy Policy + * `authentik_policies_event_matcher.eventmatcherpolicy` - Event Matcher Policy + * `authentik_policies_expiry.passwordexpirypolicy` - Password Expiry Policy + * `authentik_policies_expression.expressionpolicy` - Expression Policy + * `authentik_policies_password.passwordpolicy` - Password Policy + * `authentik_policies_reputation.reputationpolicy` - Reputation Policy + * `authentik_policies_reputation.reputation` - reputation + * `authentik_policies.policybinding` - Policy Binding + * `authentik_providers_ldap.ldapprovider` - LDAP Provider + * `authentik_providers_oauth2.scopemapping` - Scope Mapping + * `authentik_providers_oauth2.oauth2provider` - OAuth2/OpenID Provider + * `authentik_providers_oauth2.authorizationcode` - Authorization Code + * `authentik_providers_oauth2.accesstoken` - OAuth2 Access Token + * `authentik_providers_oauth2.refreshtoken` - OAuth2 Refresh Token + * `authentik_providers_proxy.proxyprovider` - Proxy Provider + * `authentik_providers_radius.radiusprovider` - Radius Provider + * `authentik_providers_saml.samlprovider` - SAML Provider + * `authentik_providers_saml.samlpropertymapping` - SAML Property Mapping + * `authentik_providers_scim.scimprovider` - SCIM Provider + * `authentik_providers_scim.scimmapping` - SCIM Mapping + * `authentik_sources_ldap.ldapsource` - LDAP Source + * `authentik_sources_ldap.ldappropertymapping` - LDAP Property Mapping + * `authentik_sources_oauth.oauthsource` - OAuth Source + * `authentik_sources_oauth.useroauthsourceconnection` - User OAuth Source Connection + * `authentik_sources_plex.plexsource` - Plex Source + * `authentik_sources_plex.plexsourceconnection` - User Plex Source Connection + * `authentik_sources_saml.samlsource` - SAML Source + * `authentik_sources_saml.usersamlsourceconnection` - User SAML Source Connection + * `authentik_stages_authenticator_duo.authenticatorduostage` - Duo Authenticator Setup Stage + * `authentik_stages_authenticator_duo.duodevice` - Duo Device + * `authentik_stages_authenticator_sms.authenticatorsmsstage` - SMS Authenticator Setup Stage + * `authentik_stages_authenticator_sms.smsdevice` - SMS Device + * `authentik_stages_authenticator_static.authenticatorstaticstage` - Static Authenticator Stage + * `authentik_stages_authenticator_totp.authenticatortotpstage` - TOTP Authenticator Setup Stage + * `authentik_stages_authenticator_validate.authenticatorvalidatestage` - Authenticator Validation Stage + * `authentik_stages_authenticator_webauthn.authenticatewebauthnstage` - WebAuthn Authenticator Setup Stage + * `authentik_stages_authenticator_webauthn.webauthndevice` - WebAuthn Device + * `authentik_stages_captcha.captchastage` - Captcha Stage + * `authentik_stages_consent.consentstage` - Consent Stage + * `authentik_stages_consent.userconsent` - User Consent + * `authentik_stages_deny.denystage` - Deny Stage + * `authentik_stages_dummy.dummystage` - Dummy Stage + * `authentik_stages_email.emailstage` - Email Stage + * `authentik_stages_identification.identificationstage` - Identification Stage + * `authentik_stages_invitation.invitationstage` - Invitation Stage + * `authentik_stages_invitation.invitation` - Invitation + * `authentik_stages_password.passwordstage` - Password Stage + * `authentik_stages_prompt.prompt` - Prompt + * `authentik_stages_prompt.promptstage` - Prompt Stage + * `authentik_stages_user_delete.userdeletestage` - User Delete Stage + * `authentik_stages_user_login.userloginstage` - User Login Stage + * `authentik_stages_user_logout.userlogoutstage` - User Logout Stage + * `authentik_stages_user_write.userwritestage` - User Write Stage + * `authentik_tenants.tenant` - Tenant + * `authentik_blueprints.blueprintinstance` - Blueprint Instance + * `authentik_core.group` - group + * `authentik_core.user` - User + * `authentik_core.application` - Application + * `authentik_core.token` - Token PatchedEventRequest: type: object description: Event Serializer diff --git a/web/src/admin/policies/event_matcher/EventMatcherPolicyForm.ts b/web/src/admin/policies/event_matcher/EventMatcherPolicyForm.ts index 28044a284..4b0ae53fb 100644 --- a/web/src/admin/policies/event_matcher/EventMatcherPolicyForm.ts +++ b/web/src/admin/policies/event_matcher/EventMatcherPolicyForm.ts @@ -27,12 +27,6 @@ export class EventMatcherPolicyForm extends ModelForm { - this.apps = await new AdminApi(DEFAULT_CONFIG).adminAppsList(); - } - - apps?: App[]; - getSuccessMessage(): string { if (this.instance) { return msg("Successfully updated policy."); @@ -133,25 +127,61 @@ export class EventMatcherPolicyForm extends ModelForm - + => { + const items = await new AdminApi(DEFAULT_CONFIG).adminAppsList(); + return items.filter((item) => + query ? item.name.includes(query) : true, + ); + }} + .renderElement=${(item: App): string => { + return item.label; + }} + .value=${(item: App | undefined): string | undefined => { + return item?.name; + }} + .selected=${(item: App): boolean => { + return this.instance?.app === item.name; + }} + ?blankable=${true} + > +

${msg( "Match events created by selected application. When left empty, all applications are matched.", )}

+ + => { + const items = await new AdminApi(DEFAULT_CONFIG).adminModelsList(); + return items + .filter((item) => (query ? item.name.includes(query) : true)) + .sort((a, b) => { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + }} + .renderElement=${(item: App): string => { + return `${item.label} (${item.name.split(".")[0]})`; + }} + .value=${(item: App | undefined): string | undefined => { + return item?.name; + }} + .selected=${(item: App): boolean => { + return this.instance?.model === item.name; + }} + ?blankable=${true} + > + +

+ ${msg( + "Match events created by selected model. When left empty, all models are matched.", + )} +

+
`;