root: bump python deps (django 5) (#7862)

* bump python deps

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* vendor pickle serializer for now

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

#7761

* cleanup some things and re-build api scheme

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web and go

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* actually fix go...?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better annotate json fields

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use jsondictfield wherever

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove all virtualenvs?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* ?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* final version bump

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-12-18 22:07:59 +01:00 committed by GitHub
parent ba174d810b
commit 729ef4d786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 960 additions and 919 deletions

View File

@ -61,10 +61,6 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup authentik env
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: checkout stable - name: checkout stable
run: | run: |
# Delete all poetry envs # Delete all poetry envs
@ -76,7 +72,7 @@ jobs:
git checkout version/$(python -c "from authentik import __version__; print(__version__)") git checkout version/$(python -c "from authentik import __version__; print(__version__)")
rm -rf .github/ scripts/ rm -rf .github/ scripts/
mv ../.github ../scripts . mv ../.github ../scripts .
- name: Setup authentik env (ensure stable deps are installed) - name: Setup authentik env (stable)
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
postgresql_version: ${{ matrix.psql }} postgresql_version: ${{ matrix.psql }}
@ -90,14 +86,13 @@ jobs:
git clean -d -fx . git clean -d -fx .
git checkout $GITHUB_SHA git checkout $GITHUB_SHA
# Delete previous poetry env # Delete previous poetry env
rm -rf $(poetry env info --path) rm -rf /home/runner/.cache/pypoetry/virtualenvs/*
- name: Setup authentik env (ensure latest deps are installed) - name: Setup authentik env (ensure latest deps are installed)
uses: ./.github/actions/setup uses: ./.github/actions/setup
with: with:
postgresql_version: ${{ matrix.psql }} postgresql_version: ${{ matrix.psql }}
- name: migrate to latest - name: migrate to latest
run: | run: |
poetry install
poetry run python -m lifecycle.migrate poetry run python -m lifecycle.migrate
test-unittest: test-unittest:
name: test-unittest - PostgreSQL ${{ matrix.psql }} name: test-unittest - PostgreSQL ${{ matrix.psql }}

View File

@ -115,8 +115,9 @@ gen-diff: ## (Release) generate the changelog diff between the current schema a
npx prettier --write diff.md npx prettier --write diff.md
gen-clean: gen-clean:
rm -rf web/api/src/ rm -rf gen-go-api/
rm -rf api/ rm -rf gen-ts-api/
rm -rf web/node_modules/@goauthentik/api/
gen-client-ts: ## Build and install the authentik API for Typescript into the authentik UI Application gen-client-ts: ## Build and install the authentik API for Typescript into the authentik UI Application
docker run \ docker run \

View File

@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema, inline_serializer from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, DateTimeField, JSONField from rest_framework.fields import CharField, DateTimeField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ListSerializer, ModelSerializer from rest_framework.serializers import ListSerializer, ModelSerializer
@ -15,7 +15,7 @@ from authentik.blueprints.v1.importer import Importer
from authentik.blueprints.v1.oci import OCI_PREFIX from authentik.blueprints.v1.oci import OCI_PREFIX
from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import JSONDictField, PassiveSerializer
class ManagedSerializer: class ManagedSerializer:
@ -28,7 +28,7 @@ class MetadataSerializer(PassiveSerializer):
"""Serializer for blueprint metadata""" """Serializer for blueprint metadata"""
name = CharField() name = CharField()
labels = JSONField() labels = JSONDictField()
class BlueprintInstanceSerializer(ModelSerializer): class BlueprintInstanceSerializer(ModelSerializer):

View File

@ -2,11 +2,11 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import BooleanField, JSONField from rest_framework.fields import BooleanField
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.blueprints.v1.meta.registry import BaseMetaModel, MetaResult, registry from authentik.blueprints.v1.meta.registry import BaseMetaModel, MetaResult, registry
from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.api.utils import JSONDictField, PassiveSerializer
if TYPE_CHECKING: if TYPE_CHECKING:
from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.models import BlueprintInstance
@ -17,7 +17,7 @@ LOGGER = get_logger()
class ApplyBlueprintMetaSerializer(PassiveSerializer): class ApplyBlueprintMetaSerializer(PassiveSerializer):
"""Serializer for meta apply blueprint model""" """Serializer for meta apply blueprint model"""
identifiers = JSONField(validators=[is_dict]) identifiers = JSONDictField()
required = BooleanField(default=True) required = BooleanField(default=True)
# We cannot override `instance` as that will confuse rest_framework # We cannot override `instance` as that will confuse rest_framework

View File

@ -8,7 +8,7 @@ from django_filters.filterset import FilterSet
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, IntegerField, JSONField from rest_framework.fields import CharField, IntegerField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError
@ -16,7 +16,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import Group, User from authentik.core.models import Group, User
from authentik.rbac.api.roles import RoleSerializer from authentik.rbac.api.roles import RoleSerializer
@ -24,7 +24,7 @@ from authentik.rbac.api.roles import RoleSerializer
class GroupMemberSerializer(ModelSerializer): class GroupMemberSerializer(ModelSerializer):
"""Stripped down user serializer to show relevant users for groups""" """Stripped down user serializer to show relevant users for groups"""
attributes = JSONField(validators=[is_dict], required=False) attributes = JSONDictField(required=False)
uid = CharField(read_only=True) uid = CharField(read_only=True)
class Meta: class Meta:
@ -44,7 +44,7 @@ class GroupMemberSerializer(ModelSerializer):
class GroupSerializer(ModelSerializer): class GroupSerializer(ModelSerializer):
"""Group Serializer""" """Group Serializer"""
attributes = JSONField(validators=[is_dict], required=False) attributes = JSONDictField(required=False)
users_obj = ListSerializer( users_obj = ListSerializer(
child=GroupMemberSerializer(), read_only=True, source="users", required=False child=GroupMemberSerializer(), read_only=True, source="users", required=False
) )

View File

@ -32,13 +32,7 @@ from drf_spectacular.utils import (
) )
from guardian.shortcuts import get_anonymous_user, get_objects_for_user from guardian.shortcuts import get_anonymous_user, get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import ( from rest_framework.fields import CharField, IntegerField, ListField, SerializerMethodField
CharField,
IntegerField,
JSONField,
ListField,
SerializerMethodField,
)
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ( from rest_framework.serializers import (
@ -57,7 +51,7 @@ from authentik.admin.api.metrics import CoordinateSerializer
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict from authentik.core.api.utils import JSONDictField, LinkSerializer, PassiveSerializer
from authentik.core.middleware import ( from authentik.core.middleware import (
SESSION_KEY_IMPERSONATE_ORIGINAL_USER, SESSION_KEY_IMPERSONATE_ORIGINAL_USER,
SESSION_KEY_IMPERSONATE_USER, SESSION_KEY_IMPERSONATE_USER,
@ -89,7 +83,7 @@ LOGGER = get_logger()
class UserGroupSerializer(ModelSerializer): class UserGroupSerializer(ModelSerializer):
"""Simplified Group Serializer for user's groups""" """Simplified Group Serializer for user's groups"""
attributes = JSONField(required=False) attributes = JSONDictField(required=False)
parent_name = CharField(source="parent.name", read_only=True) parent_name = CharField(source="parent.name", read_only=True)
class Meta: class Meta:
@ -110,7 +104,7 @@ class UserSerializer(ModelSerializer):
is_superuser = BooleanField(read_only=True) is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True) avatar = CharField(read_only=True)
attributes = JSONField(validators=[is_dict], required=False) attributes = JSONDictField(required=False)
groups = PrimaryKeyRelatedField( groups = PrimaryKeyRelatedField(
allow_empty=True, many=True, source="ak_groups", queryset=Group.objects.all(), default=list allow_empty=True, many=True, source="ak_groups", queryset=Group.objects.all(), default=list
) )

View File

@ -2,6 +2,9 @@
from typing import Any from typing import Any
from django.db.models import Model from django.db.models import Model
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
from drf_spectacular.plumbing import build_basic_type
from drf_spectacular.types import OpenApiTypes
from rest_framework.fields import CharField, IntegerField, JSONField from rest_framework.fields import CharField, IntegerField, JSONField
from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError
@ -13,6 +16,21 @@ def is_dict(value: Any):
raise ValidationError("Value must be a dictionary, and not have any duplicate keys.") raise ValidationError("Value must be a dictionary, and not have any duplicate keys.")
class JSONDictField(JSONField):
"""JSON Field which only allows dictionaries"""
default_validators = [is_dict]
class JSONExtension(OpenApiSerializerFieldExtension):
"""Generate API Schema for JSON fields as"""
target_class = "authentik.core.api.utils.JSONDictField"
def map_serializer_field(self, auto_schema, direction):
return build_basic_type(OpenApiTypes.OBJECT)
class PassiveSerializer(Serializer): class PassiveSerializer(Serializer):
"""Base serializer class which doesn't implement create/update methods""" """Base serializer class which doesn't implement create/update methods"""
@ -26,7 +44,7 @@ class PassiveSerializer(Serializer):
class PropertyMappingPreviewSerializer(PassiveSerializer): class PropertyMappingPreviewSerializer(PassiveSerializer):
"""Preview how the current user is mapped via the property mappings selected in a provider""" """Preview how the current user is mapped via the property mappings selected in a provider"""
preview = JSONField(read_only=True) preview = JSONDictField(read_only=True)
class MetaNameSerializer(PassiveSerializer): class MetaNameSerializer(PassiveSerializer):

View File

@ -9,13 +9,13 @@ from rest_framework.fields import BooleanField, CharField, DateTimeField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import JSONField, ModelSerializer, ValidationError from rest_framework.serializers import ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik import get_build_hash from authentik import get_build_hash
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.outposts.api.service_connections import ServiceConnectionSerializer from authentik.outposts.api.service_connections import ServiceConnectionSerializer
from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.apps import MANAGED_OUTPOST
@ -34,7 +34,7 @@ from authentik.providers.radius.models import RadiusProvider
class OutpostSerializer(ModelSerializer): class OutpostSerializer(ModelSerializer):
"""Outpost Serializer""" """Outpost Serializer"""
config = JSONField(validators=[is_dict], source="_config") config = JSONDictField(source="_config")
# Need to set allow_empty=True for the embedded outpost with no providers # Need to set allow_empty=True for the embedded outpost with no providers
# is checked for other providers in the API Viewset # is checked for other providers in the API Viewset
providers = PrimaryKeyRelatedField( providers = PrimaryKeyRelatedField(
@ -95,7 +95,7 @@ class OutpostSerializer(ModelSerializer):
class OutpostDefaultConfigSerializer(PassiveSerializer): class OutpostDefaultConfigSerializer(PassiveSerializer):
"""Global default outpost config""" """Global default outpost config"""
config = JSONField(read_only=True) config = JSONDictField(read_only=True)
class OutpostHealthSerializer(PassiveSerializer): class OutpostHealthSerializer(PassiveSerializer):

View File

@ -1,8 +1,8 @@
"""Serializer for policy execution""" """Serializer for policy execution"""
from rest_framework.fields import BooleanField, CharField, DictField, JSONField, ListField from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import User from authentik.core.models import User
@ -10,7 +10,7 @@ class PolicyTestSerializer(PassiveSerializer):
"""Test policy execution for a user with context""" """Test policy execution for a user with context"""
user = PrimaryKeyRelatedField(queryset=User.objects.all()) user = PrimaryKeyRelatedField(queryset=User.objects.all())
context = JSONField(required=False, validators=[is_dict]) context = JSONDictField(required=False)
class PolicyTestResultSerializer(PassiveSerializer): class PolicyTestResultSerializer(PassiveSerializer):

View File

View File

@ -0,0 +1,22 @@
"""
Module for abstract serializer/unserializer base classes.
"""
import pickle # nosec
class PickleSerializer:
"""
Simple wrapper around pickle to be used in signing.dumps()/loads() and
cache backends.
"""
def __init__(self, protocol=None):
self.protocol = pickle.HIGHEST_PROTOCOL if protocol is None else protocol
def dumps(self, obj):
"""Pickle data to be stored in redis"""
return pickle.dumps(obj, self.protocol)
def loads(self, data):
"""Unpickle data to be loaded from redis"""
return pickle.loads(data) # nosec

View File

@ -138,6 +138,7 @@ SPECTACULAR_SETTINGS = {
"EventActions": "authentik.events.models.EventAction", "EventActions": "authentik.events.models.EventAction",
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes", "ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation", "FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode", "PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
"ProxyMode": "authentik.providers.proxy.models.ProxyMode", "ProxyMode": "authentik.providers.proxy.models.ProxyMode",
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes", "PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
@ -204,7 +205,7 @@ DJANGO_REDIS_SCAN_ITERSIZE = 1000
DJANGO_REDIS_IGNORE_EXCEPTIONS = True DJANGO_REDIS_IGNORE_EXCEPTIONS = True
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer" SESSION_SERIALIZER = "authentik.root.sessions.pickle.PickleSerializer"
SESSION_CACHE_ALIAS = "default" SESSION_CACHE_ALIAS = "default"
# Configured via custom SessionMiddleware # Configured via custom SessionMiddleware
# SESSION_COOKIE_SAMESITE = "None" # SESSION_COOKIE_SAMESITE = "None"

View File

@ -22,8 +22,8 @@ from authentik.stages.authenticator.util import hex_validator, random_hex
class TOTPDigits(models.TextChoices): class TOTPDigits(models.TextChoices):
"""OTP Time Digits""" """OTP Time Digits"""
SIX = 6, _("6 digits, widely compatible") SIX = "6", _("6 digits, widely compatible")
EIGHT = 8, _("8 digits, not compatible with apps like Google Authenticator") EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator")
class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage): class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage):

View File

@ -7,7 +7,7 @@ from django.http.response import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as __ from django.utils.translation import gettext as __
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.fields import CharField, JSONField from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from webauthn.authentication.generate_authentication_options import generate_authentication_options from webauthn.authentication.generate_authentication_options import generate_authentication_options
@ -16,7 +16,7 @@ from webauthn.helpers.base64url_to_bytes import base64url_to_bytes
from webauthn.helpers.exceptions import InvalidAuthenticationResponse from webauthn.helpers.exceptions import InvalidAuthenticationResponse
from webauthn.helpers.structs import AuthenticationCredential from webauthn.helpers.structs import AuthenticationCredential
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import Application, User from authentik.core.models import Application, User
from authentik.core.signals import login_failed from authentik.core.signals import login_failed
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -40,7 +40,7 @@ class DeviceChallenge(PassiveSerializer):
device_class = CharField() device_class = CharField()
device_uid = CharField() device_uid = CharField()
challenge = JSONField() challenge = JSONDictField()
def get_challenge_for_device( def get_challenge_for_device(

View File

@ -6,10 +6,10 @@ from typing import Optional
from django.conf import settings from django.conf import settings
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from jwt import PyJWTError, decode, encode from jwt import PyJWTError, decode, encode
from rest_framework.fields import CharField, IntegerField, JSONField, ListField, UUIDField from rest_framework.fields import CharField, IntegerField, ListField, UUIDField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import User from authentik.core.models import User
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
@ -68,7 +68,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
selected_stage = CharField(required=False) selected_stage = CharField(required=False)
code = CharField(required=False) code = CharField(required=False)
webauthn = JSONField(required=False) webauthn = JSONDictField(required=False)
duo = IntegerField(required=False) duo = IntegerField(required=False)
component = CharField(default="ak-stage-authenticator-validate") component = CharField(default="ak-stage-authenticator-validate")

View File

@ -1,7 +1,7 @@
"""WebAuthn stage""" """WebAuthn stage"""
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict from django.http.request import QueryDict
from rest_framework.fields import CharField, JSONField from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from webauthn.helpers.bytes_to_base64url import bytes_to_base64url from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
from webauthn.helpers.exceptions import InvalidRegistrationResponse from webauthn.helpers.exceptions import InvalidRegistrationResponse
@ -16,6 +16,7 @@ from webauthn.registration.verify_registration_response import (
verify_registration_response, verify_registration_response,
) )
from authentik.core.api.utils import JSONDictField
from authentik.core.models import User from authentik.core.models import User
from authentik.flows.challenge import ( from authentik.flows.challenge import (
Challenge, Challenge,
@ -33,14 +34,14 @@ SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challe
class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge): class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge):
"""WebAuthn Challenge""" """WebAuthn Challenge"""
registration = JSONField() registration = JSONDictField()
component = CharField(default="ak-stage-authenticator-webauthn") component = CharField(default="ak-stage-authenticator-webauthn")
class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse): class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
"""WebAuthn Challenge response""" """WebAuthn Challenge response"""
response = JSONField() response = JSONDictField()
component = CharField(default="ak-stage-authenticator-webauthn") component = CharField(default="ak-stage-authenticator-webauthn")
request: HttpRequest request: HttpRequest

View File

@ -1,13 +1,12 @@
"""Invitation Stage API Views""" """Invitation Stage API Views"""
from django_filters.filters import BooleanFilter from django_filters.filters import BooleanFilter
from django_filters.filterset import FilterSet from django_filters.filterset import FilterSet
from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.groups import GroupMemberSerializer from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict from authentik.core.api.utils import JSONDictField
from authentik.flows.api.flows import FlowSerializer from authentik.flows.api.flows import FlowSerializer
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.models import Invitation, InvitationStage
@ -47,7 +46,7 @@ class InvitationSerializer(ModelSerializer):
"""Invitation Serializer""" """Invitation Serializer"""
created_by = GroupMemberSerializer(read_only=True) created_by = GroupMemberSerializer(read_only=True)
fixed_data = JSONField(validators=[is_dict], required=False) fixed_data = JSONDictField(required=False)
flow_obj = FlowSerializer(read_only=True, required=False, source="flow") flow_obj = FlowSerializer(read_only=True, required=False, source="flow")
class Meta: class Meta:

View File

@ -81,7 +81,6 @@ var rootCmd = &cobra.Command{
for { for {
<-ex <-ex
} }
}, },
} }

2
go.mod
View File

@ -82,3 +82,5 @@ require (
gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
replace goauthentik.io/api/v3 => ./gen-go-api

View File

@ -76,7 +76,6 @@ func NewAPIController(akURL url.URL, token string) *APIController {
// Because we don't know the outpost UUID, we simply do a list and pick the first // Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost // The service account this token belongs to should only have access to a single outpost
outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute() outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration, retrying in 3 seconds") log.WithError(err).Error("Failed to fetch outpost configuration, retrying in 3 seconds")
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
@ -168,7 +167,6 @@ func (a *APIController) OnRefresh() error {
// Because we don't know the outpost UUID, we simply do a list and pick the first // Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost // The service account this token belongs to should only have access to a single outpost
outposts, _, err := a.Client.OutpostsApi.OutpostsInstancesList(context.Background()).Execute() outposts, _, err := a.Client.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration") log.WithError(err).Error("Failed to fetch outpost configuration")
return err return err

View File

@ -14,8 +14,10 @@ import (
webutils "goauthentik.io/internal/utils/web" webutils "goauthentik.io/internal/utils/web"
) )
var initialSetup = false var (
var tlsTransport *http.RoundTripper = nil initialSetup = false
tlsTransport *http.RoundTripper = nil
)
func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) { func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) {
l := log.WithField("logger", "authentik.outpost") l := log.WithField("logger", "authentik.outpost")

View File

@ -1,4 +1,3 @@
package handler package handler
type Handler interface { type Handler interface{}
}

View File

@ -78,5 +78,4 @@ func (ls *LDAPServer) fallbackRootDSE(req *search.Request) (ldap.ServerSearchRes
}, },
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
}, nil }, nil
} }

View File

@ -59,12 +59,12 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
userAttributes := c.Proxy.UserAttributes userAttributes := c.Proxy.UserAttributes
a.setAuthorizationHeader(headers, c) a.setAuthorizationHeader(headers, c)
// Check if user has additional headers set that we should sent // Check if user has additional headers set that we should sent
if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]interface{}); ok { if additionalHeaders, ok := userAttributes["additionalHeaders"]; ok {
a.log.WithField("headers", additionalHeaders).Trace("setting additional headers") a.log.WithField("headers", additionalHeaders).Trace("setting additional headers")
if additionalHeaders == nil { if additionalHeaders == nil {
return return
} }
for key, value := range additionalHeaders { for key, value := range additionalHeaders.(map[string]interface{}) {
headers.Set(key, toString(value)) headers.Set(key, toString(value))
} }
} }

View File

@ -1,7 +1,9 @@
package constants package constants
const SessionOAuthState = "oauth_state" const (
const SessionClaims = "claims" SessionOAuthState = "oauth_state"
SessionClaims = "claims"
)
const SessionRedirect = "redirect" const SessionRedirect = "redirect"

View File

@ -15,7 +15,7 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{ fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
"username": username, "username": username,
"client": r.RemoteAddr(), "client": r.RemoteAddr(),
"requestId": r.ID, "requestId": r.ID(),
}) })
fe.DelegateClientIP(r.RemoteAddr()) fe.DelegateClientIP(r.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/radius", "true") fe.Params.Add("goauthentik.io/outpost/radius", "true")
@ -27,7 +27,6 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
} }
passed, err := fe.Execute() passed, err := fe.Execute()
if err != nil { if err != nil {
r.Log().WithField("username", username).WithError(err).Warning("failed to execute flow") r.Log().WithField("username", username).WithError(err).Warning("failed to execute flow")
metrics.RequestsRejected.With(prometheus.Labels{ metrics.RequestsRejected.With(prometheus.Labels{

View File

@ -14,12 +14,10 @@ import (
"goauthentik.io/internal/utils/sentry" "goauthentik.io/internal/utils/sentry"
) )
var ( var Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_main_request_duration_seconds", Name: "authentik_main_request_duration_seconds",
Help: "API request latencies in seconds", Help: "API request latencies in seconds",
}, []string{"dest"}) }, []string{"dest"})
)
func (ws *WebServer) runMetricsServer() { func (ws *WebServer) runMetricsServer() {
m := mux.NewRouter() m := mux.NewRouter()

1461
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -18334,6 +18334,7 @@ paths:
- tr - tr
- tt - tt
- udm - udm
- ug
- uk - uk
- ur - ur
- uz - uz
@ -29205,9 +29206,7 @@ components:
path: path:
type: string type: string
default: '' default: ''
context: context: {}
type: object
additionalProperties: {}
last_applied: last_applied:
type: string type: string
format: date-time format: date-time
@ -29227,8 +29226,6 @@ components:
type: string type: string
readOnly: true readOnly: true
metadata: metadata:
type: object
additionalProperties: {}
readOnly: true readOnly: true
content: content:
type: string type: string
@ -29250,9 +29247,7 @@ components:
path: path:
type: string type: string
default: '' default: ''
context: context: {}
type: object
additionalProperties: {}
enabled: enabled:
type: boolean type: boolean
content: content:
@ -29772,10 +29767,24 @@ components:
cancel_url: cancel_url:
type: string type: string
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/ContextualFlowInfoLayoutEnum'
required: required:
- cancel_url - cancel_url
- layout - layout
ContextualFlowInfoLayoutEnum:
enum:
- stacked
- content_left
- content_right
- sidebar_left
- sidebar_right
type: string
description: |-
* `stacked` - STACKED
* `content_left` - CONTENT_LEFT
* `content_right` - CONTENT_RIGHT
* `sidebar_left` - SIDEBAR_LEFT
* `sidebar_right` - SIDEBAR_RIGHT
Coordinate: Coordinate:
type: object type: object
description: Coordinates for diagrams description: Coordinates for diagrams
@ -30493,16 +30502,12 @@ components:
format: uuid format: uuid
readOnly: true readOnly: true
title: Event uuid title: Event uuid
user: user: {}
type: object
additionalProperties: {}
action: action:
$ref: '#/components/schemas/EventActions' $ref: '#/components/schemas/EventActions'
app: app:
type: string type: string
context: context: {}
type: object
additionalProperties: {}
client_ip: client_ip:
type: string type: string
nullable: true nullable: true
@ -30513,9 +30518,7 @@ components:
expires: expires:
type: string type: string
format: date-time format: date-time
tenant: tenant: {}
type: object
additionalProperties: {}
required: required:
- action - action
- app - app
@ -30992,17 +30995,13 @@ components:
type: object type: object
description: Event Serializer description: Event Serializer
properties: properties:
user: user: {}
type: object
additionalProperties: {}
action: action:
$ref: '#/components/schemas/EventActions' $ref: '#/components/schemas/EventActions'
app: app:
type: string type: string
minLength: 1 minLength: 1
context: context: {}
type: object
additionalProperties: {}
client_ip: client_ip:
type: string type: string
nullable: true nullable: true
@ -31010,9 +31009,7 @@ components:
expires: expires:
type: string type: string
format: date-time format: date-time
tenant: tenant: {}
type: object
additionalProperties: {}
required: required:
- action - action
- app - app
@ -31311,7 +31308,7 @@ components:
description: Get export URL for flow description: Get export URL for flow
readOnly: true readOnly: true
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/FlowLayoutEnum'
denied_action: denied_action:
allOf: allOf:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
@ -31498,6 +31495,20 @@ components:
- next_planned_stage - next_planned_stage
- plan_context - plan_context
- session_id - session_id
FlowLayoutEnum:
enum:
- stacked
- content_left
- content_right
- sidebar_left
- sidebar_right
type: string
description: |-
* `stacked` - Stacked
* `content_left` - Content Left
* `content_right` - Content Right
* `sidebar_left` - Sidebar Left
* `sidebar_right` - Sidebar Right
FlowRequest: FlowRequest:
type: object type: object
description: Flow Serializer description: Flow Serializer
@ -31535,7 +31546,7 @@ components:
description: Enable compatibility mode, increases compatibility with password description: Enable compatibility mode, increases compatibility with password
managers on mobile devices. managers on mobile devices.
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/FlowLayoutEnum'
denied_action: denied_action:
allOf: allOf:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
@ -31613,7 +31624,7 @@ components:
description: Get export URL for flow description: Get export URL for flow
readOnly: true readOnly: true
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/FlowLayoutEnum'
denied_action: denied_action:
allOf: allOf:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
@ -31669,7 +31680,7 @@ components:
description: Enable compatibility mode, increases compatibility with password description: Enable compatibility mode, increases compatibility with password
managers on mobile devices. managers on mobile devices.
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/FlowLayoutEnum'
denied_action: denied_action:
allOf: allOf:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
@ -32361,8 +32372,6 @@ components:
description: Return internal model name description: Return internal model name
readOnly: true readOnly: true
kubeconfig: kubeconfig:
type: object
additionalProperties: {}
description: Paste your kubeconfig here. authentik will automatically use description: Paste your kubeconfig here. authentik will automatically use
the currently selected context. the currently selected context.
verify_ssl: verify_ssl:
@ -32387,8 +32396,6 @@ components:
description: If enabled, use the local connection. Required Docker socket/Kubernetes description: If enabled, use the local connection. Required Docker socket/Kubernetes
Integration Integration
kubeconfig: kubeconfig:
type: object
additionalProperties: {}
description: Paste your kubeconfig here. authentik will automatically use description: Paste your kubeconfig here. authentik will automatically use
the currently selected context. the currently selected context.
verify_ssl: verify_ssl:
@ -33053,20 +33060,6 @@ components:
required: required:
- is_running - is_running
- tasks - tasks
LayoutEnum:
enum:
- stacked
- content_left
- content_right
- sidebar_left
- sidebar_right
type: string
description: |-
* `stacked` - STACKED
* `content_left` - CONTENT_LEFT
* `content_right` - CONTENT_RIGHT
* `sidebar_left` - SIDEBAR_LEFT
* `sidebar_right` - SIDEBAR_RIGHT
License: License:
type: object type: object
description: License Serializer description: License Serializer
@ -34052,9 +34045,7 @@ components:
type: string type: string
oidc_jwks_url: oidc_jwks_url:
type: string type: string
oidc_jwks: oidc_jwks: {}
type: object
additionalProperties: {}
required: required:
- callback_url - callback_url
- component - component
@ -34151,9 +34142,7 @@ components:
type: string type: string
oidc_jwks_url: oidc_jwks_url:
type: string type: string
oidc_jwks: oidc_jwks: {}
type: object
additionalProperties: {}
required: required:
- consumer_key - consumer_key
- consumer_secret - consumer_secret
@ -36042,9 +36031,7 @@ components:
path: path:
type: string type: string
default: '' default: ''
context: context: {}
type: object
additionalProperties: {}
enabled: enabled:
type: boolean type: boolean
content: content:
@ -36441,17 +36428,13 @@ components:
type: object type: object
description: Event Serializer description: Event Serializer
properties: properties:
user: user: {}
type: object
additionalProperties: {}
action: action:
$ref: '#/components/schemas/EventActions' $ref: '#/components/schemas/EventActions'
app: app:
type: string type: string
minLength: 1 minLength: 1
context: context: {}
type: object
additionalProperties: {}
client_ip: client_ip:
type: string type: string
nullable: true nullable: true
@ -36459,9 +36442,7 @@ components:
expires: expires:
type: string type: string
format: date-time format: date-time
tenant: tenant: {}
type: object
additionalProperties: {}
PatchedExpressionPolicyRequest: PatchedExpressionPolicyRequest:
type: object type: object
description: Group Membership Policy Serializer description: Group Membership Policy Serializer
@ -36513,7 +36494,7 @@ components:
description: Enable compatibility mode, increases compatibility with password description: Enable compatibility mode, increases compatibility with password
managers on mobile devices. managers on mobile devices.
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/FlowLayoutEnum'
denied_action: denied_action:
allOf: allOf:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
@ -36703,8 +36684,6 @@ components:
description: If enabled, use the local connection. Required Docker socket/Kubernetes description: If enabled, use the local connection. Required Docker socket/Kubernetes
Integration Integration
kubeconfig: kubeconfig:
type: object
additionalProperties: {}
description: Paste your kubeconfig here. authentik will automatically use description: Paste your kubeconfig here. authentik will automatically use
the currently selected context. the currently selected context.
verify_ssl: verify_ssl:
@ -37165,9 +37144,7 @@ components:
type: string type: string
oidc_jwks_url: oidc_jwks_url:
type: string type: string
oidc_jwks: oidc_jwks: {}
type: object
additionalProperties: {}
PatchedOutpostRequest: PatchedOutpostRequest:
type: object type: object
description: Outpost Serializer description: Outpost Serializer
@ -38011,9 +37988,7 @@ components:
format: uuid format: uuid
nullable: true nullable: true
description: Web Certificate used by the authentik Core webserver. description: Web Certificate used by the authentik Core webserver.
attributes: attributes: {}
type: object
additionalProperties: {}
PatchedTokenRequest: PatchedTokenRequest:
type: object type: object
description: Token Serializer description: Token Serializer
@ -39686,9 +39661,7 @@ components:
type: string type: string
ip: ip:
type: string type: string
ip_geo_data: ip_geo_data: {}
type: object
additionalProperties: {}
score: score:
type: integer type: integer
maximum: 9223372036854775807 maximum: 9223372036854775807
@ -41415,9 +41388,7 @@ components:
format: uuid format: uuid
nullable: true nullable: true
description: Web Certificate used by the authentik Core webserver. description: Web Certificate used by the authentik Core webserver.
attributes: attributes: {}
type: object
additionalProperties: {}
required: required:
- domain - domain
- tenant_uuid - tenant_uuid
@ -41474,9 +41445,7 @@ components:
format: uuid format: uuid
nullable: true nullable: true
description: Web Certificate used by the authentik Core webserver. description: Web Certificate used by the authentik Core webserver.
attributes: attributes: {}
type: object
additionalProperties: {}
required: required:
- domain - domain
Token: Token:

View File

@ -18,8 +18,8 @@ import {
DeniedActionEnum, DeniedActionEnum,
Flow, Flow,
FlowDesignationEnum, FlowDesignationEnum,
FlowLayoutEnum,
FlowsApi, FlowsApi,
LayoutEnum,
PolicyEngineMode, PolicyEngineMode,
} from "@goauthentik/api"; } from "@goauthentik/api";
@ -302,34 +302,34 @@ export class FlowForm extends ModelForm<Flow, string> {
> >
<select class="pf-c-form-control"> <select class="pf-c-form-control">
<option <option
value=${LayoutEnum.Stacked} value=${FlowLayoutEnum.Stacked}
?selected=${this.instance?.layout === LayoutEnum.Stacked} ?selected=${this.instance?.layout === FlowLayoutEnum.Stacked}
> >
${LayoutToLabel(LayoutEnum.Stacked)} ${LayoutToLabel(FlowLayoutEnum.Stacked)}
</option> </option>
<option <option
value=${LayoutEnum.ContentLeft} value=${FlowLayoutEnum.ContentLeft}
?selected=${this.instance?.layout === LayoutEnum.ContentLeft} ?selected=${this.instance?.layout === FlowLayoutEnum.ContentLeft}
> >
${LayoutToLabel(LayoutEnum.ContentLeft)} ${LayoutToLabel(FlowLayoutEnum.ContentLeft)}
</option> </option>
<option <option
value=${LayoutEnum.ContentRight} value=${FlowLayoutEnum.ContentRight}
?selected=${this.instance?.layout === LayoutEnum.ContentRight} ?selected=${this.instance?.layout === FlowLayoutEnum.ContentRight}
> >
${LayoutToLabel(LayoutEnum.ContentRight)} ${LayoutToLabel(FlowLayoutEnum.ContentRight)}
</option> </option>
<option <option
value=${LayoutEnum.SidebarLeft} value=${FlowLayoutEnum.SidebarLeft}
?selected=${this.instance?.layout === LayoutEnum.SidebarLeft} ?selected=${this.instance?.layout === FlowLayoutEnum.SidebarLeft}
> >
${LayoutToLabel(LayoutEnum.SidebarLeft)} ${LayoutToLabel(FlowLayoutEnum.SidebarLeft)}
</option> </option>
<option <option
value=${LayoutEnum.SidebarRight} value=${FlowLayoutEnum.SidebarRight}
?selected=${this.instance?.layout === LayoutEnum.SidebarRight} ?selected=${this.instance?.layout === FlowLayoutEnum.SidebarRight}
> >
${LayoutToLabel(LayoutEnum.SidebarRight)} ${LayoutToLabel(FlowLayoutEnum.SidebarRight)}
</option> </option>
</select> </select>
</ak-form-element-horizontal> </ak-form-element-horizontal>

View File

@ -1,6 +1,6 @@
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { Flow, FlowDesignationEnum, LayoutEnum } from "@goauthentik/api"; import { Flow, FlowDesignationEnum, FlowLayoutEnum } from "@goauthentik/api";
export function RenderFlowOption(flow: Flow): string { export function RenderFlowOption(flow: Flow): string {
return `${flow.slug} (${flow.name})`; return `${flow.slug} (${flow.name})`;
@ -27,19 +27,19 @@ export function DesignationToLabel(designation: FlowDesignationEnum): string {
} }
} }
export function LayoutToLabel(layout: LayoutEnum): string { export function LayoutToLabel(layout: FlowLayoutEnum): string {
switch (layout) { switch (layout) {
case LayoutEnum.Stacked: case FlowLayoutEnum.Stacked:
return msg("Stacked"); return msg("Stacked");
case LayoutEnum.ContentLeft: case FlowLayoutEnum.ContentLeft:
return msg("Content left"); return msg("Content left");
case LayoutEnum.ContentRight: case FlowLayoutEnum.ContentRight:
return msg("Content right"); return msg("Content right");
case LayoutEnum.SidebarLeft: case FlowLayoutEnum.SidebarLeft:
return msg("Sidebar left"); return msg("Sidebar left");
case LayoutEnum.SidebarRight: case FlowLayoutEnum.SidebarRight:
return msg("Sidebar right"); return msg("Sidebar right");
case LayoutEnum.UnknownDefaultOpenApi: case FlowLayoutEnum.UnknownDefaultOpenApi:
return msg("Unknown layout"); return msg("Unknown layout");
} }
} }

View File

@ -37,8 +37,8 @@ import {
ContextualFlowInfo, ContextualFlowInfo,
FlowChallengeResponseRequest, FlowChallengeResponseRequest,
FlowErrorChallenge, FlowErrorChallenge,
FlowLayoutEnum,
FlowsApi, FlowsApi,
LayoutEnum,
ResponseError, ResponseError,
ShellChallenge, ShellChallenge,
UiThemeEnum, UiThemeEnum,
@ -451,7 +451,7 @@ export class FlowExecutor extends Interface implements StageHost {
} }
getLayout(): string { getLayout(): string {
const prefilledFlow = globalAK()?.flow?.layout || LayoutEnum.Stacked; const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
if (this.challenge) { if (this.challenge) {
return this.challenge?.flowInfo?.layout || prefilledFlow; return this.challenge?.flowInfo?.layout || prefilledFlow;
} }
@ -461,11 +461,11 @@ export class FlowExecutor extends Interface implements StageHost {
getLayoutClass(): string { getLayoutClass(): string {
const layout = this.getLayout(); const layout = this.getLayout();
switch (layout) { switch (layout) {
case LayoutEnum.ContentLeft: case FlowLayoutEnum.ContentLeft:
return "pf-c-login__container"; return "pf-c-login__container";
case LayoutEnum.ContentRight: case FlowLayoutEnum.ContentRight:
return "pf-c-login__container content-right"; return "pf-c-login__container content-right";
case LayoutEnum.Stacked: case FlowLayoutEnum.Stacked:
default: default:
return "ak-login-container"; return "ak-login-container";
} }