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
with:
fetch-depth: 0
- name: Setup authentik env
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: checkout stable
run: |
# Delete all poetry envs
@ -76,7 +72,7 @@ jobs:
git checkout version/$(python -c "from authentik import __version__; print(__version__)")
rm -rf .github/ scripts/
mv ../.github ../scripts .
- name: Setup authentik env (ensure stable deps are installed)
- name: Setup authentik env (stable)
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
@ -90,15 +86,20 @@ jobs:
git clean -d -fx .
git checkout $GITHUB_SHA
# 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)
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: migrate to latest
run: |
poetry install
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:
name: test-unittest - PostgreSQL ${{ matrix.psql }}
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
gen-clean:
rm -rf web/api/src/
rm -rf api/
rm -rf gen-go-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
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.tests.utils import create_test_admin_user, create_test_flow
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.models import AccessToken, OAuth2Provider
@ -49,8 +51,12 @@ class TestAPIAuth(TestCase):
with self.assertRaises(AuthenticationFailed):
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"""
outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first()
outpost.user.delete()
outpost.delete()
with self.assertRaises(AuthenticationFailed):
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 rest_framework.decorators import action
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.response import Response
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.tasks import apply_blueprint, blueprints_find_dict
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:
@ -28,7 +28,7 @@ class MetadataSerializer(PassiveSerializer):
"""Serializer for blueprint metadata"""
name = CharField()
labels = JSONField()
labels = JSONDictField()
class BlueprintInstanceSerializer(ModelSerializer):

View File

@ -2,11 +2,11 @@
from typing import TYPE_CHECKING
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 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:
from authentik.blueprints.models import BlueprintInstance
@ -17,7 +17,7 @@ LOGGER = get_logger()
class ApplyBlueprintMetaSerializer(PassiveSerializer):
"""Serializer for meta apply blueprint model"""
identifiers = JSONField(validators=[is_dict])
identifiers = JSONDictField()
required = BooleanField(default=True)
# 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 guardian.shortcuts import get_objects_for_user
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.response import Response
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.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.rbac.api.roles import RoleSerializer
@ -24,7 +24,7 @@ from authentik.rbac.api.roles import RoleSerializer
class GroupMemberSerializer(ModelSerializer):
"""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)
class Meta:
@ -44,7 +44,7 @@ class GroupMemberSerializer(ModelSerializer):
class GroupSerializer(ModelSerializer):
"""Group Serializer"""
attributes = JSONField(validators=[is_dict], required=False)
attributes = JSONDictField(required=False)
users_obj = ListSerializer(
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 rest_framework.decorators import action
from rest_framework.fields import (
CharField,
IntegerField,
JSONField,
ListField,
SerializerMethodField,
)
from rest_framework.fields import CharField, IntegerField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
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.brands.models import Brand
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 (
SESSION_KEY_IMPERSONATE_ORIGINAL_USER,
SESSION_KEY_IMPERSONATE_USER,
@ -88,7 +82,7 @@ LOGGER = get_logger()
class UserGroupSerializer(ModelSerializer):
"""Simplified Group Serializer for user's groups"""
attributes = JSONField(required=False)
attributes = JSONDictField(required=False)
parent_name = CharField(source="parent.name", read_only=True)
class Meta:
@ -109,7 +103,7 @@ class UserSerializer(ModelSerializer):
is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True)
attributes = JSONField(validators=[is_dict], required=False)
attributes = JSONDictField(required=False)
groups = PrimaryKeyRelatedField(
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 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.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.")
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):
"""Base serializer class which doesn't implement create/update methods"""
@ -26,7 +44,7 @@ class PassiveSerializer(Serializer):
class PropertyMappingPreviewSerializer(PassiveSerializer):
"""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):

View File

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

View File

@ -44,28 +44,14 @@
{% block body %}
<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>
<ak-message-container></ak-message-container>
<div class="pf-c-login">
<div class="pf-c-login stacked">
<div class="ak-login-container">
<header class="pf-c-login__header">
<div class="pf-c-brand ak-brand">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo }}" alt="authentik Logo" />
</div>
</header>
{% block main_container %}
<main class="pf-c-login__main">
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
@ -77,7 +63,6 @@
{% endblock %}
</div>
</main>
{% endblock %}
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
{% 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.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.policies.models import PolicyBindingModel
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.email.utils import TemplateEmailMessage
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["on_behalf_of"] = get_user(request.session[SESSION_KEY_IMPERSONATE_USER])
# 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
self.with_geoip()
# 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
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
@ -31,4 +31,19 @@ class Migration(migrations.Migration):
operations = [
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_UNAUTHENTICATED = "require_unauthenticated"
REQUIRE_SUPERUSER = "require_superuser"
REQUIRE_OUTPOST = "require_outpost"
class NotConfiguredAction(models.TextChoices):

View File

@ -23,6 +23,7 @@ from authentik.flows.models import (
)
from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger()
PLAN_CONTEXT_PENDING_USER = "pending_user"
@ -141,6 +142,10 @@ class FlowPlanner:
and not request.user.is_superuser
):
raise FlowNonApplicableException()
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
outpost_user = ClientIPMiddleware.get_outpost_user(request)
if not outpost_user:
raise FlowNonApplicableException()
def plan(
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 guardian.shortcuts import get_anonymous_user
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
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.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
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.models import PolicyBinding
from authentik.policies.types import PolicyResult
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.dummy.models import DummyStage
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
@ -68,6 +72,34 @@ class TestFlowPlanner(TestCase):
planner.allow_empty_flows = True
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(
"authentik.policies.engine.PolicyEngine.result",
POLICY_RETURN_FALSE,

View File

@ -8,6 +8,8 @@ postgresql:
password: "env://POSTGRES_PASSWORD"
use_pgbouncer: false
use_pgpool: false
test:
name: test_authentik
listen:
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.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.root.middleware import ClientIPMiddleware
class TestHTTP(TestCase):
@ -22,12 +22,12 @@ class TestHTTP(TestCase):
def test_normal(self):
"""Test normal request"""
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):
"""Test x-forwarded-for request"""
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):
"""Test faked IP which is overridden by an outpost"""
@ -38,28 +38,28 @@ class TestHTTP(TestCase):
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: "abc",
ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
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
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key,
ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
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
self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
self.user.save()
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key,
ClientIPMiddleware.outpost_remote_ip_header: "1.2.3.4",
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"""
from typing import Any, Optional
from django.http import HttpRequest
from requests.sessions import Session
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
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()
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:
"""Get a common user agent"""
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.request import Request
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 authentik import get_build_hash
from authentik.core.api.providers import ProviderSerializer
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.outposts.api.service_connections import ServiceConnectionSerializer
from authentik.outposts.apps import MANAGED_OUTPOST
@ -34,7 +34,7 @@ from authentik.providers.radius.models import RadiusProvider
class OutpostSerializer(ModelSerializer):
"""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
# is checked for other providers in the API Viewset
providers = PrimaryKeyRelatedField(
@ -95,7 +95,7 @@ class OutpostSerializer(ModelSerializer):
class OutpostDefaultConfigSerializer(PassiveSerializer):
"""Global default outpost config"""
config = JSONField(read_only=True)
config = JSONDictField(read_only=True)
class OutpostHealthSerializer(PassiveSerializer):

View File

@ -1,8 +1,8 @@
"""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 authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import User
@ -10,7 +10,7 @@ class PolicyTestSerializer(PassiveSerializer):
"""Test policy execution for a user with context"""
user = PrimaryKeyRelatedField(queryset=User.objects.all())
context = JSONField(required=False, validators=[is_dict])
context = JSONDictField(required=False)
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.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.http import get_client_ip
from authentik.policies.exceptions import PolicyException
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger()
if TYPE_CHECKING:
@ -49,7 +49,7 @@ class PolicyEvaluator(BaseEvaluator):
"""Update context based on http request"""
# update website/docs/expressions/_objects.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
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.lib.config import CONFIG
from authentik.lib.models import SerializerModel
from authentik.lib.utils.http import get_client_ip
from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger()
CACHE_KEY_PREFIX = "goauthentik.io/policies/reputation/scores/"
@ -44,7 +44,7 @@ class ReputationPolicy(Policy):
return "ak-policy-reputation-form"
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()
if self.check_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.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.tasks import save_reputation
from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.identification.signals import identification_failed
LOGGER = get_logger()
@ -18,7 +18,7 @@ CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_reputation")
def update_score(request: HttpRequest, identifier: str, amount: int):
"""Update score for IP and User"""
remote_ip = get_client_ip(request)
remote_ip = ClientIPMiddleware.get_client_ip(request)
try:
# 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 authentik.events.geo import GEOIP_READER
from authentik.lib.utils.http import get_client_ip
if TYPE_CHECKING:
from authentik.core.models import User
@ -38,10 +37,12 @@ class PolicyRequest:
def set_http_request(self, request: HttpRequest): # pragma: no cover
"""Load data from HTTP request, including geoip when enabled"""
from authentik.root.middleware import ClientIPMiddleware
self.http_request = request
if not GEOIP_READER.enabled:
return
client_ip = get_client_ip(request)
client_ip = ClientIPMiddleware.get_client_ip(request)
if not client_ip:
return
self.context["geoip"] = GEOIP_READER.city(client_ip)

View File

@ -1,2 +1,3 @@
"""SCIM constants"""
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.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
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.exceptions import SCIMRequestException, StopSync
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")
users_paginator = Paginator(provider.get_user_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():
try:
for page in users_paginator.page_range:
@ -69,7 +72,10 @@ def scim_sync(self: MonitoredTask, provider_pk: int) -> None:
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):
"""Sync single or multiple users to SCIM"""
messages = []

View File

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

View File

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

View File

@ -2,7 +2,7 @@
from hashlib import sha512
from time import time
from timeit import default_timer
from typing import Callable
from typing import Any, Callable, Optional
from django.conf import settings
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.http import http_date
from jwt import PyJWTError, decode, encode
from sentry_sdk.hub import Hub
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")
ACR_AUTHENTIK_SESSION = "goauthentik.io/core/default"
@ -156,6 +157,111 @@ class CsrfViewMiddleware(UpstreamCsrfViewMiddleware):
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:
"""Logging middleware for channels"""
@ -201,7 +307,7 @@ class LoggingMiddleware:
"""Log request"""
LOGGER.info(
request.get_full_path(),
remote=get_client_ip(request),
remote=ClientIPMiddleware.get_client_ip(request),
method=request.method,
scheme=request.scheme,
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",
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
@ -219,7 +220,7 @@ DJANGO_REDIS_SCAN_ITERSIZE = 1000
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
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"
# Configured via custom SessionMiddleware
# SESSION_COOKIE_SAMESITE = "None"
@ -232,7 +233,7 @@ MIDDLEWARE = [
"django_tenants.middleware.default.DefaultTenantMiddleware",
"authentik.root.middleware.LoggingMiddleware",
"django_prometheus.middleware.PrometheusBeforeMiddleware",
"authentik.brands.middleware.BrandMiddleware",
"authentik.root.middleware.ClientIPMiddleware",
"authentik.root.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"authentik.core.middleware.RequestIDMiddleware",
@ -295,6 +296,9 @@ DATABASES = {
"SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
"SSLCERT": CONFIG.get("postgresql.sslcert"),
"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):
"""OTP Time Digits"""
SIX = 6, _("6 digits, widely compatible")
EIGHT = 8, _("8 digits, not compatible with apps like Google Authenticator")
SIX = "6", _("6 digits, widely compatible")
EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator")
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.utils.translation import gettext 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 structlog.stdlib import get_logger
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.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.signals import login_failed
from authentik.events.models import Event, EventAction
from authentik.flows.stage import StageView
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.models import Device
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
@ -40,7 +40,7 @@ class DeviceChallenge(PassiveSerializer):
device_class = CharField()
device_uid = CharField()
challenge = JSONField()
challenge = JSONDictField()
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(
"auto",
user_id=device.duo_user_id,
ipaddr=get_client_ip(stage_view.request),
ipaddr=ClientIPMiddleware.get_client_ip(stage_view.request),
type=__(
"%(brand_name)s Login request"
% {

View File

@ -6,10 +6,10 @@ from typing import Optional
from django.conf import settings
from django.http import HttpRequest, HttpResponse
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 authentik.core.api.utils import PassiveSerializer
from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
@ -68,7 +68,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
selected_stage = CharField(required=False)
code = CharField(required=False)
webauthn = JSONField(required=False)
webauthn = JSONDictField(required=False)
duo = IntegerField(required=False)
component = CharField(default="ak-stage-authenticator-validate")

View File

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

View File

@ -12,7 +12,8 @@ from authentik.flows.challenge import (
WithUserInfoChallenge,
)
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
@ -42,7 +43,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
data={
"secret": stage.private_key,
"response": token,
"remoteip": get_client_ip(self.stage.request),
"remoteip": ClientIPMiddleware.get_client_ip(self.stage.request),
},
)
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.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
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.root.middleware import ClientIPMiddleware
from authentik.sources.oauth.types.apple import AppleLoginChallenge
from authentik.sources.plex.models import PlexAuthenticationChallenge
from authentik.stages.identification.models import IdentificationStage
@ -103,7 +103,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
self.stage.logger.info(
"invalid_login",
identifier=uid_field,
client_ip=get_client_ip(self.stage.request),
client_ip=ClientIPMiddleware.get_client_ip(self.stage.request),
action="invalid_identifier",
context={
"stage": sanitize_item(self.stage),

View File

@ -1,13 +1,12 @@
"""Invitation Stage API Views"""
from django_filters.filters import BooleanFilter
from django_filters.filterset import FilterSet
from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.groups import GroupMemberSerializer
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.stages import StageSerializer
from authentik.stages.invitation.models import Invitation, InvitationStage
@ -47,7 +46,7 @@ class InvitationSerializer(ModelSerializer):
"""Invitation Serializer"""
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")
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.views.executor import SESSION_KEY_PLAN
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.root.middleware import ClientIPMiddleware
from authentik.stages.user_login.models import UserLoginStage
@ -76,7 +76,7 @@ class TestUserLoginStage(FlowTestCase):
other_session = AuthenticatedSession.objects.create(
user=self.user,
session_key=key,
last_ip=DEFAULT_IP,
last_ip=ClientIPMiddleware.default_ip,
)
cache.set(f"{KEY_PREFIX}{other_session.session_key}", "foo")

View File

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

View File

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

View File

@ -81,7 +81,6 @@ var rootCmd = &cobra.Command{
for {
<-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-ldap/ldap/v3 v3.4.6
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/google/uuid v1.5.0
github.com/gorilla/handlers v1.5.2
@ -27,7 +27,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
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/oauth2 v0.15.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/stdr v1.2.2 // 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/jsonreference v0.20.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/metric 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/sys v0.15.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/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.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY=
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.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
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/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.9 h1:LnEGOO9qyEC1v22Bzr323M98G13paIUGPU7yeJtG9Xs=
github.com/go-openapi/strfmt v0.21.9/go.mod h1:0k3v301mglEaZRJdDDGSlN6Npq4VMVU69DE0LUyf7uA=
github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk=
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.15/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.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
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.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2023104.3 h1:MzwdB21Q+G+wACEZiX0T1iVV4l7PjopjaVv6muqJE1M=
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-20190510104115-cbcb75029529/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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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 {
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
DB int `yaml:"db" env:"AUTHENTIK_REDIS__DB"`
Username string `yaml:"username" env:"AUTHENTIK_REDIS__USERNAME"`
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
DB int `yaml:"db" env:"AUTHENTIK_REDIS__DB"`
Username string `yaml:"username" env:"AUTHENTIK_REDIS__USERNAME"`
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
}
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
// The service account this token belongs to should only have access to a single outpost
outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration, retrying in 3 seconds")
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
// The service account this token belongs to should only have access to a single outpost
outposts, _, err := a.Client.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration")
return err

View File

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

View File

@ -1,4 +1,3 @@
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,
}, nil
}

View File

@ -34,26 +34,26 @@ func (ds *DirectSearcher) SearchBase(req *search.Request) (ldap.ServerSearchResu
Values: []string{"3"},
},
{
Name: "supportedCapabilities",
Name: "supportedCapabilities",
Values: []string{
"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.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.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.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.2237", //LDAP_CAP_ACTIVE_DIRECTORY_W8_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.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.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.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.2237", // LDAP_CAP_ACTIVE_DIRECTORY_W8_OID
},
},
{
Name: "supportedControl",
Name: "supportedControl",
Values: []string{
"2.16.840.1.113730.3.4.9", //VLV Request 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.319", //Paged Result 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
"1.2.840.113556.1.4.474", // Sort result
"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.
for _, u := range g.UsersObj {
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.SetUsers([]int32{flag.UserPk})
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") {
re, err := regexp.Compile(regex)
if err != nil {
//TODO: maybe create event for this?
// TODO: maybe create event for this?
a.log.WithError(err).Warning("failed to compile SkipPathRegex")
continue
} else {

View File

@ -59,12 +59,12 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
userAttributes := c.Proxy.UserAttributes
a.setAuthorizationHeader(headers, c)
// 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")
if additionalHeaders == nil {
return
}
for key, value := range additionalHeaders {
for key, value := range additionalHeaders.(map[string]interface{}) {
headers.Set(key, toString(value))
}
}

View File

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

View File

@ -26,26 +26,13 @@
</head>
<body>
<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 class="pf-c-login">
<div class="pf-c-login stacked">
<div class="ak-login-container">
<header class="pf-c-login__header">
<div class="pf-c-brand ak-brand">
<main class="pf-c-login__main">
<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" />
</div>
</header>
<main class="pf-c-login__main">
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{{ .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{
"username": username,
"client": r.RemoteAddr(),
"requestId": r.ID,
"requestId": r.ID(),
})
fe.DelegateClientIP(r.RemoteAddr())
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()
if err != nil {
r.Log().WithField("username", username).WithError(err).Warning("failed to execute flow")
metrics.RequestsRejected.With(prometheus.Labels{

View File

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

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -100,170 +100,170 @@ msgstr ""
msgid "Create a SAML Provider by importing its Metadata."
msgstr ""
#: authentik/core/api/users.py:155
#: authentik/core/api/users.py:149
msgid "No leading or trailing slashes allowed."
msgstr ""
#: authentik/core/api/users.py:158
#: authentik/core/api/users.py:152
msgid "No empty segments in user path allowed."
msgstr ""
#: authentik/core/models.py:86
#: authentik/core/models.py:85
msgid "name"
msgstr ""
#: authentik/core/models.py:88
#: authentik/core/models.py:87
msgid "Users added to this group will be superusers."
msgstr ""
#: authentik/core/models.py:162
#: authentik/core/models.py:161
msgid "Group"
msgstr ""
#: authentik/core/models.py:163
#: authentik/core/models.py:162
msgid "Groups"
msgstr ""
#: authentik/core/models.py:178
#: authentik/core/models.py:177
msgid "User's display name."
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"
msgstr ""
#: authentik/core/models.py:275
#: authentik/core/models.py:274
msgid "Users"
msgstr ""
#: authentik/core/models.py:277
#: authentik/core/models.py:276
#: authentik/stages/email/templates/email/password_reset.html:28
msgid "Reset Password"
msgstr ""
#: authentik/core/models.py:278
#: authentik/core/models.py:277
msgid "Can impersonate other users"
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"
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"
msgstr ""
#: authentik/core/models.py:294
#: authentik/core/models.py:293
msgid ""
"Flow used for authentication when the associated application is accessed by "
"an un-authenticated user."
msgstr ""
#: authentik/core/models.py:304
#: authentik/core/models.py:303
msgid "Flow used when authorizing this provider."
msgstr ""
#: authentik/core/models.py:316
#: authentik/core/models.py:315
msgid ""
"Accessed from applications; optional backchannel providers for protocols "
"like LDAP and SCIM."
msgstr ""
#: authentik/core/models.py:371
#: authentik/core/models.py:370
msgid "Application's display Name."
msgstr ""
#: authentik/core/models.py:372
#: authentik/core/models.py:371
msgid "Internal application name, used in URLs."
msgstr ""
#: authentik/core/models.py:384
#: authentik/core/models.py:383
msgid "Open launch URL in a new browser tab or window."
msgstr ""
#: authentik/core/models.py:448
#: authentik/core/models.py:447
msgid "Application"
msgstr ""
#: authentik/core/models.py:449
#: authentik/core/models.py:448
msgid "Applications"
msgstr ""
#: authentik/core/models.py:455
#: authentik/core/models.py:454
msgid "Use the source-specific identifier"
msgstr ""
#: authentik/core/models.py:457
#: authentik/core/models.py:456
msgid ""
"Link to a user with identical email address. Can have security implications "
"when a source doesn't validate email addresses."
msgstr ""
#: authentik/core/models.py:461
#: authentik/core/models.py:460
msgid ""
"Use the user's email address, but deny enrollment when the email address "
"already exists."
msgstr ""
#: authentik/core/models.py:464
#: authentik/core/models.py:463
msgid ""
"Link to a user with identical username. Can have security implications when "
"a username is used with another source."
msgstr ""
#: authentik/core/models.py:468
#: authentik/core/models.py:467
msgid ""
"Use the user's username, but deny enrollment when the username already "
"exists."
msgstr ""
#: authentik/core/models.py:475
#: authentik/core/models.py:474
msgid "Source's display Name."
msgstr ""
#: authentik/core/models.py:476
#: authentik/core/models.py:475
msgid "Internal source name, used in URLs."
msgstr ""
#: authentik/core/models.py:495
#: authentik/core/models.py:494
msgid "Flow to use when authenticating existing users."
msgstr ""
#: authentik/core/models.py:504
#: authentik/core/models.py:503
msgid "Flow to use when enrolling new users."
msgstr ""
#: authentik/core/models.py:512
#: authentik/core/models.py:511
msgid ""
"How the source determines if an existing user should be authenticated or a "
"new user enrolled."
msgstr ""
#: authentik/core/models.py:684
#: authentik/core/models.py:683
msgid "Token"
msgstr ""
#: authentik/core/models.py:685
#: authentik/core/models.py:684
msgid "Tokens"
msgstr ""
#: authentik/core/models.py:690
#: authentik/core/models.py:689
msgid "View token's key"
msgstr ""
#: authentik/core/models.py:726
#: authentik/core/models.py:725
msgid "Property Mapping"
msgstr ""
#: authentik/core/models.py:727
#: authentik/core/models.py:726
msgid "Property Mappings"
msgstr ""
#: authentik/core/models.py:762
#: authentik/core/models.py:763
msgid "Authenticated Session"
msgstr ""
#: authentik/core/models.py:763
#: authentik/core/models.py:764
msgid "Authenticated Sessions"
msgstr ""
@ -338,7 +338,7 @@ msgstr ""
msgid "Go home"
msgstr ""
#: authentik/core/templates/login/base_full.html:90
#: authentik/core/templates/login/base_full.html:75
msgid "Powered by authentik"
msgstr ""
@ -388,105 +388,105 @@ msgstr ""
msgid "License Usage Records"
msgstr ""
#: authentik/events/models.py:294
#: authentik/events/models.py:295
msgid "Event"
msgstr ""
#: authentik/events/models.py:295
#: authentik/events/models.py:296
msgid "Events"
msgstr ""
#: authentik/events/models.py:301
#: authentik/events/models.py:302
msgid "authentik inbuilt notifications"
msgstr ""
#: authentik/events/models.py:302
#: authentik/events/models.py:303
msgid "Generic Webhook"
msgstr ""
#: authentik/events/models.py:303
#: authentik/events/models.py:304
msgid "Slack Webhook (Slack/Discord)"
msgstr ""
#: authentik/events/models.py:304
#: authentik/events/models.py:305
msgid "Email"
msgstr ""
#: authentik/events/models.py:322
#: authentik/events/models.py:323
msgid ""
"Only send notification once, for example when sending a webhook into a chat "
"channel."
msgstr ""
#: authentik/events/models.py:387
#: authentik/events/models.py:388
msgid "Severity"
msgstr ""
#: authentik/events/models.py:392
#: authentik/events/models.py:393
msgid "Dispatched for user"
msgstr ""
#: authentik/events/models.py:401
#: authentik/events/models.py:402
msgid "Event user"
msgstr ""
#: authentik/events/models.py:495
#: authentik/events/models.py:496
msgid "Notification Transport"
msgstr ""
#: authentik/events/models.py:496
#: authentik/events/models.py:497
msgid "Notification Transports"
msgstr ""
#: authentik/events/models.py:502
#: authentik/events/models.py:503
msgid "Notice"
msgstr ""
#: authentik/events/models.py:503
#: authentik/events/models.py:504
msgid "Warning"
msgstr ""
#: authentik/events/models.py:504
#: authentik/events/models.py:505
msgid "Alert"
msgstr ""
#: authentik/events/models.py:529
#: authentik/events/models.py:530
msgid "Notification"
msgstr ""
#: authentik/events/models.py:530
#: authentik/events/models.py:531
msgid "Notifications"
msgstr ""
#: authentik/events/models.py:540
#: authentik/events/models.py:541
msgid ""
"Select which transports should be used to notify the user. If none are "
"selected, the notification will only be shown in the authentik UI."
msgstr ""
#: authentik/events/models.py:548
#: authentik/events/models.py:549
msgid "Controls which severity level the created notifications will have."
msgstr ""
#: authentik/events/models.py:553
#: authentik/events/models.py:554
msgid ""
"Define which group of users this notification should be sent and shown to. "
"If left empty, Notification won't ben sent."
msgstr ""
#: authentik/events/models.py:571
#: authentik/events/models.py:572
msgid "Notification Rule"
msgstr ""
#: authentik/events/models.py:572
#: authentik/events/models.py:573
msgid "Notification Rules"
msgstr ""
#: authentik/events/models.py:592
#: authentik/events/models.py:593
msgid "Webhook Mapping"
msgstr ""
#: authentik/events/models.py:593
#: authentik/events/models.py:594
msgid "Webhook Mappings"
msgstr ""
@ -547,7 +547,7 @@ msgstr ""
msgid "Pre-flow policies"
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"
msgstr ""
@ -555,72 +555,72 @@ msgstr ""
msgid "Flow does not apply to current user."
msgstr ""
#: authentik/flows/models.py:114
#: authentik/flows/models.py:115
#, python-format
msgid "Dynamic In-memory stage: %(doc)s"
msgstr ""
#: authentik/flows/models.py:129
#: authentik/flows/models.py:130
msgid "Visible in the URL."
msgstr ""
#: authentik/flows/models.py:131
#: authentik/flows/models.py:132
msgid "Shown as the Title in Flow pages."
msgstr ""
#: authentik/flows/models.py:138
#: authentik/flows/models.py:139
msgid ""
"Decides what this Flow is used for. For example, the Authentication flow is "
"redirect to when an un-authenticated user visits authentik."
msgstr ""
#: authentik/flows/models.py:147
#: authentik/flows/models.py:148
msgid "Background shown during execution"
msgstr ""
#: authentik/flows/models.py:154
#: authentik/flows/models.py:155
msgid ""
"Enable compatibility mode, increases compatibility with password managers on "
"mobile devices."
msgstr ""
#: authentik/flows/models.py:162
#: authentik/flows/models.py:163
msgid "Configure what should happen when a flow denies access to a user."
msgstr ""
#: authentik/flows/models.py:168
#: authentik/flows/models.py:169
msgid "Required level of authentication and authorization to access a flow."
msgstr ""
#: authentik/flows/models.py:194
#: authentik/flows/models.py:195
msgid "Flows"
msgstr ""
#: authentik/flows/models.py:197
#: authentik/flows/models.py:198
msgid "Can export a Flow"
msgstr ""
#: authentik/flows/models.py:198
#: authentik/flows/models.py:199
msgid "Can inspect a Flow's execution"
msgstr ""
#: authentik/flows/models.py:199
#: authentik/flows/models.py:200
msgid "View Flow's cache metrics"
msgstr ""
#: authentik/flows/models.py:200
#: authentik/flows/models.py:201
msgid "Clear Flow's cache metrics"
msgstr ""
#: authentik/flows/models.py:216
#: authentik/flows/models.py:217
msgid "Evaluate policies during the Flow planning process."
msgstr ""
#: authentik/flows/models.py:220
#: authentik/flows/models.py:221
msgid "Evaluate policies when the Stage is present to the user."
msgstr ""
#: authentik/flows/models.py:227
#: authentik/flows/models.py:228
msgid ""
"Configure how the flow executor should handle an invalid response to a "
"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."
msgstr ""
#: authentik/flows/models.py:250
#: authentik/flows/models.py:251
msgid "Flow Stage Binding"
msgstr ""
#: authentik/flows/models.py:251
#: authentik/flows/models.py:252
msgid "Flow Stage Bindings"
msgstr ""
#: authentik/flows/models.py:266
#: authentik/flows/models.py:267
msgid ""
"Flow used by an authenticated user to configure this Stage. If empty, user "
"will not be able to configure this stage."
msgstr ""
#: authentik/flows/models.py:306
#: authentik/flows/models.py:307
msgid "Flow Token"
msgstr ""
#: authentik/flows/models.py:307
#: authentik/flows/models.py:308
msgid "Flow Tokens"
msgstr ""
@ -1526,27 +1526,27 @@ msgstr ""
msgid "Starting full SCIM sync"
msgstr ""
#: authentik/providers/scim/tasks.py:59
#: authentik/providers/scim/tasks.py:62
#, python-format
msgid "Syncing page %(page)d of users"
msgstr ""
#: authentik/providers/scim/tasks.py:63
#: authentik/providers/scim/tasks.py:66
#, python-format
msgid "Syncing page %(page)d of groups"
msgstr ""
#: authentik/providers/scim/tasks.py:92
#: authentik/providers/scim/tasks.py:98
#, python-format
msgid "Failed to sync user %(user_name)s due to remote error: %(error)s"
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
msgid "Stopping sync due to error: %(error)s"
msgstr ""
#: authentik/providers/scim/tasks.py:133
#: authentik/providers/scim/tasks.py:139
#, python-format
msgid "Failed to sync group %(group_name)s due to remote error: %(error)s"
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.core` - authentik Core
* `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:
type: object
description: Application Serializer
@ -28850,12 +28810,14 @@ components:
- require_authenticated
- require_unauthenticated
- require_superuser
- require_outpost
type: string
description: |-
* `none` - None
* `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
AuthenticatorAttachmentEnum:
enum:
- platform
@ -29026,47 +28988,6 @@ components:
- client_id
- client_secret
- 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:
type: object
description: AuthenticatorSMSStage Serializer
@ -29192,44 +29113,6 @@ components:
- from_number
- name
- 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:
type: object
description: AuthenticatorStaticStage Serializer
@ -29316,46 +29199,6 @@ components:
minimum: 0
required:
- 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:
type: object
description: AuthenticatorTOTPStage Serializer
@ -29540,104 +29383,6 @@ components:
* `discouraged` - Discouraged
required:
- 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:
type: object
description: Pseudo class for autosubmit response
@ -29928,50 +29673,6 @@ components:
* `can_impersonate` - Can Impersonate
* `can_debug` - Can Debug
* `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:
type: object
description: CaptchaStage Serializer
@ -30174,20 +29875,10 @@ components:
ChallengeTypes:
oneOf:
- $ref: '#/components/schemas/AccessDeniedChallenge'
- $ref: '#/components/schemas/AppleLoginChallenge'
- $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/CaptchaChallenge'
- $ref: '#/components/schemas/ConsentChallenge'
- $ref: '#/components/schemas/DummyChallenge'
- $ref: '#/components/schemas/EmailChallenge'
- $ref: '#/components/schemas/FlowErrorChallenge'
- $ref: '#/components/schemas/IdentificationChallenge'
- $ref: '#/components/schemas/OAuthDeviceCodeChallenge'
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
- $ref: '#/components/schemas/PasswordChallenge'
@ -30195,25 +29886,14 @@ components:
- $ref: '#/components/schemas/PromptChallenge'
- $ref: '#/components/schemas/RedirectChallenge'
- $ref: '#/components/schemas/ShellChallenge'
- $ref: '#/components/schemas/UserLoginChallenge'
discriminator:
propertyName: component
mapping:
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
ak-source-oauth-apple: '#/components/schemas/AppleLoginChallenge'
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-captcha: '#/components/schemas/CaptchaChallenge'
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-identification: '#/components/schemas/IdentificationChallenge'
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge'
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
ak-stage-password: '#/components/schemas/PasswordChallenge'
@ -30221,7 +29901,6 @@ components:
ak-stage-prompt: '#/components/schemas/PromptChallenge'
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
xak-flow-shell: '#/components/schemas/ShellChallenge'
ak-stage-user-login: '#/components/schemas/UserLoginChallenge'
ClientTypeEnum:
enum:
- confidential
@ -30404,7 +30083,7 @@ components:
cancel_url:
type: string
layout:
$ref: '#/components/schemas/LayoutEnum'
$ref: '#/components/schemas/FlowLayoutEnum'
required:
- cancel_url
- layout
@ -30565,38 +30244,6 @@ components:
- type
- verbose_name
- 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:
enum:
- static
@ -30753,33 +30400,6 @@ components:
required:
- domain
- 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:
type: object
description: Dummy Policy Serializer
@ -30954,35 +30574,6 @@ components:
* `success` - Success
* `waiting` - Waiting
* `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:
type: object
description: EmailStage Serializer
@ -31983,7 +31574,7 @@ components:
description: Get export URL for flow
readOnly: true
layout:
$ref: '#/components/schemas/LayoutEnum'
$ref: '#/components/schemas/FlowLayoutEnum'
denied_action:
allOf:
- $ref: '#/components/schemas/DeniedActionEnum'
@ -32003,6 +31594,7 @@ components:
* `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
required:
- background
- cache_count
@ -32017,47 +31609,25 @@ components:
- title
FlowChallengeResponseRequest:
oneOf:
- $ref: '#/components/schemas/AppleChallengeResponseRequest'
- $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/CaptchaChallengeResponseRequest'
- $ref: '#/components/schemas/ConsentChallengeResponseRequest'
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
- $ref: '#/components/schemas/OAuthDeviceCodeChallengeResponseRequest'
- $ref: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
- $ref: '#/components/schemas/PasswordChallengeResponseRequest'
- $ref: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
- $ref: '#/components/schemas/PromptChallengeResponseRequest'
- $ref: '#/components/schemas/UserLoginChallengeResponseRequest'
discriminator:
propertyName: component
mapping:
ak-source-oauth-apple: '#/components/schemas/AppleChallengeResponseRequest'
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-captcha: '#/components/schemas/CaptchaChallengeResponseRequest'
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-finish: '#/components/schemas/OAuthDeviceCodeFinishChallengeResponseRequest'
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
ak-source-plex: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
ak-stage-prompt: '#/components/schemas/PromptChallengeResponseRequest'
ak-stage-user-login: '#/components/schemas/UserLoginChallengeResponseRequest'
FlowDesignationEnum:
enum:
- authentication
@ -32170,6 +31740,20 @@ components:
- next_planned_stage
- plan_context
- 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:
type: object
description: Flow Serializer
@ -32207,7 +31791,7 @@ components:
description: Enable compatibility mode, increases compatibility with password
managers on mobile devices.
layout:
$ref: '#/components/schemas/LayoutEnum'
$ref: '#/components/schemas/FlowLayoutEnum'
denied_action:
allOf:
- $ref: '#/components/schemas/DeniedActionEnum'
@ -32227,6 +31811,7 @@ components:
* `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
required:
- designation
- name
@ -32285,7 +31870,7 @@ components:
description: Get export URL for flow
readOnly: true
layout:
$ref: '#/components/schemas/LayoutEnum'
$ref: '#/components/schemas/FlowLayoutEnum'
denied_action:
allOf:
- $ref: '#/components/schemas/DeniedActionEnum'
@ -32341,7 +31926,7 @@ components:
description: Enable compatibility mode, increases compatibility with password
managers on mobile devices.
layout:
$ref: '#/components/schemas/LayoutEnum'
$ref: '#/components/schemas/FlowLayoutEnum'
denied_action:
allOf:
- $ref: '#/components/schemas/DeniedActionEnum'
@ -32626,68 +32211,6 @@ components:
format: uuid
required:
- 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:
type: object
description: IdentificationStage Serializer
@ -33725,20 +33248,6 @@ components:
required:
- is_running
- 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:
type: object
description: License Serializer
@ -33833,17 +33342,6 @@ components:
type: string
required:
- 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:
type: object
description: Login Metrics per 1h
@ -33867,20 +33365,6 @@ components:
- authorizations
- logins
- 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:
type: object
description: Serializer for blueprint metadata
@ -34690,7 +34174,9 @@ components:
starts with http it is returned as-is
readOnly: true
provider_type:
$ref: '#/components/schemas/ProviderTypeEnum'
allOf:
- $ref: '#/components/schemas/ProviderTypeEnum'
description: ''
request_token_url:
type: string
nullable: true
@ -34788,7 +34274,9 @@ components:
type: string
minLength: 1
provider_type:
$ref: '#/components/schemas/ProviderTypeEnum'
allOf:
- $ref: '#/components/schemas/ProviderTypeEnum'
description: ''
request_token_url:
type: string
nullable: true
@ -37281,7 +36769,7 @@ components:
description: Enable compatibility mode, increases compatibility with password
managers on mobile devices.
layout:
$ref: '#/components/schemas/LayoutEnum'
$ref: '#/components/schemas/FlowLayoutEnum'
denied_action:
allOf:
- $ref: '#/components/schemas/DeniedActionEnum'
@ -37301,6 +36789,7 @@ components:
* `require_authenticated` - Require Authenticated
* `require_unauthenticated` - Require Unauthenticated
* `require_superuser` - Require Superuser
* `require_outpost` - Require Outpost
PatchedFlowStageBindingRequest:
type: object
description: FlowStageBinding Serializer
@ -37894,7 +37383,9 @@ components:
type: string
minLength: 1
provider_type:
$ref: '#/components/schemas/ProviderTypeEnum'
allOf:
- $ref: '#/components/schemas/ProviderTypeEnum'
description: ''
request_token_url:
type: string
nullable: true
@ -39866,35 +39357,8 @@ components:
- authorization_flow
- name
ProviderTypeEnum:
enum:
- apple
- 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
enum: []
type: boolean
ProxyMode:
enum:
- proxy
@ -41517,24 +40981,6 @@ components:
- expression
- 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:
type: object
description: ServiceConnection Serializer
@ -42702,43 +42148,6 @@ components:
additionalProperties: {}
required:
- 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:
type: object
description: UserLoginStage Serializer

View File

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

View File

@ -4,13 +4,13 @@
"type": "module",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@wdio/cli": "^8.26.1",
"@wdio/local-runner": "^8.26.1",
"@wdio/mocha-framework": "^8.24.12",
"@wdio/spec-reporter": "^8.24.12",
"eslint": "^8.55.0",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"@wdio/cli": "^8.26.3",
"@wdio/local-runner": "^8.26.3",
"@wdio/mocha-framework": "^8.26.3",
"@wdio/spec-reporter": "^8.26.3",
"eslint": "^8.56.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.23.0",
"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 type { StorybookConfig } from "@storybook/web-components-vite";
import { cwd } from "process";
import modify from "rollup-plugin-modify";
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
import { cssImportMaps } from "./css-import-maps";
export const isProdBuild = process.env.NODE_ENV === "production";
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
@ -27,9 +30,7 @@ const config: StorybookConfig = {
return {
...config,
plugins: [
...config.plugins,
postcssLit(),
tsconfigPaths(),
modify(cssImportMaps),
replace({
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development",
@ -38,6 +39,9 @@ const config: StorybookConfig = {
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
"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": "run-s build-locales tsc:execute",
"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": {
"@codemirror/lang-html": "^6.4.7",
@ -39,7 +42,7 @@
"@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.3",
"@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/task": "^3.1.0",
"@lit/localize": "^0.11.4",
@ -83,26 +86,26 @@
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@storybook/addon-essentials": "^7.6.4",
"@storybook/addon-links": "^7.6.4",
"@storybook/api": "^7.6.4",
"@storybook/addon-essentials": "^7.6.5",
"@storybook/addon-links": "^7.6.5",
"@storybook/api": "^7.6.5",
"@storybook/blocks": "^7.6.4",
"@storybook/manager-api": "^7.6.4",
"@storybook/web-components": "^7.6.4",
"@storybook/web-components-vite": "^7.6.4",
"@storybook/manager-api": "^7.6.5",
"@storybook/web-components": "^7.6.5",
"@storybook/web-components-vite": "^7.6.5",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3",
"cross-env": "^7.0.3",
"eslint": "^8.55.0",
"eslint": "^8.56.0",
"eslint-config-google": "^0.14.0",
"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-storybook": "^0.6.15",
"lit-analyzer": "^2.0.2",
@ -112,11 +115,12 @@
"pyright": "=1.1.338",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^4.9.0",
"rollup": "^4.9.1",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-cssimport": "^1.0.3",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.4",
"storybook": "^7.6.5",
"storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.1",
"tslib": "^2.6.2",
@ -125,9 +129,9 @@
"vite-tsconfig-paths": "^4.2.2"
},
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.19.9",
"@esbuild/darwin-arm64": "^0.19.10",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.19.9"
"@esbuild/linux-arm64": "^0.19.10"
},
"engines": {
"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,
Flow,
FlowDesignationEnum,
FlowLayoutEnum,
FlowsApi,
LayoutEnum,
PolicyEngineMode,
} from "@goauthentik/api";
@ -196,6 +196,13 @@ export class FlowForm extends ModelForm<Flow, string> {
>
${msg("Require superuser.")}
</option>
<option
value=${AuthenticationEnum.RequireOutpost}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireOutpost}
>
${msg("Require Outpost (flow can only be executed from an outpost).")}
</option>
</select>
<p class="pf-c-form__helper-text">
${msg("Required authentication level for this flow.")}
@ -302,34 +309,34 @@ export class FlowForm extends ModelForm<Flow, string> {
>
<select class="pf-c-form-control">
<option
value=${LayoutEnum.Stacked}
?selected=${this.instance?.layout === LayoutEnum.Stacked}
value=${FlowLayoutEnum.Stacked}
?selected=${this.instance?.layout === FlowLayoutEnum.Stacked}
>
${LayoutToLabel(LayoutEnum.Stacked)}
${LayoutToLabel(FlowLayoutEnum.Stacked)}
</option>
<option
value=${LayoutEnum.ContentLeft}
?selected=${this.instance?.layout === LayoutEnum.ContentLeft}
value=${FlowLayoutEnum.ContentLeft}
?selected=${this.instance?.layout === FlowLayoutEnum.ContentLeft}
>
${LayoutToLabel(LayoutEnum.ContentLeft)}
${LayoutToLabel(FlowLayoutEnum.ContentLeft)}
</option>
<option
value=${LayoutEnum.ContentRight}
?selected=${this.instance?.layout === LayoutEnum.ContentRight}
value=${FlowLayoutEnum.ContentRight}
?selected=${this.instance?.layout === FlowLayoutEnum.ContentRight}
>
${LayoutToLabel(LayoutEnum.ContentRight)}
${LayoutToLabel(FlowLayoutEnum.ContentRight)}
</option>
<option
value=${LayoutEnum.SidebarLeft}
?selected=${this.instance?.layout === LayoutEnum.SidebarLeft}
value=${FlowLayoutEnum.SidebarLeft}
?selected=${this.instance?.layout === FlowLayoutEnum.SidebarLeft}
>
${LayoutToLabel(LayoutEnum.SidebarLeft)}
${LayoutToLabel(FlowLayoutEnum.SidebarLeft)}
</option>
<option
value=${LayoutEnum.SidebarRight}
?selected=${this.instance?.layout === LayoutEnum.SidebarRight}
value=${FlowLayoutEnum.SidebarRight}
?selected=${this.instance?.layout === FlowLayoutEnum.SidebarRight}
>
${LayoutToLabel(LayoutEnum.SidebarRight)}
${LayoutToLabel(FlowLayoutEnum.SidebarRight)}
</option>
</select>
</ak-form-element-horizontal>

View File

@ -1,6 +1,6 @@
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 {
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) {
case LayoutEnum.Stacked:
case FlowLayoutEnum.Stacked:
return msg("Stacked");
case LayoutEnum.ContentLeft:
case FlowLayoutEnum.ContentLeft:
return msg("Content left");
case LayoutEnum.ContentRight:
case FlowLayoutEnum.ContentRight:
return msg("Content right");
case LayoutEnum.SidebarLeft:
case FlowLayoutEnum.SidebarLeft:
return msg("Sidebar left");
case LayoutEnum.SidebarRight:
case FlowLayoutEnum.SidebarRight:
return msg("Sidebar right");
case LayoutEnum.UnknownDefaultOpenApi:
case FlowLayoutEnum.UnknownDefaultOpenApi:
return msg("Unknown layout");
}
}

View File

@ -124,3 +124,27 @@ html > form > input {
margin-right: 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,
FlowChallengeResponseRequest,
FlowErrorChallenge,
FlowLayoutEnum,
FlowsApi,
LayoutEnum,
ResponseError,
ShellChallenge,
UiThemeEnum,
@ -115,8 +115,10 @@ export class FlowExecutor extends Interface implements StageHost {
background-color: transparent;
}
/* layouts */
.pf-c-login.stacked .pf-c-login__main {
margin-top: 13rem;
@media (min-height: 60rem) {
.pf-c-login.stacked .pf-c-login__main {
margin-top: 13rem;
}
}
.pf-c-login__container.content-right {
grid-template-areas:
@ -451,7 +453,7 @@ export class FlowExecutor extends Interface implements StageHost {
}
getLayout(): string {
const prefilledFlow = globalAK()?.flow?.layout || LayoutEnum.Stacked;
const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
if (this.challenge) {
return this.challenge?.flowInfo?.layout || prefilledFlow;
}
@ -461,11 +463,11 @@ export class FlowExecutor extends Interface implements StageHost {
getLayoutClass(): string {
const layout = this.getLayout();
switch (layout) {
case LayoutEnum.ContentLeft:
case FlowLayoutEnum.ContentLeft:
return "pf-c-login__container";
case LayoutEnum.ContentRight:
case FlowLayoutEnum.ContentRight:
return "pf-c-login__container content-right";
case LayoutEnum.Stacked:
case FlowLayoutEnum.Stacked:
default:
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">
<source>Event volume</source>
<target>事件容量</target>
</trans-unit>
<trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source>
<target>需要前哨(流程只能从前哨执行)。</target>
</trans-unit>
</body>
</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 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`
- Access token URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/oauth2/v2.0/token`
- OIDC Well-known URL: `https://login.microsoftonline.com/*Directory (tenant) ID* from above/v2.0/.well-known/openid-configuration`
![](./authentik_01.png)

View File

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

View File

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

View File

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