Merge branch 'main' into multi-tenant-django-tenants

This commit is contained in:
Marc 'risson' Schmitt 2023-12-20 09:45:34 +01:00
commit 72fcaa92dd
No known key found for this signature in database
GPG Key ID: 9C3FA22FABF1AA8D
89 changed files with 50054 additions and 69402 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,15 +86,20 @@ 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
- name: run tests
env:
# Test in the main database that we just migrated from the previous stable version
AUTHENTIK_POSTGRESQL__TEST__NAME: authentik
run: |
poetry run make test
test-unittest: test-unittest:
name: test-unittest - PostgreSQL ${{ matrix.psql }} name: test-unittest - PostgreSQL ${{ matrix.psql }}
runs-on: ubuntu-latest runs-on: ubuntu-latest

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

@ -12,6 +12,8 @@ from authentik.blueprints.tests import reconcile_app
from authentik.core.models import Token, TokenIntents, User, UserTypes from authentik.core.models import Token, TokenIntents, User, UserTypes
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
@ -49,8 +51,12 @@ class TestAPIAuth(TestCase):
with self.assertRaises(AuthenticationFailed): with self.assertRaises(AuthenticationFailed):
bearer_auth(f"Bearer {token.key}".encode()) bearer_auth(f"Bearer {token.key}".encode())
def test_managed_outpost(self): @reconcile_app("authentik_outposts")
def test_managed_outpost_fail(self):
"""Test managed outpost""" """Test managed outpost"""
outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first()
outpost.user.delete()
outpost.delete()
with self.assertRaises(AuthenticationFailed): with self.assertRaises(AuthenticationFailed):
bearer_auth(f"Bearer {settings.SECRET_KEY}".encode()) bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())

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 (
@ -58,7 +52,7 @@ 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.brands.models import Brand from authentik.brands.models import Brand
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,
@ -88,7 +82,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:
@ -109,7 +103,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

@ -30,7 +30,6 @@ from authentik.lib.models import (
DomainlessFormattedURLValidator, DomainlessFormattedURLValidator,
SerializerModel, SerializerModel,
) )
from authentik.lib.utils.http import get_client_ip
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
from authentik.root.install_id import get_install_id from authentik.root.install_id import get_install_id
@ -748,12 +747,14 @@ class AuthenticatedSession(ExpiringModel):
@staticmethod @staticmethod
def from_request(request: HttpRequest, user: User) -> Optional["AuthenticatedSession"]: def from_request(request: HttpRequest, user: User) -> Optional["AuthenticatedSession"]:
"""Create a new session from a http request""" """Create a new session from a http request"""
from authentik.root.middleware import ClientIPMiddleware
if not hasattr(request, "session") or not request.session.session_key: if not hasattr(request, "session") or not request.session.session_key:
return None return None
return AuthenticatedSession( return AuthenticatedSession(
session_key=request.session.session_key, session_key=request.session.session_key,
user=user, user=user,
last_ip=get_client_ip(request), last_ip=ClientIPMiddleware.get_client_ip(request),
last_user_agent=request.META.get("HTTP_USER_AGENT", ""), last_user_agent=request.META.get("HTTP_USER_AGENT", ""),
expires=request.session.get_expiry_date(), expires=request.session.get_expiry_date(),
) )

View File

@ -44,28 +44,14 @@
{% block body %} {% block body %}
<div class="pf-c-background-image"> <div class="pf-c-background-image">
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
<filter id="image_overlay">
<feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
<feFuncA type="table" tableValues="0 1"></feFuncA>
</feComponentTransfer>
</filter>
</svg>
</div> </div>
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<div class="pf-c-login"> <div class="pf-c-login stacked">
<div class="ak-login-container"> <div class="ak-login-container">
<header class="pf-c-login__header"> <main class="pf-c-login__main">
<div class="pf-c-brand ak-brand"> <div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo }}" alt="authentik Logo" /> <img src="{{ brand.branding_logo }}" alt="authentik Logo" />
</div> </div>
</header>
{% block main_container %}
<main class="pf-c-login__main">
<header class="pf-c-login__main-header"> <header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl"> <h1 class="pf-c-title pf-m-3xl">
{% block card_title %} {% block card_title %}
@ -77,7 +63,6 @@
{% endblock %} {% endblock %}
</div> </div>
</main> </main>
{% endblock %}
<footer class="pf-c-login__footer"> <footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline"> <ul class="pf-c-list pf-m-inline">
{% for link in footer_links %} {% for link in footer_links %}

View File

@ -38,9 +38,10 @@ from authentik.events.utils import (
) )
from authentik.lib.models import DomainlessURLValidator, SerializerModel from authentik.lib.models import DomainlessURLValidator, SerializerModel
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import get_client_ip, get_http_session from authentik.lib.utils.http import get_http_session
from authentik.lib.utils.time import timedelta_from_string from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -247,7 +248,7 @@ class Event(SerializerModel, ExpiringModel):
self.user = get_user(request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER]) self.user = get_user(request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER])
self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER]) self.user["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
# User 255.255.255.255 as fallback if IP cannot be determined # User 255.255.255.255 as fallback if IP cannot be determined
self.client_ip = get_client_ip(request) self.client_ip = ClientIPMiddleware.get_client_ip(request)
# Apply GeoIP Data, when enabled # Apply GeoIP Data, when enabled
self.with_geoip() self.with_geoip()
# If there's no app set, we get it from the requests too # If there's no app set, we get it from the requests too

View File

@ -1,7 +1,7 @@
# Generated by Django 4.2.6 on 2023-10-28 14:24 # Generated by Django 4.2.6 on 2023-10-28 14:24
from django.apps.registry import Apps from django.apps.registry import Apps
from django.db import migrations from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
@ -31,4 +31,19 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RunPython(set_oobe_flow_authentication), migrations.RunPython(set_oobe_flow_authentication),
migrations.AlterField(
model_name="flow",
name="authentication",
field=models.TextField(
choices=[
("none", "None"),
("require_authenticated", "Require Authenticated"),
("require_unauthenticated", "Require Unauthenticated"),
("require_superuser", "Require Superuser"),
("require_outpost", "Require Outpost"),
],
default="none",
help_text="Required level of authentication and authorization to access a flow.",
),
),
] ]

View File

@ -31,6 +31,7 @@ class FlowAuthenticationRequirement(models.TextChoices):
REQUIRE_AUTHENTICATED = "require_authenticated" REQUIRE_AUTHENTICATED = "require_authenticated"
REQUIRE_UNAUTHENTICATED = "require_unauthenticated" REQUIRE_UNAUTHENTICATED = "require_unauthenticated"
REQUIRE_SUPERUSER = "require_superuser" REQUIRE_SUPERUSER = "require_superuser"
REQUIRE_OUTPOST = "require_outpost"
class NotConfiguredAction(models.TextChoices): class NotConfiguredAction(models.TextChoices):

View File

