From ff611f21cdcb8f05d09e8758b7b9982bcbf39c33 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 29 May 2021 17:35:56 +0200 Subject: [PATCH 01/11] tenants: initial implementation Signed-off-by: Jens Langhammer --- authentik/api/v2/config.py | 14 - authentik/api/v2/urls.py | 2 + authentik/lib/default.yml | 3 - authentik/managed/apps.py | 2 +- .../0015_alter_eventmatcherpolicy_app.py | 90 ++++ authentik/root/settings.py | 2 + authentik/tenants/__init__.py | 0 authentik/tenants/api.py | 74 ++++ authentik/tenants/apps.py | 10 + authentik/tenants/middleware.py | 22 + authentik/tenants/migrations/0001_initial.py | 95 ++++ authentik/tenants/migrations/__init__.py | 0 authentik/tenants/models.py | 51 +++ authentik/tenants/utils.py | 17 + schema.yml | 408 +++++++++++++++++- web/src/api/Config.ts | 10 +- web/src/elements/PageHeader.ts | 8 +- web/src/elements/sidebar/SidebarBrand.ts | 17 +- web/src/flows/FlowExecutor.ts | 23 +- web/src/locales/en.po | 27 ++ web/src/locales/pseudo-LOCALE.po | 27 ++ 21 files changed, 843 insertions(+), 59 deletions(-) create mode 100644 authentik/policies/event_matcher/migrations/0015_alter_eventmatcherpolicy_app.py create mode 100644 authentik/tenants/__init__.py create mode 100644 authentik/tenants/api.py create mode 100644 authentik/tenants/apps.py create mode 100644 authentik/tenants/middleware.py create mode 100644 authentik/tenants/migrations/0001_initial.py create mode 100644 authentik/tenants/migrations/__init__.py create mode 100644 authentik/tenants/models.py create mode 100644 authentik/tenants/utils.py diff --git a/authentik/api/v2/config.py b/authentik/api/v2/config.py index d1f45167a..37b5287dd 100644 --- a/authentik/api/v2/config.py +++ b/authentik/api/v2/config.py @@ -14,13 +14,6 @@ from authentik.core.api.utils import PassiveSerializer from authentik.lib.config import CONFIG -class FooterLinkSerializer(PassiveSerializer): - """Links returned in Config API""" - - href = CharField(read_only=True) - name = CharField(read_only=True) - - class Capabilities(models.TextChoices): """Define capabilities which influence which APIs can/should be used""" @@ -30,10 +23,6 @@ class Capabilities(models.TextChoices): class ConfigSerializer(PassiveSerializer): """Serialize authentik Config into DRF Object""" - branding_logo = CharField(read_only=True) - branding_title = CharField(read_only=True) - ui_footer_links = ListField(child=FooterLinkSerializer(), read_only=True) - error_reporting_enabled = BooleanField(read_only=True) error_reporting_environment = CharField(read_only=True) error_reporting_send_pii = BooleanField(read_only=True) @@ -59,12 +48,9 @@ class ConfigView(APIView): """Retrive public configuration options""" config = ConfigSerializer( { - "branding_logo": CONFIG.y("authentik.branding.logo"), - "branding_title": CONFIG.y("authentik.branding.title"), "error_reporting_enabled": CONFIG.y("error_reporting.enabled"), "error_reporting_environment": CONFIG.y("error_reporting.environment"), "error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"), - "ui_footer_links": CONFIG.y("authentik.footer_links"), "capabilities": self.get_capabilities(), } ) diff --git a/authentik/api/v2/urls.py b/authentik/api/v2/urls.py index 6dce33066..dade31d5e 100644 --- a/authentik/api/v2/urls.py +++ b/authentik/api/v2/urls.py @@ -100,6 +100,7 @@ from authentik.stages.user_delete.api import UserDeleteStageViewSet from authentik.stages.user_login.api import UserLoginStageViewSet from authentik.stages.user_logout.api import UserLogoutStageViewSet from authentik.stages.user_write.api import UserWriteStageViewSet +from authentik.tenants.api import TenantViewSet router = routers.DefaultRouter() @@ -111,6 +112,7 @@ router.register("core/groups", GroupViewSet) router.register("core/users", UserViewSet) router.register("core/user_consent", UserConsentViewSet) router.register("core/tokens", TokenViewSet) +router.register("core/tenants", TenantViewSet) router.register("outposts/instances", OutpostViewSet) router.register("outposts/service_connections/all", ServiceConnectionViewSet) diff --git a/authentik/lib/default.yml b/authentik/lib/default.yml index 4cc831e88..993e09156 100644 --- a/authentik/lib/default.yml +++ b/authentik/lib/default.yml @@ -48,9 +48,6 @@ outposts: authentik: avatars: gravatar # gravatar or none geoip: "" - branding: - title: authentik - logo: /static/dist/assets/icons/icon_left_brand.svg # Optionally add links to the footer on the login page footer_links: - name: Documentation diff --git a/authentik/managed/apps.py b/authentik/managed/apps.py index ee33fce1b..f12d2d304 100644 --- a/authentik/managed/apps.py +++ b/authentik/managed/apps.py @@ -6,7 +6,7 @@ class AuthentikManagedConfig(AppConfig): """authentik Managed app""" name = "authentik.managed" - label = "authentik_Managed" + label = "authentik_managed" verbose_name = "authentik Managed" def ready(self) -> None: diff --git a/authentik/policies/event_matcher/migrations/0015_alter_eventmatcherpolicy_app.py b/authentik/policies/event_matcher/migrations/0015_alter_eventmatcherpolicy_app.py new file mode 100644 index 000000000..4513d2b42 --- /dev/null +++ b/authentik/policies/event_matcher/migrations/0015_alter_eventmatcherpolicy_app.py @@ -0,0 +1,90 @@ +# Generated by Django 3.2.3 on 2021-05-25 12:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_policies_event_matcher", "0014_alter_eventmatcherpolicy_app"), + ] + + operations = [ + migrations.AlterField( + model_name="eventmatcherpolicy", + name="app", + field=models.TextField( + blank=True, + choices=[ + ("authentik.admin", "authentik Admin"), + ("authentik.api", "authentik API"), + ("authentik.events", "authentik Events"), + ("authentik.crypto", "authentik Crypto"), + ("authentik.flows", "authentik Flows"), + ("authentik.outposts", "authentik Outpost"), + ("authentik.lib", "authentik lib"), + ("authentik.policies", "authentik Policies"), + ("authentik.policies.dummy", "authentik Policies.Dummy"), + ( + "authentik.policies.event_matcher", + "authentik Policies.Event Matcher", + ), + ("authentik.policies.expiry", "authentik Policies.Expiry"), + ("authentik.policies.expression", "authentik Policies.Expression"), + ("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"), + ("authentik.policies.password", "authentik Policies.Password"), + ("authentik.policies.reputation", "authentik Policies.Reputation"), + ("authentik.providers.proxy", "authentik Providers.Proxy"), + ("authentik.providers.ldap", "authentik Providers.LDAP"), + ("authentik.providers.oauth2", "authentik Providers.OAuth2"), + ("authentik.providers.saml", "authentik Providers.SAML"), + ("authentik.recovery", "authentik Recovery"), + ("authentik.sources.ldap", "authentik Sources.LDAP"), + ("authentik.sources.oauth", "authentik Sources.OAuth"), + ("authentik.sources.plex", "authentik Sources.Plex"), + ("authentik.sources.saml", "authentik Sources.SAML"), + ( + "authentik.stages.authenticator_duo", + "authentik Stages.Authenticator.Duo", + ), + ( + "authentik.stages.authenticator_static", + "authentik Stages.Authenticator.Static", + ), + ( + "authentik.stages.authenticator_totp", + "authentik Stages.Authenticator.TOTP", + ), + ( + "authentik.stages.authenticator_validate", + "authentik Stages.Authenticator.Validate", + ), + ( + "authentik.stages.authenticator_webauthn", + "authentik Stages.Authenticator.WebAuthn", + ), + ("authentik.stages.captcha", "authentik Stages.Captcha"), + ("authentik.stages.consent", "authentik Stages.Consent"), + ("authentik.stages.deny", "authentik Stages.Deny"), + ("authentik.stages.dummy", "authentik Stages.Dummy"), + ("authentik.stages.email", "authentik Stages.Email"), + ( + "authentik.stages.identification", + "authentik Stages.Identification", + ), + ("authentik.stages.invitation", "authentik Stages.User Invitation"), + ("authentik.stages.password", "authentik Stages.Password"), + ("authentik.stages.prompt", "authentik Stages.Prompt"), + ("authentik.stages.user_delete", "authentik Stages.User Delete"), + ("authentik.stages.user_login", "authentik Stages.User Login"), + ("authentik.stages.user_logout", "authentik Stages.User Logout"), + ("authentik.stages.user_write", "authentik Stages.User Write"), + ("authentik.tenants", "authentik Tenants"), + ("authentik.core", "authentik Core"), + ("authentik.managed", "authentik Managed"), + ], + default="", + help_text="Match events created by selected application. When left empty, all applications are matched.", + ), + ), + ] diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 86ebb8656..5ceb0554f 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -127,6 +127,7 @@ INSTALLED_APPS = [ "authentik.stages.user_login", "authentik.stages.user_logout", "authentik.stages.user_write", + "authentik.tenants", "rest_framework", "django_filters", "drf_spectacular", @@ -208,6 +209,7 @@ MIDDLEWARE = [ "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "authentik.core.middleware.RequestIDMiddleware", + "authentik.tenants.middleware.TenantMiddleware", "authentik.events.middleware.AuditMiddleware", "django.middleware.security.SecurityMiddleware", "django.middleware.common.CommonMiddleware", diff --git a/authentik/tenants/__init__.py b/authentik/tenants/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/tenants/api.py b/authentik/tenants/api.py new file mode 100644 index 000000000..5aa8b31e5 --- /dev/null +++ b/authentik/tenants/api.py @@ -0,0 +1,74 @@ +"""Serializer for tenant models""" +from drf_spectacular.utils import extend_schema +from rest_framework.decorators import action +from rest_framework.fields import CharField, ListField +from rest_framework.permissions import AllowAny +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import ModelViewSet + +from authentik.core.api.utils import PassiveSerializer +from authentik.lib.config import CONFIG +from authentik.tenants.models import Tenant + + +class FooterLinkSerializer(PassiveSerializer): + """Links returned in Config API""" + + href = CharField(read_only=True) + name = CharField(read_only=True) + + +class TenantSerializer(ModelSerializer): + """Tenant Serializer""" + + class Meta: + + model = Tenant + fields = [ + "tenant_uuid", + "domain", + "default", + "branding_title", + "branding_logo", + "flow_authentication", + "flow_invalidation", + "flow_recovery", + "flow_enrollment", + "flow_unenrollment", + ] + + +class CurrentTenantSerializer(PassiveSerializer): + """Partial tenant information for styling""" + + branding_title = CharField() + branding_logo = CharField() + ui_footer_links = ListField( + child=FooterLinkSerializer(), + read_only=True, + default=CONFIG.y("authentik.footer_links"), + ) + + +class TenantViewSet(ModelViewSet): + """Tenant Viewset""" + + queryset = Tenant.objects.all() + serializer_class = TenantSerializer + search_fields = [ + "domain", + "branding_title", + ] + ordering = ["domain"] + + @extend_schema( + responses=CurrentTenantSerializer(many=False), + ) + @action(methods=["GET"], detail=False, permission_classes=[AllowAny]) + # pylint: disable=invalid-name, unused-argument + def current(self, request: Request) -> Response: + """Get current tenant""" + tenant: Tenant = request._request.tenant + return Response(CurrentTenantSerializer(tenant).data) diff --git a/authentik/tenants/apps.py b/authentik/tenants/apps.py new file mode 100644 index 000000000..f84933aba --- /dev/null +++ b/authentik/tenants/apps.py @@ -0,0 +1,10 @@ +"""authentik tenant app""" +from django.apps import AppConfig + + +class AuthentikTenantsConfig(AppConfig): + """authentik Tenant app""" + + name = "authentik.tenants" + label = "authentik_tenants" + verbose_name = "authentik Tenants" diff --git a/authentik/tenants/middleware.py b/authentik/tenants/middleware.py new file mode 100644 index 000000000..3739422fe --- /dev/null +++ b/authentik/tenants/middleware.py @@ -0,0 +1,22 @@ +"""Inject tenant into current request""" +from typing import Callable + +from django.http.request import HttpRequest +from django.http.response import HttpResponse + +from authentik.tenants.utils import get_tenant_for_request + + +class TenantMiddleware: + """Add current tenant to http request""" + + get_response: Callable[[HttpRequest], HttpResponse] + + def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): + self.get_response = get_response + + def __call__(self, request: HttpRequest) -> HttpResponse: + if not hasattr(request, "tenant"): + tenant = get_tenant_for_request(request) + setattr(request, "tenant", tenant) + return self.get_response(request) diff --git a/authentik/tenants/migrations/0001_initial.py b/authentik/tenants/migrations/0001_initial.py new file mode 100644 index 000000000..3f0abc978 --- /dev/null +++ b/authentik/tenants/migrations/0001_initial.py @@ -0,0 +1,95 @@ +# Generated by Django 3.2.3 on 2021-05-29 12:18 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("authentik_flows", "0018_oob_flows"), + ] + + operations = [ + migrations.CreateModel( + name="Tenant", + fields=[ + ( + "tenant_uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "domain", + models.TextField( + help_text="Domain that activates this tenant. Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`" + ), + ), + ("default", models.BooleanField(default=False)), + ("branding_title", models.TextField(default="authentik")), + ( + "branding_logo", + models.TextField( + default="/static/dist/assets/icons/icon_left_brand.svg" + ), + ), + ( + "flow_authentication", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tenant_authentication", + to="authentik_flows.flow", + ), + ), + ( + "flow_enrollment", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tenant_enrollment", + to="authentik_flows.flow", + ), + ), + ( + "flow_invalidation", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tenant_invalidation", + to="authentik_flows.flow", + ), + ), + ( + "flow_recovery", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tenant_recovery", + to="authentik_flows.flow", + ), + ), + ( + "flow_unenrollment", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tenant_unenrollment", + to="authentik_flows.flow", + ), + ), + ], + options={ + "verbose_name": "Tenant", + "verbose_name_plural": "Tenants", + }, + ), + ] diff --git a/authentik/tenants/migrations/__init__.py b/authentik/tenants/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/tenants/models.py b/authentik/tenants/models.py new file mode 100644 index 000000000..342db514b --- /dev/null +++ b/authentik/tenants/models.py @@ -0,0 +1,51 @@ +"""tenant models""" +from uuid import uuid4 + +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from authentik.flows.models import Flow + + +class Tenant(models.Model): + """Single tenant""" + + tenant_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) + domain = models.TextField( + help_text=_( + "Domain that activates this tenant. " + "Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`" + ) + ) + default = models.BooleanField( + default=False, + ) + + branding_title = models.TextField(default="authentik") + branding_logo = models.TextField( + default="/static/dist/assets/icons/icon_left_brand.svg" + ) + + flow_authentication = models.ForeignKey( + 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" + ) + flow_recovery = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_recovery" + ) + flow_enrollment = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_enrollment" + ) + flow_unenrollment = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_unenrollment" + ) + + def __str__(self) -> str: + return self.domain + + class Meta: + + verbose_name = _("Tenant") + verbose_name_plural = _("Tenants") diff --git a/authentik/tenants/utils.py b/authentik/tenants/utils.py new file mode 100644 index 000000000..bcb901a3b --- /dev/null +++ b/authentik/tenants/utils.py @@ -0,0 +1,17 @@ +"""Tenant utilities""" +from django.db.models import Q +from django.http.request import HttpRequest + +from authentik.tenants.models import Tenant + +_q_default = Q(default=True) + + +def get_tenant_for_request(request: HttpRequest) -> Tenant: + """Get tenant object for current request""" + db_tenants = Tenant.objects.filter( + Q(domain__iendswith=request.get_host()) | _q_default + ) + if not db_tenants.exists(): + return Tenant() + return db_tenants.first() diff --git a/schema.yml b/schema.yml index 09c03aa20..17dad63b6 100644 --- a/schema.yml +++ b/schema.yml @@ -1712,6 +1712,231 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $ref: '#/components/schemas/GenericError' + /api/v2beta/core/tenants/: + get: + operationId: core_tenants_list + description: Tenant Viewset + 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 + tags: + - core + security: + - authentik: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTenantList' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' + post: + operationId: core_tenants_create + description: Tenant Viewset + tags: + - core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TenantRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TenantRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TenantRequest' + required: true + security: + - authentik: [] + - cookieAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Tenant' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' + /api/v2beta/core/tenants/{tenant_uuid}/: + get: + operationId: core_tenants_retrieve + description: Tenant Viewset + parameters: + - in: path + name: tenant_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Tenant. + required: true + tags: + - core + security: + - authentik: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Tenant' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' + put: + operationId: core_tenants_update + description: Tenant Viewset + parameters: + - in: path + name: tenant_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Tenant. + required: true + tags: + - core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TenantRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TenantRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TenantRequest' + required: true + security: + - authentik: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Tenant' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' + patch: + operationId: core_tenants_partial_update + description: Tenant Viewset + parameters: + - in: path + name: tenant_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Tenant. + required: true + tags: + - core + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTenantRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTenantRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTenantRequest' + security: + - authentik: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Tenant' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' + delete: + operationId: core_tenants_destroy + description: Tenant Viewset + parameters: + - in: path + name: tenant_uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Tenant. + required: true + tags: + - core + security: + - authentik: [] + - cookieAuth: [] + responses: + '204': + description: No response body + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' + /api/v2beta/core/tenants/current/: + get: + operationId: core_tenants_current_retrieve + description: Get current tenant + tags: + - core + security: + - authentik: [] + - cookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrentTenant' + description: '' + '400': + $ref: '#/components/schemas/ValidationError' + '403': + $ref: '#/components/schemas/GenericError' /api/v2beta/core/tokens/: get: operationId: core_tokens_list @@ -15291,6 +15516,7 @@ components: - authentik.stages.user_login - authentik.stages.user_logout - authentik.stages.user_write + - authentik.tenants - authentik.core - authentik.managed type: string @@ -16151,17 +16377,6 @@ components: type: object description: Serialize authentik Config into DRF Object properties: - branding_logo: - type: string - readOnly: true - branding_title: - type: string - readOnly: true - ui_footer_links: - type: array - items: - $ref: '#/components/schemas/FooterLink' - readOnly: true error_reporting_enabled: type: boolean readOnly: true @@ -16176,13 +16391,10 @@ components: items: $ref: '#/components/schemas/CapabilitiesEnum' required: - - branding_logo - - branding_title - capabilities - error_reporting_enabled - error_reporting_environment - error_reporting_send_pii - - ui_footer_links ConsentChallenge: type: object description: Challenge info for consent screens @@ -16298,6 +16510,28 @@ components: required: - x_cord - y_cord + CurrentTenant: + type: object + description: Partial tenant information for styling + properties: + branding_title: + type: string + branding_logo: + type: string + ui_footer_links: + type: array + items: + $ref: '#/components/schemas/FooterLink' + readOnly: true + default: + - href: https://goauthentik.io/docs/ + name: Documentation + - href: https://goauthentik.io/ + name: authentik Website + required: + - branding_logo + - branding_title + - ui_footer_links DenyStage: type: object description: DenyStage Serializer @@ -20893,6 +21127,41 @@ components: required: - pagination - results + PaginatedTenantList: + 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/Tenant' + required: + - pagination + - results PaginatedTokenList: type: object properties: @@ -22830,6 +23099,40 @@ components: type: string description: The human-readable name of this device. maxLength: 64 + PatchedTenantRequest: + type: object + description: Tenant Serializer + properties: + domain: + type: string + description: Domain that activates this tenant. Can be a superset, i.e. + `a.b` for `aa.b` and `ba.b` + default: + type: boolean + branding_title: + type: string + branding_logo: + type: string + flow_authentication: + type: string + format: uuid + nullable: true + flow_invalidation: + type: string + format: uuid + nullable: true + flow_recovery: + type: string + format: uuid + nullable: true + flow_enrollment: + type: string + format: uuid + nullable: true + flow_unenrollment: + type: string + format: uuid + nullable: true PatchedTokenRequest: type: object description: Token Serializer @@ -24779,6 +25082,83 @@ components: - task_description - task_finish_timestamp - task_name + Tenant: + type: object + description: Tenant Serializer + properties: + tenant_uuid: + type: string + format: uuid + readOnly: true + domain: + type: string + description: Domain that activates this tenant. Can be a superset, i.e. + `a.b` for `aa.b` and `ba.b` + default: + type: boolean + branding_title: + type: string + branding_logo: + type: string + flow_authentication: + type: string + format: uuid + nullable: true + flow_invalidation: + type: string + format: uuid + nullable: true + flow_recovery: + type: string + format: uuid + nullable: true + flow_enrollment: + type: string + format: uuid + nullable: true + flow_unenrollment: + type: string + format: uuid + nullable: true + required: + - domain + - tenant_uuid + TenantRequest: + type: object + description: Tenant Serializer + properties: + domain: + type: string + description: Domain that activates this tenant. Can be a superset, i.e. + `a.b` for `aa.b` and `ba.b` + default: + type: boolean + branding_title: + type: string + branding_logo: + type: string + flow_authentication: + type: string + format: uuid + nullable: true + flow_invalidation: + type: string + format: uuid + nullable: true + flow_recovery: + type: string + format: uuid + nullable: true + flow_enrollment: + type: string + format: uuid + nullable: true + flow_unenrollment: + type: string + format: uuid + nullable: true + required: + - domain Token: type: object description: Token Serializer diff --git a/web/src/api/Config.ts b/web/src/api/Config.ts index 83374bab8..5190778b7 100644 --- a/web/src/api/Config.ts +++ b/web/src/api/Config.ts @@ -1,4 +1,4 @@ -import { Config, Configuration, Middleware, ResponseContext, RootApi } from "authentik-api"; +import { Config, Configuration, CoreApi, CurrentTenant, Middleware, ResponseContext, RootApi, Tenant } from "authentik-api"; import { getCookie } from "../utils"; import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer"; import { MessageMiddleware } from "../elements/messages/Middleware"; @@ -20,6 +20,14 @@ export function config(): Promise { return globalConfigPromise; } +let globalTenantPromise: Promise; +export function tenant(): Promise { + if (!globalTenantPromise) { + globalTenantPromise = new CoreApi(DEFAULT_CONFIG).coreTenantsCurrentRetrieve(); + } + return globalTenantPromise; +} + export const DEFAULT_CONFIG = new Configuration({ basePath: "", headers: { diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts index e0aff235a..1ec36d1cb 100644 --- a/web/src/elements/PageHeader.ts +++ b/web/src/elements/PageHeader.ts @@ -5,7 +5,7 @@ import AKGlobal from "../authentik.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import { EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants"; -import { config } from "../api/Config"; +import { tenant } from "../api/Config"; @customElement("ak-page-header") export class PageHeader extends LitElement { @@ -18,11 +18,11 @@ export class PageHeader extends LitElement { @property() set header(value: string) { - config().then(config => { + tenant().then(tenant => { if (value !== "") { - document.title = `${value} - ${config.brandingTitle}`; + document.title = `${value} - ${tenant.brandingTitle}`; } else { - document.title = config.brandingTitle || TITLE_DEFAULT; + document.title = tenant.brandingTitle || TITLE_DEFAULT; } }); this._header = value; diff --git a/web/src/elements/sidebar/SidebarBrand.ts b/web/src/elements/sidebar/SidebarBrand.ts index c4a1a8f7d..a9392cf6c 100644 --- a/web/src/elements/sidebar/SidebarBrand.ts +++ b/web/src/elements/sidebar/SidebarBrand.ts @@ -6,29 +6,25 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import AKGlobal from "../../authentik.css"; import { configureSentry } from "../../api/Sentry"; -import { Config } from "authentik-api"; +import { CurrentTenant } from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; import { EVENT_SIDEBAR_TOGGLE } from "../../constants"; +import { tenant } from "../../api/Config"; // If the viewport is wider than MIN_WIDTH, the sidebar // is shown besides the content, and not overlayed. export const MIN_WIDTH = 1200; -export const DefaultConfig: Config = { +export const DefaultTenant: CurrentTenant = { brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg", brandingTitle: "authentik", - - errorReportingEnabled: false, - errorReportingEnvironment: "", - errorReportingSendPii: false, uiFooterLinks: [], - capabilities: [], }; @customElement("ak-sidebar-brand") export class SidebarBrand extends LitElement { @property({attribute: false}) - config: Config = DefaultConfig; + tenant: CurrentTenant = DefaultTenant; static get styles(): CSSResult[] { return [ @@ -68,7 +64,8 @@ export class SidebarBrand extends LitElement { } firstUpdated(): void { - configureSentry(true).then((c) => {this.config = c;}); + configureSentry(true); + tenant().then(tenant => this.tenant = tenant); } render(): TemplateResult { @@ -89,7 +86,7 @@ export class SidebarBrand extends LitElement { ` : html``}
- authentik icon + authentik icon
`; } diff --git a/web/src/flows/FlowExecutor.ts b/web/src/flows/FlowExecutor.ts index 98bdf4fd8..8f580717a 100644 --- a/web/src/flows/FlowExecutor.ts +++ b/web/src/flows/FlowExecutor.ts @@ -26,8 +26,8 @@ import "./stages/password/PasswordStage"; import "./stages/prompt/PromptStage"; import "./sources/plex/PlexLoginInit"; import { StageHost } from "./stages/base"; -import { ChallengeChoices, Config, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api"; -import { config, DEFAULT_CONFIG } from "../api/Config"; +import { ChallengeChoices, CurrentTenant, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api"; +import { DEFAULT_CONFIG, tenant } from "../api/Config"; import { ifDefined } from "lit-html/directives/if-defined"; import { until } from "lit-html/directives/until"; import { PFSize } from "../elements/Spinner"; @@ -46,7 +46,7 @@ export class FlowExecutor extends LitElement implements StageHost { loading = false; @property({ attribute: false }) - config?: Config; + tenant?: CurrentTenant; static get styles(): CSSResult[] { return [PFBase, PFLogin, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal].concat(css` @@ -85,11 +85,11 @@ export class FlowExecutor extends LitElement implements StageHost { } private postUpdate(): void { - config().then(config => { + tenant().then(tenant => { if (this.challenge?.title) { - document.title = `${this.challenge.title} - ${config.brandingTitle}`; + document.title = `${this.challenge.title} - ${tenant.brandingTitle}`; } else { - document.title = config.brandingTitle || TITLE_DEFAULT; + document.title = tenant.brandingTitle || TITLE_DEFAULT; } }); } @@ -115,9 +115,8 @@ export class FlowExecutor extends LitElement implements StageHost { } firstUpdated(): void { - configureSentry().then((config) => { - this.config = config; - }); + configureSentry(); + tenant().then(tenant => this.tenant = tenant); this.loading = true; new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({ flowSlug: this.flowSlug, @@ -255,7 +254,7 @@ export class FlowExecutor extends LitElement implements StageHost { From 7bd93ed18e275949d89c137b6a9544ffee45c4d7 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 29 May 2021 18:47:00 +0200 Subject: [PATCH 07/11] web/admin: add webui for tenants Signed-off-by: Jens Langhammer --- web/src/interfaces/AdminInterface.ts | 3 + web/src/pages/tenants/TenantForm.ts | 158 ++++++++++++++++++ web/src/pages/tenants/TenantListPage.ts | 101 +++++++++++ .../pages/user-settings/UserDetailsPage.ts | 3 +- web/src/routes.ts | 4 +- 5 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 web/src/pages/tenants/TenantForm.ts create mode 100644 web/src/pages/tenants/TenantListPage.ts diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index a9e7f4f9f..fd6e87426 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -40,6 +40,9 @@ export class AdminInterface extends Interface { ${ID_REGEX})$`]}> ${t`Providers`} + + ${t`Tenants`} + diff --git a/web/src/pages/tenants/TenantForm.ts b/web/src/pages/tenants/TenantForm.ts new file mode 100644 index 000000000..b0c78c780 --- /dev/null +++ b/web/src/pages/tenants/TenantForm.ts @@ -0,0 +1,158 @@ +import { CoreApi, FlowsApi, FlowsInstancesListDesignationEnum, Tenant } from "authentik-api"; +import { t } from "@lingui/macro"; +import { customElement } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import "../../elements/forms/HorizontalFormElement"; +import { first } from "../../utils"; +import { ModelForm } from "../../elements/forms/ModelForm"; +import { until } from "lit-html/directives/until"; + +@customElement("ak-tenant-form") +export class TenantForm extends ModelForm { + + loadInstance(pk: string): Promise { + return new CoreApi(DEFAULT_CONFIG).coreTenantsRetrieve({ + tenantUuid: pk + }); + } + + getSuccessMessage(): string { + if (this.instance) { + return t`Successfully updated tenant.`; + } else { + return t`Successfully created tenant.`; + } + } + + send = (data: Tenant): Promise => { + if (this.instance?.tenantUuid) { + return new CoreApi(DEFAULT_CONFIG).coreTenantsUpdate({ + tenantUuid: this.instance.tenantUuid, + tenantRequest: data + }); + } else { + return new CoreApi(DEFAULT_CONFIG).coreTenantsCreate({ + tenantRequest: data + }); + } + }; + + renderForm(): TemplateResult { + return html`
+ + + + +
+ + +
+

${t`Use this tenant for each domain that doesn't have a dedicated tenant.`}

+
+ + + + ${t`Branding settings`} + +
+ + +

${t`Branding shown in page title and several other places.`}

+
+ + +

${t`Icon shown in sidebar/header and flow executor.`}

+
+
+
+ + + ${t`Default flows`} + +
+ + +

${t`Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used.`}

+
+ + +

${t`Flow used to logout. If left empty, the first applicable flow sorted by the slug is used.`}

+
+ + +

${t`Recovery flow. If left empty, the first applicable flow sorted by the slug is used.`}

+
+ + +

${t`If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown.`}

+
+
+
+
`; + } + +} diff --git a/web/src/pages/tenants/TenantListPage.ts b/web/src/pages/tenants/TenantListPage.ts new file mode 100644 index 000000000..2974218fa --- /dev/null +++ b/web/src/pages/tenants/TenantListPage.ts @@ -0,0 +1,101 @@ +import { t } from "@lingui/macro"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { AKResponse } from "../../api/Client"; +import { TablePage } from "../../elements/table/TablePage"; + +import "../../elements/forms/DeleteForm"; +import "../../elements/buttons/SpinnerButton"; +import { TableColumn } from "../../elements/table/Table"; +import { PAGE_SIZE } from "../../constants"; +import { CoreApi, Tenant } from "authentik-api"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import "../../elements/forms/ModalForm"; +import "./TenantForm"; + +@customElement("ak-tenant-list") +export class TenantListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return t`Tenants`; + } + pageDescription(): string { + return t`Configure visual settings and defaults for different domains.`; + } + pageIcon(): string { + return "pf-icon pf-icon-tenant"; + } + + @property() + order = "domain"; + + apiEndpoint(page: number): Promise> { + return new CoreApi(DEFAULT_CONFIG).coreTenantsList({ + ordering: this.order, + page: page, + pageSize: PAGE_SIZE, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn(t`Domain`, "domain"), + new TableColumn(t`Default?`, "default"), + new TableColumn(""), + ]; + } + + row(item: Tenant): TemplateResult[] { + return [ + html`${item.domain}`, + html`${item._default ? t`Yes` : t`No`}`, + html` + + + ${t`Update`} + + + ${t`Update Tenant`} + + + + + + { + return new CoreApi(DEFAULT_CONFIG).coreTenantsDestroy({ + tenantUuid: item.tenantUuid + }); + }}> + + `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + ${t`Create`} + + + ${t`Create Tenant`} + + + + + + ${super.renderToolbar()} + `; + } +} diff --git a/web/src/pages/user-settings/UserDetailsPage.ts b/web/src/pages/user-settings/UserDetailsPage.ts index 87c77aba7..f729758ba 100644 --- a/web/src/pages/user-settings/UserDetailsPage.ts +++ b/web/src/pages/user-settings/UserDetailsPage.ts @@ -10,13 +10,12 @@ import { CoreApi, User } from "authentik-api"; import { me } from "../../api/Users"; import { FlowURLManager } from "../../api/legacy"; import { ifDefined } from "lit-html/directives/if-defined"; -import { DEFAULT_CONFIG } from "../../api/Config"; +import { DEFAULT_CONFIG, tenant } from "../../api/Config"; import "../../elements/forms/FormElement"; import "../../elements/EmptyState"; import "../../elements/forms/Form"; import "../../elements/forms/HorizontalFormElement"; import { until } from "lit-html/directives/until"; -import { tenant } from "authentik-api/dist/src/api/Config"; @customElement("ak-user-details") export class UserDetailsPage extends LitElement { diff --git a/web/src/routes.ts b/web/src/routes.ts index e3f909576..e159c6d47 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -25,10 +25,11 @@ import "./pages/stages/invitation/InvitationListPage"; import "./pages/stages/prompt/PromptListPage"; import "./pages/stages/StageListPage"; import "./pages/system-tasks/SystemTaskListPage"; +import "./pages/tenants/TenantListPage"; import "./pages/tokens/TokenListPage"; +import "./pages/user-settings/UserSettingsPage"; import "./pages/users/UserListPage"; import "./pages/users/UserViewPage"; -import "./pages/user-settings/UserSettingsPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -51,6 +52,7 @@ export const ROUTES: Route[] = [ }), new Route(new RegExp("^/core/property-mappings$"), html``), new Route(new RegExp("^/core/tokens$"), html``), + new Route(new RegExp("^/core/tenants$"), html``), new Route(new RegExp("^/policy/policies$"), html``), new Route(new RegExp("^/identity/groups$"), html``), new Route(new RegExp("^/identity/users$"), html``), From 4d9b362dbfd2497d6dabf853b84706b5854397e9 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 29 May 2021 19:00:04 +0200 Subject: [PATCH 08/11] tenants: add migration to add default tenant Signed-off-by: Jens Langhammer --- authentik/tenants/migrations/0002_default.py | 40 +++++++ authentik/tenants/utils.py | 8 +- web/src/elements/table/Table.ts | 2 +- web/src/locales/en.po | 112 ++++++++++++++++++- web/src/locales/pseudo-LOCALE.po | 112 ++++++++++++++++++- 5 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 authentik/tenants/migrations/0002_default.py diff --git a/authentik/tenants/migrations/0002_default.py b/authentik/tenants/migrations/0002_default.py new file mode 100644 index 000000000..b0e70ba9a --- /dev/null +++ b/authentik/tenants/migrations/0002_default.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2.3 on 2021-05-29 16:55 + +from django.apps.registry import Apps +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor + + +def create_default_tenant(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): + Flow = apps.get_model("authentik_flows", "Flow") + Tenant = apps.get_model("authentik_tenants", "Tenant") + + db_alias = schema_editor.connection.alias + + default_authentication = ( + Flow.objects.using(db_alias).filter(slug="default-authentication-flow").first() + ) + default_invalidation = ( + Flow.objects.using(db_alias).filter(slug="default-invalidation-flow").first() + ) + + tenant, _ = Tenant.objects.using(db_alias).update_or_create( + domain="authentik-default", + default=True, + defaults={ + "flow_authentication": default_authentication, + "flow_invalidation": default_invalidation, + }, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_tenants", "0001_initial"), + ("authentik_flows", "0008_default_flows"), + ] + + operations = [ + migrations.RunPython(create_default_tenant), + ] diff --git a/authentik/tenants/utils.py b/authentik/tenants/utils.py index f6ad0d21d..00367cab6 100644 --- a/authentik/tenants/utils.py +++ b/authentik/tenants/utils.py @@ -1,11 +1,11 @@ """Tenant utilities""" -from authentik.lib.config import CONFIG from typing import Any from django.db.models import Q from django.http.request import HttpRequest from authentik import __version__ +from authentik.lib.config import CONFIG from authentik.tenants.models import Tenant _q_default = Q(default=True) @@ -23,4 +23,8 @@ def get_tenant_for_request(request: HttpRequest) -> Tenant: def context_processor(request: HttpRequest) -> dict[str, Any]: """Context Processor that injects tenant object into every template""" - return {"tenant": request.tenant, "ak_version": __version__, "footer_links": CONFIG.y("authentik.footer_links")} + return { + "tenant": request.tenant, + "ak_version": __version__, + "footer_links": CONFIG.y("authentik.footer_links"), + } diff --git a/web/src/elements/table/Table.ts b/web/src/elements/table/Table.ts index af59253a0..993e3b924 100644 --- a/web/src/elements/table/Table.ts +++ b/web/src/elements/table/Table.ts @@ -167,7 +167,7 @@ export abstract class Table extends LitElement {
- ${inner ? inner : html``} + ${inner ? inner : html``}
diff --git a/web/src/locales/en.po b/web/src/locales/en.po index b62eb3de3..c0d115e4a 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -308,6 +308,7 @@ msgstr "Authentication" #: src/pages/sources/oauth/OAuthSourceForm.ts #: src/pages/sources/plex/PlexSourceForm.ts #: src/pages/sources/saml/SAMLSourceForm.ts +#: src/pages/tenants/TenantForm.ts msgid "Authentication flow" msgstr "Authentication flow" @@ -422,6 +423,14 @@ msgstr "Binding" msgid "Binding Type" msgstr "Binding Type" +#: src/pages/tenants/TenantForm.ts +msgid "Branding settings" +msgstr "Branding settings" + +#: src/pages/tenants/TenantForm.ts +msgid "Branding shown in page title and several other places." +msgstr "Branding shown in page title and several other places." + #: src/pages/admin-overview/cards/VersionStatusCard.ts msgid "Build hash: {0}" msgstr "Build hash: {0}" @@ -676,6 +685,10 @@ msgstr "Configure settings relevant to your user profile." msgid "Configure the maximum allowed time drift for an asseration." msgstr "Configure the maximum allowed time drift for an asseration." +#: src/pages/tenants/TenantListPage.ts +msgid "Configure visual settings and defaults for different domains." +msgstr "Configure visual settings and defaults for different domains." + #: src/pages/providers/oauth2/OAuth2ProviderForm.ts msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine." msgstr "Configure what data should be used as unique User Identifier. For most cases, the default should be fine." @@ -812,6 +825,8 @@ msgstr "Copy download URL" #: src/pages/stages/prompt/PromptListPage.ts #: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts +#: src/pages/tenants/TenantListPage.ts +#: src/pages/tenants/TenantListPage.ts #: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/users/UserListPage.ts @@ -881,6 +896,10 @@ msgstr "Create Stage" msgid "Create Stage binding" msgstr "Create Stage binding" +#: src/pages/tenants/TenantListPage.ts +msgid "Create Tenant" +msgstr "Create Tenant" + #: src/pages/user-settings/tokens/UserTokenList.ts msgid "Create Token" msgstr "Create Token" @@ -940,6 +959,18 @@ msgstr "Date Time" msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgstr "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." +#: src/pages/tenants/TenantForm.ts +msgid "Default" +msgstr "Default" + +#: src/pages/tenants/TenantForm.ts +msgid "Default flows" +msgstr "Default flows" + +#: src/pages/tenants/TenantListPage.ts +msgid "Default?" +msgstr "Default?" + #: src/pages/events/TransportListPage.ts msgid "Define how notifications are sent to users, like Email or Webhook." msgstr "Define how notifications are sent to users, like Email or Webhook." @@ -960,6 +991,7 @@ msgstr "Define how notifications are sent to users, like Email or Webhook." #: src/pages/stages/StageListPage.ts #: src/pages/stages/invitation/InvitationListPage.ts #: src/pages/stages/prompt/PromptListPage.ts +#: src/pages/tenants/TenantListPage.ts #: src/pages/tokens/TokenListPage.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts #: src/pages/user-settings/tokens/UserTokenList.ts @@ -1083,6 +1115,11 @@ msgstr "Disconnect" msgid "Docker URL" msgstr "Docker URL" +#: src/pages/tenants/TenantForm.ts +#: src/pages/tenants/TenantListPage.ts +msgid "Domain" +msgstr "Domain" + #: src/pages/crypto/CertificateKeyPairListPage.ts #: src/pages/providers/saml/SAMLProviderViewPage.ts #: src/pages/sources/saml/SAMLSourceViewPage.ts @@ -1145,6 +1182,7 @@ msgstr "Each provider has a different issuer, based on the application slug." #: src/pages/sources/saml/SAMLSourceViewPage.ts #: src/pages/stages/StageListPage.ts #: src/pages/stages/prompt/PromptListPage.ts +#: src/pages/tenants/TenantListPage.ts #: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/users/UserListPage.ts #: src/pages/users/UserViewPage.ts @@ -1489,6 +1527,14 @@ msgstr "Flow used by an authenticated user to configure this Stage. If empty, us msgid "Flow used for users to authenticate. Currently only identification and password stages are supported." msgstr "Flow used for users to authenticate. Currently only identification and password stages are supported." +#: src/pages/tenants/TenantForm.ts +msgid "Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used." +msgstr "Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used." + +#: src/pages/tenants/TenantForm.ts +msgid "Flow used to logout. If left empty, the first applicable flow sorted by the slug is used." +msgstr "Flow used to logout. If left empty, the first applicable flow sorted by the slug is used." + #: src/pages/providers/oauth2/OAuth2ProviderForm.ts #: src/pages/providers/proxy/ProxyProviderForm.ts #: src/pages/providers/saml/SAMLProviderForm.ts @@ -1656,6 +1702,10 @@ msgstr "ID" msgid "Icon" msgstr "Icon" +#: src/pages/tenants/TenantForm.ts +msgid "Icon shown in sidebar/header and flow executor." +msgstr "Icon shown in sidebar/header and flow executor." + #: src/pages/flows/FlowListPage.ts #: src/pages/system-tasks/SystemTaskListPage.ts #: src/pages/tokens/TokenListPage.ts @@ -1677,6 +1727,10 @@ msgstr "If enabled, use the local connection. Required Docker socket/Kubernetes msgid "If left empty, authentik will try to extract the launch URL based on the selected provider." msgstr "If left empty, authentik will try to extract the launch URL based on the selected provider." +#: src/pages/tenants/TenantForm.ts +msgid "If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown." +msgstr "If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown." + #: src/pages/stages/invitation/InvitationStageForm.ts msgid "If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given." msgstr "If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given." @@ -1738,6 +1792,10 @@ msgstr "Internal host SSL Validation" msgid "Invalidation" msgstr "Invalidation" +#: src/pages/tenants/TenantForm.ts +msgid "Invalidation flow" +msgstr "Invalidation flow" + #: src/interfaces/AdminInterface.ts #: src/pages/stages/invitation/InvitationListPage.ts msgid "Invitations" @@ -1934,6 +1992,10 @@ msgstr "Loading" #: src/pages/stages/password/PasswordStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts +#: src/pages/tenants/TenantForm.ts +#: src/pages/tenants/TenantForm.ts +#: src/pages/tenants/TenantForm.ts +#: src/pages/tenants/TenantForm.ts msgid "Loading..." msgstr "Loading..." @@ -1964,6 +2026,10 @@ msgstr "Logins" msgid "Logins over the last 24 hours" msgstr "Logins over the last 24 hours" +#: src/pages/tenants/TenantForm.ts +msgid "Logo" +msgstr "Logo" + #: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts msgid "Logout URL" msgstr "Logout URL" @@ -2155,6 +2221,7 @@ msgstr "New version available!" #: src/pages/policies/PolicyTestForm.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts +#: src/pages/tenants/TenantListPage.ts #: src/pages/tokens/TokenListPage.ts #: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/users/UserListPage.ts @@ -2182,10 +2249,6 @@ msgstr "No Stages bound" msgid "No additional data available." msgstr "No additional data available." -#: src/elements/table/Table.ts -msgid "No elements found." -msgstr "No elements found." - #: src/elements/forms/ModalForm.ts msgid "No form found" msgstr "No form found" @@ -2195,6 +2258,10 @@ msgstr "No form found" msgid "No matching events could be found." msgstr "No matching events could be found." +#: src/elements/table/Table.ts +msgid "No objects found." +msgstr "No objects found." + #: src/pages/policies/BoundPoliciesList.ts msgid "No policies are currently bound to this object." msgstr "No policies are currently bound to this object." @@ -2691,9 +2758,14 @@ msgid "Recovery" msgstr "Recovery" #: src/pages/stages/identification/IdentificationStageForm.ts +#: src/pages/tenants/TenantForm.ts msgid "Recovery flow" msgstr "Recovery flow" +#: src/pages/tenants/TenantForm.ts +msgid "Recovery flow. If left empty, the first applicable flow sorted by the slug is used." +msgstr "Recovery flow. If left empty, the first applicable flow sorted by the slug is used." + #: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts msgid "Recovery keys" msgstr "Recovery keys" @@ -3311,6 +3383,10 @@ msgstr "Successfully created source." msgid "Successfully created stage." msgstr "Successfully created stage." +#: src/pages/tenants/TenantForm.ts +msgid "Successfully created tenant." +msgstr "Successfully created tenant." + #: src/pages/user-settings/tokens/UserTokenForm.ts msgid "Successfully created token." msgstr "Successfully created token." @@ -3451,6 +3527,10 @@ msgstr "Successfully updated source." msgid "Successfully updated stage." msgstr "Successfully updated stage." +#: src/pages/tenants/TenantForm.ts +msgid "Successfully updated tenant." +msgstr "Successfully updated tenant." + #: src/pages/user-settings/tokens/UserTokenForm.ts msgid "Successfully updated token." msgstr "Successfully updated token." @@ -3540,6 +3620,15 @@ msgstr "Task finished with warnings" msgid "Template" msgstr "Template" +#: src/pages/tenants/TenantListPage.ts +msgid "Tenant" +msgstr "Tenant" + +#: src/interfaces/AdminInterface.ts +#: src/pages/tenants/TenantListPage.ts +msgid "Tenants" +msgstr "Tenants" + #: src/pages/applications/ApplicationViewPage.ts #: src/pages/events/TransportListPage.ts #: src/pages/policies/PolicyListPage.ts @@ -3634,6 +3723,7 @@ msgid "Timeout" msgstr "Timeout" #: src/pages/flows/FlowForm.ts +#: src/pages/tenants/TenantForm.ts msgid "Title" msgstr "Title" @@ -3750,6 +3840,10 @@ msgstr "Unbound policies" msgid "Unenrollment" msgstr "Unenrollment" +#: src/pages/tenants/TenantForm.ts +msgid "Unenrollment flow" +msgstr "Unenrollment flow" + #: src/pages/outposts/ServiceConnectionListPage.ts msgid "Unhealthy" msgstr "Unhealthy" @@ -3799,6 +3893,7 @@ msgstr "Up-to-date!" #: src/pages/sources/saml/SAMLSourceViewPage.ts #: src/pages/stages/StageListPage.ts #: src/pages/stages/prompt/PromptListPage.ts +#: src/pages/tenants/TenantListPage.ts #: src/pages/user-settings/UserDetailsPage.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts @@ -3884,6 +3979,10 @@ msgstr "Update SAML Source" msgid "Update Stage binding" msgstr "Update Stage binding" +#: src/pages/tenants/TenantListPage.ts +msgid "Update Tenant" +msgstr "Update Tenant" + #: src/pages/user-settings/tokens/UserTokenList.ts msgid "Update Token" msgstr "Update Token" @@ -3948,6 +4047,10 @@ msgstr "Use the user's username, but deny enrollment when the username already e msgid "Use this redirect URL:" msgstr "Use this redirect URL:" +#: src/pages/tenants/TenantForm.ts +msgid "Use this tenant for each domain that doesn't have a dedicated tenant." +msgstr "Use this tenant for each domain that doesn't have a dedicated tenant." + #: src/elements/events/ObjectChangelog.ts #: src/elements/events/UserEvents.ts #: src/pages/applications/ApplicationCheckAccessForm.ts @@ -4213,6 +4316,7 @@ msgstr "X509 Subject" #: src/pages/policies/PolicyTestForm.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts +#: src/pages/tenants/TenantListPage.ts #: src/pages/tokens/TokenListPage.ts #: src/pages/user-settings/tokens/UserTokenList.ts #: src/pages/users/UserListPage.ts diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index cce4799db..f57d337d2 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -304,6 +304,7 @@ msgstr "" #: #: #: +#: msgid "Authentication flow" msgstr "" @@ -418,6 +419,14 @@ msgstr "" msgid "Binding Type" msgstr "" +#: +msgid "Branding settings" +msgstr "" + +#: +msgid "Branding shown in page title and several other places." +msgstr "" + #: msgid "Build hash: {0}" msgstr "" @@ -670,6 +679,10 @@ msgstr "" msgid "Configure the maximum allowed time drift for an asseration." msgstr "" +#: +msgid "Configure visual settings and defaults for different domains." +msgstr "" + #: msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine." msgstr "" @@ -810,6 +823,8 @@ msgstr "" #: #: #: +#: +#: msgid "Create" msgstr "" @@ -875,6 +890,10 @@ msgstr "" msgid "Create Stage binding" msgstr "" +#: +msgid "Create Tenant" +msgstr "" + #: msgid "Create Token" msgstr "" @@ -934,6 +953,18 @@ msgstr "" msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." msgstr "" +#: +msgid "Default" +msgstr "" + +#: +msgid "Default flows" +msgstr "" + +#: +msgid "Default?" +msgstr "" + #: msgid "Define how notifications are sent to users, like Email or Webhook." msgstr "" @@ -958,6 +989,7 @@ msgstr "" #: #: #: +#: msgid "Delete" msgstr "" @@ -1075,6 +1107,11 @@ msgstr "" msgid "Docker URL" msgstr "" +#: +#: +msgid "Domain" +msgstr "" + #: #: #: @@ -1140,6 +1177,7 @@ msgstr "" #: #: #: +#: msgid "Edit" msgstr "" @@ -1481,6 +1519,14 @@ msgstr "" msgid "Flow used for users to authenticate. Currently only identification and password stages are supported." msgstr "" +#: +msgid "Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used." +msgstr "" + +#: +msgid "Flow used to logout. If left empty, the first applicable flow sorted by the slug is used." +msgstr "" + #: #: #: @@ -1648,6 +1694,10 @@ msgstr "" msgid "Icon" msgstr "" +#: +msgid "Icon shown in sidebar/header and flow executor." +msgstr "" + #: #: #: @@ -1669,6 +1719,10 @@ msgstr "" msgid "If left empty, authentik will try to extract the launch URL based on the selected provider." msgstr "" +#: +msgid "If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown." +msgstr "" + #: msgid "If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given." msgstr "" @@ -1730,6 +1784,10 @@ msgstr "" msgid "Invalidation" msgstr "" +#: +msgid "Invalidation flow" +msgstr "" + #: #: msgid "Invitations" @@ -1926,6 +1984,10 @@ msgstr "" #: #: #: +#: +#: +#: +#: msgid "Loading..." msgstr "" @@ -1956,6 +2018,10 @@ msgstr "" msgid "Logins over the last 24 hours" msgstr "" +#: +msgid "Logo" +msgstr "" + #: msgid "Logout URL" msgstr "" @@ -2150,6 +2216,7 @@ msgstr "" #: #: #: +#: msgid "No" msgstr "" @@ -2174,10 +2241,6 @@ msgstr "" msgid "No additional data available." msgstr "" -#: -msgid "No elements found." -msgstr "" - #: msgid "No form found" msgstr "" @@ -2187,6 +2250,10 @@ msgstr "" msgid "No matching events could be found." msgstr "" +#: +msgid "No objects found." +msgstr "" + #: msgid "No policies are currently bound to this object." msgstr "" @@ -2682,10 +2749,15 @@ msgstr "" msgid "Recovery" msgstr "" +#: #: msgid "Recovery flow" msgstr "" +#: +msgid "Recovery flow. If left empty, the first applicable flow sorted by the slug is used." +msgstr "" + #: msgid "Recovery keys" msgstr "" @@ -3303,6 +3375,10 @@ msgstr "" msgid "Successfully created stage." msgstr "" +#: +msgid "Successfully created tenant." +msgstr "" + #: msgid "Successfully created token." msgstr "" @@ -3443,6 +3519,10 @@ msgstr "" msgid "Successfully updated stage." msgstr "" +#: +msgid "Successfully updated tenant." +msgstr "" + #: msgid "Successfully updated token." msgstr "" @@ -3532,6 +3612,15 @@ msgstr "" msgid "Template" msgstr "" +#: +msgid "Tenant" +msgstr "" + +#: +#: +msgid "Tenants" +msgstr "" + #: #: #: @@ -3621,6 +3710,7 @@ msgstr "" msgid "Timeout" msgstr "" +#: #: msgid "Title" msgstr "" @@ -3738,6 +3828,10 @@ msgstr "" msgid "Unenrollment" msgstr "" +#: +msgid "Unenrollment flow" +msgstr "" + #: msgid "Unhealthy" msgstr "" @@ -3795,6 +3889,7 @@ msgstr "" #: #: #: +#: msgid "Update" msgstr "" @@ -3872,6 +3967,10 @@ msgstr "" msgid "Update Stage binding" msgstr "" +#: +msgid "Update Tenant" +msgstr "" + #: msgid "Update Token" msgstr "" @@ -3936,6 +4035,10 @@ msgstr "" msgid "Use this redirect URL:" msgstr "" +#: +msgid "Use this tenant for each domain that doesn't have a dedicated tenant." +msgstr "" + #: #: #: @@ -4202,6 +4305,7 @@ msgstr "" #: #: #: +#: msgid "Yes" msgstr "" From 3b31b7ce83585c3d505fcb0dee0840c42f17e21c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 29 May 2021 19:07:54 +0200 Subject: [PATCH 09/11] core: add http host in log messages Signed-off-by: Jens Langhammer --- authentik/core/middleware.py | 7 ++++++- web/src/pages/tenants/TenantForm.ts | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/authentik/core/middleware.py b/authentik/core/middleware.py index 1f09555b7..64400b64b 100644 --- a/authentik/core/middleware.py +++ b/authentik/core/middleware.py @@ -42,10 +42,14 @@ class RequestIDMiddleware: if not hasattr(request, "request_id"): request_id = uuid4().hex setattr(request, "request_id", request_id) - LOCAL.authentik = {"request_id": request_id} + LOCAL.authentik = { + "request_id": request_id, + "host": request.get_host(), + } response = self.get_response(request) response[RESPONSE_HEADER_ID] = request.request_id del LOCAL.authentik["request_id"] + del LOCAL.authentik["host"] return response @@ -54,4 +58,5 @@ def structlog_add_request_id(logger: Logger, method_name: str, event_dict): """If threadlocal has authentik defined, add request_id to log""" if hasattr(LOCAL, "authentik"): event_dict["request_id"] = LOCAL.authentik.get("request_id", "") + event_dict["host"] = LOCAL.authentik.get("host", "") return event_dict diff --git a/web/src/pages/tenants/TenantForm.ts b/web/src/pages/tenants/TenantForm.ts index b0c78c780..b90513044 100644 --- a/web/src/pages/tenants/TenantForm.ts +++ b/web/src/pages/tenants/TenantForm.ts @@ -43,10 +43,10 @@ export class TenantForm extends ModelForm { + name="domain"> - +