diff --git a/authentik/admin/api/system.py b/authentik/admin/api/system.py index c1e74672a..2d339fc2c 100644 --- a/authentik/admin/api/system.py +++ b/authentik/admin/api/system.py @@ -18,6 +18,7 @@ from authentik.core.api.utils import PassiveSerializer from authentik.lib.utils.reflection import get_env from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.models import Outpost +from authentik.tenants.utils import get_tenant class RuntimeDict(TypedDict): @@ -77,7 +78,7 @@ class SystemSerializer(PassiveSerializer): def get_tenant(self, request: Request) -> str: """Currently active tenant""" - return str(request._request.tenant) + return str(get_tenant(request)) def get_server_time(self, request: Request) -> datetime: """Current server time""" diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 61c3351d4..e2d827f0a 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -76,7 +76,7 @@ from authentik.interfaces.views import reverse_interface from authentik.stages.email.models import EmailStage from authentik.stages.email.tasks import send_mails from authentik.stages.email.utils import TemplateEmailMessage -from authentik.tenants.models import Tenant +from authentik.tenants.utils import get_tenant LOGGER = get_logger() @@ -322,7 +322,7 @@ class UserViewSet(UsedByMixin, ModelViewSet): def _create_recovery_link(self) -> tuple[Optional[str], Optional[Token]]: """Create a recovery link (when the current tenant has a recovery flow set), that can either be shown to an admin or sent to the user directly""" - tenant: Tenant = self.request._request.tenant + tenant = get_tenant(self.request) # Check that there is a recovery flow, if not return an error flow = tenant.flow_recovery if not flow: diff --git a/authentik/core/models.py b/authentik/core/models.py index b6505e5ea..715be3443 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -33,6 +33,7 @@ from authentik.lib.models import ( ) from authentik.lib.utils.http import get_client_ip from authentik.policies.models import PolicyBindingModel +from authentik.tenants.utils import get_tenant LOGGER = get_logger() USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug" @@ -168,7 +169,7 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): including the users attributes""" final_attributes = {} if request and hasattr(request, "tenant"): - always_merger.merge(final_attributes, request.tenant.attributes) + always_merger.merge(final_attributes, get_tenant(request).attributes) for group in self.ak_groups.all().order_by("name"): always_merger.merge(final_attributes, group.attributes) always_merger.merge(final_attributes, self.attributes) @@ -227,7 +228,7 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): except Exception as exc: LOGGER.warning("Failed to get default locale", exc=exc) if request: - return request.tenant.locale + return get_tenant(request).locale return "" @property diff --git a/authentik/events/models.py b/authentik/events/models.py index e2f8e4ec6..8470cf48e 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -41,8 +41,7 @@ from authentik.lib.utils.http import get_client_ip, get_http_session from authentik.lib.utils.time import timedelta_from_string from authentik.policies.models import PolicyBindingModel from authentik.stages.email.utils import TemplateEmailMessage -from authentik.tenants.models import Tenant -from authentik.tenants.utils import DEFAULT_TENANT +from authentik.tenants.utils import get_fallback_tenant, get_tenant LOGGER = get_logger() if TYPE_CHECKING: @@ -57,7 +56,7 @@ def default_event_duration(): def default_tenant(): """Get a default value for tenant""" - return sanitize_dict(model_to_dict(DEFAULT_TENANT)) + return sanitize_dict(model_to_dict(get_fallback_tenant())) class NotificationTransportError(SentryIgnoredException): @@ -227,7 +226,7 @@ class Event(SerializerModel, ExpiringModel): wrapped = self.context["http_request"]["args"][QS_QUERY] self.context["http_request"]["args"] = QueryDict(wrapped) if hasattr(request, "tenant"): - tenant: Tenant = request.tenant + tenant = get_tenant(request) # Because self.created only gets set on save, we can't use it's value here # hence we set self.created to now and then use it self.created = now() diff --git a/authentik/interfaces/views.py b/authentik/interfaces/views.py index 3b6330f7a..16b1361b8 100644 --- a/authentik/interfaces/views.py +++ b/authentik/interfaces/views.py @@ -21,7 +21,7 @@ from authentik.flows.models import Flow from authentik.interfaces.models import Interface, InterfaceType from authentik.lib.utils.urls import reverse_with_qs from authentik.tenants.api import CurrentTenantSerializer -from authentik.tenants.models import Tenant +from authentik.tenants.utils import get_tenant LOGGER = get_logger() @@ -48,7 +48,7 @@ def reverse_interface( request: HttpRequest, interface_type: InterfaceType, query: Optional[QueryDict] = None, **kwargs ): """Reverse URL to configured default interface""" - tenant: Tenant = request.tenant + tenant = get_tenant(request) interface: Interface = None if interface_type == InterfaceType.USER: @@ -90,7 +90,7 @@ class InterfaceView(View): """Get template context""" return { "config_json": dumps(ConfigView(request=Request(self.request)).get_config().data), - "tenant_json": dumps(CurrentTenantSerializer(self.request.tenant).data), + "tenant_json": dumps(CurrentTenantSerializer(get_tenant(self.request)).data), "version_family": f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}", "version_subdomain": f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}", "build": get_build_hash(), diff --git a/authentik/policies/password/models.py b/authentik/policies/password/models.py index b17fb0b4f..dcf6117b9 100644 --- a/authentik/policies/password/models.py +++ b/authentik/policies/password/models.py @@ -12,6 +12,7 @@ from authentik.lib.utils.http import get_http_session from authentik.policies.models import Policy from authentik.policies.types import PolicyRequest, PolicyResult from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT +from authentik.tenants.utils import get_tenant LOGGER = get_logger() RE_LOWER = re.compile("[a-z]") @@ -143,7 +144,8 @@ class PasswordPolicy(Policy): user_inputs.append(request.user.name) user_inputs.append(request.user.email) if request.http_request: - user_inputs.append(request.http_request.tenant.branding_title) + tenant = get_tenant(request.http_request) + user_inputs.append(tenant.branding_title) # Only calculate result for the first 100 characters, as with over 100 char # long passwords we can be reasonably sure that they'll surpass the score anyways # See https://github.com/dropbox/zxcvbn#runtime-latency diff --git a/authentik/providers/oauth2/views/device_init.py b/authentik/providers/oauth2/views/device_init.py index 7b17cac5c..03392e861 100644 --- a/authentik/providers/oauth2/views/device_init.py +++ b/authentik/providers/oauth2/views/device_init.py @@ -27,7 +27,7 @@ from authentik.stages.consent.stage import ( PLAN_CONTEXT_CONSENT_HEADER, PLAN_CONTEXT_CONSENT_PERMISSIONS, ) -from authentik.tenants.models import Tenant +from authentik.tenants.utils import get_tenant LOGGER = get_logger() QS_KEY_CODE = "code" # nosec @@ -89,7 +89,7 @@ class DeviceEntryView(View): """View used to initiate the device-code flow, url entered by endusers""" def dispatch(self, request: HttpRequest) -> HttpResponse: - tenant: Tenant = request.tenant + tenant = get_tenant(request) device_flow = tenant.flow_device_code if not device_flow: LOGGER.info("Tenant has no device code flow configured", tenant=tenant) diff --git a/authentik/providers/oauth2/views/github.py b/authentik/providers/oauth2/views/github.py index 910ef9f6d..9126f5d0c 100644 --- a/authentik/providers/oauth2/views/github.py +++ b/authentik/providers/oauth2/views/github.py @@ -9,6 +9,7 @@ from django.views.decorators.csrf import csrf_exempt from authentik.providers.oauth2.constants import SCOPE_GITHUB_ORG_READ, SCOPE_GITHUB_USER_EMAIL from authentik.providers.oauth2.models import RefreshToken from authentik.providers.oauth2.utils import protected_resource_view +from authentik.tenants.utils import get_tenant @method_decorator(csrf_exempt, name="dispatch") @@ -76,6 +77,7 @@ class GitHubUserTeamsView(View): def get(self, request: HttpRequest, token: RefreshToken) -> HttpResponse: """Emulate GitHub's /user/teams API Endpoint""" user = token.user + tenant = get_tenant(request) orgs_response = [] for org in user.ak_groups.all(): @@ -97,7 +99,7 @@ class GitHubUserTeamsView(View): "created_at": "", "updated_at": "", "organization": { - "login": slugify(request.tenant.branding_title), + "login": slugify(tenant.branding_title), "id": 1, "node_id": "", "url": "", @@ -109,7 +111,7 @@ class GitHubUserTeamsView(View): "public_members_url": "", "avatar_url": "", "description": "", - "name": request.tenant.branding_title, + "name": tenant.branding_title, "company": "", "blog": "", "location": "", diff --git a/authentik/stages/authenticator_totp/stage.py b/authentik/stages/authenticator_totp/stage.py index 80ad8a075..221092c60 100644 --- a/authentik/stages/authenticator_totp/stage.py +++ b/authentik/stages/authenticator_totp/stage.py @@ -17,6 +17,7 @@ from authentik.flows.challenge import ( from authentik.flows.stage import ChallengeStageView from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage from authentik.stages.authenticator_totp.settings import OTP_TOTP_ISSUER +from authentik.tenants.utils import get_tenant SESSION_TOTP_DEVICE = "totp_device" @@ -57,7 +58,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView): data={ "type": ChallengeTypes.NATIVE.value, "config_url": device.config_url.replace( - OTP_TOTP_ISSUER, quote(self.request.tenant.branding_title) + OTP_TOTP_ISSUER, quote(get_tenant(self.request).branding_title) ), } ) diff --git a/authentik/stages/authenticator_validate/challenge.py b/authentik/stages/authenticator_validate/challenge.py index beb7f3a90..a28638c10 100644 --- a/authentik/stages/authenticator_validate/challenge.py +++ b/authentik/stages/authenticator_validate/challenge.py @@ -33,6 +33,7 @@ from authentik.stages.authenticator_validate.models import AuthenticatorValidate from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id +from authentik.tenants.utils import get_tenant LOGGER = get_logger() @@ -187,7 +188,7 @@ def validate_challenge_duo(device_pk: int, stage_view: StageView, user: User) -> type=__( "%(brand_name)s Login request" % { - "brand_name": stage_view.request.tenant.branding_title, + "brand_name": get_tenant(stage_view.request).branding_title, } ), display_username=user.username, diff --git a/authentik/stages/authenticator_validate/tests/test_duo.py b/authentik/stages/authenticator_validate/tests/test_duo.py index 22d1fe746..2eb1db3a2 100644 --- a/authentik/stages/authenticator_validate/tests/test_duo.py +++ b/authentik/stages/authenticator_validate/tests/test_duo.py @@ -19,7 +19,7 @@ from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, Duo from authentik.stages.authenticator_validate.challenge import validate_challenge_duo from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses from authentik.stages.user_login.models import UserLoginStage -from authentik.tenants.utils import get_tenant_for_request +from authentik.tenants.utils import lookup_tenant_for_request class AuthenticatorValidateStageDuoTests(FlowTestCase): @@ -36,7 +36,7 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase): middleware = SessionMiddleware(dummy_get_response) middleware.process_request(request) request.session.save() - setattr(request, "tenant", get_tenant_for_request(request)) + setattr(request, "tenant", lookup_tenant_for_request(request)) stage = AuthenticatorDuoStage.objects.create( name=generate_id(), diff --git a/authentik/stages/authenticator_webauthn/stage.py b/authentik/stages/authenticator_webauthn/stage.py index 41132f4aa..5b2a327d8 100644 --- a/authentik/stages/authenticator_webauthn/stage.py +++ b/authentik/stages/authenticator_webauthn/stage.py @@ -29,6 +29,7 @@ from authentik.flows.challenge import ( from authentik.flows.stage import ChallengeStageView from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage, WebAuthnDevice from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id +from authentik.tenants.utils import get_tenant SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challenge" @@ -92,7 +93,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView): registration_options: PublicKeyCredentialCreationOptions = generate_registration_options( rp_id=get_rp_id(self.request), - rp_name=self.request.tenant.branding_title, + rp_name=get_tenant(self.request).branding_title, user_id=user.uid, user_name=user.username, user_display_name=user.name, diff --git a/authentik/tenants/api.py b/authentik/tenants/api.py index 2535e1094..a15eca967 100644 --- a/authentik/tenants/api.py +++ b/authentik/tenants/api.py @@ -18,6 +18,7 @@ from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import PassiveSerializer from authentik.lib.config import CONFIG from authentik.tenants.models import Tenant +from authentik.tenants.utils import get_tenant class FooterLinkSerializer(PassiveSerializer): @@ -139,5 +140,4 @@ class TenantViewSet(UsedByMixin, ModelViewSet): @action(methods=["GET"], detail=False, permission_classes=[AllowAny]) def current(self, request: Request) -> Response: """Get current tenant""" - tenant: Tenant = request._request.tenant - return Response(CurrentTenantSerializer(tenant).data) + return Response(CurrentTenantSerializer(get_tenant(request)).data) diff --git a/authentik/tenants/middleware.py b/authentik/tenants/middleware.py index 300a8f512..6ccf58d12 100644 --- a/authentik/tenants/middleware.py +++ b/authentik/tenants/middleware.py @@ -6,7 +6,7 @@ from django.http.response import HttpResponse from django.utils.translation import activate from sentry_sdk.api import set_tag -from authentik.tenants.utils import get_tenant_for_request +from authentik.tenants.utils import lookup_tenant_for_request class TenantMiddleware: @@ -19,7 +19,7 @@ class TenantMiddleware: def __call__(self, request: HttpRequest) -> HttpResponse: if not hasattr(request, "tenant"): - tenant = get_tenant_for_request(request) + tenant = lookup_tenant_for_request(request) setattr(request, "tenant", tenant) set_tag("authentik.tenant_uuid", tenant.tenant_uuid.hex) set_tag("authentik.tenant_domain", tenant.domain) diff --git a/authentik/tenants/migrations/0005_tenant_interface_admin_tenant_interface_flow_and_more.py b/authentik/tenants/migrations/0005_tenant_interface_admin_tenant_interface_flow_and_more.py index 4c4f045ed..5c722a319 100644 --- a/authentik/tenants/migrations/0005_tenant_interface_admin_tenant_interface_flow_and_more.py +++ b/authentik/tenants/migrations/0005_tenant_interface_admin_tenant_interface_flow_and_more.py @@ -13,7 +13,7 @@ def migrate_set_default(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.v1.importer import Importer - from authentik.blueprints.v1.tasks import blueprints_discover + from authentik.blueprints.v1.tasks import blueprints_discovery from authentik.interfaces.models import InterfaceType # If we don't have any tenants yet, we don't need wait for the default interface blueprint @@ -22,7 +22,7 @@ def migrate_set_default(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): interface_blueprint = BlueprintInstance.objects.filter(path="system/interfaces.yaml").first() if not interface_blueprint: - blueprints_discover.delay().get() + blueprints_discovery.delay().get() interface_blueprint = BlueprintInstance.objects.filter( path="system/interfaces.yaml" ).first() diff --git a/authentik/tenants/models.py b/authentik/tenants/models.py index de20fdebc..664782087 100644 --- a/authentik/tenants/models.py +++ b/authentik/tenants/models.py @@ -7,8 +7,6 @@ from rest_framework.serializers import Serializer from structlog.stdlib import get_logger from authentik.crypto.models import CertificateKeyPair -from authentik.flows.models import Flow -from authentik.interfaces.models import Interface from authentik.lib.models import SerializerModel from authentik.lib.utils.time import timedelta_string_validator @@ -34,38 +32,56 @@ class Tenant(SerializerModel): branding_favicon = models.TextField(default="/static/dist/assets/icons/icon.png") flow_authentication = models.ForeignKey( - Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_authentication" + "authentik_flows.Flow", + null=True, + on_delete=models.SET_NULL, + related_name="tenant_authentication", ) flow_invalidation = models.ForeignKey( - Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_invalidation" + "authentik_flows.Flow", + null=True, + on_delete=models.SET_NULL, + related_name="tenant_invalidation", ) flow_recovery = models.ForeignKey( - Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_recovery" + "authentik_flows.Flow", null=True, on_delete=models.SET_NULL, related_name="tenant_recovery" ) flow_unenrollment = models.ForeignKey( - Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_unenrollment" + "authentik_flows.Flow", + null=True, + on_delete=models.SET_NULL, + related_name="tenant_unenrollment", ) flow_user_settings = models.ForeignKey( - Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_user_settings" + "authentik_flows.Flow", + null=True, + on_delete=models.SET_NULL, + related_name="tenant_user_settings", ) flow_device_code = models.ForeignKey( - Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_device_code" + "authentik_flows.Flow", + null=True, + on_delete=models.SET_NULL, + related_name="tenant_device_code", ) interface_flow = models.ForeignKey( - Interface, + "authentik_interfaces.Interface", + default=None, on_delete=models.SET_NULL, null=True, related_name="tenant_flow", ) interface_user = models.ForeignKey( - Interface, + "authentik_interfaces.Interface", + default=None, on_delete=models.SET_NULL, null=True, related_name="tenant_user", ) interface_admin = models.ForeignKey( - Interface, + "authentik_interfaces.Interface", + default=None, on_delete=models.SET_NULL, null=True, related_name="tenant_admin", diff --git a/authentik/tenants/tests.py b/authentik/tenants/tests.py index 73bd8f7d3..40481f424 100644 --- a/authentik/tenants/tests.py +++ b/authentik/tenants/tests.py @@ -75,7 +75,7 @@ class TestTenants(APITestCase): ) factory = RequestFactory() request = factory.get("/") - request.tenant = tenant + setattr(request, "tenant", tenant) event = Event.new(action=EventAction.SYSTEM_EXCEPTION, message="test").from_http(request) self.assertEqual(event.expires.day, (event.created + timedelta_from_string("weeks=3")).day) self.assertEqual( diff --git a/authentik/tenants/utils.py b/authentik/tenants/utils.py index 8acefbe34..1e176ae2d 100644 --- a/authentik/tenants/utils.py +++ b/authentik/tenants/utils.py @@ -1,27 +1,47 @@ """Tenant utilities""" -from functools import cache from typing import Any from django.db.models import F, Q from django.db.models import Value as V from django.http.request import HttpRequest +from rest_framework.request import Request from sentry_sdk.hub import Hub from authentik import get_full_version +from authentik.interfaces.models import Interface, InterfaceType from authentik.lib.config import CONFIG from authentik.tenants.models import Tenant _q_default = Q(default=True) -@cache def get_fallback_tenant(): """Get fallback tenant""" - return Tenant(domain="fallback") + + fallback_interface = Interface( + url_name="fallback", + type=InterfaceType.FLOW, + template="Fallback interface", + ) + return Tenant( + domain="fallback", + interface_flow=fallback_interface, + interface_user=fallback_interface, + interface_admin=fallback_interface, + ) -def get_tenant_for_request(request: HttpRequest) -> Tenant: +def get_tenant(request: HttpRequest | Request) -> "Tenant": + """Get the request's tenant, falls back to a fallback tenant object""" + if isinstance(request, Request): + request = request._request + return getattr(request, "tenant", get_fallback_tenant()) + + +def lookup_tenant_for_request(request: HttpRequest) -> "Tenant": """Get tenant object for current request""" + from authentik.tenants.models import Tenant + db_tenants = ( Tenant.objects.annotate(host_domain=V(request.get_host())) .filter(Q(host_domain__iendswith=F("domain")) | _q_default) @@ -29,13 +49,13 @@ def get_tenant_for_request(request: HttpRequest) -> Tenant: ) tenants = list(db_tenants.all()) if len(tenants) < 1: - return DEFAULT_TENANT + return get_fallback_tenant() return tenants[0] def context_processor(request: HttpRequest) -> dict[str, Any]: """Context Processor that injects tenant object into every template""" - tenant = getattr(request, "tenant", DEFAULT_TENANT) + tenant = getattr(request, "tenant", get_fallback_tenant()) trace = "" span = Hub.current.scope.span if span: @@ -46,6 +66,3 @@ def context_processor(request: HttpRequest) -> dict[str, Any]: "sentry_trace": trace, "version": get_full_version(), } - - -DEFAULT_TENANT = get_fallback_tenant() diff --git a/schema.yml b/schema.yml index fdc7e7fa2..d57b7e09d 100644 --- a/schema.yml +++ b/schema.yml @@ -3676,6 +3676,24 @@ paths: description: flow_user_settings schema: type: string + - name: interface_admin + required: false + in: query + description: interface_admin + schema: + type: string + - name: interface_flow + required: false + in: query + description: interface_flow + schema: + type: string + - name: interface_user + required: false + in: query + description: interface_user + schema: + type: string - name: ordering required: false in: query @@ -7731,6 +7749,295 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' + /interfaces/: + get: + operationId: interfaces_list + description: Interface serializer + parameters: + - 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: template + schema: + type: string + - in: query + name: type + schema: + type: string + enum: + - admin + - flow + - user + description: |- + * `user` - User + * `admin` - Admin + * `flow` - Flow + + * `user` - User + * `admin` - Admin + * `flow` - Flow + - in: query + name: url_name + schema: + type: string + tags: + - interfaces + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInterfaceList' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + post: + operationId: interfaces_create + description: Interface serializer + tags: + - interfaces + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InterfaceRequest' + required: true + security: + - authentik: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Interface' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + /interfaces/{interface_uuid}/: + get: + operationId: interfaces_retrieve + description: Interface serializer + parameters: + - in: path + name: interface_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this interface. + required: true + tags: + - interfaces + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interface' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + put: + operationId: interfaces_update + description: Interface serializer + parameters: + - in: path + name: interface_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this interface. + required: true + tags: + - interfaces + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InterfaceRequest' + required: true + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interface' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + patch: + operationId: interfaces_partial_update + description: Interface serializer + parameters: + - in: path + name: interface_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this interface. + required: true + tags: + - interfaces + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedInterfaceRequest' + security: + - authentik: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Interface' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' + delete: + operationId: interfaces_destroy + description: Interface serializer + parameters: + - in: path + name: interface_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this interface. + required: true + tags: + - interfaces + 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: '' + /interfaces/{interface_uuid}/used_by/: + get: + operationId: interfaces_used_by_list + description: Get a list of all objects that use this object + parameters: + - in: path + name: interface_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this interface. + required: true + tags: + - interfaces + 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: '' /managed/blueprints/: get: operationId: managed_blueprints_list @@ -26307,6 +26614,7 @@ components: - authentik.admin - authentik.api - authentik.crypto + - authentik.interfaces - authentik.events - authentik.flows - authentik.lib @@ -26357,6 +26665,7 @@ components: * `authentik.admin` - authentik Admin * `authentik.api` - authentik API * `authentik.crypto` - authentik Crypto + * `authentik.interfaces` - authentik Interfaces * `authentik.events` - authentik Events * `authentik.flows` - authentik Flows * `authentik.lib` - authentik lib @@ -29074,6 +29383,7 @@ components: * `authentik.admin` - authentik Admin * `authentik.api` - authentik API * `authentik.crypto` - authentik Crypto + * `authentik.interfaces` - authentik Interfaces * `authentik.events` - authentik Events * `authentik.flows` - authentik Flows * `authentik.lib` - authentik lib @@ -29184,6 +29494,7 @@ components: * `authentik.admin` - authentik Admin * `authentik.api` - authentik API * `authentik.crypto` - authentik Crypto + * `authentik.interfaces` - authentik Interfaces * `authentik.events` - authentik Events * `authentik.flows` - authentik Flows * `authentik.lib` - authentik lib @@ -30297,6 +30608,55 @@ components: * `api` - Intent Api * `recovery` - Intent Recovery * `app_password` - Intent App Password + Interface: + type: object + description: Interface serializer + properties: + interface_uuid: + type: string + format: uuid + readOnly: true + url_name: + type: string + maxLength: 50 + pattern: ^[-a-zA-Z0-9_]+$ + type: + $ref: '#/components/schemas/InterfaceTypeEnum' + template: + type: string + required: + - interface_uuid + - template + - type + - url_name + InterfaceRequest: + type: object + description: Interface serializer + properties: + url_name: + type: string + minLength: 1 + maxLength: 50 + pattern: ^[-a-zA-Z0-9_]+$ + type: + $ref: '#/components/schemas/InterfaceTypeEnum' + template: + type: string + minLength: 1 + required: + - template + - type + - url_name + InterfaceTypeEnum: + enum: + - user + - admin + - flow + type: string + description: |- + * `user` - User + * `admin` - Admin + * `flow` - Flow InvalidResponseActionEnum: enum: - retry @@ -33051,6 +33411,41 @@ components: required: - pagination - results + PaginatedInterfaceList: + type: object + properties: + pagination: + type: object + properties: + next: + type: number + previous: + type: number + count: + type: number + current: + type: number + total_pages: + type: number + start_index: + type: number + end_index: + type: number + required: + - next + - previous + - count + - current + - total_pages + - start_index + - end_index + results: + type: array + items: + $ref: '#/components/schemas/Interface' + required: + - pagination + - results PaginatedInvitationList: type: object properties: @@ -35870,6 +36265,7 @@ components: * `authentik.admin` - authentik Admin * `authentik.api` - authentik API * `authentik.crypto` - authentik Crypto + * `authentik.interfaces` - authentik Interfaces * `authentik.events` - authentik Events * `authentik.flows` - authentik Flows * `authentik.lib` - authentik lib @@ -36121,6 +36517,20 @@ components: description: Specify which sources should be shown. show_source_labels: type: boolean + PatchedInterfaceRequest: + type: object + description: Interface serializer + properties: + url_name: + type: string + minLength: 1 + maxLength: 50 + pattern: ^[-a-zA-Z0-9_]+$ + type: + $ref: '#/components/schemas/InterfaceTypeEnum' + template: + type: string + minLength: 1 PatchedInvitationRequest: type: object description: Invitation Serializer @@ -37405,6 +37815,18 @@ components: type: string format: uuid nullable: true + interface_admin: + type: string + format: uuid + nullable: true + interface_user: + type: string + format: uuid + nullable: true + interface_flow: + type: string + format: uuid + nullable: true event_retention: type: string minLength: 1 @@ -40576,6 +40998,18 @@ components: type: string format: uuid nullable: true + interface_admin: + type: string + format: uuid + nullable: true + interface_user: + type: string + format: uuid + nullable: true + interface_flow: + type: string + format: uuid + nullable: true event_retention: type: string description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).' @@ -40634,6 +41068,18 @@ components: type: string format: uuid nullable: true + interface_admin: + type: string + format: uuid + nullable: true + interface_user: + type: string + format: uuid + nullable: true + interface_flow: + type: string + format: uuid + nullable: true event_retention: type: string minLength: 1