tenants: initial implementation

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-29 17:35:56 +02:00
parent a1b6e09e8a
commit ff611f21cd
21 changed files with 843 additions and 59 deletions

View File

@ -14,13 +14,6 @@ from authentik.core.api.utils import PassiveSerializer
from authentik.lib.config import CONFIG 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): class Capabilities(models.TextChoices):
"""Define capabilities which influence which APIs can/should be used""" """Define capabilities which influence which APIs can/should be used"""
@ -30,10 +23,6 @@ class Capabilities(models.TextChoices):
class ConfigSerializer(PassiveSerializer): class ConfigSerializer(PassiveSerializer):
"""Serialize authentik Config into DRF Object""" """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_enabled = BooleanField(read_only=True)
error_reporting_environment = CharField(read_only=True) error_reporting_environment = CharField(read_only=True)
error_reporting_send_pii = BooleanField(read_only=True) error_reporting_send_pii = BooleanField(read_only=True)
@ -59,12 +48,9 @@ class ConfigView(APIView):
"""Retrive public configuration options""" """Retrive public configuration options"""
config = ConfigSerializer( 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_enabled": CONFIG.y("error_reporting.enabled"),
"error_reporting_environment": CONFIG.y("error_reporting.environment"), "error_reporting_environment": CONFIG.y("error_reporting.environment"),
"error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"), "error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"),
"ui_footer_links": CONFIG.y("authentik.footer_links"),
"capabilities": self.get_capabilities(), "capabilities": self.get_capabilities(),
} }
) )

View File

@ -100,6 +100,7 @@ from authentik.stages.user_delete.api import UserDeleteStageViewSet
from authentik.stages.user_login.api import UserLoginStageViewSet from authentik.stages.user_login.api import UserLoginStageViewSet
from authentik.stages.user_logout.api import UserLogoutStageViewSet from authentik.stages.user_logout.api import UserLogoutStageViewSet
from authentik.stages.user_write.api import UserWriteStageViewSet from authentik.stages.user_write.api import UserWriteStageViewSet
from authentik.tenants.api import TenantViewSet
router = routers.DefaultRouter() router = routers.DefaultRouter()
@ -111,6 +112,7 @@ router.register("core/groups", GroupViewSet)
router.register("core/users", UserViewSet) router.register("core/users", UserViewSet)
router.register("core/user_consent", UserConsentViewSet) router.register("core/user_consent", UserConsentViewSet)
router.register("core/tokens", TokenViewSet) router.register("core/tokens", TokenViewSet)
router.register("core/tenants", TenantViewSet)
router.register("outposts/instances", OutpostViewSet) router.register("outposts/instances", OutpostViewSet)
router.register("outposts/service_connections/all", ServiceConnectionViewSet) router.register("outposts/service_connections/all", ServiceConnectionViewSet)

View File

@ -48,9 +48,6 @@ outposts:
authentik: authentik:
avatars: gravatar # gravatar or none avatars: gravatar # gravatar or none
geoip: "" geoip: ""
branding:
title: authentik
logo: /static/dist/assets/icons/icon_left_brand.svg
# Optionally add links to the footer on the login page # Optionally add links to the footer on the login page
footer_links: footer_links:
- name: Documentation - name: Documentation

View File

@ -6,7 +6,7 @@ class AuthentikManagedConfig(AppConfig):
"""authentik Managed app""" """authentik Managed app"""
name = "authentik.managed" name = "authentik.managed"
label = "authentik_Managed" label = "authentik_managed"
verbose_name = "authentik Managed" verbose_name = "authentik Managed"
def ready(self) -> None: def ready(self) -> None:

View File

@ -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.",
),
),
]

View File

@ -127,6 +127,7 @@ INSTALLED_APPS = [
"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",
"rest_framework", "rest_framework",
"django_filters", "django_filters",
"drf_spectacular", "drf_spectacular",
@ -208,6 +209,7 @@ MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"authentik.core.middleware.RequestIDMiddleware", "authentik.core.middleware.RequestIDMiddleware",
"authentik.tenants.middleware.TenantMiddleware",
"authentik.events.middleware.AuditMiddleware", "authentik.events.middleware.AuditMiddleware",
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",

View File

74
authentik/tenants/api.py Normal file
View File

@ -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)

10
authentik/tenants/apps.py Normal file
View File

@ -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"

View File

@ -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)

View File

@ -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",
},
),
]

View File

View File

@ -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")

View File

@ -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()

View File

@ -1712,6 +1712,231 @@ paths:
$ref: '#/components/schemas/ValidationError' $ref: '#/components/schemas/ValidationError'
'403': '403':
$ref: '#/components/schemas/GenericError' $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/: /api/v2beta/core/tokens/:
get: get:
operationId: core_tokens_list operationId: core_tokens_list
@ -15291,6 +15516,7 @@ components:
- 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.core - authentik.core
- authentik.managed - authentik.managed
type: string type: string
@ -16151,17 +16377,6 @@ components:
type: object type: object
description: Serialize authentik Config into DRF Object description: Serialize authentik Config into DRF Object
properties: 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: error_reporting_enabled:
type: boolean type: boolean
readOnly: true readOnly: true
@ -16176,13 +16391,10 @@ components:
items: items:
$ref: '#/components/schemas/CapabilitiesEnum' $ref: '#/components/schemas/CapabilitiesEnum'
required: required:
- branding_logo
- branding_title
- capabilities - capabilities
- error_reporting_enabled - error_reporting_enabled
- error_reporting_environment - error_reporting_environment
- error_reporting_send_pii - error_reporting_send_pii
- ui_footer_links
ConsentChallenge: ConsentChallenge:
type: object type: object
description: Challenge info for consent screens description: Challenge info for consent screens
@ -16298,6 +16510,28 @@ components:
required: required:
- x_cord - x_cord
- y_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: DenyStage:
type: object type: object
description: DenyStage Serializer description: DenyStage Serializer
@ -20893,6 +21127,41 @@ components:
required: required:
- pagination - pagination
- results - 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: PaginatedTokenList:
type: object type: object
properties: properties:
@ -22830,6 +23099,40 @@ components:
type: string type: string
description: The human-readable name of this device. description: The human-readable name of this device.
maxLength: 64 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: PatchedTokenRequest:
type: object type: object
description: Token Serializer description: Token Serializer
@ -24779,6 +25082,83 @@ components:
- task_description - task_description
- task_finish_timestamp - task_finish_timestamp
- task_name - 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: Token:
type: object type: object
description: Token Serializer description: Token Serializer

View File

@ -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 { getCookie } from "../utils";
import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer"; import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer";
import { MessageMiddleware } from "../elements/messages/Middleware"; import { MessageMiddleware } from "../elements/messages/Middleware";
@ -20,6 +20,14 @@ export function config(): Promise<Config> {
return globalConfigPromise; return globalConfigPromise;
} }
let globalTenantPromise: Promise<CurrentTenant>;
export function tenant(): Promise<CurrentTenant> {
if (!globalTenantPromise) {
globalTenantPromise = new CoreApi(DEFAULT_CONFIG).coreTenantsCurrentRetrieve();
}
return globalTenantPromise;
}
export const DEFAULT_CONFIG = new Configuration({ export const DEFAULT_CONFIG = new Configuration({
basePath: "", basePath: "",
headers: { headers: {

View File

@ -5,7 +5,7 @@ import AKGlobal from "../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import { EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants"; import { EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants";
import { config } from "../api/Config"; import { tenant } from "../api/Config";
@customElement("ak-page-header") @customElement("ak-page-header")
export class PageHeader extends LitElement { export class PageHeader extends LitElement {
@ -18,11 +18,11 @@ export class PageHeader extends LitElement {
@property() @property()
set header(value: string) { set header(value: string) {
config().then(config => { tenant().then(tenant => {
if (value !== "") { if (value !== "") {
document.title = `${value} - ${config.brandingTitle}`; document.title = `${value} - ${tenant.brandingTitle}`;
} else { } else {
document.title = config.brandingTitle || TITLE_DEFAULT; document.title = tenant.brandingTitle || TITLE_DEFAULT;
} }
}); });
this._header = value; this._header = value;

View File

@ -6,29 +6,25 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../authentik.css"; import AKGlobal from "../../authentik.css";
import { configureSentry } from "../../api/Sentry"; import { configureSentry } from "../../api/Sentry";
import { Config } from "authentik-api"; import { CurrentTenant } from "authentik-api";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { EVENT_SIDEBAR_TOGGLE } from "../../constants"; import { EVENT_SIDEBAR_TOGGLE } from "../../constants";
import { tenant } from "../../api/Config";
// If the viewport is wider than MIN_WIDTH, the sidebar // If the viewport is wider than MIN_WIDTH, the sidebar
// is shown besides the content, and not overlayed. // is shown besides the content, and not overlayed.
export const MIN_WIDTH = 1200; export const MIN_WIDTH = 1200;
export const DefaultConfig: Config = { export const DefaultTenant: CurrentTenant = {
brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg", brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg",
brandingTitle: "authentik", brandingTitle: "authentik",
errorReportingEnabled: false,
errorReportingEnvironment: "",
errorReportingSendPii: false,
uiFooterLinks: [], uiFooterLinks: [],
capabilities: [],
}; };
@customElement("ak-sidebar-brand") @customElement("ak-sidebar-brand")
export class SidebarBrand extends LitElement { export class SidebarBrand extends LitElement {
@property({attribute: false}) @property({attribute: false})
config: Config = DefaultConfig; tenant: CurrentTenant = DefaultTenant;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
@ -68,7 +64,8 @@ export class SidebarBrand extends LitElement {
} }
firstUpdated(): void { firstUpdated(): void {
configureSentry(true).then((c) => {this.config = c;}); configureSentry(true);
tenant().then(tenant => this.tenant = tenant);
} }
render(): TemplateResult { render(): TemplateResult {
@ -89,7 +86,7 @@ export class SidebarBrand extends LitElement {
` : html``} ` : html``}
<a href="#/" class="pf-c-page__header-brand-link"> <a href="#/" class="pf-c-page__header-brand-link">
<div class="pf-c-brand ak-brand"> <div class="pf-c-brand ak-brand">
<img src="${ifDefined(this.config.brandingLogo)}" alt="authentik icon" loading="lazy" /> <img src="${ifDefined(this.tenant.brandingLogo)}" alt="authentik icon" loading="lazy" />
</div> </div>
</a>`; </a>`;
} }

View File

@ -26,8 +26,8 @@ import "./stages/password/PasswordStage";
import "./stages/prompt/PromptStage"; import "./stages/prompt/PromptStage";
import "./sources/plex/PlexLoginInit"; import "./sources/plex/PlexLoginInit";
import { StageHost } from "./stages/base"; import { StageHost } from "./stages/base";
import { ChallengeChoices, Config, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api"; import { ChallengeChoices, CurrentTenant, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api";
import { config, DEFAULT_CONFIG } from "../api/Config"; import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PFSize } from "../elements/Spinner"; import { PFSize } from "../elements/Spinner";
@ -46,7 +46,7 @@ export class FlowExecutor extends LitElement implements StageHost {
loading = false; loading = false;
@property({ attribute: false }) @property({ attribute: false })
config?: Config; tenant?: CurrentTenant;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal].concat(css` return [PFBase, PFLogin, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal].concat(css`
@ -85,11 +85,11 @@ export class FlowExecutor extends LitElement implements StageHost {
} }
private postUpdate(): void { private postUpdate(): void {
config().then(config => { tenant().then(tenant => {
if (this.challenge?.title) { if (this.challenge?.title) {
document.title = `${this.challenge.title} - ${config.brandingTitle}`; document.title = `${this.challenge.title} - ${tenant.brandingTitle}`;
} else { } 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 { firstUpdated(): void {
configureSentry().then((config) => { configureSentry();
this.config = config; tenant().then(tenant => this.tenant = tenant);
});
this.loading = true; this.loading = true;
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({ new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
flowSlug: this.flowSlug, flowSlug: this.flowSlug,
@ -255,7 +254,7 @@ export class FlowExecutor extends LitElement implements StageHost {
<div class="ak-login-container"> <div class="ak-login-container">
<header class="pf-c-login__header"> <header class="pf-c-login__header">
<div class="pf-c-brand ak-brand"> <div class="pf-c-brand ak-brand">
<img src="${ifDefined(this.config?.brandingLogo)}" alt="authentik icon" /> <img src="${ifDefined(this.tenant?.brandingLogo)}" alt="authentik icon" />
</div> </div>
</header> </header>
<div class="pf-c-login__main"> <div class="pf-c-login__main">
@ -264,12 +263,12 @@ export class FlowExecutor extends LitElement implements StageHost {
<footer class="pf-c-login__footer"> <footer class="pf-c-login__footer">
<p></p> <p></p>
<ul class="pf-c-list pf-m-inline"> <ul class="pf-c-list pf-m-inline">
${until(this.config?.uiFooterLinks?.map((link) => { ${until(this.tenant?.uiFooterLinks?.map((link) => {
return html`<li> return html`<li>
<a href="${link.href || ""}">${link.name}</a> <a href="${link.href || ""}">${link.name}</a>
</li>`; </li>`;
}))} }))}
${this.config?.brandingTitle != "authentik" ? html` ${this.tenant?.brandingTitle != "authentik" ? html`
<li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li>` : html``} <li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li>` : html``}
</ul> </ul>
</footer> </footer>

View File

@ -522,6 +522,14 @@ msgstr "Changelog"
msgid "Characters which are considered as symbols." msgid "Characters which are considered as symbols."
msgstr "Characters which are considered as symbols." msgstr "Characters which are considered as symbols."
#: src/pages/applications/ApplicationViewPage.ts
msgid "Check"
msgstr "Check"
#: src/pages/applications/ApplicationViewPage.ts
msgid "Check Application access"
msgstr "Check Application access"
#: src/pages/policies/reputation/ReputationPolicyForm.ts #: src/pages/policies/reputation/ReputationPolicyForm.ts
msgid "Check IP" msgid "Check IP"
msgstr "Check IP" msgstr "Check IP"
@ -530,6 +538,10 @@ msgstr "Check IP"
msgid "Check Username" msgid "Check Username"
msgstr "Check Username" msgstr "Check Username"
#: src/pages/applications/ApplicationViewPage.ts
msgid "Check access"
msgstr "Check access"
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
msgid "Check status" msgid "Check status"
msgstr "Check status" msgstr "Check status"
@ -1625,6 +1637,7 @@ msgstr "Hide service-accounts"
#: src/pages/sources/plex/PlexSourceForm.ts #: src/pages/sources/plex/PlexSourceForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts #: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/password/PasswordStageForm.ts #: src/pages/stages/password/PasswordStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts
@ -1866,6 +1879,7 @@ msgid "Loading"
msgstr "Loading" msgstr "Loading"
#: src/elements/Spinner.ts #: src/elements/Spinner.ts
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/applications/ApplicationForm.ts #: src/pages/applications/ApplicationForm.ts
#: src/pages/events/RuleForm.ts #: src/pages/events/RuleForm.ts
#: src/pages/events/RuleForm.ts #: src/pages/events/RuleForm.ts
@ -1916,6 +1930,7 @@ msgstr "Loading"
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts #: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts #: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/password/PasswordStageForm.ts #: src/pages/stages/password/PasswordStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts #: src/pages/stages/prompt/PromptStageForm.ts
@ -1986,6 +2001,7 @@ msgstr "Maximum age (in days)"
msgid "Members" msgid "Members"
msgstr "Members" msgstr "Members"
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/events/EventInfo.ts #: src/pages/events/EventInfo.ts
#: src/pages/policies/PolicyTestForm.ts #: src/pages/policies/PolicyTestForm.ts
#: src/pages/system-tasks/SystemTaskListPage.ts #: src/pages/system-tasks/SystemTaskListPage.ts
@ -2130,6 +2146,7 @@ msgstr "Need an account?"
msgid "New version available!" msgid "New version available!"
msgstr "New version available!" msgstr "New version available!"
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/crypto/CertificateKeyPairListPage.ts #: src/pages/crypto/CertificateKeyPairListPage.ts
#: src/pages/groups/GroupListPage.ts #: src/pages/groups/GroupListPage.ts
#: src/pages/groups/MemberSelectModal.ts #: src/pages/groups/MemberSelectModal.ts
@ -2408,6 +2425,7 @@ msgstr "Parent"
msgid "Pass policy?" msgid "Pass policy?"
msgstr "Pass policy?" msgstr "Pass policy?"
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/events/EventInfo.ts #: src/pages/events/EventInfo.ts
#: src/pages/policies/PolicyTestForm.ts #: src/pages/policies/PolicyTestForm.ts
msgid "Passing" msgid "Passing"
@ -2887,6 +2905,10 @@ msgstr "Select an identification method."
msgid "Select one of the sources below to login." msgid "Select one of the sources below to login."
msgstr "Select one of the sources below to login." msgstr "Select one of the sources below to login."
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP."
msgstr "Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP."
#: src/pages/groups/MemberSelectModal.ts #: src/pages/groups/MemberSelectModal.ts
msgid "Select users to add" msgid "Select users to add"
msgstr "Select users to add" msgstr "Select users to add"
@ -3054,6 +3076,7 @@ msgstr "Source {0}"
#: src/interfaces/AdminInterface.ts #: src/interfaces/AdminInterface.ts
#: src/pages/sources/SourcesListPage.ts #: src/pages/sources/SourcesListPage.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "Sources" msgid "Sources"
msgstr "Sources" msgstr "Sources"
@ -3321,6 +3344,7 @@ msgstr "Successfully imported flow."
msgid "Successfully imported provider." msgid "Successfully imported provider."
msgstr "Successfully imported provider." msgstr "Successfully imported provider."
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/policies/PolicyTestForm.ts #: src/pages/policies/PolicyTestForm.ts
#: src/pages/property-mappings/PropertyMappingTestForm.ts #: src/pages/property-mappings/PropertyMappingTestForm.ts
msgid "Successfully sent test-request." msgid "Successfully sent test-request."
@ -3516,6 +3540,7 @@ msgstr "Task finished with warnings"
msgid "Template" msgid "Template"
msgstr "Template" msgstr "Template"
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/events/TransportListPage.ts #: src/pages/events/TransportListPage.ts
#: src/pages/policies/PolicyListPage.ts #: src/pages/policies/PolicyListPage.ts
#: src/pages/policies/PolicyListPage.ts #: src/pages/policies/PolicyListPage.ts
@ -3925,6 +3950,7 @@ msgstr "Use this redirect URL:"
#: src/elements/events/ObjectChangelog.ts #: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts #: src/elements/events/UserEvents.ts
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/events/EventInfo.ts #: src/pages/events/EventInfo.ts
#: src/pages/events/EventListPage.ts #: src/pages/events/EventListPage.ts
#: src/pages/policies/PolicyBindingForm.ts #: src/pages/policies/PolicyBindingForm.ts
@ -4178,6 +4204,7 @@ msgstr ""
msgid "X509 Subject" msgid "X509 Subject"
msgstr "X509 Subject" msgstr "X509 Subject"
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/crypto/CertificateKeyPairListPage.ts #: src/pages/crypto/CertificateKeyPairListPage.ts
#: src/pages/groups/GroupListPage.ts #: src/pages/groups/GroupListPage.ts
#: src/pages/groups/MemberSelectModal.ts #: src/pages/groups/MemberSelectModal.ts

View File

@ -518,6 +518,14 @@ msgstr ""
msgid "Characters which are considered as symbols." msgid "Characters which are considered as symbols."
msgstr "" msgstr ""
#:
msgid "Check"
msgstr ""
#:
msgid "Check Application access"
msgstr ""
#: #:
msgid "Check IP" msgid "Check IP"
msgstr "" msgstr ""
@ -526,6 +534,10 @@ msgstr ""
msgid "Check Username" msgid "Check Username"
msgstr "" msgstr ""
#:
msgid "Check access"
msgstr ""
#: #:
msgid "Check status" msgid "Check status"
msgstr "" msgstr ""
@ -1620,6 +1632,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Hold control/command to select multiple items." msgid "Hold control/command to select multiple items."
msgstr "" msgstr ""
@ -1911,6 +1924,8 @@ msgstr ""
#: #:
#: #:
#: #:
#:
#:
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1981,6 +1996,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Messages" msgid "Messages"
msgstr "" msgstr ""
@ -2133,6 +2149,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "No" msgid "No"
msgstr "" msgstr ""
@ -2400,6 +2417,7 @@ msgstr ""
msgid "Pass policy?" msgid "Pass policy?"
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Passing" msgid "Passing"
@ -2879,6 +2897,10 @@ msgstr ""
msgid "Select one of the sources below to login." msgid "Select one of the sources below to login."
msgstr "" msgstr ""
#:
msgid "Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP."
msgstr ""
#: #:
msgid "Select users to add" msgid "Select users to add"
msgstr "" msgstr ""
@ -3044,6 +3066,7 @@ msgstr ""
msgid "Source {0}" msgid "Source {0}"
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Sources" msgid "Sources"
@ -3313,6 +3336,7 @@ msgstr ""
msgid "Successfully imported provider." msgid "Successfully imported provider."
msgstr "" msgstr ""
#:
#: #:
#: #:
msgid "Successfully sent test-request." msgid "Successfully sent test-request."
@ -3513,6 +3537,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Test" msgid "Test"
msgstr "" msgstr ""
@ -3923,6 +3948,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "User" msgid "User"
msgstr "" msgstr ""
@ -4175,6 +4201,7 @@ msgstr ""
#: #:
#: #:
#: #:
#:
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""