@ -23,6 +23,7 @@ from authentik.flows.models import (
) )
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger() LOGGER = get_logger()
PLAN_CONTEXT_PENDING_USER = "pending_user" PLAN_CONTEXT_PENDING_USER = "pending_user"
@ -141,6 +142,10 @@ class FlowPlanner:
and not request.user.is_superuser and not request.user.is_superuser
): ):
raise FlowNonApplicableException() raise FlowNonApplicableException()
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
outpost_user = ClientIPMiddleware.get_outpost_user(request)
if not outpost_user:
raise FlowNonApplicableException()
def plan( def plan(
self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None

View File

@ -8,6 +8,7 @@ from django.test import RequestFactory, TestCase
from django.urls import reverse from django.urls import reverse
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import User from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
@ -15,9 +16,12 @@ from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.lib.tests.utils import dummy_get_response from authentik.lib.tests.utils import dummy_get_response
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
from authentik.policies.dummy.models import DummyPolicy from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
from authentik.policies.types import PolicyResult from authentik.policies.types import PolicyResult
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.dummy.models import DummyStage from authentik.stages.dummy.models import DummyStage
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False)) POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
@ -68,6 +72,34 @@ class TestFlowPlanner(TestCase):
planner.allow_empty_flows = True planner.allow_empty_flows = True
planner.plan(request) planner.plan(request)
@reconcile_app("authentik_outposts")
def test_authentication_outpost(self):
"""Test flow authentication (outpost)"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.REQUIRE_OUTPOST
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
request.user = AnonymousUser()
with self.assertRaises(FlowNonApplicableException):
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
planner.plan(request)
outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first()
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
HTTP_X_AUTHENTIK_OUTPOST_TOKEN=outpost.token.key,
HTTP_X_AUTHENTIK_REMOTE_IP="1.2.3.4",
)
request.user = AnonymousUser()
middleware = ClientIPMiddleware(dummy_get_response)
middleware(request)
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
planner.plan(request)
@patch( @patch(
"authentik.policies.engine.PolicyEngine.result", "authentik.policies.engine.PolicyEngine.result",
POLICY_RETURN_FALSE, POLICY_RETURN_FALSE,

View File

@ -8,6 +8,8 @@ postgresql:
password: "env://POSTGRES_PASSWORD" password: "env://POSTGRES_PASSWORD"
use_pgbouncer: false use_pgbouncer: false
use_pgpool: false use_pgpool: false
test:
name: test_authentik
listen: listen:
listen_http: 0.0.0.0:9000 listen_http: 0.0.0.0:9000

View File

@ -3,8 +3,8 @@ from django.test import RequestFactory, TestCase
from authentik.core.models import Token, TokenIntents, UserTypes from authentik.core.models import Token, TokenIntents, UserTypes
from authentik.core.tests.utils import create_test_admin_user from authentik.core.tests.utils import create_test_admin_user
from authentik.lib.utils.http import OUTPOST_REMOTE_IP_HEADER, OUTPOST_TOKEN_HEADER, get_client_ip
from authentik.lib.views import bad_request_message from authentik.lib.views import bad_request_message
from authentik.root.middleware import ClientIPMiddleware
class TestHTTP(TestCase): class TestHTTP(TestCase):
@ -22,12 +22,12 @@ class TestHTTP(TestCase):
def test_normal(self): def test_normal(self):
"""Test normal request""" """Test normal request"""
request = self.factory.get("/") request = self.factory.get("/")
self.assertEqual(get_client_ip(request), "127.0.0.1") self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
def test_forward_for(self): def test_forward_for(self):
"""Test x-forwarded-for request""" """Test x-forwarded-for request"""
request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2") request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2")
self.assertEqual(get_client_ip(request), "127.0.0.2") self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.2")
def test_fake_outpost(self): def test_fake_outpost(self):
"""Test faked IP which is overridden by an outpost""" """Test faked IP which is overridden by an outpost"""
@ -38,28 +38,28 @@ class TestHTTP(TestCase):
request = self.factory.get( request = self.factory.get(
"/", "/",
**{ **{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4", ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
OUTPOST_TOKEN_HEADER: "abc", ClientIPMiddleware.outpost_token_header: "abc",
}, },
) )
self.assertEqual(get_client_ip(request), "127.0.0.1") self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
# Invalid, user doesn't have permissions # Invalid, user doesn't have permissions
request = self.factory.get( request = self.factory.get(
"/", "/",
**{ **{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4", ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key, ClientIPMiddleware.outpost_token_header: token.key,
}, },
) )
self.assertEqual(get_client_ip(request), "127.0.0.1") self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1")
# Valid # Valid
self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
self.user.save() self.user.save()
request = self.factory.get( request = self.factory.get(
"/", "/",
**{ **{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4", ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key, ClientIPMiddleware.outpost_token_header: token.key,
}, },
) )
self.assertEqual(get_client_ip(request), "1.2.3.4") self.assertEqual(ClientIPMiddleware.get_client_ip(request), "1.2.3.4")

View File

@ -1,82 +1,12 @@
"""http helpers""" """http helpers"""
from typing import Any, Optional
from django.http import HttpRequest
from requests.sessions import Session from requests.sessions import Session
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import get_full_version from authentik import get_full_version
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
OUTPOST_TOKEN_HEADER = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
DEFAULT_IP = "255.255.255.255"
LOGGER = get_logger() LOGGER = get_logger()
def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found
No additional validation is done here as requests are expected to only arrive here
via the go proxy, which deals with validating these headers for us"""
headers = (
"HTTP_X_FORWARDED_FOR",
"REMOTE_ADDR",
)
for _header in headers:
if _header in meta:
ips: list[str] = meta.get(_header).split(",")
return ips[0].strip()
return DEFAULT_IP
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
"""Get the actual remote IP when set by an outpost. Only
allowed when the request is authenticated, by an outpost internal service account"""
from authentik.core.models import Token, TokenIntents, UserTypes
if OUTPOST_REMOTE_IP_HEADER not in request.META or OUTPOST_TOKEN_HEADER not in request.META:
return None
fake_ip = request.META[OUTPOST_REMOTE_IP_HEADER]
token = (
Token.filter_not_expired(
key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API
)
.select_related("user")
.first()
)
if not token:
LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip)
return None
user = token.user
if user.type != UserTypes.INTERNAL_SERVICE_ACCOUNT:
LOGGER.warning(
"Remote-IP override: user doesn't have permission",
user=user,
fake_ip=fake_ip,
)
return None
# Update sentry scope to include correct IP
user = Hub.current.scope._user
if not user:
user = {}
user["ip_address"] = fake_ip
Hub.current.scope.set_user(user)
return fake_ip
def get_client_ip(request: Optional[HttpRequest]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found"""
if not request:
return DEFAULT_IP
override = _get_outpost_override_ip(request)
if override:
return override
return _get_client_ip_from_meta(request.META)
def authentik_user_agent() -> str: def authentik_user_agent() -> str:
"""Get a common user agent""" """Get a common user agent"""
return f"authentik@{get_full_version()}" return f"authentik@{get_full_version()}"

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

@ -7,9 +7,9 @@ from structlog.stdlib import get_logger
from authentik.flows.planner import PLAN_CONTEXT_SSO from authentik.flows.planner import PLAN_CONTEXT_SSO
from authentik.lib.expression.evaluator import BaseEvaluator from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.http import get_client_ip
from authentik.policies.exceptions import PolicyException from authentik.policies.exceptions import PolicyException
from authentik.policies.types import PolicyRequest, PolicyResult from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger() LOGGER = get_logger()
if TYPE_CHECKING: if TYPE_CHECKING:
@ -49,7 +49,7 @@ class PolicyEvaluator(BaseEvaluator):
"""Update context based on http request""" """Update context based on http request"""
# update website/docs/expressions/_objects.md # update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md # update website/docs/expressions/_functions.md
self._context["ak_client_ip"] = ip_address(get_client_ip(request)) self._context["ak_client_ip"] = ip_address(ClientIPMiddleware.get_client_ip(request))
self._context["http_request"] = request self._context["http_request"] = request
def handle_error(self, exc: Exception, expression_source: str): def handle_error(self, exc: Exception, expression_source: str):

View File

@ -13,9 +13,9 @@ from structlog import get_logger
from authentik.core.models import ExpiringModel from authentik.core.models import ExpiringModel
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
from authentik.lib.utils.http import get_client_ip
from authentik.policies.models import Policy from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger() LOGGER = get_logger()
CACHE_KEY_PREFIX = "goauthentik.io/policies/reputation/scores/" CACHE_KEY_PREFIX = "goauthentik.io/policies/reputation/scores/"
@ -44,7 +44,7 @@ class ReputationPolicy(Policy):
return "ak-policy-reputation-form" return "ak-policy-reputation-form"
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
remote_ip = get_client_ip(request.http_request) remote_ip = ClientIPMiddleware.get_client_ip(request.http_request)
query = Q() query = Q()
if self.check_ip: if self.check_ip:
query |= Q(ip=remote_ip) query |= Q(ip=remote_ip)

View File

@ -7,9 +7,9 @@ from structlog.stdlib import get_logger
from authentik.core.signals import login_failed from authentik.core.signals import login_failed
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.utils.http import get_client_ip
from authentik.policies.reputation.models import CACHE_KEY_PREFIX from authentik.policies.reputation.models import CACHE_KEY_PREFIX
from authentik.policies.reputation.tasks import save_reputation from authentik.policies.reputation.tasks import save_reputation
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.identification.signals import identification_failed from authentik.stages.identification.signals import identification_failed
LOGGER = get_logger() LOGGER = get_logger()
@ -18,7 +18,7 @@ CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_reputation")
def update_score(request: HttpRequest, identifier: str, amount: int): def update_score(request: HttpRequest, identifier: str, amount: int):
"""Update score for IP and User""" """Update score for IP and User"""
remote_ip = get_client_ip(request) remote_ip = ClientIPMiddleware.get_client_ip(request)
try: try:
# We only update the cache here, as its faster than writing to the DB # We only update the cache here, as its faster than writing to the DB

View File

@ -9,7 +9,6 @@ from django.http import HttpRequest
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.geo import GEOIP_READER from authentik.events.geo import GEOIP_READER
from authentik.lib.utils.http import get_client_ip
if TYPE_CHECKING: if TYPE_CHECKING:
from authentik.core.models import User from authentik.core.models import User
@ -38,10 +37,12 @@ class PolicyRequest:
def set_http_request(self, request: HttpRequest): # pragma: no cover def set_http_request(self, request: HttpRequest): # pragma: no cover
"""Load data from HTTP request, including geoip when enabled""" """Load data from HTTP request, including geoip when enabled"""
from authentik.root.middleware import ClientIPMiddleware
self.http_request = request self.http_request = request
if not GEOIP_READER.enabled: if not GEOIP_READER.enabled:
return return
client_ip = get_client_ip(request) client_ip = ClientIPMiddleware.get_client_ip(request)
if not client_ip: if not client_ip:
return return
self.context["geoip"] = GEOIP_READER.city(client_ip) self.context["geoip"] = GEOIP_READER.city(client_ip)

View File

@ -1,2 +1,3 @@
"""SCIM constants""" """SCIM constants"""
PAGE_SIZE = 100 PAGE_SIZE = 100
PAGE_TIMEOUT = 60 * 60 * 0.5 # Half an hour

View File

@ -12,7 +12,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Group, User from authentik.core.models import Group, User
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.utils.reflection import path_to_class from authentik.lib.utils.reflection import path_to_class
from authentik.providers.scim.clients import PAGE_SIZE from authentik.providers.scim.clients import PAGE_SIZE, PAGE_TIMEOUT
from authentik.providers.scim.clients.base import SCIMClient from authentik.providers.scim.clients.base import SCIMClient
from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync from authentik.providers.scim.clients.exceptions import SCIMRequestException, StopSync
from authentik.providers.scim.clients.group import SCIMGroupClient from authentik.providers.scim.clients.group import SCIMGroupClient
@ -53,6 +53,9 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
LOGGER.debug("Starting SCIM sync") LOGGER.debug("Starting SCIM sync")
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE) users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE) groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
self.soft_time_limit = self.time_limit = (
users_paginator.count + groups_paginator.count
) * PAGE_TIMEOUT
with allow_join_result(): with allow_join_result():
try: try:
for page in users_paginator.page_range: for page in users_paginator.page_range:
@ -69,7 +72,10 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
self.set_status(result) self.set_status(result)
@CELERY_APP.task() @CELERY_APP.task(
soft_time_limit=PAGE_TIMEOUT,
task_time_limit=PAGE_TIMEOUT,
)
def scim_sync_users(page: int, provider_pk: int): def scim_sync_users(page: int, provider_pk: int):
"""Sync single or multiple users to SCIM""" """Sync single or multiple users to SCIM"""
messages = [] messages = []

View File

@ -93,7 +93,7 @@ class SCIMMembershipTests(TestCase):
"emails": [], "emails": [],
"active": True, "active": True,
"externalId": user.uid, "externalId": user.uid,
"name": {"familyName": "", "formatted": "", "givenName": ""}, "name": {"familyName": " ", "formatted": " ", "givenName": ""},
"displayName": "", "displayName": "",
"userName": user.username, "userName": user.username,
}, },
@ -184,7 +184,7 @@ class SCIMMembershipTests(TestCase):
"displayName": "", "displayName": "",
"emails": [], "emails": [],
"externalId": user.uid, "externalId": user.uid,
"name": {"familyName": "", "formatted": "", "givenName": ""}, "name": {"familyName": " ", "formatted": " ", "givenName": ""},
"userName": user.username, "userName": user.username,
}, },
) )

View File

@ -61,7 +61,7 @@ class SCIMUserTests(TestCase):
uid = generate_id() uid = generate_id()
user = User.objects.create( user = User.objects.create(
username=uid, username=uid,
name=uid, name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io", email=f"{uid}@goauthentik.io",
) )
self.assertEqual(mock.call_count, 2) self.assertEqual(mock.call_count, 2)
@ -81,11 +81,11 @@ class SCIMUserTests(TestCase):
], ],
"externalId": user.uid, "externalId": user.uid,
"name": { "name": {
"familyName": "", "familyName": uid,
"formatted": uid, "formatted": f"{uid} {uid}",
"givenName": uid, "givenName": uid,
}, },
"displayName": uid, "displayName": f"{uid} {uid}",
"userName": uid, "userName": uid,
}, },
) )
@ -114,7 +114,7 @@ class SCIMUserTests(TestCase):
uid = generate_id() uid = generate_id()
user = User.objects.create( user = User.objects.create(
username=uid, username=uid,
name=uid, name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io", email=f"{uid}@goauthentik.io",
) )
self.assertEqual(mock.call_count, 2) self.assertEqual(mock.call_count, 2)
@ -135,11 +135,11 @@ class SCIMUserTests(TestCase):
"value": f"{uid}@goauthentik.io", "value": f"{uid}@goauthentik.io",
} }
], ],
"displayName": uid, "displayName": f"{uid} {uid}",
"externalId": user.uid, "externalId": user.uid,
"name": { "name": {
"familyName": "", "familyName": uid,
"formatted": uid, "formatted": f"{uid} {uid}",
"givenName": uid, "givenName": uid,
}, },
"userName": uid, "userName": uid,
@ -170,7 +170,7 @@ class SCIMUserTests(TestCase):
uid = generate_id() uid = generate_id()
user = User.objects.create( user = User.objects.create(
username=uid, username=uid,
name=uid, name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io", email=f"{uid}@goauthentik.io",
) )
self.assertEqual(mock.call_count, 2) self.assertEqual(mock.call_count, 2)
@ -190,11 +190,11 @@ class SCIMUserTests(TestCase):
], ],
"externalId": user.uid, "externalId": user.uid,
"name": { "name": {
"familyName": "", "familyName": uid,
"formatted": uid, "formatted": f"{uid} {uid}",
"givenName": uid, "givenName": uid,
}, },
"displayName": uid, "displayName": f"{uid} {uid}",
"userName": uid, "userName": uid,
}, },
) )
@ -234,7 +234,7 @@ class SCIMUserTests(TestCase):
) )
user = User.objects.create( user = User.objects.create(
username=uid, username=uid,
name=uid, name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io", email=f"{uid}@goauthentik.io",
) )
@ -258,11 +258,11 @@ class SCIMUserTests(TestCase):
], ],
"externalId": user.uid, "externalId": user.uid,
"name": { "name": {
"familyName": "", "familyName": uid,
"formatted": uid, "formatted": f"{uid} {uid}",
"givenName": uid, "givenName": uid,
}, },
"displayName": uid, "displayName": f"{uid} {uid}",
"userName": uid, "userName": uid,
}, },
) )

View File

@ -2,7 +2,7 @@
from hashlib import sha512 from hashlib import sha512
from time import time from time import time
from timeit import default_timer from timeit import default_timer
from typing import Callable from typing import Any, Callable, Optional
from django.conf import settings from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError from django.contrib.sessions.backends.base import UpdateError
@ -15,9 +15,10 @@ from django.middleware.csrf import CsrfViewMiddleware as UpstreamCsrfViewMiddlew
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from django.utils.http import http_date from django.utils.http import http_date
from jwt import PyJWTError, decode, encode from jwt import PyJWTError, decode, encode
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.lib.utils.http import get_client_ip from authentik.core.models import Token, TokenIntents, User, UserTypes
LOGGER = get_logger("authentik.asgi") LOGGER = get_logger("authentik.asgi")
ACR_AUTHENTIK_SESSION = "goauthentik.io/core/default" ACR_AUTHENTIK_SESSION = "goauthentik.io/core/default"
@ -156,6 +157,111 @@ class CsrfViewMiddleware(UpstreamCsrfViewMiddleware):
patch_vary_headers(response, ("Cookie",)) patch_vary_headers(response, ("Cookie",))
class ClientIPMiddleware:
"""Set a "known-good" client IP on the request, by default based off of x-forwarded-for
which is set by the go proxy, but also allowing the remote IP to be overridden by an outpost
for protocols like LDAP"""
get_response: Callable[[HttpRequest], HttpResponse]
outpost_remote_ip_header = "HTTP_X_AUTHENTIK_REMOTE_IP"
outpost_token_header = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
default_ip = "255.255.255.255"
request_attr_client_ip = "client_ip"
request_attr_outpost_user = "outpost_user"
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response
def _get_client_ip_from_meta(self, meta: dict[str, Any]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found
No additional validation is done here as requests are expected to only arrive here
via the go proxy, which deals with validating these headers for us"""
headers = (
"HTTP_X_FORWARDED_FOR",
"REMOTE_ADDR",
)
for _header in headers:
if _header in meta:
ips: list[str] = meta.get(_header).split(",")
return ips[0].strip()
return self.default_ip
# FIXME: this should probably not be in `root` but rather in a middleware in `outposts`
# but for now it's fine
def _get_outpost_override_ip(self, request: HttpRequest) -> Optional[str]:
"""Get the actual remote IP when set by an outpost. Only
allowed when the request is authenticated, by an outpost internal service account"""
if (
self.outpost_remote_ip_header not in request.META
or self.outpost_token_header not in request.META
):
return None
delegated_ip = request.META[self.outpost_remote_ip_header]
token = (
Token.filter_not_expired(
key=request.META.get(self.outpost_token_header), intent=TokenIntents.INTENT_API
)
.select_related("user")
.first()
)
if not token:
LOGGER.warning("Attempted remote-ip override without token", delegated_ip=delegated_ip)
return None
user: User = token.user
if user.type != UserTypes.INTERNAL_SERVICE_ACCOUNT:
LOGGER.warning(
"Remote-IP override: user doesn't have permission",
user=user,
delegated_ip=delegated_ip,
)
return None
# Update sentry scope to include correct IP
user = Hub.current.scope._user
if not user:
user = {}
user["ip_address"] = delegated_ip
Hub.current.scope.set_user(user)
# Set the outpost service account on the request
setattr(request, self.request_attr_outpost_user, user)
return delegated_ip
def _get_client_ip(self, request: Optional[HttpRequest]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found"""
if not request:
return self.default_ip
override = self._get_outpost_override_ip(request)
if override:
return override
return self._get_client_ip_from_meta(request.META)
@staticmethod
def get_outpost_user(request: HttpRequest) -> Optional[User]:
"""Get outpost user that authenticated this request"""
return getattr(request, ClientIPMiddleware.request_attr_outpost_user, None)
@staticmethod
def get_client_ip(request: HttpRequest) -> str:
"""Get correct client IP, including any overrides from outposts that
have the permission to do so"""
if request and not hasattr(request, ClientIPMiddleware.request_attr_client_ip):
ClientIPMiddleware(lambda request: request).set_ip(request)
return getattr(
request, ClientIPMiddleware.request_attr_client_ip, ClientIPMiddleware.default_ip
)
def set_ip(self, request: HttpRequest):
"""Set the IP"""
setattr(request, self.request_attr_client_ip, self._get_client_ip(request))
def __call__(self, request: HttpRequest) -> HttpResponse:
self.set_ip(request)
return self.get_response(request)
class ChannelsLoggingMiddleware: class ChannelsLoggingMiddleware:
"""Logging middleware for channels""" """Logging middleware for channels"""
@ -201,7 +307,7 @@ class LoggingMiddleware:
"""Log request""" """Log request"""
LOGGER.info( LOGGER.info(
request.get_full_path(), request.get_full_path(),
remote=get_client_ip(request), remote=ClientIPMiddleware.get_client_ip(request),
method=request.method, method=request.method,
scheme=request.scheme, scheme=request.scheme,
status=status_code, status=status_code,

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

@ -151,6 +151,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",
@ -219,7 +220,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"
@ -232,7 +233,7 @@ MIDDLEWARE = [
"django_tenants.middleware.default.DefaultTenantMiddleware", "django_tenants.middleware.default.DefaultTenantMiddleware",
"authentik.root.middleware.LoggingMiddleware", "authentik.root.middleware.LoggingMiddleware",
"django_prometheus.middleware.PrometheusBeforeMiddleware", "django_prometheus.middleware.PrometheusBeforeMiddleware",
"authentik.brands.middleware.BrandMiddleware", "authentik.root.middleware.ClientIPMiddleware",
"authentik.root.middleware.SessionMiddleware", "authentik.root.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"authentik.core.middleware.RequestIDMiddleware", "authentik.core.middleware.RequestIDMiddleware",
@ -295,6 +296,9 @@ DATABASES = {
"SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"), "SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
"SSLCERT": CONFIG.get("postgresql.sslcert"), "SSLCERT": CONFIG.get("postgresql.sslcert"),
"SSLKEY": CONFIG.get("postgresql.sslkey"), "SSLKEY": CONFIG.get("postgresql.sslkey"),
"TEST": {
"NAME": CONFIG.get("postgresql.test.name"),
},
} }
} }

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,13 +16,13 @@ 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
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
from authentik.lib.utils.http import get_client_ip from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.authenticator import match_token from authentik.stages.authenticator import match_token
from authentik.stages.authenticator.models import Device from authentik.stages.authenticator.models import Device
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
@ -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(
@ -197,7 +197,7 @@ def validate_challenge_duo(device_pk: int, stage_view: StageView, user: User) ->
response = stage.auth_client().auth( response = stage.auth_client().auth(
"auto", "auto",
user_id=device.duo_user_id, user_id=device.duo_user_id,
ipaddr=get_client_ip(stage_view.request), ipaddr=ClientIPMiddleware.get_client_ip(stage_view.request),
type=__( type=__(
"%(brand_name)s Login request" "%(brand_name)s Login request"
% { % {

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

@ -12,7 +12,8 @@ from authentik.flows.challenge import (
WithUserInfoChallenge, WithUserInfoChallenge,
) )
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.lib.utils.http import get_client_ip, get_http_session from authentik.lib.utils.http import get_http_session
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.captcha.models import CaptchaStage from authentik.stages.captcha.models import CaptchaStage
@ -42,7 +43,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
data={ data={
"secret": stage.private_key, "secret": stage.private_key,
"response": token, "response": token,
"remoteip": get_client_ip(self.stage.request), "remoteip": ClientIPMiddleware.get_client_ip(self.stage.request),
}, },
) )
response.raise_for_status() response.raise_for_status()

View File

@ -26,8 +26,8 @@ from authentik.flows.models import FlowDesignation
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET
from authentik.lib.utils.http import get_client_ip
from authentik.lib.utils.urls import reverse_with_qs from authentik.lib.utils.urls import reverse_with_qs
from authentik.root.middleware import ClientIPMiddleware
from authentik.sources.oauth.types.apple import AppleLoginChallenge from authentik.sources.oauth.types.apple import AppleLoginChallenge
from authentik.sources.plex.models import PlexAuthenticationChallenge from authentik.sources.plex.models import PlexAuthenticationChallenge
from authentik.stages.identification.models import IdentificationStage from authentik.stages.identification.models import IdentificationStage
@ -103,7 +103,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
self.stage.logger.info( self.stage.logger.info(
"invalid_login", "invalid_login",
identifier=uid_field, identifier=uid_field,
client_ip=get_client_ip(self.stage.request), client_ip=ClientIPMiddleware.get_client_ip(self.stage.request),
action="invalid_identifier", action="invalid_identifier",
context={ context={
"stage": sanitize_item(self.stage), "stage": sanitize_item(self.stage),

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

@ -16,8 +16,8 @@ from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.lib.utils.http import DEFAULT_IP
from authentik.lib.utils.time import timedelta_from_string from authentik.lib.utils.time import timedelta_from_string
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.user_login.models import UserLoginStage from authentik.stages.user_login.models import UserLoginStage
@ -76,7 +76,7 @@ class TestUserLoginStage(FlowTestCase):
other_session = AuthenticatedSession.objects.create( other_session = AuthenticatedSession.objects.create(
user=self.user, user=self.user,
session_key=key, session_key=key,
last_ip=DEFAULT_IP, last_ip=ClientIPMiddleware.default_ip,
) )
cache.set(f"{KEY_PREFIX}{other_session.session_key}", "foo") cache.set(f"{KEY_PREFIX}{other_session.session_key}", "foo")

View File

@ -3282,7 +3282,8 @@
"none", "none",
"require_authenticated", "require_authenticated",
"require_unauthenticated", "require_unauthenticated",
"require_superuser" "require_superuser",
"require_outpost"
], ],
"title": "Authentication", "title": "Authentication",
"description": "Required level of authentication and authorization to access a flow." "description": "Required level of authentication and authorization to access a flow."
@ -5415,22 +5416,8 @@
"title": "Icon" "title": "Icon"
}, },
"provider_type": { "provider_type": {
"type": "string", "type": [],
"enum": [ "enum": [],
"apple",
"azuread",
"discord",
"facebook",
"github",
"google",
"mailcow",
"openidconnect",
"okta",
"patreon",
"reddit",
"twitch",
"twitter"
],
"title": "Provider type" "title": "Provider type"
}, },
"request_token_url": { "request_token_url": {

View File

@ -11,13 +11,15 @@ entries:
name: "authentik default SCIM Mapping: User" name: "authentik default SCIM Mapping: User"
expression: | expression: |
# Some implementations require givenName and familyName to be set # Some implementations require givenName and familyName to be set
givenName, familyName = request.user.name, "" givenName, familyName = request.user.name, " "
formatted = request.user.name + " "
# This default sets givenName to the name before the first space # This default sets givenName to the name before the first space
# and the remainder as family name # and the remainder as family name
# if the user's name has no space the givenName is the entire name # if the user's name has no space the givenName is the entire name
# (this might cause issues with some SCIM implementations) # (this might cause issues with some SCIM implementations)
if " " in request.user.name: if " " in request.user.name:
givenName, _, familyName = request.user.name.partition(" ") givenName, _, familyName = request.user.name.partition(" ")
formatted = request.user.name
# photos supports URLs to images, however authentik might return data URIs # photos supports URLs to images, however authentik might return data URIs
avatar = request.user.avatar avatar = request.user.avatar
@ -39,7 +41,7 @@ entries:
return { return {
"userName": request.user.username, "userName": request.user.username,
"name": { "name": {
"formatted": request.user.name, "formatted": formatted,
"givenName": givenName, "givenName": givenName,
"familyName": familyName, "familyName": familyName,
}, },

View File

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

8
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-openapi/runtime v0.26.2 github.com/go-openapi/runtime v0.26.2
github.com/go-openapi/strfmt v0.21.9 github.com/go-openapi/strfmt v0.21.10
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.5.0 github.com/google/uuid v1.5.0
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
@ -27,7 +27,7 @@ require (
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
goauthentik.io/api/v3 v3.2023104.2 goauthentik.io/api/v3 v3.2023104.3
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.15.0 golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.5.0
@ -49,7 +49,7 @@ require (
github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect github.com/go-openapi/errors v0.21.0 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/loads v0.21.2 // indirect
@ -73,7 +73,7 @@ require (
go.opentelemetry.io/otel v1.17.0 // indirect go.opentelemetry.io/otel v1.17.0 // indirect
go.opentelemetry.io/otel/metric v1.17.0 // indirect go.opentelemetry.io/otel/metric v1.17.0 // indirect
go.opentelemetry.io/otel/trace v1.17.0 // indirect go.opentelemetry.io/otel/trace v1.17.0 // indirect
golang.org/x/crypto v0.16.0 // indirect golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect

16
go.sum
View File

@ -98,8 +98,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY=
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
@ -116,8 +116,8 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6
github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY= github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY=
github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-openapi/strfmt v0.21.9 h1:LnEGOO9qyEC1v22Bzr323M98G13paIUGPU7yeJtG9Xs= github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk=
github.com/go-openapi/strfmt v0.21.9/go.mod h1:0k3v301mglEaZRJdDDGSlN6Npq4VMVU69DE0LUyf7uA= github.com/go-openapi/strfmt v0.21.10/go.mod h1:vNDMwbilnl7xKiO/Ve/8H8Bb2JIInBnH+lqiw6QWgis=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
@ -309,8 +309,8 @@ go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYO
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2023104.2 h1:TV3SdaPGhjVE7If0l1kt+H23xwgEabWUFgX4ijkkeSc= goauthentik.io/api/v3 v3.2023104.3 h1:MzwdB21Q+G+wACEZiX0T1iVV4l7PjopjaVv6muqJE1M=
goauthentik.io/api/v3 v3.2023104.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= goauthentik.io/api/v3 v3.2023104.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -320,8 +320,8 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=

View File

@ -25,13 +25,13 @@ type Config struct {
} }
type RedisConfig struct { type RedisConfig struct {
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"` Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"` Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
DB int `yaml:"db" env:"AUTHENTIK_REDIS__DB"` DB int `yaml:"db" env:"AUTHENTIK_REDIS__DB"`
Username string `yaml:"username" env:"AUTHENTIK_REDIS__USERNAME"` Username string `yaml:"username" env:"AUTHENTIK_REDIS__USERNAME"`
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"` Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"` TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"` TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
} }
type ListenConfig struct { type ListenConfig struct {

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

@ -34,26 +34,26 @@ func (ds *DirectSearcher) SearchBase(req *search.Request) (ldap.ServerSearchResu
Values: []string{"3"}, Values: []string{"3"},
}, },
{ {
Name: "supportedCapabilities", Name: "supportedCapabilities",
Values: []string{ Values: []string{
"1.2.840.113556.1.4.800", //LDAP_CAP_ACTIVE_DIRECTORY_OID "1.2.840.113556.1.4.800", // LDAP_CAP_ACTIVE_DIRECTORY_OID
"1.2.840.113556.1.4.1791", //LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID "1.2.840.113556.1.4.1791", // LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID
"1.2.840.113556.1.4.1670", //LDAP_CAP_ACTIVE_DIRECTORY_V51_OID "1.2.840.113556.1.4.1670", // LDAP_CAP_ACTIVE_DIRECTORY_V51_OID
"1.2.840.113556.1.4.1880", //LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST_OID "1.2.840.113556.1.4.1880", // LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST_OID
"1.2.840.113556.1.4.1851", //LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID "1.2.840.113556.1.4.1851", // LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID
"1.2.840.113556.1.4.1920", //LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID "1.2.840.113556.1.4.1920", // LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID
"1.2.840.113556.1.4.1935", //LDAP_CAP_ACTIVE_DIRECTORY_V60_OID "1.2.840.113556.1.4.1935", // LDAP_CAP_ACTIVE_DIRECTORY_V60_OID
"1.2.840.113556.1.4.2080", //LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID "1.2.840.113556.1.4.2080", // LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID
"1.2.840.113556.1.4.2237", //LDAP_CAP_ACTIVE_DIRECTORY_W8_OID "1.2.840.113556.1.4.2237", // LDAP_CAP_ACTIVE_DIRECTORY_W8_OID
}, },
}, },
{ {
Name: "supportedControl", Name: "supportedControl",
Values: []string{ Values: []string{
"2.16.840.1.113730.3.4.9", //VLV Request LDAPv3 Control "2.16.840.1.113730.3.4.9", // VLV Request LDAPv3 Control
"2.16.840.1.113730.3.4.10", //VLV Response LDAPv3 Control "2.16.840.1.113730.3.4.10", // VLV Response LDAPv3 Control
"1.2.840.113556.1.4.474", //Sort result "1.2.840.113556.1.4.474", // Sort result
"1.2.840.113556.1.4.319", //Paged Result Control "1.2.840.113556.1.4.319", // Paged Result Control
}, },
}, },
{ {

View File

@ -143,7 +143,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
// as a member. // as a member.
for _, u := range g.UsersObj { for _, u := range g.UsersObj {
if flag.UserPk == u.Pk { if flag.UserPk == u.Pk {
//TODO: Is there a better way to clone this object? // TODO: Is there a better way to clone this object?
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{}) fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{})
fg.SetUsers([]int32{flag.UserPk}) fg.SetUsers([]int32{flag.UserPk})
if g.Parent.IsSet() { if g.Parent.IsSet() {

View File

@ -220,7 +220,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A
for _, regex := range strings.Split(*p.SkipPathRegex, "\n") { for _, regex := range strings.Split(*p.SkipPathRegex, "\n") {
re, err := regexp.Compile(regex) re, err := regexp.Compile(regex)
if err != nil { if err != nil {
//TODO: maybe create event for this? // TODO: maybe create event for this?
a.log.WithError(err).Warning("failed to compile SkipPathRegex") a.log.WithError(err).Warning("failed to compile SkipPathRegex")
continue continue
} else { } else {

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

@ -26,26 +26,13 @@
</head> </head>
<body> <body>
<div class="pf-c-background-image"> <div class="pf-c-background-image">
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
<filter id="image_overlay">
<feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
<feFuncA type="table" tableValues="0 1"></feFuncA>
</feComponentTransfer>
</filter>
</svg>
</div> </div>
<div class="pf-c-login"> <div class="pf-c-login stacked">
<div class="ak-login-container"> <div class="ak-login-container">
<header class="pf-c-login__header"> <main class="pf-c-login__main">
<div class="pf-c-brand ak-brand"> <div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" /> <img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" />
</div> </div>
</header>
<main class="pf-c-login__main">
<header class="pf-c-login__main-header"> <header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl"> <h1 class="pf-c-title pf-m-3xl">
{{ .Title }} {{ .Title }}

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

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-12-18 09:56+0000\n" "POT-Creation-Date: 2023-12-20 08:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -100,170 +100,170 @@ msgstr ""
msgid "Create a SAML Provider by importing its Metadata." msgid "Create a SAML Provider by importing its Metadata."
msgstr "" msgstr ""
#: authentik/core/api/users.py:155 #: authentik/core/api/users.py:149
msgid "No leading or trailing slashes allowed." msgid "No leading or trailing slashes allowed."
msgstr "" msgstr ""
#: authentik/core/api/users.py:158 #: authentik/core/api/users.py:152
msgid "No empty segments in user path allowed." msgid "No empty segments in user path allowed."
msgstr "" msgstr ""
#: authentik/core/models.py:86 #: authentik/core/models.py:85
msgid "name" msgid "name"
msgstr "" msgstr ""
#: authentik/core/models.py:88 #: authentik/core/models.py:87
msgid "Users added to this group will be superusers." msgid "Users added to this group will be superusers."
msgstr "" msgstr ""
#: authentik/core/models.py:162 #: authentik/core/models.py:161
msgid "Group" msgid "Group"
msgstr "" msgstr ""
#: authentik/core/models.py:163 #: authentik/core/models.py:162
msgid "Groups" msgid "Groups"
msgstr "" msgstr ""
#: authentik/core/models.py:178 #: authentik/core/models.py:177
msgid "User's display name." msgid "User's display name."
msgstr "" msgstr ""
#: authentik/core/models.py:274 authentik/providers/oauth2/models.py:295 #: authentik/core/models.py:273 authentik/providers/oauth2/models.py:295
msgid "User" msgid "User"
msgstr "" msgstr ""
#: authentik/core/models.py:275 #: authentik/core/models.py:274
msgid "Users" msgid "Users"
msgstr "" msgstr ""
#: authentik/core/models.py:277 #: authentik/core/models.py:276
#: authentik/stages/email/templates/email/password_reset.html:28 #: authentik/stages/email/templates/email/password_reset.html:28
msgid "Reset Password" msgid "Reset Password"
msgstr "" msgstr ""
#: authentik/core/models.py:278 #: authentik/core/models.py:277
msgid "Can impersonate other users" msgid "Can impersonate other users"
msgstr "" msgstr ""
#: authentik/core/models.py:279 authentik/rbac/models.py:54 #: authentik/core/models.py:278 authentik/rbac/models.py:54
msgid "Can assign permissions to users" msgid "Can assign permissions to users"
msgstr "" msgstr ""
#: authentik/core/models.py:280 authentik/rbac/models.py:55 #: authentik/core/models.py:279 authentik/rbac/models.py:55
msgid "Can unassign permissions from users" msgid "Can unassign permissions from users"
msgstr "" msgstr ""
#: authentik/core/models.py:294 #: authentik/core/models.py:293
msgid "" msgid ""
"Flow used for authentication when the associated application is accessed by " "Flow used for authentication when the associated application is accessed by "
"an un-authenticated user." "an un-authenticated user."
msgstr "" msgstr ""
#: authentik/core/models.py:304 #: authentik/core/models.py:303
msgid "Flow used when authorizing this provider." msgid "Flow used when authorizing this provider."
msgstr "" msgstr ""
#: authentik/core/models.py:316 #: authentik/core/models.py:315
msgid "" msgid ""
"Accessed from applications; optional backchannel providers for protocols " "Accessed from applications; optional backchannel providers for protocols "
"like LDAP and SCIM." "like LDAP and SCIM."
msgstr "" msgstr ""
#: authentik/core/models.py:371 #: authentik/core/models.py:370
msgid "Application's display Name." msgid "Application's display Name."
msgstr "" msgstr ""
#: authentik/core/models.py:372 #: authentik/core/models.py:371
msgid "Internal application name, used in URLs." msgid "Internal application name, used in URLs."
msgstr "" msgstr ""
#: authentik/core/models.py:384 #: authentik/core/models.py:383
msgid "Open launch URL in a new browser tab or window." msgid "Open launch URL in a new browser tab or window."
msgstr "" msgstr ""
#: authentik/core/models.py:448 #: authentik/core/models.py:447
msgid "Application" msgid "Application"
msgstr "" msgstr ""
#: authentik/core/models.py:449 #: authentik/core/models.py:448
msgid "Applications" msgid "Applications"
msgstr "" msgstr ""
#: authentik/core/models.py:455 #: authentik/core/models.py:454
msgid "Use the source-specific identifier" msgid "Use the source-specific identifier"
msgstr "" msgstr ""
#: authentik/core/models.py:457 #: authentik/core/models.py:456
msgid "" msgid ""
"Link to a user with identical email address. Can have security implications " "Link to a user with identical email address. Can have security implications "
"when a source doesn't validate email addresses." "when a source doesn't validate email addresses."
msgstr "" msgstr ""
#: authentik/core/models.py:461 #: authentik/core/models.py:460
msgid "" msgid ""
"Use the user's email address, but deny enrollment when the email address " "Use the user's email address, but deny enrollment when the email address "
"already exists." "already exists."
msgstr "" msgstr ""
#: authentik/core/models.py:464 #: authentik/core/models.py:463
msgid "" msgid ""
"Link to a user with identical username. Can have security implications when " "Link to a user with identical username. Can have security implications when "
"a username is used with another source." "a username is used with another source."
msgstr "" msgstr ""
#: authentik/core/models.py:468 #: authentik/core/models.py:467
msgid "" msgid ""
"Use the user's username, but deny enrollment when the username already " "Use the user's username, but deny enrollment when the username already "
"exists." "exists."
msgstr "" msgstr ""
#: authentik/core/models.py:475 #: authentik/core/models.py:474
msgid "Source's display Name." msgid "Source's display Name."
msgstr "" msgstr ""
#: authentik/core/models.py:476 #: authentik/core/models.py:475
msgid "Internal source name, used in URLs." msgid "Internal source name, used in URLs."
msgstr "" msgstr ""
#: authentik/core/models.py:495 #: authentik/core/models.py:494
msgid "Flow to use when authenticating existing users." msgid "Flow to use when authenticating existing users."
msgstr "" msgstr ""
#: authentik/core/models.py:504 #: authentik/core/models.py:503
msgid "Flow to use when enrolling new users." msgid "Flow to use when enrolling new users."
msgstr "" msgstr ""
#: authentik/core/models.py:512 #: authentik/core/models.py:511
msgid "" msgid ""
"How the source determines if an existing user should be authenticated or a " "How the source determines if an existing user should be authenticated or a "
"new user enrolled." "new user enrolled."
msgstr "" msgstr ""
#: authentik/core/models.py:684 #: authentik/core/models.py:683
msgid "Token" msgid "Token"
msgstr "" msgstr ""
#: authentik/core/models.py:685 #: authentik/core/models.py:684
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
#: authentik/core/models.py:690 #: authentik/core/models.py:689
msgid "View token's key" msgid "View token's key"
msgstr "" msgstr ""
#: authentik/core/models.py:726 #: authentik/core/models.py:725
msgid "Property Mapping" msgid "Property Mapping"
msgstr "" msgstr ""
#: authentik/core/models.py:727 #: authentik/core/models.py:726
msgid "Property Mappings" msgid "Property Mappings"
msgstr "" msgstr ""
#: authentik/core/models.py:762 #: authentik/core/models.py:763
msgid "Authenticated Session" msgid "Authenticated Session"
msgstr "" msgstr ""
#: authentik/core/models.py:763 #: authentik/core/models.py:764
msgid "Authenticated Sessions" msgid "Authenticated Sessions"
msgstr "" msgstr ""
@ -338,7 +338,7 @@ msgstr ""
msgid "Go home" msgid "Go home"
msgstr "" msgstr ""
#: authentik/core/templates/login/base_full.html:90 #: authentik/core/templates/login/base_full.html:75
msgid "Powered by authentik" msgid "Powered by authentik"
msgstr "" msgstr ""
@ -388,105 +388,105 @@ msgstr ""
msgid "License Usage Records" msgid "License Usage Records"
msgstr "" msgstr ""
#: authentik/events/models.py:294 #: authentik/events/models.py:295
msgid "Event" msgid "Event"
msgstr "" msgstr ""
#: authentik/events/models.py:295 #: authentik/events/models.py:296
msgid "Events" msgid "Events"
msgstr "" msgstr ""
#: authentik/events/models.py:301 #: authentik/events/models.py:302
msgid "authentik inbuilt notifications" msgid "authentik inbuilt notifications"
msgstr "" msgstr ""
#: authentik/events/models.py:302 #: authentik/events/models.py:303
msgid "Generic Webhook" msgid "Generic Webhook"
msgstr "" msgstr ""
#: authentik/events/models.py:303 #: authentik/events/models.py:304
msgid "Slack Webhook (Slack/Discord)" msgid "Slack Webhook (Slack/Discord)"
msgstr "" msgstr ""
#: authentik/events/models.py:304 #: authentik/events/models.py:305
msgid "Email" msgid "Email"
msgstr "" msgstr ""
#: authentik/events/models.py:322 #: authentik/events/models.py:323
msgid "" msgid ""
"Only send notification once, for example when sending a webhook into a chat " "Only send notification once, for example when sending a webhook into a chat "
"channel." "channel."
msgstr "" msgstr ""
#: authentik/events/models.py:387 #: authentik/events/models.py:388
msgid "Severity" msgid "Severity"
msgstr "" msgstr ""
#: authentik/events/models.py:392 #: authentik/events/models.py:393
msgid "Dispatched for user" msgid "Dispatched for user"
msgstr "" msgstr ""
#: authentik/events/models.py:401 #: authentik/events/models.py:402
msgid "Event user" msgid "Event user"
msgstr "" msgstr ""
#: authentik/events/models.py:495 #: authentik/events/models.py:496
msgid "Notification Transport" msgid "Notification Transport"
msgstr "" msgstr ""
#: authentik/events/models.py:496 #: authentik/events/models.py:497
msgid "Notification Transports" msgid "Notification Transports"
msgstr "" msgstr ""
#: authentik/events/models.py:502 #: authentik/events/models.py:503
msgid "Notice" msgid "Notice"
msgstr "" msgstr ""
#: authentik/events/models.py:503 #: authentik/events/models.py:504
msgid "Warning" msgid "Warning"
msgstr "" msgstr ""
#: authentik/events/models.py:504 #: authentik/events/models.py:505
msgid "Alert" msgid "Alert"
msgstr "" msgstr ""
#: authentik/events/models.py:529 #: authentik/events/models.py:530
msgid "Notification" msgid "Notification"
msgstr "" msgstr ""
#: authentik/events/models.py:530 #: authentik/events/models.py:531
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
#: authentik/events/models.py:540 #: authentik/events/models.py:541
msgid "" msgid ""
"Select which transports should be used to notify the user. If none are " "Select which transports should be used to notify the user. If none are "
"selected, the notification will only be shown in the authentik UI." "selected, the notification will only be shown in the authentik UI."
msgstr "" msgstr ""
#: authentik/events/models.py:548 #: authentik/events/models.py:549
msgid "Controls which severity level the created notifications will have." msgid "Controls which severity level the created notifications will have."
msgstr "" msgstr ""
#: authentik/events/models.py:553 #: authentik/events/models.py:554
msgid "" msgid ""
"Define which group of users this notification should be sent and shown to. " "Define which group of users this notification should be sent and shown to. "
"If left empty, Notification won't ben sent." "If left empty, Notification won't ben sent."
msgstr "" msgstr ""
#: authentik/events/models.py:571 #: authentik/events/models.py:572
msgid "Notification Rule" msgid "Notification Rule"
msgstr "" msgstr ""
#: authentik/events/models.py:572 #: authentik/events/models.py:573
msgid "Notification Rules" msgid "Notification Rules"
msgstr "" msgstr ""
#: authentik/events/models.py:592 #: authentik/events/models.py:593
msgid "Webhook Mapping" msgid "Webhook Mapping"
msgstr "" msgstr ""
#: authentik/events/models.py:593 #: authentik/events/models.py:594
msgid "Webhook Mappings" msgid "Webhook Mappings"
msgstr "" msgstr ""
@ -547,7 +547,7 @@ msgstr ""
msgid "Pre-flow policies" msgid "Pre-flow policies"
msgstr "" msgstr ""
#: authentik/flows/api/flows_diagram.py:214 authentik/flows/models.py:193 #: authentik/flows/api/flows_diagram.py:214 authentik/flows/models.py:194
msgid "Flow" msgid "Flow"
msgstr "" msgstr ""
@ -555,72 +555,72 @@ msgstr ""
msgid "Flow does not apply to current user." msgid "Flow does not apply to current user."
msgstr "" msgstr ""
#: authentik/flows/models.py:114 #: authentik/flows/models.py:115
#, python-format #, python-format
msgid "Dynamic In-memory stage: %(doc)s" msgid "Dynamic In-memory stage: %(doc)s"
msgstr "" msgstr ""
#: authentik/flows/models.py:129 #: authentik/flows/models.py:130
msgid "Visible in the URL." msgid "Visible in the URL."
msgstr "" msgstr ""
#: authentik/flows/models.py:131 #: authentik/flows/models.py:132
msgid "Shown as the Title in Flow pages." msgid "Shown as the Title in Flow pages."
msgstr "" msgstr ""
#: authentik/flows/models.py:138 #: authentik/flows/models.py:139
msgid "" msgid ""
"Decides what this Flow is used for. For example, the Authentication flow is " "Decides what this Flow is used for. For example, the Authentication flow is "
"redirect to when an un-authenticated user visits authentik." "redirect to when an un-authenticated user visits authentik."
msgstr "" msgstr ""
#: authentik/flows/models.py:147 #: authentik/flows/models.py:148
msgid "Background shown during execution" msgid "Background shown during execution"
msgstr "" msgstr ""
#: authentik/flows/models.py:154 #: authentik/flows/models.py:155
msgid "" msgid ""
"Enable compatibility mode, increases compatibility with password managers on " "Enable compatibility mode, increases compatibility with password managers on "
"mobile devices." "mobile devices."
msgstr "" msgstr ""
#: authentik/flows/models.py:162 #: authentik/flows/models.py:163
msgid "Configure what should happen when a flow denies access to a user." msgid "Configure what should happen when a flow denies access to a user."
msgstr "" msgstr ""
#: authentik/flows/models.py:168 #: authentik/flows/models.py:169
msgid "Required level of authentication and authorization to access a flow." msgid "Required level of authentication and authorization to access a flow."
msgstr "" msgstr ""
#: authentik/flows/models.py:194 #: authentik/flows/models.py:195
msgid "Flows" msgid "Flows"
msgstr "" msgstr ""
#: authentik/flows/models.py:197 #: authentik/flows/models.py:198
msgid "Can export a Flow" msgid "Can export a Flow"
msgstr "" msgstr ""
#: authentik/flows/models.py:198 #: authentik/flows/models.py:199
msgid "Can inspect a Flow's execution" msgid "Can inspect a Flow's execution"
msgstr "" msgstr ""
#: authentik/flows/models.py:199 #: authentik/flows/models.py:200
msgid "View Flow's cache metrics" msgid "View Flow's cache metrics"
msgstr "" msgstr ""
#: authentik/flows/models.py:200 #: authentik/flows/models.py:201
msgid "Clear Flow's cache metrics" msgid "Clear Flow's cache metrics"
msgstr "" msgstr ""
#: authentik/flows/models.py:216 #: authentik/flows/models.py:217
msgid "Evaluate policies during the Flow planning process." msgid "Evaluate policies during the Flow planning process."
msgstr "" msgstr ""
#: authentik/flows/models.py:220 #: authentik/flows/models.py:221
msgid "Evaluate policies when the Stage is present to the user." msgid "Evaluate policies when the Stage is present to the user."
msgstr "" msgstr ""
#: authentik/flows/models.py:227 #: authentik/flows/models.py:228
msgid "" msgid ""
"Configure how the flow executor should handle an invalid response to a " "Configure how the flow executor should handle an invalid response to a "
"challenge. RETRY returns the error message and a similar challenge to the " "challenge. RETRY returns the error message and a similar challenge to the "
@ -628,25 +628,25 @@ msgid ""
"RESTART_WITH_CONTEXT restarts the flow while keeping the current context." "RESTART_WITH_CONTEXT restarts the flow while keeping the current context."
msgstr "" msgstr ""
#: authentik/flows/models.py:250 #: authentik/flows/models.py:251
msgid "Flow Stage Binding" msgid "Flow Stage Binding"
msgstr "" msgstr ""
#: authentik/flows/models.py:251 #: authentik/flows/models.py:252
msgid "Flow Stage Bindings" msgid "Flow Stage Bindings"
msgstr "" msgstr ""
#: authentik/flows/models.py:266 #: authentik/flows/models.py:267
msgid "" msgid ""
"Flow used by an authenticated user to configure this Stage. If empty, user " "Flow used by an authenticated user to configure this Stage. If empty, user "
"will not be able to configure this stage." "will not be able to configure this stage."
msgstr "" msgstr ""
#: authentik/flows/models.py:306 #: authentik/flows/models.py:307
msgid "Flow Token" msgid "Flow Token"
msgstr "" msgstr ""
#: authentik/flows/models.py:307 #: authentik/flows/models.py:308
msgid "Flow Tokens" msgid "Flow Tokens"
msgstr "" msgstr ""
@ -1526,27 +1526,27 @@ msgstr ""
msgid "Starting full SCIM sync" msgid "Starting full SCIM sync"
msgstr "" msgstr ""
#: authentik/providers/scim/tasks.py:59 #: authentik/providers/scim/tasks.py:62
#, python-format #, python-format
msgid "Syncing page %(page)d of users" msgid "Syncing page %(page)d of users"
msgstr "" msgstr ""
#: authentik/providers/scim/tasks.py:63 #: authentik/providers/scim/tasks.py:66
#, python-format #, python-format
msgid "Syncing page %(page)d of groups" msgid "Syncing page %(page)d of groups"
msgstr "" msgstr ""
#: authentik/providers/scim/tasks.py:92 #: authentik/providers/scim/tasks.py:98
#, python-format #, python-format
msgid "Failed to sync user %(user_name)s due to remote error: %(error)s" msgid "Failed to sync user %(user_name)s due to remote error: %(error)s"
msgstr "" msgstr ""
#: authentik/providers/scim/tasks.py:103 authentik/providers/scim/tasks.py:144 #: authentik/providers/scim/tasks.py:109 authentik/providers/scim/tasks.py:150
#, python-format #, python-format
msgid "Stopping sync due to error: %(error)s" msgid "Stopping sync due to error: %(error)s"
msgstr "" msgstr ""
#: authentik/providers/scim/tasks.py:133 #: authentik/providers/scim/tasks.py:139
#, python-format #, python-format
msgid "Failed to sync group %(group_name)s due to remote error: %(error)s" msgid "Failed to sync group %(group_name)s due to remote error: %(error)s"
msgstr "" msgstr ""

1197
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -28487,46 +28487,6 @@ components:
* `authentik.blueprints` - authentik Blueprints * `authentik.blueprints` - authentik Blueprints
* `authentik.core` - authentik Core * `authentik.core` - authentik Core
* `authentik.enterprise` - authentik Enterprise * `authentik.enterprise` - authentik Enterprise
AppleChallengeResponseRequest:
type: object
description: Pseudo class for plex response
properties:
component:
type: string
minLength: 1
default: ak-source-oauth-apple
AppleLoginChallenge:
type: object
description: Special challenge for apple-native authentication flow, which happens
on the client.
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-source-oauth-apple
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
client_id:
type: string
scope:
type: string
redirect_uri:
type: string
state:
type: string
required:
- client_id
- redirect_uri
- scope
- state
- type
Application: Application:
type: object type: object
description: Application Serializer description: Application Serializer
@ -28850,12 +28810,14 @@ components:
- require_authenticated - require_authenticated
- require_unauthenticated - require_unauthenticated
- require_superuser - require_superuser
- require_outpost
type: string type: string
description: |- description: |-
* `none` - None * `none` - None
* `require_authenticated` - Require Authenticated * `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated * `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser * `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
AuthenticatorAttachmentEnum: AuthenticatorAttachmentEnum:
enum: enum:
- platform - platform
@ -29026,47 +28988,6 @@ components:
- client_id - client_id
- client_secret - client_secret
- name - name
AuthenticatorSMSChallenge:
type: object
description: SMS Setup challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-authenticator-sms
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
phone_number_required:
type: boolean
default: true
required:
- pending_user
- pending_user_avatar
- type
AuthenticatorSMSChallengeResponseRequest:
type: object
description: SMS Challenge response, device is set by get_response_instance
properties:
component:
type: string
minLength: 1
default: ak-stage-authenticator-sms
code:
type: integer
phone_number:
type: string
minLength: 1
AuthenticatorSMSStage: AuthenticatorSMSStage:
type: object type: object
description: AuthenticatorSMSStage Serializer description: AuthenticatorSMSStage Serializer
@ -29192,44 +29113,6 @@ components:
- from_number - from_number
- name - name
- provider - provider
AuthenticatorStaticChallenge:
type: object
description: Static authenticator challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-authenticator-static
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
codes:
type: array
items:
type: string
required:
- codes
- pending_user
- pending_user_avatar
- type
AuthenticatorStaticChallengeResponseRequest:
type: object
description: Pseudo class for static response
properties:
component:
type: string
minLength: 1
default: ak-stage-authenticator-static
AuthenticatorStaticStage: AuthenticatorStaticStage:
type: object type: object
description: AuthenticatorStaticStage Serializer description: AuthenticatorStaticStage Serializer
@ -29316,46 +29199,6 @@ components:
minimum: 0 minimum: 0
required: required:
- name - name
AuthenticatorTOTPChallenge:
type: object
description: TOTP Setup challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-authenticator-totp
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
config_url:
type: string
required:
- config_url
- pending_user
- pending_user_avatar
- type
AuthenticatorTOTPChallengeResponseRequest:
type: object
description: TOTP Challenge response, device is set by get_response_instance
properties:
component:
type: string
minLength: 1
default: ak-stage-authenticator-totp
code:
type: integer
required:
- code
AuthenticatorTOTPStage: AuthenticatorTOTPStage:
type: object type: object
description: AuthenticatorTOTPStage Serializer description: AuthenticatorTOTPStage Serializer
@ -29540,104 +29383,6 @@ components:
* `discouraged` - Discouraged * `discouraged` - Discouraged
required: required:
- name - name
AuthenticatorValidationChallenge:
type: object
description: Authenticator challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-authenticator-validate
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
device_challenges:
type: array
items:
$ref: '#/components/schemas/DeviceChallenge'
configuration_stages:
type: array
items:
$ref: '#/components/schemas/SelectableStage'
required:
- configuration_stages
- device_challenges
- pending_user
- pending_user_avatar
- type
AuthenticatorValidationChallengeResponseRequest:
type: object
description: Challenge used for Code-based and WebAuthn authenticators
properties:
component:
type: string
minLength: 1
default: ak-stage-authenticator-validate
selected_challenge:
$ref: '#/components/schemas/DeviceChallengeRequest'
selected_stage:
type: string
minLength: 1
code:
type: string
minLength: 1
webauthn:
type: object
additionalProperties: {}
duo:
type: integer
AuthenticatorWebAuthnChallenge:
type: object
description: WebAuthn Challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-authenticator-webauthn
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
registration:
type: object
additionalProperties: {}
required:
- pending_user
- pending_user_avatar
- registration
- type
AuthenticatorWebAuthnChallengeResponseRequest:
type: object
description: WebAuthn Challenge response
properties:
component:
type: string
minLength: 1
default: ak-stage-authenticator-webauthn
response:
type: object
additionalProperties: {}
required:
- response
AutoSubmitChallengeResponseRequest: AutoSubmitChallengeResponseRequest:
type: object type: object
description: Pseudo class for autosubmit response description: Pseudo class for autosubmit response
@ -29928,50 +29673,6 @@ components:
* `can_impersonate` - Can Impersonate * `can_impersonate` - Can Impersonate
* `can_debug` - Can Debug * `can_debug` - Can Debug
* `is_enterprise` - Is Enterprise * `is_enterprise` - Is Enterprise
CaptchaChallenge:
type: object
description: Site public key
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-captcha
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
site_key:
type: string
js_url:
type: string
required:
- js_url
- pending_user
- pending_user_avatar
- site_key
- type
CaptchaChallengeResponseRequest:
type: object
description: Validate captcha token
properties:
component:
type: string
minLength: 1
default: ak-stage-captcha
token:
type: string
minLength: 1
required:
- token
CaptchaStage: CaptchaStage:
type: object type: object
description: CaptchaStage Serializer description: CaptchaStage Serializer
@ -30174,20 +29875,10 @@ components:
ChallengeTypes: ChallengeTypes:
oneOf: oneOf:
- $ref: '#/components/schemas/AccessDeniedChallenge' - $ref: '#/components/schemas/AccessDeniedChallenge'
- $ref: '#/components/schemas/AppleLoginChallenge'
- $ref: '#/components/schemas/AuthenticatorDuoChallenge' - $ref: '#/components/schemas/AuthenticatorDuoChallenge'
- $ref: '#/components/schemas/AuthenticatorSMSChallenge'
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
- $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
- $ref: '#/components/schemas/AuthenticatorValidationChallenge'
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge'
- $ref: '#/components/schemas/AutosubmitChallenge' - $ref: '#/components/schemas/AutosubmitChallenge'
- $ref: '#/components/schemas/CaptchaChallenge'
- $ref: '#/components/schemas/ConsentChallenge' - $ref: '#/components/schemas/ConsentChallenge'
- $ref: '#/components/schemas/DummyChallenge'
- $ref: '#/components/schemas/EmailChallenge'
- $ref: '#/components/schemas/FlowErrorChallenge' - $ref: '#/components/schemas/FlowErrorChallenge'
- $ref: '#/components/schemas/IdentificationChallenge'
- $ref: '#/components/schemas/OAuthDeviceCodeChallenge' - $ref: '#/components/schemas/OAuthDeviceCodeChallenge'
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallenge' - $ref: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
- $ref: '#/components/schemas/PasswordChallenge' - $ref: '#/components/schemas/PasswordChallenge'
@ -30195,25 +29886,14 @@ components:
- $ref: '#/components/schemas/PromptChallenge' - $ref: '#/components/schemas/PromptChallenge'
- $ref: '#/components/schemas/RedirectChallenge' - $ref: '#/components/schemas/RedirectChallenge'
- $ref: '#/components/schemas/ShellChallenge' - $ref: '#/components/schemas/ShellChallenge'
- $ref: '#/components/schemas/UserLoginChallenge'
discriminator: discriminator:
propertyName: component propertyName: component
mapping: mapping:
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge' ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
ak-source-oauth-apple: '#/components/schemas/AppleLoginChallenge'
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge' ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallenge'
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge'
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge'
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge'
ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge' ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge'
ak-stage-captcha: '#/components/schemas/CaptchaChallenge'
ak-stage-consent: '#/components/schemas/ConsentChallenge' ak-stage-consent: '#/components/schemas/ConsentChallenge'
ak-stage-dummy: '#/components/schemas/DummyChallenge'
ak-stage-email: '#/components/schemas/EmailChallenge'
ak-stage-flow-error: '#/components/schemas/FlowErrorChallenge' ak-stage-flow-error: '#/components/schemas/FlowErrorChallenge'
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge' ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge'
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge' ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
ak-stage-password: '#/components/schemas/PasswordChallenge' ak-stage-password: '#/components/schemas/PasswordChallenge'
@ -30221,7 +29901,6 @@ components:
ak-stage-prompt: '#/components/schemas/PromptChallenge' ak-stage-prompt: '#/components/schemas/PromptChallenge'
xak-flow-redirect: '#/components/schemas/RedirectChallenge' xak-flow-redirect: '#/components/schemas/RedirectChallenge'
xak-flow-shell: '#/components/schemas/ShellChallenge' xak-flow-shell: '#/components/schemas/ShellChallenge'
ak-stage-user-login: '#/components/schemas/UserLoginChallenge'
ClientTypeEnum: ClientTypeEnum:
enum: enum:
- confidential - confidential
@ -30404,7 +30083,7 @@ components:
cancel_url: cancel_url:
type: string type: string
layout: layout:
$ref: '#/components/schemas/LayoutEnum' $ref: '#/components/schemas/FlowLayoutEnum'
required: required:
- cancel_url - cancel_url
- layout - layout
@ -30565,38 +30244,6 @@ components:
- type - type
- verbose_name - verbose_name
- verbose_name_plural - verbose_name_plural
DeviceChallenge:
type: object
description: Single device challenge
properties:
device_class:
type: string
device_uid:
type: string
challenge:
type: object
additionalProperties: {}
required:
- challenge
- device_class
- device_uid
DeviceChallengeRequest:
type: object
description: Single device challenge
properties:
device_class:
type: string
minLength: 1
device_uid:
type: string
minLength: 1
challenge:
type: object
additionalProperties: {}
required:
- challenge
- device_class
- device_uid
DeviceClassesEnum: DeviceClassesEnum:
enum: enum:
- static - static
@ -30753,33 +30400,6 @@ components:
required: required:
- domain - domain
- tenant - tenant
DummyChallenge:
type: object
description: Dummy challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-dummy
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
required:
- type
DummyChallengeResponseRequest:
type: object
description: Dummy challenge response
properties:
component:
type: string
minLength: 1
default: ak-stage-dummy
DummyPolicy: DummyPolicy:
type: object type: object
description: Dummy Policy Serializer description: Dummy Policy Serializer
@ -30954,35 +30574,6 @@ components:
* `success` - Success * `success` - Success
* `waiting` - Waiting * `waiting` - Waiting
* `invalid` - Invalid * `invalid` - Invalid
EmailChallenge:
type: object
description: Email challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-email
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
required:
- type
EmailChallengeResponseRequest:
type: object
description: |-
Email challenge resposen. No fields. This challenge is
always declared invalid to give the user a chance to retry
properties:
component:
type: string
minLength: 1
default: ak-stage-email
EmailStage: EmailStage:
type: object type: object
description: EmailStage Serializer description: EmailStage Serializer
@ -31983,7 +31574,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'
@ -32003,6 +31594,7 @@ components:
* `require_authenticated` - Require Authenticated * `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated * `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser * `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
required: required:
- background - background
- cache_count - cache_count
@ -32017,47 +31609,25 @@ components:
- title - title
FlowChallengeResponseRequest: FlowChallengeResponseRequest:
oneOf: oneOf:
- $ref: '#/components/schemas/AppleChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest' - $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
- $ref: '#/components/schemas/AutoSubmitChallengeResponseRequest' - $ref: '#/components/schemas/AutoSubmitChallengeResponseRequest'
- $ref: '#/components/schemas/CaptchaChallengeResponseRequest'
- $ref: '#/components/schemas/ConsentChallengeResponseRequest' - $ref: '#/components/schemas/ConsentChallengeResponseRequest'
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
- $ref: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest' - $ref: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest' - $ref: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
- $ref: '#/components/schemas/PasswordChallengeResponseRequest' - $ref: '#/components/schemas/PasswordChallengeResponseRequest'
- $ref: '#/components/schemas/PlexAuthenticationChallengeResponseRequest' - $ref: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
- $ref: '#/components/schemas/PromptChallengeResponseRequest' - $ref: '#/components/schemas/PromptChallengeResponseRequest'
- $ref: '#/components/schemas/UserLoginChallengeResponseRequest'
discriminator: discriminator:
propertyName: component propertyName: component
mapping: mapping:
ak-source-oauth-apple: '#/components/schemas/AppleChallengeResponseRequest'
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest' ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
ak-stage-autosubmit: '#/components/schemas/AutoSubmitChallengeResponseRequest' ak-stage-autosubmit: '#/components/schemas/AutoSubmitChallengeResponseRequest'
ak-stage-captcha: '#/components/schemas/CaptchaChallengeResponseRequest'
ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest' ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest'
ak-stage-dummy: '#/components/schemas/DummyChallengeResponseRequest'
ak-stage-email: '#/components/schemas/EmailChallengeResponseRequest'
ak-stage-identification: '#/components/schemas/IdentificationChallengeResponseRequest'
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest' ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest' ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest' ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
ak-source-plex: '#/components/schemas/PlexAuthenticationChallengeResponseRequest' ak-source-plex: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
ak-stage-prompt: '#/components/schemas/PromptChallengeResponseRequest' ak-stage-prompt: '#/components/schemas/PromptChallengeResponseRequest'
ak-stage-user-login: '#/components/schemas/UserLoginChallengeResponseRequest'
FlowDesignationEnum: FlowDesignationEnum:
enum: enum:
- authentication - authentication
@ -32170,6 +31740,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
@ -32207,7 +31791,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'
@ -32227,6 +31811,7 @@ components:
* `require_authenticated` - Require Authenticated * `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated * `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser * `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
required: required:
- designation - designation
- name - name
@ -32285,7 +31870,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'
@ -32341,7 +31926,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'
@ -32626,68 +32211,6 @@ components:
format: uuid format: uuid
required: required:
- name - name
IdentificationChallenge:
type: object
description: Identification challenges with all UI elements
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-identification
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
user_fields:
type: array
items:
type: string
nullable: true
password_fields:
type: boolean
application_pre:
type: string
enroll_url:
type: string
recovery_url:
type: string
passwordless_url:
type: string
primary_action:
type: string
sources:
type: array
items:
$ref: '#/components/schemas/LoginSource'
show_source_labels:
type: boolean
required:
- password_fields
- primary_action
- show_source_labels
- type
- user_fields
IdentificationChallengeResponseRequest:
type: object
description: Identification challenge
properties:
component:
type: string
minLength: 1
default: ak-stage-identification
uid_field:
type: string
minLength: 1
password:
type: string
nullable: true
required:
- uid_field
IdentificationStage: IdentificationStage:
type: object type: object
description: IdentificationStage Serializer description: IdentificationStage Serializer
@ -33725,20 +33248,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
@ -33833,17 +33342,6 @@ components:
type: string type: string
required: required:
- link - link
LoginChallengeTypes:
oneOf:
- $ref: '#/components/schemas/RedirectChallenge'
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
- $ref: '#/components/schemas/AppleLoginChallenge'
discriminator:
propertyName: component
mapping:
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
ak-source-plex: '#/components/schemas/PlexAuthenticationChallenge'
ak-source-oauth-apple: '#/components/schemas/AppleLoginChallenge'
LoginMetrics: LoginMetrics:
type: object type: object
description: Login Metrics per 1h description: Login Metrics per 1h
@ -33867,20 +33365,6 @@ components:
- authorizations - authorizations
- logins - logins
- logins_failed - logins_failed
LoginSource:
type: object
description: Serializer for Login buttons of sources
properties:
name:
type: string
icon_url:
type: string
nullable: true
challenge:
$ref: '#/components/schemas/LoginChallengeTypes'
required:
- challenge
- name
Metadata: Metadata:
type: object type: object
description: Serializer for blueprint metadata description: Serializer for blueprint metadata
@ -34690,7 +34174,9 @@ components:
starts with http it is returned as-is starts with http it is returned as-is
readOnly: true readOnly: true
provider_type: provider_type:
$ref: '#/components/schemas/ProviderTypeEnum' allOf:
- $ref: '#/components/schemas/ProviderTypeEnum'
description: ''
request_token_url: request_token_url:
type: string type: string
nullable: true nullable: true
@ -34788,7 +34274,9 @@ components:
type: string type: string
minLength: 1 minLength: 1
provider_type: provider_type:
$ref: '#/components/schemas/ProviderTypeEnum' allOf:
- $ref: '#/components/schemas/ProviderTypeEnum'
description: ''
request_token_url: request_token_url:
type: string type: string
nullable: true nullable: true
@ -37281,7 +36769,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'
@ -37301,6 +36789,7 @@ components:
* `require_authenticated` - Require Authenticated * `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated * `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser * `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
PatchedFlowStageBindingRequest: PatchedFlowStageBindingRequest:
type: object type: object
description: FlowStageBinding Serializer description: FlowStageBinding Serializer
@ -37894,7 +37383,9 @@ components:
type: string type: string
minLength: 1 minLength: 1
provider_type: provider_type:
$ref: '#/components/schemas/ProviderTypeEnum' allOf:
- $ref: '#/components/schemas/ProviderTypeEnum'
description: ''
request_token_url: request_token_url:
type: string type: string
nullable: true nullable: true
@ -39866,35 +39357,8 @@ components:
- authorization_flow - authorization_flow
- name - name
ProviderTypeEnum: ProviderTypeEnum:
enum: enum: []
- apple type: boolean
- azuread
- discord
- facebook
- github
- google
- mailcow
- openidconnect
- okta
- patreon
- reddit
- twitch
- twitter
type: string
description: |-
* `apple` - Apple
* `azuread` - Azure AD
* `discord` - Discord
* `facebook` - Facebook
* `github` - GitHub
* `google` - Google
* `mailcow` - Mailcow
* `openidconnect` - OpenID Connect
* `okta` - Okta
* `patreon` - Patreon
* `reddit` - Reddit
* `twitch` - Twitch
* `twitter` - Twitter
ProxyMode: ProxyMode:
enum: enum:
- proxy - proxy
@ -41517,24 +40981,6 @@ components:
- expression - expression
- name - name
- scope_name - scope_name
SelectableStage:
type: object
description: Serializer for stages which can be selected by users
properties:
pk:
type: string
format: uuid
name:
type: string
verbose_name:
type: string
meta_model_name:
type: string
required:
- meta_model_name
- name
- pk
- verbose_name
ServiceConnection: ServiceConnection:
type: object type: object
description: ServiceConnection Serializer description: ServiceConnection Serializer
@ -42702,43 +42148,6 @@ components:
additionalProperties: {} additionalProperties: {}
required: required:
- name - name
UserLoginChallenge:
type: object
description: Empty challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-user-login
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
required:
- pending_user
- pending_user_avatar
- type
UserLoginChallengeResponseRequest:
type: object
description: User login challenge
properties:
component:
type: string
minLength: 1
default: ak-stage-user-login
remember_me:
type: boolean
required:
- remember_me
UserLoginStage: UserLoginStage:
type: object type: object
description: UserLoginStage Serializer description: UserLoginStage Serializer

View File

@ -7,13 +7,13 @@
"name": "@goauthentik/web-tests", "name": "@goauthentik/web-tests",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.14.0", "@typescript-eslint/parser": "^6.15.0",
"@wdio/cli": "^8.26.1", "@wdio/cli": "^8.26.3",
"@wdio/local-runner": "^8.26.1", "@wdio/local-runner": "^8.26.3",
"@wdio/mocha-framework": "^8.24.12", "@wdio/mocha-framework": "^8.26.3",
"@wdio/spec-reporter": "^8.24.12", "@wdio/spec-reporter": "^8.26.3",
"eslint": "^8.55.0", "eslint": "^8.56.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
@ -382,9 +382,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "8.55.0", "version": "8.56.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -946,16 +946,16 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz",
"integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==", "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.14.0", "@typescript-eslint/scope-manager": "6.15.0",
"@typescript-eslint/type-utils": "6.14.0", "@typescript-eslint/type-utils": "6.15.0",
"@typescript-eslint/utils": "6.14.0", "@typescript-eslint/utils": "6.15.0",
"@typescript-eslint/visitor-keys": "6.14.0", "@typescript-eslint/visitor-keys": "6.15.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -981,15 +981,15 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz",
"integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "6.14.0", "@typescript-eslint/scope-manager": "6.15.0",
"@typescript-eslint/types": "6.14.0", "@typescript-eslint/types": "6.15.0",
"@typescript-eslint/typescript-estree": "6.14.0", "@typescript-eslint/typescript-estree": "6.15.0",
"@typescript-eslint/visitor-keys": "6.14.0", "@typescript-eslint/visitor-keys": "6.15.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -1009,13 +1009,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz",
"integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==", "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.14.0", "@typescript-eslint/types": "6.15.0",
"@typescript-eslint/visitor-keys": "6.14.0" "@typescript-eslint/visitor-keys": "6.15.0"
}, },
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -1026,13 +1026,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz",
"integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==", "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "6.14.0", "@typescript-eslint/typescript-estree": "6.15.0",
"@typescript-eslint/utils": "6.14.0", "@typescript-eslint/utils": "6.15.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
@ -1053,9 +1053,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz",
"integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==", "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.0.0 || >=18.0.0" "node": "^16.0.0 || >=18.0.0"
@ -1066,13 +1066,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz",
"integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==", "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.14.0", "@typescript-eslint/types": "6.15.0",
"@typescript-eslint/visitor-keys": "6.14.0", "@typescript-eslint/visitor-keys": "6.15.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1093,17 +1093,17 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz",
"integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==", "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.14.0", "@typescript-eslint/scope-manager": "6.15.0",
"@typescript-eslint/types": "6.14.0", "@typescript-eslint/types": "6.15.0",
"@typescript-eslint/typescript-estree": "6.14.0", "@typescript-eslint/typescript-estree": "6.15.0",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
@ -1118,12 +1118,12 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "6.14.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz",
"integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==", "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "6.14.0", "@typescript-eslint/types": "6.15.0",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
@ -1141,18 +1141,18 @@
"dev": true "dev": true
}, },
"node_modules/@wdio/cli": { "node_modules/@wdio/cli": {
"version": "8.26.1", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.26.3.tgz",
"integrity": "sha512-KZ3MVyH4N6j0Gdy6RL6Wv0uf5OeggFe0WRpOwZFjjQpYVEV8IEuB4kDcw8ld7f3kp9YYQGabMAkGrO6tnz5T8w==", "integrity": "sha512-wrq145sNBw4DrsF5GEK8TBxqVWln7GZpNpM5QeDqCcZzVHIqDud4f7nADgZGbR8dJ96NVfC3rby6wbeRQUA+eg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.1", "@types/node": "^20.1.1",
"@wdio/config": "8.24.12", "@wdio/config": "8.26.3",
"@wdio/globals": "8.26.1", "@wdio/globals": "8.26.3",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.24.12", "@wdio/protocols": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"@wdio/utils": "8.24.12", "@wdio/utils": "8.26.3",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
@ -1167,7 +1167,7 @@
"lodash.union": "^4.6.0", "lodash.union": "^4.6.0",
"read-pkg-up": "^10.0.0", "read-pkg-up": "^10.0.0",
"recursive-readdir": "^2.2.3", "recursive-readdir": "^2.2.3",
"webdriverio": "8.26.1", "webdriverio": "8.26.3",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@ -1190,14 +1190,14 @@
} }
}, },
"node_modules/@wdio/config": { "node_modules/@wdio/config": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.24.12.tgz", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.26.3.tgz",
"integrity": "sha512-3HW7qG1rIHzOIybV6oHR1CqLghsN0G3Xzs90ZciGL8dYhtcLtYCHwuWmBw4mkaB5xViU4AmZDuj7ChiG8Cr6Qw==", "integrity": "sha512-NWh2JXRSyP4gY+jeC79u0L3hSXW/s3rOWez4M6qAglT91fZTXFbIl1GM8lnZlCq03ye2qFPqYrZ+4tGNQj7YxQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"@wdio/utils": "8.24.12", "@wdio/utils": "8.26.3",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"glob": "^10.2.2", "glob": "^10.2.2",
@ -1208,29 +1208,29 @@
} }
}, },
"node_modules/@wdio/globals": { "node_modules/@wdio/globals": {
"version": "8.26.1", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.26.3.tgz",
"integrity": "sha512-hNJ4mvSHzvAzDcBisaNgwRzJ2sLN4B/fno6VZcskgfYlg7UyWpVHigyxaddP2e1OeoxLL9pc2hKCtwgDr4UozA==", "integrity": "sha512-RW3UsvnUb4DjxVOqIngXQMcDJlbH+QL/LeChznUF0FW+Mqg/mZWukBld5/dDwgQHk9F2TOzc8ctk5FM3s1AoWQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"expect-webdriverio": "^4.6.1", "expect-webdriverio": "^4.6.1",
"webdriverio": "8.26.1" "webdriverio": "8.26.3"
} }
}, },
"node_modules/@wdio/local-runner": { "node_modules/@wdio/local-runner": {
"version": "8.26.1", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.26.3.tgz",
"integrity": "sha512-n7iiB/mKt7u6bd3uJTgqOTaN2r/EUVQpBMsrXyv5XidYEr9QHuq2OOE3biswdxSez24p0zZGU35fu6oUwz5J1Q==", "integrity": "sha512-YWxTBp6tc8Dlz09rnRhV2GXV4b3w5G0WyYEf81D+kXDI3QxDvYn6QujByr6Q7fQ9yLwJU4ptnT2uL5IbAwVo2A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/repl": "8.24.12", "@wdio/repl": "8.24.12",
"@wdio/runner": "8.26.1", "@wdio/runner": "8.26.3",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"split2": "^4.1.0", "split2": "^4.1.0",
"stream-buffers": "^3.0.2" "stream-buffers": "^3.0.2"
@ -1267,16 +1267,16 @@
} }
}, },
"node_modules/@wdio/mocha-framework": { "node_modules/@wdio/mocha-framework": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.24.12.tgz", "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.26.3.tgz",
"integrity": "sha512-SHN7CYZnDkVUNYxLp8iMV92xcmU/4gq5dqA0pRrK4m5nIU7BoL0flm0kA+ydYUQyNedQh2ru1V63uNyTOyCKAg==", "integrity": "sha512-r9uHUcXhh6TKFqTBCacnbtQx0nZjFsnV9Pony8CY9qcNCn2q666ucQbrtMfZdjuevhn5N0E710+El4eAvK3jyw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"@wdio/utils": "8.24.12", "@wdio/utils": "8.26.3",
"mocha": "^10.0.0" "mocha": "^10.0.0"
}, },
"engines": { "engines": {
@ -1302,14 +1302,14 @@
} }
}, },
"node_modules/@wdio/reporter": { "node_modules/@wdio/reporter": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.24.12.tgz", "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.26.3.tgz",
"integrity": "sha512-FtLzDTBXdgxXf4T9HJQ2bNpYYSKEw//jojFm9XzB4fPwzPeFY3HC+dbePucVW1SSLrVzVxqIOyHiwCLqQ/4cQw==", "integrity": "sha512-F/sF1Hwxp1osM2wto4JydONQGxqkbOhwbLM0o4Y8eHPgK7/m+Kn9uygBDqPVpgQnpf0kUfhpICe9gaZzG4Jt+g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"diff": "^5.0.0", "diff": "^5.0.0",
"object-inspect": "^1.12.0" "object-inspect": "^1.12.0"
}, },
@ -1318,35 +1318,35 @@
} }
}, },
"node_modules/@wdio/runner": { "node_modules/@wdio/runner": {
"version": "8.26.1", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.26.3.tgz",
"integrity": "sha512-Uhz82HD53LvfW6hLwd/cX+vYVZpiKtAr7ipTxaLt5VPEIwcDlrVz1hoVkVMGyfvkAuHxDKLZx16bigfMg+5eTQ==", "integrity": "sha512-mbZGkBbXTRtj1hL5QUbNxpJvhE4rkXvYlUuea1uOVk3e2/+k2dZeGeKPgh1Q7Dt07118dfujCB7pQCYldE/dGg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.24.12", "@wdio/config": "8.26.3",
"@wdio/globals": "8.26.1", "@wdio/globals": "8.26.3",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"@wdio/utils": "8.24.12", "@wdio/utils": "8.26.3",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"expect-webdriverio": "^4.6.1", "expect-webdriverio": "^4.6.1",
"gaze": "^1.1.2", "gaze": "^1.1.2",
"webdriver": "8.24.12", "webdriver": "8.26.3",
"webdriverio": "8.26.1" "webdriverio": "8.26.3"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
} }
}, },
"node_modules/@wdio/spec-reporter": { "node_modules/@wdio/spec-reporter": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.24.12.tgz", "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.26.3.tgz",
"integrity": "sha512-Ng3ErWK8eESamCYwIr2Uv49+46RvmT8FnmGaJ6irJoAp101K8zENEs1pyqYHJReucN+ka/wM87blfc2k8NEHCA==", "integrity": "sha512-YfKlBOmxGyJk08BpDnsjPsp85XyhG7Cu2qoAVxtJ8kkJOZaGfUg9TBV9DXDqvdZcxCMnPfDfQIda6LzfkZf58Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/reporter": "8.24.12", "@wdio/reporter": "8.26.3",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"chalk": "^5.1.2", "chalk": "^5.1.2",
"easy-table": "^1.2.0", "easy-table": "^1.2.0",
"pretty-ms": "^7.0.0" "pretty-ms": "^7.0.0"
@ -1368,9 +1368,9 @@
} }
}, },
"node_modules/@wdio/types": { "node_modules/@wdio/types": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.24.12.tgz", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.26.3.tgz",
"integrity": "sha512-SaD3OacDiW06DvSgAQ7sDBbpiI9qZRg7eoVYeBg3uSGVtUq84vTETRhhV7D6xTC00IqZu+mmN2TY5/q+7Gqy7w==", "integrity": "sha512-WOxvSV4sKJ5QCRNTJHeKCzgO2TZmcK1eDlJ+FObt9Pnt+4pCRy/881eVY/Aj2bozn2hhzq0AK/h6oPAUV/gjCg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0" "@types/node": "^20.1.0"
@ -1380,14 +1380,14 @@
} }
}, },
"node_modules/@wdio/utils": { "node_modules/@wdio/utils": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.24.12.tgz", "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.26.3.tgz",
"integrity": "sha512-uzwZyBVgqz0Wz1KL3aOUaQsxT8TNkzxti4NNTSMrU256qAPqc/n75rB7V73QASapCMpy70mZZTsuPgQYYj4ytQ==", "integrity": "sha512-LA/iCKgJQemAAXoN6vHyKBtngdkFUWEnjB8Yd1Xm3gUQTvY4GVlvcqOxC2RF5Th7/L2LNxc6TWuErYv/mm5H+w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@puppeteer/browsers": "^1.6.0", "@puppeteer/browsers": "^1.6.0",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"edgedriver": "^5.3.5", "edgedriver": "^5.3.5",
@ -2456,9 +2456,9 @@
} }
}, },
"node_modules/devtools-protocol": { "node_modules/devtools-protocol": {
"version": "0.0.1233178", "version": "0.0.1237913",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1233178.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1237913.tgz",
"integrity": "sha512-jmMfyaqlzddwmDaSR1AQ+5ek+f7rupZdxKuPdkRcoxrZoF70Idg/4dTgXA08TLPmwAwB54gh49Wm2l/gRM0eUg==", "integrity": "sha512-Pxtmz2ZIqBkpU82HaIdsvCQBG94yTC4xajrEsWx9p38QKEfBCJktSazsHkrjf9j3dVVNPhg5LR21F6KWeXpjiQ==",
"dev": true "dev": true
}, },
"node_modules/diff": { "node_modules/diff": {
@ -2806,15 +2806,15 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.55.0", "version": "8.56.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4", "@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.55.0", "@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@ -3887,6 +3887,43 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/got": {
"version": "12.6.1",
"resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
"integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^5.2.0",
"@szmarczak/http-timer": "^5.0.1",
"cacheable-lookup": "^7.0.0",
"cacheable-request": "^10.2.8",
"decompress-response": "^6.0.0",
"form-data-encoder": "^2.1.2",
"get-stream": "^6.0.1",
"http2-wrapper": "^2.1.10",
"lowercase-keys": "^3.0.0",
"p-cancelable": "^3.0.0",
"responselike": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/got/node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -8458,18 +8495,18 @@
} }
}, },
"node_modules/webdriver": { "node_modules/webdriver": {
"version": "8.24.12", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.24.12.tgz", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.26.3.tgz",
"integrity": "sha512-03DQIClHoaAqTsmDkxGwo4HwHfkn9LzJ1wfNyUerzKg8DnyXeiT6ILqj6EXLfsvh5zddU2vhYGLFXSerPgkuOQ==", "integrity": "sha512-vHbMj0BFXPMtKVmJsVIkFVbdOT8eXkjFeJ7LmJL8cMMe1S5Lt44DqRjSBBoGsqYoYgIBmKpqAQcDrHrv9m7smQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@wdio/config": "8.24.12", "@wdio/config": "8.26.3",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.24.12", "@wdio/protocols": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"@wdio/utils": "8.24.12", "@wdio/utils": "8.26.3",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"got": "^12.6.1", "got": "^12.6.1",
"ky": "^0.33.0", "ky": "^0.33.0",
@ -8479,61 +8516,24 @@
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
} }
}, },
"node_modules/webdriver/node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/webdriver/node_modules/got": {
"version": "12.6.1",
"resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
"integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^5.2.0",
"@szmarczak/http-timer": "^5.0.1",
"cacheable-lookup": "^7.0.0",
"cacheable-request": "^10.2.8",
"decompress-response": "^6.0.0",
"form-data-encoder": "^2.1.2",
"get-stream": "^6.0.1",
"http2-wrapper": "^2.1.10",
"lowercase-keys": "^3.0.0",
"p-cancelable": "^3.0.0",
"responselike": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/webdriverio": { "node_modules/webdriverio": {
"version": "8.26.1", "version": "8.26.3",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.26.1.tgz", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.26.3.tgz",
"integrity": "sha512-KnM92UPqk7FmPJpZf3krHrqn0ydjSdyAMn+i4uENxLBqm1OyQ12gSKtIatt8FOP9/C+UrFXATSOd+jRkU2xMkw==", "integrity": "sha512-5Ka8MOQoK866EI3whiCvzD1IiKFBq9niWF3lh92uMt6ZjbUZZoe5esWIHhFsHFxT6dOOU8uXR/Gr6qsBJFZReA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.24.12", "@wdio/config": "8.26.3",
"@wdio/logger": "8.24.12", "@wdio/logger": "8.24.12",
"@wdio/protocols": "8.24.12", "@wdio/protocols": "8.24.12",
"@wdio/repl": "8.24.12", "@wdio/repl": "8.24.12",
"@wdio/types": "8.24.12", "@wdio/types": "8.26.3",
"@wdio/utils": "8.24.12", "@wdio/utils": "8.26.3",
"archiver": "^6.0.0", "archiver": "^6.0.0",
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"css-shorthand-properties": "^1.1.1", "css-shorthand-properties": "^1.1.1",
"css-value": "^0.0.1", "css-value": "^0.0.1",
"devtools-protocol": "^0.0.1233178", "devtools-protocol": "^0.0.1237913",
"grapheme-splitter": "^1.0.2", "grapheme-splitter": "^1.0.2",
"import-meta-resolve": "^4.0.0", "import-meta-resolve": "^4.0.0",
"is-plain-obj": "^4.1.0", "is-plain-obj": "^4.1.0",
@ -8545,7 +8545,7 @@
"resq": "^1.9.1", "resq": "^1.9.1",
"rgb2hex": "0.2.5", "rgb2hex": "0.2.5",
"serialize-error": "^11.0.1", "serialize-error": "^11.0.1",
"webdriver": "8.24.12" "webdriver": "8.26.3"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"

View File

@ -4,13 +4,13 @@
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.14.0", "@typescript-eslint/parser": "^6.15.0",
"@wdio/cli": "^8.26.1", "@wdio/cli": "^8.26.3",
"@wdio/local-runner": "^8.26.1", "@wdio/local-runner": "^8.26.3",
"@wdio/mocha-framework": "^8.24.12", "@wdio/mocha-framework": "^8.26.3",
"@wdio/spec-reporter": "^8.24.12", "@wdio/spec-reporter": "^8.26.3",
"eslint": "^8.55.0", "eslint": "^8.56.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",

View File

@ -0,0 +1,129 @@
// THIS IS A GENERATED FILE. DO NOT EDIT BY HAND.
//
// This file is generated by the build-storybook-import-maps script in the UI's base directory.
// This is a *hack* to work around an inconsistency in the way rollup, vite, and storybook
// import CSS modules.
//
// Sometime around 2030 or so, the Javascript community may finally get its collective act together
// and we'll have one unified way of doing this. I can only hope.
export const cssImportMaps = {
'import AKGlobal from "@goauthentik/common/styles/authentik.css";':
'import AKGlobal from "@goauthentik/common/styles/authentik.css?inline";',
'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";':
'import PFAlert from "@patternfly/patternfly/components/Alert/alert.css?inline";',
'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";':
'import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css?inline";',
'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";':
'import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css?inline";',
'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css";':
'import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css?inline";',
'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css";':
'import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css?inline";',
'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";':
'import PFBanner from "@patternfly/patternfly/components/Banner/banner.css?inline";',
'import PFBase from "@patternfly/patternfly/patternfly-base.css";':
'import PFBase from "@patternfly/patternfly/patternfly-base.css?inline";',
'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css";':
'import PFBrand from "@patternfly/patternfly/components/Brand/brand.css?inline";',
'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";':
'import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css?inline";',
'import PFButton from "@patternfly/patternfly/components/Button/button.css";':
'import PFButton from "@patternfly/patternfly/components/Button/button.css?inline";',
'import PFCard from "@patternfly/patternfly/components/Card/card.css";':
'import PFCard from "@patternfly/patternfly/components/Card/card.css?inline";',
'import PFCheck from "@patternfly/patternfly/components/Check/check.css";':
'import PFCheck from "@patternfly/patternfly/components/Check/check.css?inline";',
'import PFChip from "@patternfly/patternfly/components/Chip/chip.css";':
'import PFChip from "@patternfly/patternfly/components/Chip/chip.css?inline";',
'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css";':
'import PFChipGroup from "@patternfly/patternfly/components/ChipGroup/chip-group.css?inline";',
'import PFContent from "@patternfly/patternfly/components/Content/content.css";':
'import PFContent from "@patternfly/patternfly/components/Content/content.css?inline";',
'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";':
'import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css?inline";',
'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";':
'import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css?inline";',
'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";':
'import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css?inline";',
'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";':
'import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css?inline";',
'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";':
'import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css?inline";',
'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";':
'import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css?inline";',
'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css";':
'import PFExpandableSection from "@patternfly/patternfly/components/ExpandableSection/expandable-section.css?inline";',
'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css";':
'import PFFAIcons from "@patternfly/patternfly/base/patternfly-fa-icons.css?inline";',
'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";':
'import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css?inline";',
'import PFForm from "@patternfly/patternfly/components/Form/form.css";':
'import PFForm from "@patternfly/patternfly/components/Form/form.css?inline";',
'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";':
'import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css?inline";',
'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";':
'import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css?inline";',
'import PFGlobal from "@patternfly/patternfly/patternfly-base.css";':
'import PFGlobal from "@patternfly/patternfly/patternfly-base.css?inline";',
'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";':
'import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css?inline";',
'import PFHint from "@patternfly/patternfly/components/Hint/hint.css";':
'import PFHint from "@patternfly/patternfly/components/Hint/hint.css?inline";',
'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";':
'import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css?inline";',
'import PFLabel from "@patternfly/patternfly/components/Label/label.css";':
'import PFLabel from "@patternfly/patternfly/components/Label/label.css?inline";',
'import PFList from "@patternfly/patternfly/components/List/list.css";':
'import PFList from "@patternfly/patternfly/components/List/list.css?inline";',
'import PFLogin from "@patternfly/patternfly/components/Login/login.css";':
'import PFLogin from "@patternfly/patternfly/components/Login/login.css?inline";',
'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css";':
'import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css?inline";',
'import PFNav from "@patternfly/patternfly/components/Nav/nav.css";':
'import PFNav from "@patternfly/patternfly/components/Nav/nav.css?inline";',
'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";':
'import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css?inline";',
'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";':
'import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css?inline";',
'import PFPage from "@patternfly/patternfly/components/Page/page.css";':
'import PFPage from "@patternfly/patternfly/components/Page/page.css?inline";',
'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css";':
'import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css?inline";',
'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";':
'import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css?inline";',
'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";':
'import PFRadio from "@patternfly/patternfly/components/Radio/radio.css?inline";',
'import PFSelect from "@patternfly/patternfly/components/Select/select.css";':
'import PFSelect from "@patternfly/patternfly/components/Select/select.css?inline";',
'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css";':
'import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css?inline";',
'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";':
'import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css?inline";',
'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";':
'import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css?inline";',
'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";':
'import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css?inline";',
'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";':
'import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css?inline";',
'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";':
'import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css?inline";',
'import PFTable from "@patternfly/patternfly/components/Table/table.css";':
'import PFTable from "@patternfly/patternfly/components/Table/table.css?inline";',
'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";':
'import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css?inline";',
'import PFTitle from "@patternfly/patternfly/components/Title/title.css";':
'import PFTitle from "@patternfly/patternfly/components/Title/title.css?inline";',
'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";':
'import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css?inline";',
'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";':
'import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css?inline";',
'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css";':
'import PFTreeView from "@patternfly/patternfly/components/TreeView/tree-view.css?inline";',
'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";':
'import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css?inline";',
'import ThemeDark from "@goauthentik/common/styles/theme-dark.css";':
'import ThemeDark from "@goauthentik/common/styles/theme-dark.css?inline";',
'import styles from "./LibraryPageImpl.css";':
'import styles from "./LibraryPageImpl.css?inline";',
};

View File

@ -1,9 +1,12 @@
import replace from "@rollup/plugin-replace"; import replace from "@rollup/plugin-replace";
import type { StorybookConfig } from "@storybook/web-components-vite"; import type { StorybookConfig } from "@storybook/web-components-vite";
import { cwd } from "process"; import { cwd } from "process";
import modify from "rollup-plugin-modify";
import postcssLit from "rollup-plugin-postcss-lit"; import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
import { cssImportMaps } from "./css-import-maps";
export const isProdBuild = process.env.NODE_ENV === "production"; export const isProdBuild = process.env.NODE_ENV === "production";
export const apiBasePath = process.env.AK_API_BASE_PATH || ""; export const apiBasePath = process.env.AK_API_BASE_PATH || "";
@ -27,9 +30,7 @@ const config: StorybookConfig = {
return { return {
...config, ...config,
plugins: [ plugins: [
...config.plugins, modify(cssImportMaps),
postcssLit(),
tsconfigPaths(),
replace({ replace({
"process.env.NODE_ENV": JSON.stringify( "process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development", isProdBuild ? "production" : "development",
@ -38,6 +39,9 @@ const config: StorybookConfig = {
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
"preventAssignment": true, "preventAssignment": true,
}), }),
...config.plugins,
postcssLit(),
tsconfigPaths(),
], ],
}; };
}, },

1809
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,10 @@
"tsc:execute": "tsc --noEmit -p .", "tsc:execute": "tsc --noEmit -p .",
"tsc": "run-s build-locales tsc:execute", "tsc": "run-s build-locales tsc:execute",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"storybook:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' storybook build" "storybook:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' storybook build",
"storybook:build-import-map": "run-s storybook:build-import-map-script storybook:run-import-map-script",
"storybook:build-import-map-script": "cd scripts && tsc --esModuleInterop --module es2020 --target es2020 --moduleResolution 'node' build-storybook-import-maps.ts && mv build-storybook-import-maps.js build-storybook-import-maps.mjs",
"storybook:run-import-map-script": "node scripts/build-storybook-import-maps.mjs"
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-html": "^6.4.7", "@codemirror/lang-html": "^6.4.7",
@ -39,7 +42,7 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3", "@formatjs/intl-listformat": "^7.5.3",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2023.10.4-1701882394", "@goauthentik/api": "^2023.10.4-1702989148",
"@lit-labs/context": "^0.4.0", "@lit-labs/context": "^0.4.0",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4", "@lit/localize": "^0.11.4",
@ -83,26 +86,26 @@
"@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5", "@rollup/plugin-typescript": "^11.1.5",
"@storybook/addon-essentials": "^7.6.4", "@storybook/addon-essentials": "^7.6.5",
"@storybook/addon-links": "^7.6.4", "@storybook/addon-links": "^7.6.5",
"@storybook/api": "^7.6.4", "@storybook/api": "^7.6.5",
"@storybook/blocks": "^7.6.4", "@storybook/blocks": "^7.6.4",
"@storybook/manager-api": "^7.6.4", "@storybook/manager-api": "^7.6.5",
"@storybook/web-components": "^7.6.4", "@storybook/web-components": "^7.6.5",
"@storybook/web-components-vite": "^7.6.4", "@storybook/web-components-vite": "^7.6.5",
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41", "@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.7", "@types/grecaptcha": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.14.0", "@typescript-eslint/parser": "^6.15.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.55.0", "eslint": "^8.56.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.10.1", "eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^0.23.0", "eslint-plugin-sonarjs": "^0.23.0",
"eslint-plugin-storybook": "^0.6.15", "eslint-plugin-storybook": "^0.6.15",
"lit-analyzer": "^2.0.2", "lit-analyzer": "^2.0.2",
@ -112,11 +115,12 @@
"pyright": "=1.1.338", "pyright": "=1.1.338",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rollup": "^4.9.0", "rollup": "^4.9.1",
"rollup-plugin-copy": "^3.5.0", "rollup-plugin-copy": "^3.5.0",
"rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-cssimport": "^1.0.3",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0", "rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.4", "storybook": "^7.6.5",
"storybook-addon-mock": "^4.3.0", "storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.1", "ts-lit-plugin": "^2.0.1",
"tslib": "^2.6.2", "tslib": "^2.6.2",
@ -125,9 +129,9 @@
"vite-tsconfig-paths": "^4.2.2" "vite-tsconfig-paths": "^4.2.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/darwin-arm64": "^0.19.9", "@esbuild/darwin-arm64": "^0.19.10",
"@esbuild/linux-amd64": "^0.18.11", "@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.19.9" "@esbuild/linux-arm64": "^0.19.10"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"

View File

@ -0,0 +1,84 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function* walkFilesystem(dir: string): Generator<string, undefined, any> {
const openeddir = fs.opendirSync(dir);
if (!openeddir) {
return;
}
let d: fs.Dirent | null;
while ((d = openeddir?.readSync())) {
if (!d) {
break;
}
const entry = path.join(dir, d.name);
if (d.isDirectory()) yield* walkFilesystem(entry);
else if (d.isFile()) yield entry;
}
openeddir.close();
}
const import_re = /^(import \w+ from .*\.css)";/;
function extractImportLinesFromFile(path: string) {
const source = fs.readFileSync(path, { encoding: "utf8", flag: "r" });
const lines = source?.split("\n") ?? [];
return lines.filter((l) => import_re.test(l));
}
function createOneImportLine(line: string) {
const importMatch = import_re.exec(line);
if (!importMatch) {
throw new Error("How did an unmatchable line get here?");
}
const importContent = importMatch[1];
if (!importContent) {
throw new Error("How did an unmatchable line get here!?");
}
return `'${importContent}";': '${importContent}?inline";',`;
}
const isSourceFile = /\.ts$/;
function getTheSourceFiles() {
return Array.from(walkFilesystem(path.join(__dirname, "..", "src"))).filter((path) =>
isSourceFile.test(path),
);
}
function getTheImportLines(importPaths: string[]) {
const importLines: string[] = importPaths.reduce(
(acc: string[], path) => [...acc, extractImportLinesFromFile(path)].flat(),
[],
);
const uniqueImportLines = new Set(importLines);
const sortedImportLines = Array.from(uniqueImportLines.keys());
sortedImportLines.sort();
return sortedImportLines;
}
const importPaths = getTheSourceFiles();
const importLines = getTheImportLines(importPaths);
const outputFile = `
// THIS IS A GENERATED FILE. DO NOT EDIT BY HAND.
//
// This file is generated by the build-storybook-import-maps script in the UI's base directory.
// This is a *hack* to work around an inconsistency in the way rollup, vite, and storybook
// import CSS modules.
//
// Sometime around 2030 or so, the Javascript community may finally get its collective act together
// and we'll have one unified way of doing this. I can only hope.
export const cssImportMaps = {
${importLines.map(createOneImportLine).join("\n")}
};
`;
fs.writeFileSync(path.join(__dirname, "..", ".storybook", "css-import-maps.ts"), outputFile, {
encoding: "utf8",
flag: "w",
});

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";
@ -196,6 +196,13 @@ export class FlowForm extends ModelForm<Flow, string> {
> >
${msg("Require superuser.")} ${msg("Require superuser.")}
</option> </option>
<option
value=${AuthenticationEnum.RequireOutpost}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireOutpost}
>
${msg("Require Outpost (flow can only be executed from an outpost).")}
</option>
</select> </select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Required authentication level for this flow.")} ${msg("Required authentication level for this flow.")}
@ -302,34 +309,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

@ -124,3 +124,27 @@ html > form > input {
margin-right: 6px; margin-right: 6px;
margin-bottom: 6px; margin-bottom: 6px;
} }
/* Flow-card adjustments for static pages */
.pf-c-brand {
padding-top: calc(
var(--pf-c-login__main-footer-links--PaddingTop) +
var(--pf-c-login__main-footer-links--PaddingBottom) +
var(--pf-c-login__main-body--PaddingBottom)
);
max-height: 9rem;
}
.ak-brand {
display: flex;
justify-content: center;
}
.ak-brand img {
padding: 0 2rem;
max-height: inherit;
}
@media (min-height: 60rem) {
.pf-c-login.stacked .pf-c-login__main {
margin-top: 13rem;
}
}

View File

@ -37,8 +37,8 @@ import {
ContextualFlowInfo, ContextualFlowInfo,
FlowChallengeResponseRequest, FlowChallengeResponseRequest,
FlowErrorChallenge, FlowErrorChallenge,
FlowLayoutEnum,
FlowsApi, FlowsApi,
LayoutEnum,
ResponseError, ResponseError,
ShellChallenge, ShellChallenge,
UiThemeEnum, UiThemeEnum,
@ -115,8 +115,10 @@ export class FlowExecutor extends Interface implements StageHost {
background-color: transparent; background-color: transparent;
} }
/* layouts */ /* layouts */
.pf-c-login.stacked .pf-c-login__main { @media (min-height: 60rem) {
margin-top: 13rem; .pf-c-login.stacked .pf-c-login__main {
margin-top: 13rem;
}
} }
.pf-c-login__container.content-right { .pf-c-login__container.content-right {
grid-template-areas: grid-template-areas:
@ -451,7 +453,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 +463,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";
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8044,6 +8044,10 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s7513372fe60f6387"> <trans-unit id="s7513372fe60f6387">
<source>Event volume</source> <source>Event volume</source>
<target>事件容量</target> <target>事件容量</target>
</trans-unit>
<trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source>
<target>需要前哨(流程只能从前哨执行)。</target>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,376 @@
---
title: SharePoint Server SE
---
<span class="badge badge--secondary">Support level: Community</span>
## What is Microsoft SharePoint
> SharePoint is a proprietary, web-based collaborative platform that integrates natively with Microsoft 365.
>
> Launched in 2001, SharePoint is primarily sold as a document management and storage system, although it is also used for sharing information through an intranet, implementing internal applications, and for implementing business processes.
>
> -- https://en.wikipedia.org/wiki/SharePoint
> Organizations use Microsoft SharePoint to create websites.
>
> You can use it as a secure place to store, organize, share, and access information from any device.
> All you need is a web browser, such as Microsoft Edge, Internet Explorer, Chrome, or Firefox.
>
> -- https://support.microsoft.com/en-us/office/what-is-sharepoint-97b915e6-651b-43b2-827d-fb25777f446f
:::note
There are many ways to implement SSO mechanism within Microsoft SharePoint Server Subscription Edition.
These guidelines provides the procedure to integrate authentik with an OIDC provider based on Microsoft documentation.
(cf. https://learn.microsoft.com/en-us/sharepoint/security-for-sharepoint-server/set-up-oidc-auth-in-sharepoint-server-with-msaad)
In addition, it provides the procedure to enable claims augmentations in order to resolve group memberships.
For all other integration models, read Microsoft official documentation.
(cf. https://learn.microsoft.com/en-us/sharepoint/security-for-sharepoint-server/plan-user-authentication)
:::
:::caution
This setup only works starting with **authentik** version **2023.10** and Microsoft **SharePoint** Subscription Edition starting with the **Cumulative Updates** of **September 2023**.
:::
## Preparation
When you configure OIDC with authentik, you need the following resources:
1. A SharePoint Server Subscription Edition farm starting with CU of September 2023
2. An authentik instance starting with version 2023.10
3. (Optional) [LDAPCP](https://www.ldapcp.com/docs/overview/introduction/) installed on the target SharePoint farm
:::info
Ensure that the authentik and SharePoint Server clocks are synchronized.
:::
These guidelines use the following placeholders for the overall setup:
| Name | Placeholder | Sample value |
| -------------------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------- |
| authentik Application Name | `auth.applicationName` | SharePoint SE |
| authentik Application Slug | `auth.applicationSlug` | sharepoint-se |
| authentik OIDC Name | `auth.providerName` | OIDC-SP |
| authentik OIDC Configuration URL | `auth.providerConfigURL` | https://authentik.company/application/o/sharepoint-se/.well-known/openid-configuration |
| authentik OIDC Client ID | `auth.providerClientID` | 0ab1c234d567ef8a90123bc4567890e12fa3b45c |
| authentik OIDC Redirect URIs | `auth.providerRedirectURI` | https://sharepoint.company/.\* |
| (Optional) authentik LDAP Outpost URI | `ldap.outpostURI` | ak-outpost-ldap.authentik.svc.cluster.local |
| (Optional) authentik LDAP Service Account | `ldap.outpostServiceAccount` | cn=ldapservice,ou=users,dc=ldap,dc=goauthentik,dc=io |
| (Optional) authentik LDAP Service Account Password | `ldap.outpostServiceAccountPassword` | mystrongpassword |
| SharePoint Default Web Application URL | `sp.webAppURL` | https://sharepoint.company |
| SharePoint Trusted Token Issuer Name | `sp.issuerName` | Authentik |
| SharePoint Trusted Token Issuer Description | `sp.issuerDesc` | authentik IDP |
## authentik configuration
### Step 1: Create authentik OpenID Property Mappings
SharePoint requires additional properties within the OpenID and profile scopes in order to operate OIDC properly and be able to map incoming authentik OID Claims with Microsoft Claims.
Additional information from Microsoft documentation:
- https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens#validate-tokens
- https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference#payload-claims
#### Add an OpenID scope mapping for SharePoint
From the authentik Admin Dashboard:
1. Open **Customisation > Property Mappings** page from the sidebar.
2. Click **Create** from the property mapping list command bar.
3. Within the new property mapping form, select **Scope Mapping**.
4. Click **Next** and enter the following values:
- **Name**: SPopenid
- **Scope name**: openid
- **Expression**:
```python
return {
"nbf": "0", # Identifies the time before which the JWT can't be accepted for processing.
# 0 stand for the date 1970-01-01 in unix timestamp
"oid": user.uid, # This ID uniquely identifies the user across applications - two different applications signing in the same user receives the same value in the oid claim.
"upn": user.username # (Optional) User Principal Name, used for troubleshooting within JWT tokens or to setup SharePoint like ADFS
}
```
5. Click **Finish**.
#### Add a profile scope mapping for SharePoint
From the authentik Admin Dashboard:
1. Open **Customisation > Property Mappings** page from the sidebar.
2. Click **Create** from the property mapping list command bar.
3. Within the new property mapping form, select **Scope Mapping**.
4. Click **Next** and enter the following values:
- **Name**: SPprofile
- **Scope name**: profile
- **Expression**:
```python
return {
"name": request.user.name, # The name claim provides a human-readable value that identifies the subject of the token.
"given_name": request.user.name, # Interoperability with Microsoft Entra ID
"unique_name": request.user.name, # (Optional) Used for troubleshooting within JWT tokens or to setup SharePoint like ADFS
"preferred_username": request.user.username, # (Optional) The primary username that represents the user.
"nickname": request.user.username, # (Optional) Used for troubleshooting within JWT tokens or to setup SharePoint like ADFS
"roles": [group.name for group in request.user.ak_groups.all()], # The set of roles that were assigned to the user who is logging in.
}
```
5. Click **Finish**.
### Step 2: Create authentik Open ID Connect Provider
From the authentik Admin Dashboard:
1. Open **Applications > Providers** page from the sidebar.
2. Click **Create** from the provider list command bar.
3. Within the new provider form, select **OAuth2/OpenID Provider**.
4. Click **Next** and enter the following values:
- **Name**: `auth.providerName`
- **Authentication flow**: default-authentication-flow
- **Authorization flow**: default-provider-authorization-implicit-consent
:::note
use the explicit flow if user consents are required
:::
- **Redirect URIs / Origins**: `auth.providerRedirectURI`
- **Signing Key**: authentik Self-signed Certificate
:::note
The certificate is used for signing JWT tokens;, if you change it after the integration do not forget to update your SharePoint Trusted Certificate.
:::
- **Access code validity**: minutes=5
:::note
The minimum is 5 minutes, otherwise SharePoint backend might consider the access code expired.
:::
- **Access Token validity**: minutes=15
:::note
The minimum is 15 minutes, otherwise SharePoint backend will consider the access token expired.
:::
- **Scopes**: select default email, SPopenid and SPprofile
- **Subject mode**: Based on the User's hashed ID
5. Click **Finish**.
### Step 3: Create an application in authentik
From the authentik Admin Dashboard:
1. Open **Applications > Applications** page from the sidebar.
2. Click **Create** from the application list command bar.
3. Within the new application form, enter the following values:
- **Name**: `auth.applicationName`
- **Slug**: `auth.applicationSlug`
- **Provider**: `auth.providerName`
- (Optional) **Launch URL**: `sp.webAppURL`
- (Optional) **Icon**: https://res-1.cdn.office.net/files/fabric-cdn-prod_20221209.001/assets/brand-icons/product/svg/sharepoint_48x1.svg
4. Click **Create**.
### Step 4: Setup OIDC authentication in SharePoint Server
#### Pre-requisites
##### Update SharePoint farm properties
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
:::caution
- Update placeholders
- Read all script's comments
:::
```PowerShell
Add-PSSnapin microsoft.sharepoint.powershell
# Setup farm properties to work with OIDC
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -Provider 'Microsoft Enhanced RSA and AES Cryptographic Provider' -Subject "CN=SharePoint Cookie Cert"
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$fileName = $rsaCert.key.UniqueName
#If you have multiple SharePoint servers in the farm, you need to export certificate by Export-PfxCertificate and import certificate to all other SharePoint servers in the farm by Import-PfxCertificate and apply the same permissions as below.
#After certificate is successfully imported to SharePoint Server, we will need to grant access permission to certificate private key.
$path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\RSA\MachineKeys\$fileName"
$permissions = Get-Acl -Path $path
#Please replace the <web application pool account> with the real application pool account of your web application.
$access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule("$($env:computername)\WSS_WPG", 'Read', 'None', 'None', 'Allow')
$permissions.AddAccessRule($access_rule)
Set-Acl -Path $path -AclObject $permissions
#Then we update farm properties only once.
$f = Get-SPFarm
$f.Farm.Properties['SP-NonceCookieCertificateThumbprint']=$cert.Thumbprint
$f.Farm.Properties['SP-NonceCookieHMACSecretKey']='seed'
$f.Farm.Update()
```
##### SharePoint settings in case of SSL offloading
Update the SharePoint farm to accept OAuth authentication over HTTP.
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
```PowerShell
Add-PSSnapin microsoft.sharepoint.powershell
$c = get-spsecuritytokenserviceconfig
$c.AllowOAuthOverHttp = $true
$c.update()
```
#### Create SharePoint authentication provider
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
:::caution
- Update placeholders
- Read all script's comments.
:::
```PowerShell
Add-PSSnapin microsoft.sharepoint.powershell
# OIDC Settings
$metadataendpointurl = "auth.providerConfigURL"
$clientIdentifier = "auth.providerClientID"
$trustedTokenIssuerName = "sp.issuerName"
$trustedTokenIssuerDescription = "sp.issuerDesc"
# OIDC Claims Mapping
## Identity claim: oid => defined within the Authentik scope mapping
$idClaim = New-SPClaimTypeMapping "http://schemas.microsoft.com/identity/claims/objectidentifier" -IncomingClaimTypeDisplayName "oid" -SameAsIncoming
## User claims mappings
$claims = @(
$idClaim
## User Roles (Group membership)
,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::Role) -IncomingClaimTypeDisplayName "Role" -SameAsIncoming)
## User email
,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::Email) -IncomingClaimTypeDisplayName "Email" -SameAsIncoming)
## User given_name
,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::GivenName) -IncomingClaimTypeDisplayName "GivenName" -SameAsIncoming )
## (Optional) User account name
#,(New-SPClaimTypeMapping ([System.Security.Claims.ClaimTypes]::NameIdentifier) -IncomingClaimTypeDisplayName "Username" -SameAsIncoming)
)
# Trust 3rd party identity token issuer
$trustedTokenIssuer = New-SPTrustedIdentityTokenIssuer -Name $trustedTokenIssuerName -Description $trustedTokenIssuerDescription -ClaimsMappings $claims -IdentifierClaim $idClaim.InputClaimType -DefaultClientIdentifier $clientIdentifier -MetadataEndPoint $metadataendpointurl -Scope "openid email profile"
#Note: Remove the profile scope if you plan to use the LDAPCP claims augmentation.
# Create the SharePoint authentication provider based on the trusted token issuer
New-SPAuthenticationProvider -TrustedIdentityTokenIssuer $trustedTokenIssuer
```
#### Configure SharePoint web applications
From the Central Administration opened as a Farm Administrator:
1. Open the **Application Management > Manage web applications** page.
2. Select your web application `sp.webAppURL`.
3. Click **Authentication Providers** from the ribbon bar.
4. According to your environment, click on the target zone such as "Default".
5. Update the authentication provider form as following:
- Check **Trusted Identity Provider**
- Check the newly created provider named `sp.issuerName`
- (Optional) Set **Custom Sign In Page**: /\_trust/default.aspx
6. Click **Save**.
Repeat all steps for each target web applications that matches with `auth.providerRedirectURI`.
## (Optional) SharePoint enhancements
Objectives :
- Integrate SharePoint People Picker with authentik to search users and groups
- Augment SharePoint user claims at login stage
- Resolve user's membership
:::caution
[LDAPCP](https://www.ldapcp.com/docs/overview/introduction/) must be installed on the target SharePoint farm.
:::
### Step 1: Assign LDAPCP as claim provider for the identity token issuer
The following PowerShell script must be updated according to your environment and executed as **Farm Admin account** with **elevated privileges** on a SharePoint Server.
:::caution
- Update placeholders
- Read all script's comments
:::
```PowerShell
Add-PSSnapin microsoft.sharepoint.powershell
$trustedTokenIssuerName = "sp.issuerName"
$sptrust = Get-SPTrustedIdentityTokenIssuer $trustedTokenIssuerName
$sptrust.ClaimProviderName = "LDAPCP"
$sptrust.Update()
```
### Step 2: Configure LDAPCP claim types
From the SharePoint Central Administration opened as a Farm Administrator:
1. Open **Security > LDAPCP Configuration > Claim types configuration** page.
2. Update the mapping table to match these value:
| Claim type | Entity type | LDAP class | LDAP Attribute to query | LDAP attribute to display | PickerEntity metadata |
| ------------------------------------------------------------- | ----------- | ---------- | ----------------------- | ------------------------- | --------------------- |
| http://schemas.microsoft.com/identity/claims/objectidentifier | User | user | uid | sn | UserId |
| LDAP attribute linked to the main mapping for object User | User | user | mail | | Email |
| LDAP attribute linked to the main mapping for object User | User | user | sn | | DisplayName |
| http://schemas.microsoft.com/ws/2008/06/identity/claims/role | Group | group | cn | | DisplayName |
| LDAP attribute linked to the main mapping for object Group | Group | group | uid | | SPGroupID |
### Step 3: Create an authentik LDAP Outpost
From the authentik Admin Dashboard:
:::note
The following procedure apply to an authentik deployment within Kubernetes.
For other kinds of deployment, please refer to the [authentik documentation](https://goauthentik.io/docs/).
:::
1. Follow authentik [LDAP Provider Generic Setup](https://version-2023-10.goauthentik.io/docs/providers/ldap/generic_setup) with the following steps :
- **Create User/Group** to create a "service account" for `ldap.outpostServiceAccount` and a searchable group of users & groups
- **LDAP Flow** to create the authentication flow for the LDAP Provider
- **LDAP Provider** to create an LDAP provider which can be consumed by the LDAP Application
2. Open **Applications > Applications** page from the sidebar.
3. Open the edit form of your application `auth.applicationName`.
4. In the edit form:
- **Backchannel Providers**: add the LDAP provider previously created
5. Click **Update**.
### Step 4: Configure LDAPCP global configuration
From the SharePoint Central Administration opened as a Farm Administrator:
1. Open the **Security > LDAPCP Configuration > Global configuration** page.
2. Add an LDAP connection with th following properties:
- **LDAP Path**: LDAP://`ldap.outpostURI`/dc=ldap,dc=goauthentik,dc=io
- **Username**: `ldap.outpostServiceAccount`
- **Password**: `ldap.outpostServiceAccountPassword`
- **Authentication types**: check ServerBind
3. Augmentation - Check **Enable augmentation**
4. Augmentation - Select the Role claim "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
5. Augmentation - Check only "**Query this server**" for your `ldap.outpostURI`
6. User identifier properties:
- **LDAP class**: user
- **LDAP attribute**: uid
7. Display of user identifier results:
- Tick **Show the value of another LDAP attribute**: sn
8. Click on "**OK**"
_Note: The `ldap.outpostURI` should be the IP, hostname, or FQDN of the LDAP Outpost service deployed accessible by your SharePoint farm_.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -38,10 +38,9 @@ Use the following settings:
- Consumer key: `*Application (client) ID* value from above` - Consumer key: `*Application (client) ID* value from above`
- Consumer secret: `*Value* of the secret from above` - Consumer secret: `*Value* of the secret from above`
If you kept the default _Supported account types_ selection of _Single tenant_, then you must change the URLs below as well: If you kept the default _Supported account types_ selection of _Single tenant_, then you must change the URL below as well:
- Authorization URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/oauth2/v2.0/authorize` - OIDC Well-known URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/v2.0/.well-known/openid-configuration`
- Access token URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/oauth2/v2.0/token`
![](./authentik_01.png) ![](./authentik_01.png)

View File

@ -19,7 +19,7 @@
"clsx": "^2.0.0", "clsx": "^2.0.0",
"disqus-react": "^1.1.5", "disqus-react": "^1.1.5",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"prism-react-renderer": "^2.3.0", "prism-react-renderer": "^2.3.1",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-before-after-slider-component": "^1.1.8", "react-before-after-slider-component": "^1.1.8",
@ -13786,9 +13786,9 @@
} }
}, },
"node_modules/prism-react-renderer": { "node_modules/prism-react-renderer": {
"version": "2.3.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.0.tgz", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz",
"integrity": "sha512-UYRg2TkVIaI6tRVHC5OJ4/BxqPUxJkJvq/odLT/ykpt1zGYXooNperUxQcCvi87LyRnR4nCh81ceOA+e7nrydg==", "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==",
"dependencies": { "dependencies": {
"@types/prismjs": "^1.26.0", "@types/prismjs": "^1.26.0",
"clsx": "^2.0.0" "clsx": "^2.0.0"

View File

@ -26,7 +26,7 @@
"clsx": "^2.0.0", "clsx": "^2.0.0",
"disqus-react": "^1.1.5", "disqus-react": "^1.1.5",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"prism-react-renderer": "^2.3.0", "prism-react-renderer": "^2.3.1",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"react-before-after-slider-component": "^1.1.8", "react-before-after-slider-component": "^1.1.8",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -32,6 +32,7 @@ module.exports = {
"services/paperless-ng/index", "services/paperless-ng/index",
"services/rocketchat/index", "services/rocketchat/index",
"services/roundcube/index", "services/roundcube/index",
"services/sharepoint-se/index",
"services/vikunja/index", "services/vikunja/index",
"services/wekan/index", "services/wekan/index",
"services/wiki-js/index", "services/wiki-js/index",