Compare commits
4 Commits
static-con
...
fix/issue_
Author | SHA1 | Date | |
---|---|---|---|
13c8cbf03a | |||
1776981f29 | |||
5a4df95011 | |||
f2927e5725 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2025.2.4
|
current_version = 2025.2.2
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||||
@ -17,8 +17,6 @@ optional_value = final
|
|||||||
|
|
||||||
[bumpversion:file:pyproject.toml]
|
[bumpversion:file:pyproject.toml]
|
||||||
|
|
||||||
[bumpversion:file:uv.lock]
|
|
||||||
|
|
||||||
[bumpversion:file:package.json]
|
[bumpversion:file:package.json]
|
||||||
|
|
||||||
[bumpversion:file:docker-compose.yml]
|
[bumpversion:file:docker-compose.yml]
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,7 +33,6 @@ eggs/
|
|||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
dist/
|
dist/
|
||||||
out/
|
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
|
@ -43,7 +43,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Stage 3: Build go proxy
|
# Stage 3: Build go proxy
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.23-fips-bookworm AS go-builder
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
@ -76,7 +76,7 @@ COPY ./go.sum /go/src/goauthentik.io/go.sum
|
|||||||
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
||||||
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
|
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
|
||||||
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
|
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
|
||||||
CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \
|
CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \
|
||||||
go build -o /go/authentik ./cmd/server
|
go build -o /go/authentik ./cmd/server
|
||||||
|
|
||||||
# Stage 4: MaxMind GeoIP
|
# Stage 4: MaxMind GeoIP
|
||||||
@ -94,9 +94,9 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
|||||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 5: Download uv
|
# Stage 5: Download uv
|
||||||
FROM ghcr.io/astral-sh/uv:0.6.14 AS uv
|
FROM ghcr.io/astral-sh/uv:0.6.10 AS uv
|
||||||
# Stage 6: Base python image
|
# Stage 6: Base python image
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.12.9-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
ENV VENV_PATH="/ak-root/.venv" \
|
ENV VENV_PATH="/ak-root/.venv" \
|
||||||
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2025.2.4"
|
__version__ = "2025.2.2"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ LOGGER = get_logger()
|
|||||||
|
|
||||||
def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:
|
def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:
|
||||||
"""Cache key where application list for user is saved"""
|
"""Cache key where application list for user is saved"""
|
||||||
key = f"{CACHE_PREFIX}app_access/{user_pk}"
|
key = f"{CACHE_PREFIX}/app_access/{user_pk}"
|
||||||
if page_number:
|
if page_number:
|
||||||
key += f"/{page_number}"
|
key += f"/{page_number}"
|
||||||
return key
|
return key
|
||||||
|
@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django_filters.filters import BooleanFilter
|
from django_filters.filters import BooleanFilter
|
||||||
from django_filters.filterset import FilterSet
|
from django_filters.filterset import FilterSet
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.fields import ReadOnlyField, SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
from authentik.core.api.object_types import TypesMixin
|
from authentik.core.api.object_types import TypesMixin
|
||||||
@ -18,10 +18,10 @@ from authentik.core.models import Provider
|
|||||||
class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
||||||
"""Provider Serializer"""
|
"""Provider Serializer"""
|
||||||
|
|
||||||
assigned_application_slug = ReadOnlyField(source="application.slug")
|
assigned_application_slug = SerializerMethodField()
|
||||||
assigned_application_name = ReadOnlyField(source="application.name")
|
assigned_application_name = SerializerMethodField()
|
||||||
assigned_backchannel_application_slug = ReadOnlyField(source="backchannel_application.slug")
|
assigned_backchannel_application_slug = SerializerMethodField()
|
||||||
assigned_backchannel_application_name = ReadOnlyField(source="backchannel_application.name")
|
assigned_backchannel_application_name = SerializerMethodField()
|
||||||
|
|
||||||
component = SerializerMethodField()
|
component = SerializerMethodField()
|
||||||
|
|
||||||
@ -31,6 +31,38 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
|||||||
return ""
|
return ""
|
||||||
return obj.component
|
return obj.component
|
||||||
|
|
||||||
|
def get_assigned_application_slug(self, obj: Provider) -> str:
|
||||||
|
"""Get application slug, return empty string if no application exists"""
|
||||||
|
try:
|
||||||
|
return obj.application.slug
|
||||||
|
except Provider.application.RelatedObjectDoesNotExist:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_assigned_application_name(self, obj: Provider) -> str:
|
||||||
|
"""Get application name, return empty string if no application exists"""
|
||||||
|
try:
|
||||||
|
return obj.application.name
|
||||||
|
except Provider.application.RelatedObjectDoesNotExist:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_assigned_backchannel_application_slug(self, obj: Provider) -> str:
|
||||||
|
"""Get backchannel application slug.
|
||||||
|
|
||||||
|
Returns an empty string if no backchannel application exists.
|
||||||
|
"""
|
||||||
|
if not obj.backchannel_application:
|
||||||
|
return ""
|
||||||
|
return obj.backchannel_application.slug or ""
|
||||||
|
|
||||||
|
def get_assigned_backchannel_application_name(self, obj: Provider) -> str:
|
||||||
|
"""Get backchannel application name.
|
||||||
|
|
||||||
|
Returns an empty string if no backchannel application exists.
|
||||||
|
"""
|
||||||
|
if not obj.backchannel_application:
|
||||||
|
return ""
|
||||||
|
return obj.backchannel_application.name or ""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Provider
|
model = Provider
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -179,13 +179,10 @@ class UserSourceConnectionSerializer(SourceSerializer):
|
|||||||
"user",
|
"user",
|
||||||
"source",
|
"source",
|
||||||
"source_obj",
|
"source_obj",
|
||||||
"identifier",
|
|
||||||
"created",
|
"created",
|
||||||
"last_updated",
|
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"created": {"read_only": True},
|
"created": {"read_only": True},
|
||||||
"last_updated": {"read_only": True},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -202,7 +199,7 @@ class UserSourceConnectionViewSet(
|
|||||||
queryset = UserSourceConnection.objects.all()
|
queryset = UserSourceConnection.objects.all()
|
||||||
serializer_class = UserSourceConnectionSerializer
|
serializer_class = UserSourceConnectionSerializer
|
||||||
filterset_fields = ["user", "source__slug"]
|
filterset_fields = ["user", "source__slug"]
|
||||||
search_fields = ["user__username", "source__slug", "identifier"]
|
search_fields = ["source__slug"]
|
||||||
ordering = ["source__slug", "pk"]
|
ordering = ["source__slug", "pk"]
|
||||||
owner_field = "user"
|
owner_field = "user"
|
||||||
|
|
||||||
@ -221,11 +218,9 @@ class GroupSourceConnectionSerializer(SourceSerializer):
|
|||||||
"source_obj",
|
"source_obj",
|
||||||
"identifier",
|
"identifier",
|
||||||
"created",
|
"created",
|
||||||
"last_updated",
|
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"created": {"read_only": True},
|
"created": {"read_only": True},
|
||||||
"last_updated": {"read_only": True},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -242,5 +237,6 @@ class GroupSourceConnectionViewSet(
|
|||||||
queryset = GroupSourceConnection.objects.all()
|
queryset = GroupSourceConnection.objects.all()
|
||||||
serializer_class = GroupSourceConnectionSerializer
|
serializer_class = GroupSourceConnectionSerializer
|
||||||
filterset_fields = ["group", "source__slug"]
|
filterset_fields = ["group", "source__slug"]
|
||||||
search_fields = ["group__name", "source__slug", "identifier"]
|
search_fields = ["source__slug"]
|
||||||
ordering = ["source__slug", "pk"]
|
ordering = ["source__slug", "pk"]
|
||||||
|
owner_field = "user"
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
"""User API Views"""
|
"""User API Views"""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from importlib import import_module
|
|
||||||
from json import loads
|
from json import loads
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth import update_session_auth_hash
|
from django.contrib.auth import update_session_auth_hash
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.sessions.backends.base import SessionBase
|
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db.models.functions import ExtractHour
|
from django.db.models.functions import ExtractHour
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
@ -92,7 +91,6 @@ from authentik.stages.email.tasks import send_mails
|
|||||||
from authentik.stages.email.utils import TemplateEmailMessage
|
from authentik.stages.email.utils import TemplateEmailMessage
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
SessionStore: SessionBase = import_module(settings.SESSION_ENGINE).SessionStore
|
|
||||||
|
|
||||||
|
|
||||||
class UserGroupSerializer(ModelSerializer):
|
class UserGroupSerializer(ModelSerializer):
|
||||||
@ -228,7 +226,6 @@ class UserSerializer(ModelSerializer):
|
|||||||
"name",
|
"name",
|
||||||
"is_active",
|
"is_active",
|
||||||
"last_login",
|
"last_login",
|
||||||
"date_joined",
|
|
||||||
"is_superuser",
|
"is_superuser",
|
||||||
"groups",
|
"groups",
|
||||||
"groups_obj",
|
"groups_obj",
|
||||||
@ -243,7 +240,6 @@ class UserSerializer(ModelSerializer):
|
|||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"name": {"allow_blank": True},
|
"name": {"allow_blank": True},
|
||||||
"date_joined": {"read_only": True},
|
|
||||||
"password_change_date": {"read_only": True},
|
"password_change_date": {"read_only": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,7 +373,7 @@ class UsersFilter(FilterSet):
|
|||||||
method="filter_attributes",
|
method="filter_attributes",
|
||||||
)
|
)
|
||||||
|
|
||||||
is_superuser = BooleanFilter(field_name="ak_groups", method="filter_is_superuser")
|
is_superuser = BooleanFilter(field_name="ak_groups", lookup_expr="is_superuser")
|
||||||
uuid = UUIDFilter(field_name="uuid")
|
uuid = UUIDFilter(field_name="uuid")
|
||||||
|
|
||||||
path = CharFilter(field_name="path")
|
path = CharFilter(field_name="path")
|
||||||
@ -395,11 +391,6 @@ class UsersFilter(FilterSet):
|
|||||||
queryset=Group.objects.all().order_by("name"),
|
queryset=Group.objects.all().order_by("name"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_is_superuser(self, queryset, name, value):
|
|
||||||
if value:
|
|
||||||
return queryset.filter(ak_groups__is_superuser=True).distinct()
|
|
||||||
return queryset.exclude(ak_groups__is_superuser=True).distinct()
|
|
||||||
|
|
||||||
def filter_attributes(self, queryset, name, value):
|
def filter_attributes(self, queryset, name, value):
|
||||||
"""Filter attributes by query args"""
|
"""Filter attributes by query args"""
|
||||||
try:
|
try:
|
||||||
@ -778,8 +769,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
|||||||
if not instance.is_active:
|
if not instance.is_active:
|
||||||
sessions = AuthenticatedSession.objects.filter(user=instance)
|
sessions = AuthenticatedSession.objects.filter(user=instance)
|
||||||
session_ids = sessions.values_list("session_key", flat=True)
|
session_ids = sessions.values_list("session_key", flat=True)
|
||||||
for session in session_ids:
|
cache.delete_many(f"{KEY_PREFIX}{session}" for session in session_ids)
|
||||||
SessionStore(session).delete()
|
|
||||||
sessions.delete()
|
sessions.delete()
|
||||||
LOGGER.debug("Deleted user's sessions", user=instance.username)
|
LOGGER.debug("Deleted user's sessions", user=instance.username)
|
||||||
return response
|
return response
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.0.13 on 2025-04-07 14:04
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_core", "0043_alter_group_options"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="usersourceconnection",
|
|
||||||
name="new_identifier",
|
|
||||||
field=models.TextField(default=""),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,30 +0,0 @@
|
|||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
|
||||||
("authentik_sources_kerberos", "0003_migrate_userkerberossourceconnection_identifier"),
|
|
||||||
("authentik_sources_oauth", "0009_migrate_useroauthsourceconnection_identifier"),
|
|
||||||
("authentik_sources_plex", "0005_migrate_userplexsourceconnection_identifier"),
|
|
||||||
("authentik_sources_saml", "0019_migrate_usersamlsourceconnection_identifier"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name="usersourceconnection",
|
|
||||||
old_name="new_identifier",
|
|
||||||
new_name="identifier",
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name="usersourceconnection",
|
|
||||||
index=models.Index(fields=["identifier"], name="authentik_c_identif_59226f_idx"),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name="usersourceconnection",
|
|
||||||
index=models.Index(
|
|
||||||
fields=["source", "identifier"], name="authentik_c_source__649e04_idx"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -761,17 +761,11 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
|
|||||||
@property
|
@property
|
||||||
def component(self) -> str:
|
def component(self) -> str:
|
||||||
"""Return component used to edit this object"""
|
"""Return component used to edit this object"""
|
||||||
if self.managed == self.MANAGED_INBUILT:
|
|
||||||
return ""
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def property_mapping_type(self) -> "type[PropertyMapping]":
|
def property_mapping_type(self) -> "type[PropertyMapping]":
|
||||||
"""Return property mapping type used by this object"""
|
"""Return property mapping type used by this object"""
|
||||||
if self.managed == self.MANAGED_INBUILT:
|
|
||||||
from authentik.core.models import PropertyMapping
|
|
||||||
|
|
||||||
return PropertyMapping
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def ui_login_button(self, request: HttpRequest) -> UILoginButton | None:
|
def ui_login_button(self, request: HttpRequest) -> UILoginButton | None:
|
||||||
@ -786,14 +780,10 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
|
|||||||
|
|
||||||
def get_base_user_properties(self, **kwargs) -> dict[str, Any | dict[str, Any]]:
|
def get_base_user_properties(self, **kwargs) -> dict[str, Any | dict[str, Any]]:
|
||||||
"""Get base properties for a user to build final properties upon."""
|
"""Get base properties for a user to build final properties upon."""
|
||||||
if self.managed == self.MANAGED_INBUILT:
|
|
||||||
return {}
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_base_group_properties(self, **kwargs) -> dict[str, Any | dict[str, Any]]:
|
def get_base_group_properties(self, **kwargs) -> dict[str, Any | dict[str, Any]]:
|
||||||
"""Get base properties for a group to build final properties upon."""
|
"""Get base properties for a group to build final properties upon."""
|
||||||
if self.managed == self.MANAGED_INBUILT:
|
|
||||||
return {}
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -824,7 +814,6 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
|||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
source = models.ForeignKey(Source, on_delete=models.CASCADE)
|
source = models.ForeignKey(Source, on_delete=models.CASCADE)
|
||||||
identifier = models.TextField()
|
|
||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
@ -838,10 +827,6 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (("user", "source"),)
|
unique_together = (("user", "source"),)
|
||||||
indexes = (
|
|
||||||
models.Index(fields=("identifier",)),
|
|
||||||
models.Index(fields=("source", "identifier")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GroupSourceConnection(SerializerModel, CreatedUpdatedModel):
|
class GroupSourceConnection(SerializerModel, CreatedUpdatedModel):
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
"""authentik core signals"""
|
"""authentik core signals"""
|
||||||
|
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||||
from django.contrib.sessions.backends.base import SessionBase
|
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.signals import Signal
|
from django.core.signals import Signal
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
@ -28,7 +25,6 @@ password_changed = Signal()
|
|||||||
login_failed = Signal()
|
login_failed = Signal()
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
SessionStore: SessionBase = import_module(settings.SESSION_ENGINE).SessionStore
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Application)
|
@receiver(post_save, sender=Application)
|
||||||
@ -64,7 +60,8 @@ def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
|
|||||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||||
def authenticated_session_delete(sender: type[Model], instance: "AuthenticatedSession", **_):
|
def authenticated_session_delete(sender: type[Model], instance: "AuthenticatedSession", **_):
|
||||||
"""Delete session when authenticated session is deleted"""
|
"""Delete session when authenticated session is deleted"""
|
||||||
SessionStore(instance.session_key).delete()
|
cache_key = f"{KEY_PREFIX}{instance.session_key}"
|
||||||
|
cache.delete(cache_key)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save)
|
@receiver(pre_save)
|
||||||
|
@ -133,6 +133,8 @@ class TestApplicationsAPI(APITestCase):
|
|||||||
"provider_obj": {
|
"provider_obj": {
|
||||||
"assigned_application_name": "allowed",
|
"assigned_application_name": "allowed",
|
||||||
"assigned_application_slug": "allowed",
|
"assigned_application_slug": "allowed",
|
||||||
|
"assigned_backchannel_application_name": "",
|
||||||
|
"assigned_backchannel_application_slug": "",
|
||||||
"authentication_flow": None,
|
"authentication_flow": None,
|
||||||
"invalidation_flow": None,
|
"invalidation_flow": None,
|
||||||
"authorization_flow": str(self.provider.authorization_flow.pk),
|
"authorization_flow": str(self.provider.authorization_flow.pk),
|
||||||
@ -186,6 +188,8 @@ class TestApplicationsAPI(APITestCase):
|
|||||||
"provider_obj": {
|
"provider_obj": {
|
||||||
"assigned_application_name": "allowed",
|
"assigned_application_name": "allowed",
|
||||||
"assigned_application_slug": "allowed",
|
"assigned_application_slug": "allowed",
|
||||||
|
"assigned_backchannel_application_name": "",
|
||||||
|
"assigned_backchannel_application_slug": "",
|
||||||
"authentication_flow": None,
|
"authentication_flow": None,
|
||||||
"invalidation_flow": None,
|
"invalidation_flow": None,
|
||||||
"authorization_flow": str(self.provider.authorization_flow.pk),
|
"authorization_flow": str(self.provider.authorization_flow.pk),
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from authentik.core.models import PropertyMapping
|
from authentik.core.api.providers import ProviderSerializer
|
||||||
|
from authentik.core.models import Application, PropertyMapping, Provider
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
|
|
||||||
|
|
||||||
@ -24,3 +25,51 @@ class TestProvidersAPI(APITestCase):
|
|||||||
reverse("authentik_api:provider-types"),
|
reverse("authentik_api:provider-types"),
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_provider_serializer_without_application(self):
|
||||||
|
"""Test that Provider serializer handles missing application gracefully"""
|
||||||
|
# Create a provider without an application
|
||||||
|
provider = Provider.objects.create(name="test-provider")
|
||||||
|
|
||||||
|
serializer = ProviderSerializer(instance=provider)
|
||||||
|
serialized_data = serializer.data
|
||||||
|
|
||||||
|
# Check that fields return empty strings when no application exists
|
||||||
|
self.assertEqual(serialized_data["assigned_application_slug"], "")
|
||||||
|
self.assertEqual(serialized_data["assigned_application_name"], "")
|
||||||
|
self.assertEqual(serialized_data["assigned_backchannel_application_slug"], "")
|
||||||
|
self.assertEqual(serialized_data["assigned_backchannel_application_name"], "")
|
||||||
|
|
||||||
|
def test_provider_serializer_with_application(self):
|
||||||
|
"""Test that Provider serializer correctly includes application data"""
|
||||||
|
# Create an application
|
||||||
|
app = Application.objects.create(name="Test App", slug="test-app")
|
||||||
|
|
||||||
|
# Create a provider with an application
|
||||||
|
provider = Provider.objects.create(name="test-provider-with-app")
|
||||||
|
app.provider = provider
|
||||||
|
app.save()
|
||||||
|
|
||||||
|
serializer = ProviderSerializer(instance=provider)
|
||||||
|
serialized_data = serializer.data
|
||||||
|
|
||||||
|
# Check that fields return correct values when application exists
|
||||||
|
self.assertEqual(serialized_data["assigned_application_slug"], "test-app")
|
||||||
|
self.assertEqual(serialized_data["assigned_application_name"], "Test App")
|
||||||
|
self.assertEqual(serialized_data["assigned_backchannel_application_slug"], "")
|
||||||
|
self.assertEqual(serialized_data["assigned_backchannel_application_name"], "")
|
||||||
|
|
||||||
|
def test_provider_api_response(self):
|
||||||
|
"""Test that the API response includes empty strings for missing applications"""
|
||||||
|
# Create a provider without an application
|
||||||
|
provider = Provider.objects.create(name="test-provider-api")
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("authentik_api:provider-detail", kwargs={"pk": provider.pk}),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.data["assigned_application_slug"], "")
|
||||||
|
self.assertEqual(response.data["assigned_application_name"], "")
|
||||||
|
self.assertEqual(response.data["assigned_backchannel_application_slug"], "")
|
||||||
|
self.assertEqual(response.data["assigned_backchannel_application_name"], "")
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
from django.apps import apps
|
|
||||||
from django.urls import reverse
|
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
|
||||||
|
|
||||||
|
|
||||||
class TestSourceAPI(APITestCase):
|
|
||||||
def setUp(self) -> None:
|
|
||||||
self.user = create_test_admin_user()
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
|
|
||||||
def test_builtin_source_used_by(self):
|
|
||||||
"""Test Providers's types endpoint"""
|
|
||||||
apps.get_app_config("authentik_core").source_inbuilt()
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("authentik_api:source-used-by", kwargs={"slug": "authentik-built-in"}),
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
@ -1,7 +1,6 @@
|
|||||||
"""Test Users API"""
|
"""Test Users API"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from json import loads
|
|
||||||
|
|
||||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
@ -16,12 +15,7 @@ from authentik.core.models import (
|
|||||||
User,
|
User,
|
||||||
UserTypes,
|
UserTypes,
|
||||||
)
|
)
|
||||||
from authentik.core.tests.utils import (
|
from authentik.core.tests.utils import create_test_admin_user, create_test_brand, create_test_flow
|
||||||
create_test_admin_user,
|
|
||||||
create_test_brand,
|
|
||||||
create_test_flow,
|
|
||||||
create_test_user,
|
|
||||||
)
|
|
||||||
from authentik.flows.models import FlowDesignation
|
from authentik.flows.models import FlowDesignation
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
from authentik.stages.email.models import EmailStage
|
from authentik.stages.email.models import EmailStage
|
||||||
@ -32,7 +26,7 @@ class TestUsersAPI(APITestCase):
|
|||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.admin = create_test_admin_user()
|
self.admin = create_test_admin_user()
|
||||||
self.user = create_test_user()
|
self.user = User.objects.create(username="test-user")
|
||||||
|
|
||||||
def test_filter_type(self):
|
def test_filter_type(self):
|
||||||
"""Test API filtering by type"""
|
"""Test API filtering by type"""
|
||||||
@ -47,35 +41,6 @@ class TestUsersAPI(APITestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_filter_is_superuser(self):
|
|
||||||
"""Test API filtering by superuser status"""
|
|
||||||
User.objects.all().delete()
|
|
||||||
admin = create_test_admin_user()
|
|
||||||
self.client.force_login(admin)
|
|
||||||
# Test superuser
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("authentik_api:user-list"),
|
|
||||||
data={
|
|
||||||
"is_superuser": True,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
body = loads(response.content)
|
|
||||||
self.assertEqual(len(body["results"]), 1)
|
|
||||||
self.assertEqual(body["results"][0]["username"], admin.username)
|
|
||||||
# Test non-superuser
|
|
||||||
user = create_test_user()
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("authentik_api:user-list"),
|
|
||||||
data={
|
|
||||||
"is_superuser": False,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
body = loads(response.content)
|
|
||||||
self.assertEqual(len(body["results"]), 1, body)
|
|
||||||
self.assertEqual(body["results"][0]["username"], user.username)
|
|
||||||
|
|
||||||
def test_list_with_groups(self):
|
def test_list_with_groups(self):
|
||||||
"""Test listing with groups"""
|
"""Test listing with groups"""
|
||||||
self.client.force_login(self.admin)
|
self.client.force_login(self.admin)
|
||||||
@ -134,8 +99,6 @@ class TestUsersAPI(APITestCase):
|
|||||||
def test_recovery_email_no_flow(self):
|
def test_recovery_email_no_flow(self):
|
||||||
"""Test user recovery link (no recovery flow set)"""
|
"""Test user recovery link (no recovery flow set)"""
|
||||||
self.client.force_login(self.admin)
|
self.client.force_login(self.admin)
|
||||||
self.user.email = ""
|
|
||||||
self.user.save()
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("authentik_api:user-recovery-email", kwargs={"pk": self.user.pk})
|
reverse("authentik_api:user-recovery-email", kwargs={"pk": self.user.pk})
|
||||||
)
|
)
|
||||||
|
@ -13,11 +13,7 @@ from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
|||||||
from authentik.core.api.groups import GroupViewSet
|
from authentik.core.api.groups import GroupViewSet
|
||||||
from authentik.core.api.property_mappings import PropertyMappingViewSet
|
from authentik.core.api.property_mappings import PropertyMappingViewSet
|
||||||
from authentik.core.api.providers import ProviderViewSet
|
from authentik.core.api.providers import ProviderViewSet
|
||||||
from authentik.core.api.sources import (
|
from authentik.core.api.sources import SourceViewSet, UserSourceConnectionViewSet
|
||||||
GroupSourceConnectionViewSet,
|
|
||||||
SourceViewSet,
|
|
||||||
UserSourceConnectionViewSet,
|
|
||||||
)
|
|
||||||
from authentik.core.api.tokens import TokenViewSet
|
from authentik.core.api.tokens import TokenViewSet
|
||||||
from authentik.core.api.transactional_applications import TransactionalApplicationView
|
from authentik.core.api.transactional_applications import TransactionalApplicationView
|
||||||
from authentik.core.api.users import UserViewSet
|
from authentik.core.api.users import UserViewSet
|
||||||
@ -85,7 +81,6 @@ api_urlpatterns = [
|
|||||||
("core/tokens", TokenViewSet),
|
("core/tokens", TokenViewSet),
|
||||||
("sources/all", SourceViewSet),
|
("sources/all", SourceViewSet),
|
||||||
("sources/user_connections/all", UserSourceConnectionViewSet),
|
("sources/user_connections/all", UserSourceConnectionViewSet),
|
||||||
("sources/group_connections/all", GroupSourceConnectionViewSet),
|
|
||||||
("providers/all", ProviderViewSet),
|
("providers/all", ProviderViewSet),
|
||||||
("propertymappings/all", PropertyMappingViewSet),
|
("propertymappings/all", PropertyMappingViewSet),
|
||||||
("authenticators/all", DeviceViewSet, "device"),
|
("authenticators/all", DeviceViewSet, "device"),
|
||||||
|
@ -69,7 +69,6 @@ SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre"
|
|||||||
SESSION_KEY_GET = "authentik/flows/get"
|
SESSION_KEY_GET = "authentik/flows/get"
|
||||||
SESSION_KEY_POST = "authentik/flows/post"
|
SESSION_KEY_POST = "authentik/flows/post"
|
||||||
SESSION_KEY_HISTORY = "authentik/flows/history"
|
SESSION_KEY_HISTORY = "authentik/flows/history"
|
||||||
SESSION_KEY_AUTH_STARTED = "authentik/flows/auth_started"
|
|
||||||
QS_KEY_TOKEN = "flow_token" # nosec
|
QS_KEY_TOKEN = "flow_token" # nosec
|
||||||
QS_QUERY = "query"
|
QS_QUERY = "query"
|
||||||
|
|
||||||
@ -454,7 +453,6 @@ class FlowExecutorView(APIView):
|
|||||||
SESSION_KEY_APPLICATION_PRE,
|
SESSION_KEY_APPLICATION_PRE,
|
||||||
SESSION_KEY_PLAN,
|
SESSION_KEY_PLAN,
|
||||||
SESSION_KEY_GET,
|
SESSION_KEY_GET,
|
||||||
SESSION_KEY_AUTH_STARTED,
|
|
||||||
# We might need the initial POST payloads for later requests
|
# We might need the initial POST payloads for later requests
|
||||||
# SESSION_KEY_POST,
|
# SESSION_KEY_POST,
|
||||||
# We don't delete the history on purpose, as a user might
|
# We don't delete the history on purpose, as a user might
|
||||||
|
@ -6,22 +6,14 @@ from django.shortcuts import get_object_or_404
|
|||||||
from ua_parser.user_agent_parser import Parse
|
from ua_parser.user_agent_parser import Parse
|
||||||
|
|
||||||
from authentik.core.views.interface import InterfaceView
|
from authentik.core.views.interface import InterfaceView
|
||||||
from authentik.flows.models import Flow, FlowDesignation
|
from authentik.flows.models import Flow
|
||||||
from authentik.flows.views.executor import SESSION_KEY_AUTH_STARTED
|
|
||||||
|
|
||||||
|
|
||||||
class FlowInterfaceView(InterfaceView):
|
class FlowInterfaceView(InterfaceView):
|
||||||
"""Flow interface"""
|
"""Flow interface"""
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||||
flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||||
kwargs["flow"] = flow
|
|
||||||
if (
|
|
||||||
not self.request.user.is_authenticated
|
|
||||||
and flow.designation == FlowDesignation.AUTHENTICATION
|
|
||||||
):
|
|
||||||
self.request.session[SESSION_KEY_AUTH_STARTED] = True
|
|
||||||
self.request.session.save()
|
|
||||||
kwargs["inspector"] = "inspector" in self.request.GET
|
kwargs["inspector"] = "inspector" in self.request.GET
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
@ -18,15 +18,6 @@ class SerializerModel(models.Model):
|
|||||||
@property
|
@property
|
||||||
def serializer(self) -> type[BaseSerializer]:
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
"""Get serializer for this model"""
|
"""Get serializer for this model"""
|
||||||
# Special handling for built-in source
|
|
||||||
if (
|
|
||||||
hasattr(self, "managed")
|
|
||||||
and hasattr(self, "MANAGED_INBUILT")
|
|
||||||
and self.managed == self.MANAGED_INBUILT
|
|
||||||
):
|
|
||||||
from authentik.core.api.sources import SourceSerializer
|
|
||||||
|
|
||||||
return SourceSerializer
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,4 +35,3 @@ class AuthentikPoliciesConfig(ManagedAppConfig):
|
|||||||
label = "authentik_policies"
|
label = "authentik_policies"
|
||||||
verbose_name = "authentik Policies"
|
verbose_name = "authentik Policies"
|
||||||
default = True
|
default = True
|
||||||
mountpoint = "policy/"
|
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
{% extends 'login/base_full.html' %}
|
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
{{ block.super }}
|
|
||||||
<script>
|
|
||||||
let redirecting = false;
|
|
||||||
const checkAuth = async () => {
|
|
||||||
if (redirecting) return true;
|
|
||||||
const url = "{{ check_auth_url }}";
|
|
||||||
console.debug("authentik/policies/buffer: Checking authentication...");
|
|
||||||
try {
|
|
||||||
const result = await fetch(url, {
|
|
||||||
method: "HEAD",
|
|
||||||
});
|
|
||||||
if (result.status >= 400) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
console.debug("authentik/policies/buffer: Continuing");
|
|
||||||
redirecting = true;
|
|
||||||
if ("{{ auth_req_method }}" === "post") {
|
|
||||||
document.querySelector("form").submit();
|
|
||||||
} else {
|
|
||||||
window.location.assign("{{ continue_url|escapejs }}");
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let timeout = 100;
|
|
||||||
let offset = 20;
|
|
||||||
let attempt = 0;
|
|
||||||
const main = async () => {
|
|
||||||
attempt += 1;
|
|
||||||
await checkAuth();
|
|
||||||
console.debug(`authentik/policies/buffer: Waiting ${timeout}ms...`);
|
|
||||||
setTimeout(main, timeout);
|
|
||||||
timeout += (offset * attempt);
|
|
||||||
if (timeout >= 2000) {
|
|
||||||
timeout = 2000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener("visibilitychange", async () => {
|
|
||||||
if (document.hidden) return;
|
|
||||||
console.debug("authentik/policies/buffer: Checking authentication on tab activate...");
|
|
||||||
await checkAuth();
|
|
||||||
});
|
|
||||||
main();
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% trans 'Waiting for authentication...' %} - {{ brand.branding_title }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block card_title %}
|
|
||||||
{% trans 'Waiting for authentication...' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block card %}
|
|
||||||
<form class="pf-c-form" method="{{ auth_req_method }}" action="{{ continue_url }}">
|
|
||||||
{% if auth_req_method == "post" %}
|
|
||||||
{% for key, value in auth_req_body.items %}
|
|
||||||
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
<div class="pf-c-empty-state">
|
|
||||||
<div class="pf-c-empty-state__content">
|
|
||||||
<div class="pf-c-empty-state__icon">
|
|
||||||
<span class="pf-c-spinner pf-m-xl" role="progressbar">
|
|
||||||
<span class="pf-c-spinner__clipper"></span>
|
|
||||||
<span class="pf-c-spinner__lead-ball"></span>
|
|
||||||
<span class="pf-c-spinner__tail-ball"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h1 class="pf-c-title pf-m-lg">
|
|
||||||
{% trans "You're already authenticating in another tab. This page will refresh once authentication is completed." %}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-form__group pf-m-action">
|
|
||||||
<a href="{{ auth_req_url }}" class="pf-c-button pf-m-primary pf-m-block">
|
|
||||||
{% trans "Authenticate in this tab" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@ -1,121 +0,0 @@
|
|||||||
from django.contrib.auth.models import AnonymousUser
|
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.test import RequestFactory, TestCase
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from authentik.core.models import Application, Provider
|
|
||||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
|
||||||
from authentik.flows.models import FlowDesignation
|
|
||||||
from authentik.flows.planner import FlowPlan
|
|
||||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
|
||||||
from authentik.lib.generators import generate_id
|
|
||||||
from authentik.lib.tests.utils import dummy_get_response
|
|
||||||
from authentik.policies.views import (
|
|
||||||
QS_BUFFER_ID,
|
|
||||||
SESSION_KEY_BUFFER,
|
|
||||||
BufferedPolicyAccessView,
|
|
||||||
BufferView,
|
|
||||||
PolicyAccessView,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPolicyViews(TestCase):
|
|
||||||
"""Test PolicyAccessView"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.factory = RequestFactory()
|
|
||||||
self.user = create_test_user()
|
|
||||||
|
|
||||||
def test_pav(self):
|
|
||||||
"""Test simple policy access view"""
|
|
||||||
provider = Provider.objects.create(
|
|
||||||
name=generate_id(),
|
|
||||||
)
|
|
||||||
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
|
||||||
|
|
||||||
class TestView(PolicyAccessView):
|
|
||||||
def resolve_provider_application(self):
|
|
||||||
self.provider = provider
|
|
||||||
self.application = app
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
return HttpResponse("foo")
|
|
||||||
|
|
||||||
req = self.factory.get("/")
|
|
||||||
req.user = self.user
|
|
||||||
res = TestView.as_view()(req)
|
|
||||||
self.assertEqual(res.status_code, 200)
|
|
||||||
self.assertEqual(res.content, b"foo")
|
|
||||||
|
|
||||||
def test_pav_buffer(self):
|
|
||||||
"""Test simple policy access view"""
|
|
||||||
provider = Provider.objects.create(
|
|
||||||
name=generate_id(),
|
|
||||||
)
|
|
||||||
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
|
||||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
|
||||||
|
|
||||||
class TestView(BufferedPolicyAccessView):
|
|
||||||
def resolve_provider_application(self):
|
|
||||||
self.provider = provider
|
|
||||||
self.application = app
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
return HttpResponse("foo")
|
|
||||||
|
|
||||||
req = self.factory.get("/")
|
|
||||||
req.user = AnonymousUser()
|
|
||||||
middleware = SessionMiddleware(dummy_get_response)
|
|
||||||
middleware.process_request(req)
|
|
||||||
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
|
|
||||||
req.session.save()
|
|
||||||
res = TestView.as_view()(req)
|
|
||||||
self.assertEqual(res.status_code, 302)
|
|
||||||
self.assertTrue(res.url.startswith(reverse("authentik_policies:buffer")))
|
|
||||||
|
|
||||||
def test_pav_buffer_skip(self):
|
|
||||||
"""Test simple policy access view (skip buffer)"""
|
|
||||||
provider = Provider.objects.create(
|
|
||||||
name=generate_id(),
|
|
||||||
)
|
|
||||||
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
|
||||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
|
||||||
|
|
||||||
class TestView(BufferedPolicyAccessView):
|
|
||||||
def resolve_provider_application(self):
|
|
||||||
self.provider = provider
|
|
||||||
self.application = app
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
return HttpResponse("foo")
|
|
||||||
|
|
||||||
req = self.factory.get("/?skip_buffer=true")
|
|
||||||
req.user = AnonymousUser()
|
|
||||||
middleware = SessionMiddleware(dummy_get_response)
|
|
||||||
middleware.process_request(req)
|
|
||||||
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
|
|
||||||
req.session.save()
|
|
||||||
res = TestView.as_view()(req)
|
|
||||||
self.assertEqual(res.status_code, 302)
|
|
||||||
self.assertTrue(res.url.startswith(reverse("authentik_flows:default-authentication")))
|
|
||||||
|
|
||||||
def test_buffer(self):
|
|
||||||
"""Test buffer view"""
|
|
||||||
uid = generate_id()
|
|
||||||
req = self.factory.get(f"/?{QS_BUFFER_ID}={uid}")
|
|
||||||
req.user = AnonymousUser()
|
|
||||||
middleware = SessionMiddleware(dummy_get_response)
|
|
||||||
middleware.process_request(req)
|
|
||||||
ts = generate_id()
|
|
||||||
req.session[SESSION_KEY_BUFFER % uid] = {
|
|
||||||
"method": "get",
|
|
||||||
"body": {},
|
|
||||||
"url": f"/{ts}",
|
|
||||||
}
|
|
||||||
req.session.save()
|
|
||||||
|
|
||||||
res = BufferView.as_view()(req)
|
|
||||||
self.assertEqual(res.status_code, 200)
|
|
||||||
self.assertIn(ts, res.render().content.decode())
|
|
@ -1,14 +1,7 @@
|
|||||||
"""API URLs"""
|
"""API URLs"""
|
||||||
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from authentik.policies.api.bindings import PolicyBindingViewSet
|
from authentik.policies.api.bindings import PolicyBindingViewSet
|
||||||
from authentik.policies.api.policies import PolicyViewSet
|
from authentik.policies.api.policies import PolicyViewSet
|
||||||
from authentik.policies.views import BufferView
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("buffer", BufferView.as_view(), name="buffer"),
|
|
||||||
]
|
|
||||||
|
|
||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
("policies/all", PolicyViewSet),
|
("policies/all", PolicyViewSet),
|
||||||
|
@ -1,37 +1,23 @@
|
|||||||
"""authentik access helper classes"""
|
"""authentik access helper classes"""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import AccessMixin
|
from django.contrib.auth.mixins import AccessMixin
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.http import HttpRequest, HttpResponse, QueryDict
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.http import urlencode
|
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic.base import TemplateView, View
|
from django.views.generic.base import View
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import Application, Provider, User
|
from authentik.core.models import Application, Provider, User
|
||||||
from authentik.flows.models import Flow, FlowDesignation
|
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_POST
|
||||||
from authentik.flows.planner import FlowPlan
|
|
||||||
from authentik.flows.views.executor import (
|
|
||||||
SESSION_KEY_APPLICATION_PRE,
|
|
||||||
SESSION_KEY_AUTH_STARTED,
|
|
||||||
SESSION_KEY_PLAN,
|
|
||||||
SESSION_KEY_POST,
|
|
||||||
)
|
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
from authentik.policies.denied import AccessDeniedResponse
|
from authentik.policies.denied import AccessDeniedResponse
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.types import PolicyRequest, PolicyResult
|
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
QS_BUFFER_ID = "af_bf_id"
|
|
||||||
QS_SKIP_BUFFER = "skip_buffer"
|
|
||||||
SESSION_KEY_BUFFER = "authentik/policies/pav_buffer/%s"
|
|
||||||
|
|
||||||
|
|
||||||
class RequestValidationError(SentryIgnoredException):
|
class RequestValidationError(SentryIgnoredException):
|
||||||
@ -139,65 +125,3 @@ class PolicyAccessView(AccessMixin, View):
|
|||||||
for message in result.messages:
|
for message in result.messages:
|
||||||
messages.error(self.request, _(message))
|
messages.error(self.request, _(message))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def url_with_qs(url: str, **kwargs):
|
|
||||||
"""Update/set querystring of `url` with the parameters in `kwargs`. Original query string
|
|
||||||
parameters are retained"""
|
|
||||||
if "?" not in url:
|
|
||||||
return url + f"?{urlencode(kwargs)}"
|
|
||||||
url, _, qs = url.partition("?")
|
|
||||||
qs = QueryDict(qs, mutable=True)
|
|
||||||
qs.update(kwargs)
|
|
||||||
return url + f"?{urlencode(qs.items())}"
|
|
||||||
|
|
||||||
|
|
||||||
class BufferView(TemplateView):
|
|
||||||
"""Buffer view"""
|
|
||||||
|
|
||||||
template_name = "policies/buffer.html"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
buf_id = self.request.GET.get(QS_BUFFER_ID)
|
|
||||||
buffer: dict = self.request.session.get(SESSION_KEY_BUFFER % buf_id)
|
|
||||||
kwargs["auth_req_method"] = buffer["method"]
|
|
||||||
kwargs["auth_req_body"] = buffer["body"]
|
|
||||||
kwargs["auth_req_url"] = url_with_qs(buffer["url"], **{QS_SKIP_BUFFER: True})
|
|
||||||
kwargs["check_auth_url"] = reverse("authentik_api:user-me")
|
|
||||||
kwargs["continue_url"] = url_with_qs(buffer["url"], **{QS_BUFFER_ID: buf_id})
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class BufferedPolicyAccessView(PolicyAccessView):
|
|
||||||
"""PolicyAccessView which buffers access requests in case the user is not logged in"""
|
|
||||||
|
|
||||||
def handle_no_permission(self):
|
|
||||||
plan: FlowPlan | None = self.request.session.get(SESSION_KEY_PLAN)
|
|
||||||
authenticating = self.request.session.get(SESSION_KEY_AUTH_STARTED)
|
|
||||||
if plan:
|
|
||||||
flow = Flow.objects.filter(pk=plan.flow_pk).first()
|
|
||||||
if not flow or flow.designation != FlowDesignation.AUTHENTICATION:
|
|
||||||
LOGGER.debug("Not buffering request, no flow or flow not for authentication")
|
|
||||||
return super().handle_no_permission()
|
|
||||||
if not plan and authenticating is None:
|
|
||||||
LOGGER.debug("Not buffering request, no flow plan active")
|
|
||||||
return super().handle_no_permission()
|
|
||||||
if self.request.GET.get(QS_SKIP_BUFFER):
|
|
||||||
LOGGER.debug("Not buffering request, explicit skip")
|
|
||||||
return super().handle_no_permission()
|
|
||||||
buffer_id = str(uuid4())
|
|
||||||
LOGGER.debug("Buffering access request", bf_id=buffer_id)
|
|
||||||
self.request.session[SESSION_KEY_BUFFER % buffer_id] = {
|
|
||||||
"body": self.request.POST,
|
|
||||||
"url": self.request.build_absolute_uri(self.request.get_full_path()),
|
|
||||||
"method": self.request.method.lower(),
|
|
||||||
}
|
|
||||||
return redirect(
|
|
||||||
url_with_qs(reverse("authentik_policies:buffer"), **{QS_BUFFER_ID: buffer_id})
|
|
||||||
)
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
response = super().dispatch(request, *args, **kwargs)
|
|
||||||
if QS_BUFFER_ID in self.request.GET:
|
|
||||||
self.request.session.pop(SESSION_KEY_BUFFER % self.request.GET[QS_BUFFER_ID], None)
|
|
||||||
return response
|
|
||||||
|
@ -30,7 +30,7 @@ from authentik.flows.stage import StageView
|
|||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
from authentik.policies.types import PolicyRequest
|
from authentik.policies.types import PolicyRequest
|
||||||
from authentik.policies.views import BufferedPolicyAccessView, RequestValidationError
|
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
||||||
from authentik.providers.oauth2.constants import (
|
from authentik.providers.oauth2.constants import (
|
||||||
PKCE_METHOD_PLAIN,
|
PKCE_METHOD_PLAIN,
|
||||||
PKCE_METHOD_S256,
|
PKCE_METHOD_S256,
|
||||||
@ -328,7 +328,7 @@ class OAuthAuthorizationParams:
|
|||||||
return code
|
return code
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationFlowInitView(BufferedPolicyAccessView):
|
class AuthorizationFlowInitView(PolicyAccessView):
|
||||||
"""OAuth2 Flow initializer, checks access to application and starts flow"""
|
"""OAuth2 Flow initializer, checks access to application and starts flow"""
|
||||||
|
|
||||||
params: OAuthAuthorizationParams
|
params: OAuthAuthorizationParams
|
||||||
|
@ -74,6 +74,8 @@ class TestEndpointsAPI(APITestCase):
|
|||||||
"component": "ak-provider-rac-form",
|
"component": "ak-provider-rac-form",
|
||||||
"assigned_application_slug": self.app.slug,
|
"assigned_application_slug": self.app.slug,
|
||||||
"assigned_application_name": self.app.name,
|
"assigned_application_name": self.app.name,
|
||||||
|
"assigned_backchannel_application_slug": "",
|
||||||
|
"assigned_backchannel_application_name": "",
|
||||||
"verbose_name": "RAC Provider",
|
"verbose_name": "RAC Provider",
|
||||||
"verbose_name_plural": "RAC Providers",
|
"verbose_name_plural": "RAC Providers",
|
||||||
"meta_model_name": "authentik_providers_rac.racprovider",
|
"meta_model_name": "authentik_providers_rac.racprovider",
|
||||||
@ -124,6 +126,8 @@ class TestEndpointsAPI(APITestCase):
|
|||||||
"component": "ak-provider-rac-form",
|
"component": "ak-provider-rac-form",
|
||||||
"assigned_application_slug": self.app.slug,
|
"assigned_application_slug": self.app.slug,
|
||||||
"assigned_application_name": self.app.name,
|
"assigned_application_name": self.app.name,
|
||||||
|
"assigned_backchannel_application_slug": "",
|
||||||
|
"assigned_backchannel_application_name": "",
|
||||||
"connection_expiry": "hours=8",
|
"connection_expiry": "hours=8",
|
||||||
"delete_token_on_disconnect": False,
|
"delete_token_on_disconnect": False,
|
||||||
"verbose_name": "RAC Provider",
|
"verbose_name": "RAC Provider",
|
||||||
@ -153,6 +157,8 @@ class TestEndpointsAPI(APITestCase):
|
|||||||
"component": "ak-provider-rac-form",
|
"component": "ak-provider-rac-form",
|
||||||
"assigned_application_slug": self.app.slug,
|
"assigned_application_slug": self.app.slug,
|
||||||
"assigned_application_name": self.app.name,
|
"assigned_application_name": self.app.name,
|
||||||
|
"assigned_backchannel_application_slug": "",
|
||||||
|
"assigned_backchannel_application_name": "",
|
||||||
"connection_expiry": "hours=8",
|
"connection_expiry": "hours=8",
|
||||||
"delete_token_on_disconnect": False,
|
"delete_token_on_disconnect": False,
|
||||||
"verbose_name": "RAC Provider",
|
"verbose_name": "RAC Provider",
|
||||||
|
@ -18,11 +18,11 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
|
|||||||
from authentik.flows.stage import RedirectStage
|
from authentik.flows.stage import RedirectStage
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.views import BufferedPolicyAccessView
|
from authentik.policies.views import PolicyAccessView
|
||||||
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
||||||
|
|
||||||
|
|
||||||
class RACStartView(BufferedPolicyAccessView):
|
class RACStartView(PolicyAccessView):
|
||||||
"""Start a RAC connection by checking access and creating a connection token"""
|
"""Start a RAC connection by checking access and creating a connection token"""
|
||||||
|
|
||||||
endpoint: Endpoint
|
endpoint: Endpoint
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.0.13 on 2025-03-31 13:50
|
|
||||||
|
|
||||||
import authentik.lib.models
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_providers_saml", "0017_samlprovider_authn_context_class_ref_mapping"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="samlprovider",
|
|
||||||
name="acs_url",
|
|
||||||
field=models.TextField(
|
|
||||||
validators=[authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))],
|
|
||||||
verbose_name="ACS URL",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -10,7 +10,6 @@ from structlog.stdlib import get_logger
|
|||||||
from authentik.core.api.object_types import CreatableType
|
from authentik.core.api.object_types import CreatableType
|
||||||
from authentik.core.models import PropertyMapping, Provider
|
from authentik.core.models import PropertyMapping, Provider
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.lib.models import DomainlessURLValidator
|
|
||||||
from authentik.lib.utils.time import timedelta_string_validator
|
from authentik.lib.utils.time import timedelta_string_validator
|
||||||
from authentik.sources.saml.processors.constants import (
|
from authentik.sources.saml.processors.constants import (
|
||||||
DSA_SHA1,
|
DSA_SHA1,
|
||||||
@ -41,9 +40,7 @@ class SAMLBindings(models.TextChoices):
|
|||||||
class SAMLProvider(Provider):
|
class SAMLProvider(Provider):
|
||||||
"""SAML 2.0 Endpoint for applications which support SAML."""
|
"""SAML 2.0 Endpoint for applications which support SAML."""
|
||||||
|
|
||||||
acs_url = models.TextField(
|
acs_url = models.URLField(verbose_name=_("ACS URL"))
|
||||||
validators=[DomainlessURLValidator(schemes=("http", "https"))], verbose_name=_("ACS URL")
|
|
||||||
)
|
|
||||||
audience = models.TextField(
|
audience = models.TextField(
|
||||||
default="",
|
default="",
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -15,7 +15,7 @@ from authentik.flows.models import in_memory_stage
|
|||||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
|
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
|
||||||
from authentik.flows.views.executor import SESSION_KEY_POST
|
from authentik.flows.views.executor import SESSION_KEY_POST
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
from authentik.policies.views import BufferedPolicyAccessView
|
from authentik.policies.views import PolicyAccessView
|
||||||
from authentik.providers.saml.exceptions import CannotHandleAssertion
|
from authentik.providers.saml.exceptions import CannotHandleAssertion
|
||||||
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
|
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
|
||||||
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
|
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
|
||||||
@ -35,7 +35,7 @@ from authentik.stages.consent.stage import (
|
|||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class SAMLSSOView(BufferedPolicyAccessView):
|
class SAMLSSOView(PolicyAccessView):
|
||||||
"""SAML SSO Base View, which plans a flow and injects our final stage.
|
"""SAML SSO Base View, which plans a flow and injects our final stage.
|
||||||
Calls get/post handler."""
|
Calls get/post handler."""
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class SAMLSSOView(BufferedPolicyAccessView):
|
|||||||
|
|
||||||
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
|
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
|
||||||
"""GET and POST use the same handler, but we can't
|
"""GET and POST use the same handler, but we can't
|
||||||
override .dispatch easily because BufferedPolicyAccessView's dispatch"""
|
override .dispatch easily because PolicyAccessView's dispatch"""
|
||||||
return self.get(request, application_slug)
|
return self.get(request, application_slug)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ def pytest_report_header(*_, **__):
|
|||||||
|
|
||||||
|
|
||||||
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
||||||
current_id = int(environ.get("CI_RUN_ID", "0")) - 1
|
current_id = int(environ.get("CI_RUN_ID", 0)) - 1
|
||||||
total_ids = int(environ.get("CI_TOTAL_RUNS", "0"))
|
total_ids = int(environ.get("CI_TOTAL_RUNS", 0))
|
||||||
|
|
||||||
if total_ids:
|
if total_ids:
|
||||||
num_tests = len(items)
|
num_tests = len(items)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
"""Kerberos Source Serializer"""
|
||||||
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.sources import (
|
from authentik.core.api.sources import (
|
||||||
GroupSourceConnectionSerializer,
|
GroupSourceConnectionSerializer,
|
||||||
GroupSourceConnectionViewSet,
|
GroupSourceConnectionViewSet,
|
||||||
UserSourceConnectionSerializer,
|
UserSourceConnectionSerializer,
|
||||||
UserSourceConnectionViewSet,
|
|
||||||
)
|
)
|
||||||
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.sources.kerberos.models import (
|
from authentik.sources.kerberos.models import (
|
||||||
GroupKerberosSourceConnection,
|
GroupKerberosSourceConnection,
|
||||||
UserKerberosSourceConnection,
|
UserKerberosSourceConnection,
|
||||||
@ -13,20 +15,33 @@ from authentik.sources.kerberos.models import (
|
|||||||
|
|
||||||
|
|
||||||
class UserKerberosSourceConnectionSerializer(UserSourceConnectionSerializer):
|
class UserKerberosSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||||
class Meta(UserSourceConnectionSerializer.Meta):
|
"""Kerberos Source Serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
model = UserKerberosSourceConnection
|
model = UserKerberosSourceConnection
|
||||||
|
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier"]
|
||||||
|
|
||||||
|
|
||||||
class UserKerberosSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
class UserKerberosSourceConnectionViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
"""Source Viewset"""
|
||||||
|
|
||||||
queryset = UserKerberosSourceConnection.objects.all()
|
queryset = UserKerberosSourceConnection.objects.all()
|
||||||
serializer_class = UserKerberosSourceConnectionSerializer
|
serializer_class = UserKerberosSourceConnectionSerializer
|
||||||
|
filterset_fields = ["source__slug"]
|
||||||
|
search_fields = ["source__slug"]
|
||||||
|
ordering = ["source__slug"]
|
||||||
|
owner_field = "user"
|
||||||
|
|
||||||
|
|
||||||
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||||
|
"""OAuth Group-Source connection Serializer"""
|
||||||
|
|
||||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||||
model = GroupKerberosSourceConnection
|
model = GroupKerberosSourceConnection
|
||||||
|
|
||||||
|
|
||||||
class GroupKerberosSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
class GroupKerberosSourceConnectionViewSet(GroupSourceConnectionViewSet):
|
||||||
|
"""Group-source connection Viewset"""
|
||||||
|
|
||||||
queryset = GroupKerberosSourceConnection.objects.all()
|
queryset = GroupKerberosSourceConnection.objects.all()
|
||||||
serializer_class = GroupKerberosSourceConnectionSerializer
|
serializer_class = GroupKerberosSourceConnectionSerializer
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_identifier(apps, schema_editor):
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
UserKerberosSourceConnection = apps.get_model(
|
|
||||||
"authentik_sources_kerberos", "UserKerberosSourceConnection"
|
|
||||||
)
|
|
||||||
|
|
||||||
for connection in UserKerberosSourceConnection.objects.using(db_alias).all():
|
|
||||||
connection.new_identifier = connection.identifier
|
|
||||||
connection.save(using=db_alias)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_sources_kerberos", "0002_kerberossource_kadmin_type"),
|
|
||||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="userkerberossourceconnection",
|
|
||||||
name="identifier",
|
|
||||||
),
|
|
||||||
]
|
|
@ -372,6 +372,8 @@ class KerberosSourcePropertyMapping(PropertyMapping):
|
|||||||
class UserKerberosSourceConnection(UserSourceConnection):
|
class UserKerberosSourceConnection(UserSourceConnection):
|
||||||
"""Connection to configured Kerberos Sources."""
|
"""Connection to configured Kerberos Sources."""
|
||||||
|
|
||||||
|
identifier = models.TextField()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
from authentik.sources.kerberos.api.source_connection import (
|
from authentik.sources.kerberos.api.source_connection import (
|
||||||
|
@ -99,7 +99,6 @@ class LDAPSourceSerializer(SourceSerializer):
|
|||||||
"sync_groups",
|
"sync_groups",
|
||||||
"sync_parent_group",
|
"sync_parent_group",
|
||||||
"connectivity",
|
"connectivity",
|
||||||
"lookup_groups_from_user",
|
|
||||||
]
|
]
|
||||||
extra_kwargs = {"bind_password": {"write_only": True}}
|
extra_kwargs = {"bind_password": {"write_only": True}}
|
||||||
|
|
||||||
@ -135,7 +134,6 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"sync_parent_group",
|
"sync_parent_group",
|
||||||
"user_property_mappings",
|
"user_property_mappings",
|
||||||
"group_property_mappings",
|
"group_property_mappings",
|
||||||
"lookup_groups_from_user",
|
|
||||||
]
|
]
|
||||||
search_fields = ["name", "slug"]
|
search_fields = ["name", "slug"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
# Generated by Django 5.0.13 on 2025-03-26 17:06
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
(
|
|
||||||
"authentik_sources_ldap",
|
|
||||||
"0006_rename_ldappropertymapping_ldapsourcepropertymapping_and_more",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="ldapsource",
|
|
||||||
name="lookup_groups_from_user",
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -123,14 +123,6 @@ class LDAPSource(Source):
|
|||||||
Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
|
Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
|
||||||
)
|
)
|
||||||
|
|
||||||
lookup_groups_from_user = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text=_(
|
|
||||||
"Lookup group membership based on a user attribute instead of a group attribute. "
|
|
||||||
"This allows nested group resolution on systems like FreeIPA and Active Directory"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def component(self) -> str:
|
def component(self) -> str:
|
||||||
return "ak-source-ldap-form"
|
return "ak-source-ldap-form"
|
||||||
|
@ -28,17 +28,15 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
if not self._source.sync_groups:
|
if not self._source.sync_groups:
|
||||||
self.message("Group syncing is disabled for this Source")
|
self.message("Group syncing is disabled for this Source")
|
||||||
return iter(())
|
return iter(())
|
||||||
|
|
||||||
# If we are looking up groups from users, we don't need to fetch the group membership field
|
|
||||||
attributes = [self._source.object_uniqueness_field, LDAP_DISTINGUISHED_NAME]
|
|
||||||
if not self._source.lookup_groups_from_user:
|
|
||||||
attributes.append(self._source.group_membership_field)
|
|
||||||
|
|
||||||
return self.search_paginator(
|
return self.search_paginator(
|
||||||
search_base=self.base_dn_groups,
|
search_base=self.base_dn_groups,
|
||||||
search_filter=self._source.group_object_filter,
|
search_filter=self._source.group_object_filter,
|
||||||
search_scope=SUBTREE,
|
search_scope=SUBTREE,
|
||||||
attributes=attributes,
|
attributes=[
|
||||||
|
self._source.group_membership_field,
|
||||||
|
self._source.object_uniqueness_field,
|
||||||
|
LDAP_DISTINGUISHED_NAME,
|
||||||
|
],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,24 +47,9 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
return -1
|
return -1
|
||||||
membership_count = 0
|
membership_count = 0
|
||||||
for group in page_data:
|
for group in page_data:
|
||||||
if self._source.lookup_groups_from_user:
|
if "attributes" not in group:
|
||||||
group_dn = group.get("dn", {})
|
continue
|
||||||
group_filter = f"({self._source.group_membership_field}={group_dn})"
|
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
||||||
group_members = self._source.connection().extend.standard.paged_search(
|
|
||||||
search_base=self.base_dn_users,
|
|
||||||
search_filter=group_filter,
|
|
||||||
search_scope=SUBTREE,
|
|
||||||
attributes=[self._source.object_uniqueness_field],
|
|
||||||
)
|
|
||||||
members = []
|
|
||||||
for group_member in group_members:
|
|
||||||
group_member_dn = group_member.get("dn", {})
|
|
||||||
members.append(group_member_dn)
|
|
||||||
else:
|
|
||||||
if "attributes" not in group:
|
|
||||||
continue
|
|
||||||
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
|
||||||
|
|
||||||
ak_group = self.get_group(group)
|
ak_group = self.get_group(group)
|
||||||
if not ak_group:
|
if not ak_group:
|
||||||
continue
|
continue
|
||||||
@ -85,7 +68,7 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
"ak_groups__in": [ak_group],
|
"ak_groups__in": [ak_group],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).distinct()
|
)
|
||||||
membership_count += 1
|
membership_count += 1
|
||||||
membership_count += users.count()
|
membership_count += users.count()
|
||||||
ak_group.users.set(users)
|
ak_group.users.set(users)
|
||||||
|
@ -96,26 +96,6 @@ def mock_freeipa_connection(password: str) -> Connection:
|
|||||||
"objectClass": "posixAccount",
|
"objectClass": "posixAccount",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# User with groups in memberOf attribute
|
|
||||||
connection.strategy.add_entry(
|
|
||||||
"cn=user4,ou=users,dc=goauthentik,dc=io",
|
|
||||||
{
|
|
||||||
"name": "user4_sn",
|
|
||||||
"uid": "user4_sn",
|
|
||||||
"objectClass": "person",
|
|
||||||
"memberOf": [
|
|
||||||
"cn=reverse-lookup-group,ou=groups,dc=goauthentik,dc=io",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
connection.strategy.add_entry(
|
|
||||||
"cn=reverse-lookup-group,ou=groups,dc=goauthentik,dc=io",
|
|
||||||
{
|
|
||||||
"cn": "reverse-lookup-group",
|
|
||||||
"uid": "reverse-lookup-group",
|
|
||||||
"objectClass": "groupOfNames",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# Locked out user
|
# Locked out user
|
||||||
connection.strategy.add_entry(
|
connection.strategy.add_entry(
|
||||||
"cn=user-nsaccountlock,ou=users,dc=goauthentik,dc=io",
|
"cn=user-nsaccountlock,ou=users,dc=goauthentik,dc=io",
|
||||||
|
@ -162,43 +162,6 @@ class LDAPSyncTests(TestCase):
|
|||||||
self.assertFalse(User.objects.filter(username="user1_sn").exists())
|
self.assertFalse(User.objects.filter(username="user1_sn").exists())
|
||||||
self.assertFalse(User.objects.get(username="user-nsaccountlock").is_active)
|
self.assertFalse(User.objects.get(username="user-nsaccountlock").is_active)
|
||||||
|
|
||||||
def test_sync_groups_freeipa_memberOf(self):
|
|
||||||
"""Test group sync when membership is derived from memberOf user attribute"""
|
|
||||||
self.source.object_uniqueness_field = "uid"
|
|
||||||
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
|
||||||
self.source.lookup_groups_from_user = True
|
|
||||||
self.source.group_membership_field = "memberOf"
|
|
||||||
self.source.user_property_mappings.set(
|
|
||||||
LDAPSourcePropertyMapping.objects.filter(
|
|
||||||
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
|
||||||
| Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.source.group_property_mappings.set(
|
|
||||||
LDAPSourcePropertyMapping.objects.filter(
|
|
||||||
managed="goauthentik.io/sources/ldap/openldap-cn"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
connection = MagicMock(return_value=mock_freeipa_connection(LDAP_PASSWORD))
|
|
||||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
|
||||||
user_sync = UserLDAPSynchronizer(self.source)
|
|
||||||
user_sync.sync_full()
|
|
||||||
group_sync = GroupLDAPSynchronizer(self.source)
|
|
||||||
group_sync.sync_full()
|
|
||||||
membership_sync = MembershipLDAPSynchronizer(self.source)
|
|
||||||
membership_sync.sync_full()
|
|
||||||
|
|
||||||
self.assertTrue(
|
|
||||||
User.objects.filter(username="user4_sn").exists(), "User does not exist"
|
|
||||||
)
|
|
||||||
# Test if membership mapping based on memberOf works.
|
|
||||||
memberof_group = Group.objects.filter(name="reverse-lookup-group")
|
|
||||||
self.assertTrue(memberof_group.exists(), "Group does not exist")
|
|
||||||
self.assertTrue(
|
|
||||||
memberof_group.first().users.filter(username="user4_sn").exists(),
|
|
||||||
"User not a member of the group",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_sync_groups_ad(self):
|
def test_sync_groups_ad(self):
|
||||||
"""Test group sync"""
|
"""Test group sync"""
|
||||||
self.source.user_property_mappings.set(
|
self.source.user_property_mappings.set(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""OAuth Source Serializer"""
|
||||||
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.sources import (
|
from authentik.core.api.sources import (
|
||||||
@ -10,9 +12,11 @@ from authentik.sources.oauth.models import GroupOAuthSourceConnection, UserOAuth
|
|||||||
|
|
||||||
|
|
||||||
class UserOAuthSourceConnectionSerializer(UserSourceConnectionSerializer):
|
class UserOAuthSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||||
|
"""OAuth Source Serializer"""
|
||||||
|
|
||||||
class Meta(UserSourceConnectionSerializer.Meta):
|
class Meta(UserSourceConnectionSerializer.Meta):
|
||||||
model = UserOAuthSourceConnection
|
model = UserOAuthSourceConnection
|
||||||
fields = UserSourceConnectionSerializer.Meta.fields + ["access_token"]
|
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier", "access_token"]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**UserSourceConnectionSerializer.Meta.extra_kwargs,
|
**UserSourceConnectionSerializer.Meta.extra_kwargs,
|
||||||
"access_token": {"write_only": True},
|
"access_token": {"write_only": True},
|
||||||
@ -20,15 +24,21 @@ class UserOAuthSourceConnectionSerializer(UserSourceConnectionSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class UserOAuthSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
class UserOAuthSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||||
|
"""Source Viewset"""
|
||||||
|
|
||||||
queryset = UserOAuthSourceConnection.objects.all()
|
queryset = UserOAuthSourceConnection.objects.all()
|
||||||
serializer_class = UserOAuthSourceConnectionSerializer
|
serializer_class = UserOAuthSourceConnectionSerializer
|
||||||
|
|
||||||
|
|
||||||
class GroupOAuthSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
class GroupOAuthSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||||
|
"""OAuth Group-Source connection Serializer"""
|
||||||
|
|
||||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||||
model = GroupOAuthSourceConnection
|
model = GroupOAuthSourceConnection
|
||||||
|
|
||||||
|
|
||||||
class GroupOAuthSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
class GroupOAuthSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
||||||
|
"""Group-source connection Viewset"""
|
||||||
|
|
||||||
queryset = GroupOAuthSourceConnection.objects.all()
|
queryset = GroupOAuthSourceConnection.objects.all()
|
||||||
serializer_class = GroupOAuthSourceConnectionSerializer
|
serializer_class = GroupOAuthSourceConnectionSerializer
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_identifier(apps, schema_editor):
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
UserOAuthSourceConnection = apps.get_model(
|
|
||||||
"authentik_sources_oauth", "UserOAuthSourceConnection"
|
|
||||||
)
|
|
||||||
|
|
||||||
for connection in UserOAuthSourceConnection.objects.using(db_alias).all():
|
|
||||||
connection.new_identifier = connection.identifier
|
|
||||||
connection.save(using=db_alias)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_sources_oauth", "0008_groupoauthsourceconnection_and_more"),
|
|
||||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="useroauthsourceconnection",
|
|
||||||
name="identifier",
|
|
||||||
),
|
|
||||||
]
|
|
@ -286,6 +286,7 @@ class OAuthSourcePropertyMapping(PropertyMapping):
|
|||||||
class UserOAuthSourceConnection(UserSourceConnection):
|
class UserOAuthSourceConnection(UserSourceConnection):
|
||||||
"""Authorized remote OAuth provider."""
|
"""Authorized remote OAuth provider."""
|
||||||
|
|
||||||
|
identifier = models.CharField(max_length=255)
|
||||||
access_token = models.TextField(blank=True, null=True, default=None)
|
access_token = models.TextField(blank=True, null=True, default=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""Plex Source connection Serializer"""
|
||||||
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.sources import (
|
from authentik.core.api.sources import (
|
||||||
@ -10,9 +12,14 @@ from authentik.sources.plex.models import GroupPlexSourceConnection, UserPlexSou
|
|||||||
|
|
||||||
|
|
||||||
class UserPlexSourceConnectionSerializer(UserSourceConnectionSerializer):
|
class UserPlexSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||||
|
"""Plex Source connection Serializer"""
|
||||||
|
|
||||||
class Meta(UserSourceConnectionSerializer.Meta):
|
class Meta(UserSourceConnectionSerializer.Meta):
|
||||||
model = UserPlexSourceConnection
|
model = UserPlexSourceConnection
|
||||||
fields = UserSourceConnectionSerializer.Meta.fields + ["plex_token"]
|
fields = UserSourceConnectionSerializer.Meta.fields + [
|
||||||
|
"identifier",
|
||||||
|
"plex_token",
|
||||||
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**UserSourceConnectionSerializer.Meta.extra_kwargs,
|
**UserSourceConnectionSerializer.Meta.extra_kwargs,
|
||||||
"plex_token": {"write_only": True},
|
"plex_token": {"write_only": True},
|
||||||
@ -20,15 +27,21 @@ class UserPlexSourceConnectionSerializer(UserSourceConnectionSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class UserPlexSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
class UserPlexSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||||
|
"""Plex Source connection Serializer"""
|
||||||
|
|
||||||
queryset = UserPlexSourceConnection.objects.all()
|
queryset = UserPlexSourceConnection.objects.all()
|
||||||
serializer_class = UserPlexSourceConnectionSerializer
|
serializer_class = UserPlexSourceConnectionSerializer
|
||||||
|
|
||||||
|
|
||||||
class GroupPlexSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
class GroupPlexSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||||
|
"""Plex Group-Source connection Serializer"""
|
||||||
|
|
||||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||||
model = GroupPlexSourceConnection
|
model = GroupPlexSourceConnection
|
||||||
|
|
||||||
|
|
||||||
class GroupPlexSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
class GroupPlexSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
||||||
|
"""Group-source connection Viewset"""
|
||||||
|
|
||||||
queryset = GroupPlexSourceConnection.objects.all()
|
queryset = GroupPlexSourceConnection.objects.all()
|
||||||
serializer_class = GroupPlexSourceConnectionSerializer
|
serializer_class = GroupPlexSourceConnectionSerializer
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_identifier(apps, schema_editor):
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
UserPlexSourceConnection = apps.get_model("authentik_sources_plex", "UserPlexSourceConnection")
|
|
||||||
|
|
||||||
for connection in UserPlexSourceConnection.objects.using(db_alias).all():
|
|
||||||
connection.new_identifier = connection.identifier
|
|
||||||
connection.save(using=db_alias)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
(
|
|
||||||
"authentik_sources_plex",
|
|
||||||
"0004_groupplexsourceconnection_plexsourcepropertymapping_and_more",
|
|
||||||
),
|
|
||||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="userplexsourceconnection",
|
|
||||||
name="identifier",
|
|
||||||
),
|
|
||||||
]
|
|
@ -141,6 +141,7 @@ class UserPlexSourceConnection(UserSourceConnection):
|
|||||||
"""Connect user and plex source"""
|
"""Connect user and plex source"""
|
||||||
|
|
||||||
plex_token = models.TextField()
|
plex_token = models.TextField()
|
||||||
|
identifier = models.TextField()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"""SAML Source Serializer"""
|
||||||
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.sources import (
|
from authentik.core.api.sources import (
|
||||||
@ -10,20 +12,29 @@ from authentik.sources.saml.models import GroupSAMLSourceConnection, UserSAMLSou
|
|||||||
|
|
||||||
|
|
||||||
class UserSAMLSourceConnectionSerializer(UserSourceConnectionSerializer):
|
class UserSAMLSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||||
|
"""SAML Source Serializer"""
|
||||||
|
|
||||||
class Meta(UserSourceConnectionSerializer.Meta):
|
class Meta(UserSourceConnectionSerializer.Meta):
|
||||||
model = UserSAMLSourceConnection
|
model = UserSAMLSourceConnection
|
||||||
|
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier"]
|
||||||
|
|
||||||
|
|
||||||
class UserSAMLSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
class UserSAMLSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||||
|
"""Source Viewset"""
|
||||||
|
|
||||||
queryset = UserSAMLSourceConnection.objects.all()
|
queryset = UserSAMLSourceConnection.objects.all()
|
||||||
serializer_class = UserSAMLSourceConnectionSerializer
|
serializer_class = UserSAMLSourceConnectionSerializer
|
||||||
|
|
||||||
|
|
||||||
class GroupSAMLSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
class GroupSAMLSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||||
|
"""OAuth Group-Source connection Serializer"""
|
||||||
|
|
||||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||||
model = GroupSAMLSourceConnection
|
model = GroupSAMLSourceConnection
|
||||||
|
|
||||||
|
|
||||||
class GroupSAMLSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
class GroupSAMLSourceConnectionViewSet(GroupSourceConnectionViewSet):
|
||||||
|
"""Group-source connection Viewset"""
|
||||||
|
|
||||||
queryset = GroupSAMLSourceConnection.objects.all()
|
queryset = GroupSAMLSourceConnection.objects.all()
|
||||||
serializer_class = GroupSAMLSourceConnectionSerializer
|
serializer_class = GroupSAMLSourceConnectionSerializer
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
# Generated by Django 5.0.13 on 2025-03-31 13:53
|
|
||||||
|
|
||||||
import authentik.lib.models
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_sources_saml", "0017_fix_x509subjectname"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="samlsource",
|
|
||||||
name="slo_url",
|
|
||||||
field=models.TextField(
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
help_text="Optional URL if your IDP supports Single-Logout.",
|
|
||||||
null=True,
|
|
||||||
validators=[authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))],
|
|
||||||
verbose_name="SLO URL",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="samlsource",
|
|
||||||
name="sso_url",
|
|
||||||
field=models.TextField(
|
|
||||||
help_text="URL that the initial Login request is sent to.",
|
|
||||||
validators=[authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))],
|
|
||||||
verbose_name="SSO URL",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,26 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_identifier(apps, schema_editor):
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
UserSAMLSourceConnection = apps.get_model("authentik_sources_saml", "UserSAMLSourceConnection")
|
|
||||||
|
|
||||||
for connection in UserSAMLSourceConnection.objects.using(db_alias).all():
|
|
||||||
connection.new_identifier = connection.identifier
|
|
||||||
connection.save(using=db_alias)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("authentik_sources_saml", "0018_alter_samlsource_slo_url_alter_samlsource_sso_url"),
|
|
||||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="usersamlsourceconnection",
|
|
||||||
name="identifier",
|
|
||||||
),
|
|
||||||
]
|
|
@ -20,7 +20,6 @@ from authentik.crypto.models import CertificateKeyPair
|
|||||||
from authentik.flows.challenge import RedirectChallenge
|
from authentik.flows.challenge import RedirectChallenge
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.lib.expression.evaluator import BaseEvaluator
|
from authentik.lib.expression.evaluator import BaseEvaluator
|
||||||
from authentik.lib.models import DomainlessURLValidator
|
|
||||||
from authentik.lib.utils.time import timedelta_string_validator
|
from authentik.lib.utils.time import timedelta_string_validator
|
||||||
from authentik.sources.saml.processors.constants import (
|
from authentik.sources.saml.processors.constants import (
|
||||||
DSA_SHA1,
|
DSA_SHA1,
|
||||||
@ -92,13 +91,11 @@ class SAMLSource(Source):
|
|||||||
help_text=_("Also known as Entity ID. Defaults the Metadata URL."),
|
help_text=_("Also known as Entity ID. Defaults the Metadata URL."),
|
||||||
)
|
)
|
||||||
|
|
||||||
sso_url = models.TextField(
|
sso_url = models.URLField(
|
||||||
validators=[DomainlessURLValidator(schemes=("http", "https"))],
|
|
||||||
verbose_name=_("SSO URL"),
|
verbose_name=_("SSO URL"),
|
||||||
help_text=_("URL that the initial Login request is sent to."),
|
help_text=_("URL that the initial Login request is sent to."),
|
||||||
)
|
)
|
||||||
slo_url = models.TextField(
|
slo_url = models.URLField(
|
||||||
validators=[DomainlessURLValidator(schemes=("http", "https"))],
|
|
||||||
default=None,
|
default=None,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@ -318,6 +315,8 @@ class SAMLSourcePropertyMapping(PropertyMapping):
|
|||||||
class UserSAMLSourceConnection(UserSourceConnection):
|
class UserSAMLSourceConnection(UserSourceConnection):
|
||||||
"""Connection to configured SAML Sources."""
|
"""Connection to configured SAML Sources."""
|
||||||
|
|
||||||
|
identifier = models.TextField()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> Serializer:
|
def serializer(self) -> Serializer:
|
||||||
from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionSerializer
|
from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionSerializer
|
||||||
|
File diff suppressed because one or more lines are too long
@ -104,13 +104,6 @@ def send_mail(
|
|||||||
# can't be converted to json)
|
# can't be converted to json)
|
||||||
message_object.attach(logo_data())
|
message_object.attach(logo_data())
|
||||||
|
|
||||||
if (
|
|
||||||
message_object.to
|
|
||||||
and isinstance(message_object.to[0], str)
|
|
||||||
and "=?utf-8?" in message_object.to[0]
|
|
||||||
):
|
|
||||||
message_object.to = [message_object.to[0].split("<")[-1].replace(">", "")]
|
|
||||||
|
|
||||||
LOGGER.debug("Sending mail", to=message_object.to)
|
LOGGER.debug("Sending mail", to=message_object.to)
|
||||||
backend.send_messages([message_object])
|
backend.send_messages([message_object])
|
||||||
Event.new(
|
Event.new(
|
||||||
|
@ -97,37 +97,6 @@ class TestEmailStageSending(FlowTestCase):
|
|||||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||||
self.assertEqual(mail.outbox[0].to, [f"Test User Many Words <{long_user.email}>"])
|
self.assertEqual(mail.outbox[0].to, [f"Test User Many Words <{long_user.email}>"])
|
||||||
|
|
||||||
def test_utf8_name(self):
|
|
||||||
"""Test with pending user"""
|
|
||||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
|
||||||
utf8_user = create_test_user()
|
|
||||||
utf8_user.name = "Cirilo ЉМНЊ el cirilico И̂ӢЙӤ "
|
|
||||||
utf8_user.email = "cyrillic@authentik.local"
|
|
||||||
utf8_user.save()
|
|
||||||
plan.context[PLAN_CONTEXT_PENDING_USER] = utf8_user
|
|
||||||
session = self.client.session
|
|
||||||
session[SESSION_KEY_PLAN] = plan
|
|
||||||
session.save()
|
|
||||||
Event.objects.filter(action=EventAction.EMAIL_SENT).delete()
|
|
||||||
|
|
||||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
|
||||||
with patch(
|
|
||||||
"authentik.stages.email.models.EmailStage.backend_class",
|
|
||||||
PropertyMock(return_value=EmailBackend),
|
|
||||||
):
|
|
||||||
response = self.client.post(url)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertStageResponse(
|
|
||||||
response,
|
|
||||||
self.flow,
|
|
||||||
response_errors={
|
|
||||||
"non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
|
||||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
|
||||||
self.assertEqual(mail.outbox[0].to, [f"{utf8_user.email}"])
|
|
||||||
|
|
||||||
def test_pending_fake_user(self):
|
def test_pending_fake_user(self):
|
||||||
"""Test with pending (fake) user"""
|
"""Test with pending (fake) user"""
|
||||||
self.flow.designation = FlowDesignation.RECOVERY
|
self.flow.designation = FlowDesignation.RECOVERY
|
||||||
|
@ -142,38 +142,35 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||||||
raise ValidationError("Failed to authenticate.")
|
raise ValidationError("Failed to authenticate.")
|
||||||
self.pre_user = pre_user
|
self.pre_user = pre_user
|
||||||
|
|
||||||
|
# Password check
|
||||||
|
if current_stage.password_stage:
|
||||||
|
password = attrs.get("password", None)
|
||||||
|
if not password:
|
||||||
|
self.stage.logger.warning("Password not set for ident+auth attempt")
|
||||||
|
try:
|
||||||
|
with start_span(
|
||||||
|
op="authentik.stages.identification.authenticate",
|
||||||
|
name="User authenticate call (combo stage)",
|
||||||
|
):
|
||||||
|
user = authenticate(
|
||||||
|
self.stage.request,
|
||||||
|
current_stage.password_stage.backends,
|
||||||
|
current_stage,
|
||||||
|
username=self.pre_user.username,
|
||||||
|
password=password,
|
||||||
|
)
|
||||||
|
if not user:
|
||||||
|
raise ValidationError("Failed to authenticate.")
|
||||||
|
self.pre_user = user
|
||||||
|
except PermissionDenied as exc:
|
||||||
|
raise ValidationError(str(exc)) from exc
|
||||||
|
|
||||||
# Captcha check
|
# Captcha check
|
||||||
if captcha_stage := current_stage.captcha_stage:
|
if captcha_stage := current_stage.captcha_stage:
|
||||||
captcha_token = attrs.get("captcha_token", None)
|
captcha_token = attrs.get("captcha_token", None)
|
||||||
if not captcha_token:
|
if not captcha_token:
|
||||||
self.stage.logger.warning("Token not set for captcha attempt")
|
self.stage.logger.warning("Token not set for captcha attempt")
|
||||||
verify_captcha_token(captcha_stage, captcha_token, client_ip)
|
verify_captcha_token(captcha_stage, captcha_token, client_ip)
|
||||||
|
|
||||||
# Password check
|
|
||||||
if not current_stage.password_stage:
|
|
||||||
# No password stage select, don't validate the password
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
password = attrs.get("password", None)
|
|
||||||
if not password:
|
|
||||||
self.stage.logger.warning("Password not set for ident+auth attempt")
|
|
||||||
try:
|
|
||||||
with start_span(
|
|
||||||
op="authentik.stages.identification.authenticate",
|
|
||||||
name="User authenticate call (combo stage)",
|
|
||||||
):
|
|
||||||
user = authenticate(
|
|
||||||
self.stage.request,
|
|
||||||
current_stage.password_stage.backends,
|
|
||||||
current_stage,
|
|
||||||
username=self.pre_user.username,
|
|
||||||
password=password,
|
|
||||||
)
|
|
||||||
if not user:
|
|
||||||
raise ValidationError("Failed to authenticate.")
|
|
||||||
self.pre_user = user
|
|
||||||
except PermissionDenied as exc:
|
|
||||||
raise ValidationError(str(exc)) from exc
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2025.2.4 Blueprint schema",
|
"title": "authentik 2025.2.2 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
@ -6423,6 +6423,8 @@
|
|||||||
},
|
},
|
||||||
"acs_url": {
|
"acs_url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"format": "uri",
|
||||||
|
"maxLength": 200,
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "ACS URL"
|
"title": "ACS URL"
|
||||||
},
|
},
|
||||||
@ -7885,11 +7887,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
"title": "Sync parent group"
|
"title": "Sync parent group"
|
||||||
},
|
|
||||||
"lookup_groups_from_user": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Lookup groups from user",
|
|
||||||
"description": "Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
@ -8236,6 +8233,7 @@
|
|||||||
},
|
},
|
||||||
"identifier": {
|
"identifier": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"maxLength": 255,
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Identifier"
|
"title": "Identifier"
|
||||||
},
|
},
|
||||||
@ -8735,6 +8733,8 @@
|
|||||||
},
|
},
|
||||||
"sso_url": {
|
"sso_url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"format": "uri",
|
||||||
|
"maxLength": 200,
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "SSO URL",
|
"title": "SSO URL",
|
||||||
"description": "URL that the initial Login request is sent to."
|
"description": "URL that the initial Login request is sent to."
|
||||||
@ -8744,6 +8744,8 @@
|
|||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
|
"format": "uri",
|
||||||
|
"maxLength": 200,
|
||||||
"title": "SLO URL",
|
"title": "SLO URL",
|
||||||
"description": "Optional URL if your IDP supports Single-Logout."
|
"description": "Optional URL if your IDP supports Single-Logout."
|
||||||
},
|
},
|
||||||
|
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.4}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -54,7 +54,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.4}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.2}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
17
go.mod
17
go.mod
@ -1,10 +1,12 @@
|
|||||||
module goauthentik.io
|
module goauthentik.io
|
||||||
|
|
||||||
go 1.24.0
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
beryju.io/ldap v0.1.0
|
beryju.io/ldap v0.1.0
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1
|
github.com/coreos/go-oidc/v3 v3.13.0
|
||||||
github.com/getsentry/sentry-go v0.31.1
|
github.com/getsentry/sentry-go v0.31.1
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.10
|
github.com/go-ldap/ldap/v3 v3.4.10
|
||||||
@ -20,17 +22,17 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.8.0
|
github.com/pires/go-proxyproto v0.8.0
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.21.1
|
||||||
github.com/redis/go-redis/v9 v9.7.3
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
github.com/sethvargo/go-envconfig v1.1.1
|
github.com/sethvargo/go-envconfig v1.1.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2025024.1
|
goauthentik.io/api/v3 v3.2025022.6
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.29.0
|
golang.org/x/oauth2 v0.28.0
|
||||||
golang.org/x/sync v0.13.0
|
golang.org/x/sync v0.12.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||||
)
|
)
|
||||||
@ -60,6 +62,7 @@ require (
|
|||||||
github.com/go-openapi/validate v0.24.0 // indirect
|
github.com/go-openapi/validate v0.24.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
@ -76,6 +79,6 @@ require (
|
|||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
31
go.sum
31
go.sum
@ -55,8 +55,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@ -148,9 +148,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
@ -208,8 +207,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
|||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@ -240,8 +239,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
@ -300,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
goauthentik.io/api/v3 v3.2025024.1 h1:wYmpbNW1XptrjS5dlnZj8CrCs+JUGEVJYStrFdWL9aA=
|
goauthentik.io/api/v3 v3.2025022.6 h1:M5M8Cd/1N7E8KLkvYYh7VdcdKz5nfzjKPFLK+YOtOVg=
|
||||||
goauthentik.io/api/v3 v3.2025024.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025022.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@ -396,8 +395,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
|
|||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -412,8 +411,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -600,8 +599,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
|||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2025.2.4"
|
const VERSION = "2025.2.2"
|
||||||
|
5
internal/crypto/backend/fips_disabled.go
Normal file
5
internal/crypto/backend/fips_disabled.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//go:build requirefips
|
||||||
|
|
||||||
|
package backend
|
||||||
|
|
||||||
|
var FipsEnabled = true
|
5
internal/crypto/backend/fips_enabled.go
Normal file
5
internal/crypto/backend/fips_enabled.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//go:build !requirefips
|
||||||
|
|
||||||
|
package backend
|
||||||
|
|
||||||
|
var FipsEnabled = false
|
@ -2,7 +2,6 @@ package ak
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/fips140"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -204,7 +203,7 @@ func (a *APIController) getWebsocketPingArgs() map[string]interface{} {
|
|||||||
"golangVersion": runtime.Version(),
|
"golangVersion": runtime.Version(),
|
||||||
"opensslEnabled": cryptobackend.OpensslEnabled,
|
"opensslEnabled": cryptobackend.OpensslEnabled,
|
||||||
"opensslVersion": cryptobackend.OpensslVersion(),
|
"opensslVersion": cryptobackend.OpensslVersion(),
|
||||||
"fipsEnabled": fips140.Enabled(),
|
"fipsEnabled": cryptobackend.FipsEnabled,
|
||||||
}
|
}
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# Stage 1: Build
|
# Stage 1: Build
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
|
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.23-fips-bookworm AS builder
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
@ -27,7 +27,7 @@ COPY . .
|
|||||||
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
||||||
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
|
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
|
||||||
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
|
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
|
||||||
CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \
|
CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \
|
||||||
go build -o /go/ldap ./cmd/ldap
|
go build -o /go/ldap ./cmd/ldap
|
||||||
|
|
||||||
# Stage 2: Run
|
# Stage 2: Run
|
||||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1007.0",
|
"aws-cdk": "^2.1006.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -17,9 +17,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk": {
|
"node_modules/aws-cdk": {
|
||||||
"version": "2.1007.0",
|
"version": "2.1006.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1007.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1006.0.tgz",
|
||||||
"integrity": "sha512-/UOYOTGWUm+pP9qxg03tID5tL6euC+pb+xo0RBue+xhnUWwj/Bbsw6DbqbpOPMrNzTUxmM723/uMEQmM6S26dw==",
|
"integrity": "sha512-6qYnCt4mBN+3i/5F+FC2yMETkDHY/IL7gt3EuqKVPcaAO4jU7oXfVSlR60CYRkZWL4fnAurUV14RkJuJyVG/IA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1007.0",
|
"aws-cdk": "^2.1006.0",
|
||||||
"cross-env": "^7.0.3"
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ Parameters:
|
|||||||
Description: authentik Docker image
|
Description: authentik Docker image
|
||||||
AuthentikVersion:
|
AuthentikVersion:
|
||||||
Type: String
|
Type: String
|
||||||
Default: 2025.2.4
|
Default: 2025.2.2
|
||||||
Description: authentik Docker image tag
|
Description: authentik Docker image tag
|
||||||
AuthentikServerCPU:
|
AuthentikServerCPU:
|
||||||
Type: Number
|
Type: Number
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -1220,20 +1220,6 @@ msgstr ""
|
|||||||
msgid "Reputation Scores"
|
msgid "Reputation Scores"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Waiting for authentication..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid ""
|
|
||||||
"You're already authenticating in another tab. This page will refresh once "
|
|
||||||
"authentication is completed."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Authenticate in this tab"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/denied.html
|
#: authentik/policies/templates/policies/denied.html
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
# Manuel Viens, 2023
|
# Manuel Viens, 2023
|
||||||
# Mordecai, 2023
|
# Mordecai, 2023
|
||||||
# nerdinator <florian.dupret@gmail.com>, 2024
|
# nerdinator <florian.dupret@gmail.com>, 2024
|
||||||
|
# Tina, 2024
|
||||||
# Charles Leclerc, 2025
|
# Charles Leclerc, 2025
|
||||||
# Tina, 2025
|
|
||||||
# Marc Schmitt, 2025
|
# Marc Schmitt, 2025
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@ -19,7 +19,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Marc Schmitt, 2025\n"
|
"Last-Translator: Marc Schmitt, 2025\n"
|
||||||
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
|
||||||
@ -1347,22 +1347,6 @@ msgstr "Score de Réputation"
|
|||||||
msgid "Reputation Scores"
|
msgid "Reputation Scores"
|
||||||
msgstr "Scores de Réputation"
|
msgstr "Scores de Réputation"
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Waiting for authentication..."
|
|
||||||
msgstr "En attente de l'authentification..."
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid ""
|
|
||||||
"You're already authenticating in another tab. This page will refresh once "
|
|
||||||
"authentication is completed."
|
|
||||||
msgstr ""
|
|
||||||
"Vous êtes déjà en cours d'authentification dans un autre onglet. Cette page "
|
|
||||||
"se rafraîchira lorsque l'authentification sera terminée."
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Authenticate in this tab"
|
|
||||||
msgstr "S'authentifier dans cet onglet"
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/denied.html
|
#: authentik/policies/templates/policies/denied.html
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr "Permission refusée"
|
msgstr "Permission refusée"
|
||||||
|
@ -6,23 +6,23 @@
|
|||||||
# Translators:
|
# Translators:
|
||||||
# Dario Rigolin, 2022
|
# Dario Rigolin, 2022
|
||||||
# aoor9, 2023
|
# aoor9, 2023
|
||||||
|
# Matteo Piccina <altermatte@gmail.com>, 2024
|
||||||
# Enrico Campani, 2024
|
# Enrico Campani, 2024
|
||||||
# Marco Vitale, 2024
|
# Marco Vitale, 2024
|
||||||
|
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2024
|
||||||
# Nicola Mersi, 2024
|
# Nicola Mersi, 2024
|
||||||
# tmassimi, 2024
|
# tmassimi, 2024
|
||||||
# Marc Schmitt, 2024
|
# Marc Schmitt, 2024
|
||||||
# albanobattistella <albanobattistella@gmail.com>, 2024
|
# albanobattistella <albanobattistella@gmail.com>, 2024
|
||||||
# Matteo Piccina <altermatte@gmail.com>, 2025
|
|
||||||
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
|
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
"POT-Creation-Date: 2025-02-14 14:49+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
|
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2024\n"
|
||||||
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@ -130,10 +130,6 @@ msgstr "L'utente non ha accesso all'applicazione."
|
|||||||
msgid "Extra description not available"
|
msgid "Extra description not available"
|
||||||
msgstr "Descrizione extra non disponibile"
|
msgstr "Descrizione extra non disponibile"
|
||||||
|
|
||||||
#: authentik/core/api/groups.py
|
|
||||||
msgid "Cannot set group as parent of itself."
|
|
||||||
msgstr "Impossibile impostare il gruppo come padre di se stesso."
|
|
||||||
|
|
||||||
#: authentik/core/api/providers.py
|
#: authentik/core/api/providers.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"When not set all providers are returned. When set to true, only backchannel "
|
"When not set all providers are returned. When set to true, only backchannel "
|
||||||
@ -181,14 +177,6 @@ msgstr "Aggiungi utente al gruppo"
|
|||||||
msgid "Remove user from group"
|
msgid "Remove user from group"
|
||||||
msgstr "Rimuovi l'utente dal gruppo"
|
msgstr "Rimuovi l'utente dal gruppo"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
|
||||||
msgid "Enable superuser status"
|
|
||||||
msgstr "Abilita stato di superutente"
|
|
||||||
|
|
||||||
#: authentik/core/models.py
|
|
||||||
msgid "Disable superuser status"
|
|
||||||
msgstr "Disabilita stato di superutente"
|
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "User's display name."
|
msgid "User's display name."
|
||||||
msgstr "Nome visualizzato dell'utente."
|
msgstr "Nome visualizzato dell'utente."
|
||||||
@ -272,11 +260,11 @@ msgstr "Applicazioni"
|
|||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlement"
|
msgid "Application Entitlement"
|
||||||
msgstr "Entitlement Applicazione"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlements"
|
msgid "Application Entitlements"
|
||||||
msgstr "Entitlements Applicazione"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Use the source-specific identifier"
|
msgid "Use the source-specific identifier"
|
||||||
@ -563,6 +551,62 @@ msgstr "Mappatura Microsoft Entra Provider"
|
|||||||
msgid "Microsoft Entra Provider Mappings"
|
msgid "Microsoft Entra Provider Mappings"
|
||||||
msgstr "Mappature Microsoft Entra Provider"
|
msgstr "Mappature Microsoft Entra Provider"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
#: authentik/stages/user_login/models.py
|
||||||
|
msgid ""
|
||||||
|
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||||
|
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||||
|
msgstr ""
|
||||||
|
"Determina quanto può durare una sessione. Se impostato a 0, la sessione "
|
||||||
|
"durerà fino alla chiusura del browser. (Formato: "
|
||||||
|
"hours=-1;minutes=-2;seconds=-3)"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||||
|
msgstr ""
|
||||||
|
"Se impostato su vero, i token di connessione verranno eliminati alla "
|
||||||
|
"disconnessione."
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Provider"
|
||||||
|
msgstr "Fornitore di controllo dell'accesso remoto"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Providers"
|
||||||
|
msgstr "Fornitori di controllo dell'accesso remoto"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Endpoint"
|
||||||
|
msgstr "Endpoint RAC"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Endpoints"
|
||||||
|
msgstr "Endpoints RAC"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Provider Property Mapping"
|
||||||
|
msgstr "Mappatura delle proprietà del provider RAC"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Provider Property Mappings"
|
||||||
|
msgstr "Mappature proprietà del provider RAC"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Connection token"
|
||||||
|
msgstr "RAC Connection token"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/models.py
|
||||||
|
msgid "RAC Connection tokens"
|
||||||
|
msgstr "RAC Connection tokens"
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/views.py
|
||||||
|
msgid "Maximum connection limit reached."
|
||||||
|
msgstr "Limite massimo di connessioni raggiunto."
|
||||||
|
|
||||||
|
#: authentik/enterprise/providers/rac/views.py
|
||||||
|
msgid "(You are already connected in another tab/window)"
|
||||||
|
msgstr "(Sei già connesso in un'altra scheda/finestra)"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "Signing Key"
|
msgid "Signing Key"
|
||||||
@ -570,39 +614,39 @@ msgstr "Chiave di firma"
|
|||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Key used to sign the SSF Events."
|
msgid "Key used to sign the SSF Events."
|
||||||
msgstr "Chiave utilizzata per firmare gli eventi SSF."
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Provider"
|
msgid "Shared Signals Framework Provider"
|
||||||
msgstr "Fornitore Shared Signals Framework"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Providers"
|
msgid "Shared Signals Framework Providers"
|
||||||
msgstr "Fornitori Shared Signals Framework"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Add stream to SSF provider"
|
msgid "Add stream to SSF provider"
|
||||||
msgstr "Aggiungi Stream al provider SSF"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream"
|
msgid "SSF Stream"
|
||||||
msgstr "SSF Stream"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Streams"
|
msgid "SSF Streams"
|
||||||
msgstr "SSF Streams"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Event"
|
msgid "SSF Stream Event"
|
||||||
msgstr "Evento di Stream SSF"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Events"
|
msgid "SSF Stream Events"
|
||||||
msgstr "Eventi di Stream SSF"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/tasks.py
|
#: authentik/enterprise/providers/ssf/tasks.py
|
||||||
msgid "Failed to send request"
|
msgid "Failed to send request"
|
||||||
msgstr "Impossibile inviare la richiesta"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
@ -668,26 +712,9 @@ msgid "Slack Webhook (Slack/Discord)"
|
|||||||
msgstr "Slack Webhook (Slack/Discord)"
|
msgstr "Slack Webhook (Slack/Discord)"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
#: authentik/stages/authenticator_validate/models.py
|
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
|
||||||
msgid ""
|
|
||||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
|
||||||
"serializable."
|
|
||||||
msgstr ""
|
|
||||||
"Personalizza il corpo della richiesta. Il mapping dovrebbe restituire dati "
|
|
||||||
"serializzabili in JSON."
|
|
||||||
|
|
||||||
#: authentik/events/models.py
|
|
||||||
msgid ""
|
|
||||||
"Configure additional headers to be sent. Mapping should return a dictionary "
|
|
||||||
"of key-value pairs"
|
|
||||||
msgstr ""
|
|
||||||
"Configurare le intestazioni aggiuntive da inviare. Il mapping dovrebbe "
|
|
||||||
"restituire un dizionario di coppie chiave-valore."
|
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Only send notification once, for example when sending a webhook into a chat "
|
"Only send notification once, for example when sending a webhook into a chat "
|
||||||
@ -917,7 +944,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/flows/models.py
|
#: authentik/flows/models.py
|
||||||
msgid "Evaluate policies when the Stage is presented to the user."
|
msgid "Evaluate policies when the Stage is presented to the user."
|
||||||
msgstr "Valutare i criteri quando la fase viene presentata all'utente."
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/flows/models.py
|
#: authentik/flows/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -959,14 +986,6 @@ msgstr "Tokens del flusso"
|
|||||||
msgid "Invalid next URL"
|
msgid "Invalid next URL"
|
||||||
msgstr "URL successivo non valido"
|
msgstr "URL successivo non valido"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/models.py
|
|
||||||
msgid ""
|
|
||||||
"When enabled, provider will not modify or create objects in the remote "
|
|
||||||
"system."
|
|
||||||
msgstr ""
|
|
||||||
"Quando abilitato, il provider non modificherà o creerà oggetti nel sistema "
|
|
||||||
"remoto."
|
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
msgid "Starting full provider sync"
|
msgid "Starting full provider sync"
|
||||||
msgstr "Avvio della sincronizzazione completa del provider"
|
msgstr "Avvio della sincronizzazione completa del provider"
|
||||||
@ -981,10 +1000,6 @@ msgstr "Sincronizzando pagina {page} degli utenti"
|
|||||||
msgid "Syncing page {page} of groups"
|
msgid "Syncing page {page} of groups"
|
||||||
msgstr "Sincronizzando pagina {page} dei gruppi"
|
msgstr "Sincronizzando pagina {page} dei gruppi"
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
|
||||||
msgid "Dropping mutating request due to dry run"
|
|
||||||
msgstr "Richiesta di mutazione ignorata a causa della prova di funzionamento"
|
|
||||||
|
|
||||||
#: authentik/lib/sync/outgoing/tasks.py
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Stopping sync due to error: {error}"
|
msgid "Stopping sync due to error: {error}"
|
||||||
@ -1197,14 +1212,6 @@ msgstr "GeoIP: indirizzo IP del client non trovato nel database della città."
|
|||||||
msgid "Client IP is not in an allowed country."
|
msgid "Client IP is not in an allowed country."
|
||||||
msgstr "L'IP del client non si trova in un paese consentito."
|
msgstr "L'IP del client non si trova in un paese consentito."
|
||||||
|
|
||||||
#: authentik/policies/geoip/models.py
|
|
||||||
msgid "Distance from previous authentication is larger than threshold."
|
|
||||||
msgstr "La distanza dall'autenticazione precedente è maggiore della soglia."
|
|
||||||
|
|
||||||
#: authentik/policies/geoip/models.py
|
|
||||||
msgid "Distance is further than possible."
|
|
||||||
msgstr "La distanza è maggiore del possibile."
|
|
||||||
|
|
||||||
#: authentik/policies/geoip/models.py
|
#: authentik/policies/geoip/models.py
|
||||||
msgid "GeoIP Policy"
|
msgid "GeoIP Policy"
|
||||||
msgstr "Criterio GeoIP"
|
msgstr "Criterio GeoIP"
|
||||||
@ -1337,22 +1344,6 @@ msgstr "Punteggio di reputazione"
|
|||||||
msgid "Reputation Scores"
|
msgid "Reputation Scores"
|
||||||
msgstr "Punteggi di reputazione"
|
msgstr "Punteggi di reputazione"
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Waiting for authentication..."
|
|
||||||
msgstr "In attesa di autenticazione..."
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid ""
|
|
||||||
"You're already authenticating in another tab. This page will refresh once "
|
|
||||||
"authentication is completed."
|
|
||||||
msgstr ""
|
|
||||||
"Ti stai già autenticando in un'altra scheda. Questa pagina si aggiornerà una"
|
|
||||||
" volta completata l'autenticazione."
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Authenticate in this tab"
|
|
||||||
msgstr "Autenticati in questa scheda"
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/denied.html
|
#: authentik/policies/templates/policies/denied.html
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr "Permesso negato"
|
msgstr "Permesso negato"
|
||||||
@ -1540,14 +1531,6 @@ msgstr "RS256 (Crittografia Asimmetrica)"
|
|||||||
msgid "ES256 (Asymmetric Encryption)"
|
msgid "ES256 (Asymmetric Encryption)"
|
||||||
msgstr "ES256 (Crittografia Asimmetrica)"
|
msgstr "ES256 (Crittografia Asimmetrica)"
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
|
||||||
msgid "ES384 (Asymmetric Encryption)"
|
|
||||||
msgstr "ES384 (Crittografia Asimmetrica)"
|
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
|
||||||
msgid "ES512 (Asymmetric Encryption)"
|
|
||||||
msgstr "ES512 (Crittografia Asimmetrica)"
|
|
||||||
|
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "Scope used by the client"
|
msgid "Scope used by the client"
|
||||||
msgstr "Scope usato dall'utente"
|
msgstr "Scope usato dall'utente"
|
||||||
@ -1831,61 +1814,6 @@ msgstr "Provider Proxy"
|
|||||||
msgid "Proxy Providers"
|
msgid "Proxy Providers"
|
||||||
msgstr "Providers Proxy"
|
msgstr "Providers Proxy"
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
|
||||||
msgid ""
|
|
||||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
|
||||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
|
||||||
msgstr ""
|
|
||||||
"Determina quanto può durare una sessione. Se impostato a 0, la sessione "
|
|
||||||
"durerà fino alla chiusura del browser. (Formato: "
|
|
||||||
"hours=-1;minutes=-2;seconds=-3)"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
|
||||||
msgstr ""
|
|
||||||
"Se impostato su vero, i token di connessione verranno eliminati alla "
|
|
||||||
"disconnessione."
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Provider"
|
|
||||||
msgstr "Fornitore di controllo dell'accesso remoto"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Providers"
|
|
||||||
msgstr "Fornitori di controllo dell'accesso remoto"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Endpoint"
|
|
||||||
msgstr "Endpoint RAC"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Endpoints"
|
|
||||||
msgstr "Endpoints RAC"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Provider Property Mapping"
|
|
||||||
msgstr "Mappatura delle proprietà del provider RAC"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Provider Property Mappings"
|
|
||||||
msgstr "Mappature proprietà del provider RAC"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Connection token"
|
|
||||||
msgstr "RAC Connection token"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/models.py
|
|
||||||
msgid "RAC Connection tokens"
|
|
||||||
msgstr "RAC Connection tokens"
|
|
||||||
|
|
||||||
#: authentik/providers/rac/views.py
|
|
||||||
msgid "Maximum connection limit reached."
|
|
||||||
msgstr "Limite massimo di connessioni raggiunto."
|
|
||||||
|
|
||||||
#: authentik/providers/rac/views.py
|
|
||||||
msgid "(You are already connected in another tab/window)"
|
|
||||||
msgstr "(Sei già connesso in un'altra scheda/finestra)"
|
|
||||||
|
|
||||||
#: authentik/providers/radius/models.py
|
#: authentik/providers/radius/models.py
|
||||||
msgid "Shared secret between clients and server to hash packets."
|
msgid "Shared secret between clients and server to hash packets."
|
||||||
msgstr "Segreto condiviso tra client e server per hashare i pacchetti."
|
msgstr "Segreto condiviso tra client e server per hashare i pacchetti."
|
||||||
@ -1973,20 +1901,6 @@ msgstr ""
|
|||||||
"Configura il modo in cui verrà creato il valore NameID. Se lasciato vuoto, "
|
"Configura il modo in cui verrà creato il valore NameID. Se lasciato vuoto, "
|
||||||
"verrà considerato il NameIDPolicy della richiesta in arrivo"
|
"verrà considerato il NameIDPolicy della richiesta in arrivo"
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
|
||||||
msgid "AuthnContextClassRef Property Mapping"
|
|
||||||
msgstr "Mapping delle proprietà AuthnContextClassRef"
|
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
|
||||||
msgid ""
|
|
||||||
"Configure how the AuthnContextClassRef value will be created. When left "
|
|
||||||
"empty, the AuthnContextClassRef will be set based on which authentication "
|
|
||||||
"methods the user used to authenticate."
|
|
||||||
msgstr ""
|
|
||||||
"Configura come verrà creato il valore AuthnContextClassRef. Se lasciato "
|
|
||||||
"vuoto, AuthnContextClassRef verrà impostato in base ai metodi di "
|
|
||||||
"autenticazione utilizzati dall'utente."
|
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assertion valid not before current time + this value (Format: "
|
"Assertion valid not before current time + this value (Format: "
|
||||||
@ -2128,18 +2042,6 @@ msgstr "Provider SAML dai Metadati"
|
|||||||
msgid "SAML Providers from Metadata"
|
msgid "SAML Providers from Metadata"
|
||||||
msgstr "Providers SAML dai Metadati"
|
msgstr "Providers SAML dai Metadati"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
|
||||||
msgid "Default"
|
|
||||||
msgstr "Predefinito"
|
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
|
||||||
msgid "AWS"
|
|
||||||
msgstr "AWS"
|
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
|
||||||
msgid "Slack"
|
|
||||||
msgstr "Slack"
|
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Base URL to SCIM requests, usually ends in /v2"
|
msgid "Base URL to SCIM requests, usually ends in /v2"
|
||||||
msgstr "URL di base per le richieste SCIM, di solito termina con /v2"
|
msgstr "URL di base per le richieste SCIM, di solito termina con /v2"
|
||||||
@ -2148,16 +2050,6 @@ msgstr "URL di base per le richieste SCIM, di solito termina con /v2"
|
|||||||
msgid "Authentication token"
|
msgid "Authentication token"
|
||||||
msgstr "Token di autenticazione"
|
msgstr "Token di autenticazione"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
|
||||||
msgid "SCIM Compatibility Mode"
|
|
||||||
msgstr "SCIM Modalità di Compatibilità"
|
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
|
||||||
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
|
|
||||||
msgstr ""
|
|
||||||
"Modifica il comportamento di autenticazione per le implementazioni SCIM "
|
|
||||||
"specifiche del fornitore."
|
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "SCIM Provider"
|
msgid "SCIM Provider"
|
||||||
msgstr "Privider SCIM"
|
msgstr "Privider SCIM"
|
||||||
@ -2233,7 +2125,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "KAdmin server type"
|
msgid "KAdmin server type"
|
||||||
msgstr "Tipo server KAdmin"
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "Sync users from Kerberos into authentik"
|
msgid "Sync users from Kerberos into authentik"
|
||||||
@ -2838,117 +2730,6 @@ msgstr "Dispositivo Duo"
|
|||||||
msgid "Duo Devices"
|
msgid "Duo Devices"
|
||||||
msgstr "Dispositivi Duo"
|
msgstr "Dispositivi Duo"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
msgid "Email OTP"
|
|
||||||
msgstr "Email OTP"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
#: authentik/stages/email/models.py
|
|
||||||
msgid ""
|
|
||||||
"When enabled, global Email connection settings will be used and connection "
|
|
||||||
"settings below will be ignored."
|
|
||||||
msgstr ""
|
|
||||||
"Se abilitato, verranno utilizzate le impostazioni di connessione e-mail "
|
|
||||||
"globali e le impostazioni di connessione riportate di seguito verranno "
|
|
||||||
"ignorate."
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
#: authentik/stages/email/models.py
|
|
||||||
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
|
|
||||||
msgstr ""
|
|
||||||
"Tempo di validità del token inviato (formato: "
|
|
||||||
"hours=3,minutes=17,seconds=300)."
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
msgid "Email Authenticator Setup Stage"
|
|
||||||
msgstr "Fase di configurazione dell'autenticatore email"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
msgid "Email Authenticator Setup Stages"
|
|
||||||
msgstr "Fasi di configurazione dell'autenticatore email"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
|
||||||
#: authentik/stages/email/stage.py
|
|
||||||
msgid "Exception occurred while rendering E-mail template"
|
|
||||||
msgstr ""
|
|
||||||
"Eccezione verificatasi durante la visualizzazione del modello di posta "
|
|
||||||
"elettronica"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
msgid "Email Device"
|
|
||||||
msgstr "Dispositivo email"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/models.py
|
|
||||||
msgid "Email Devices"
|
|
||||||
msgstr "Dispositivi email"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
|
||||||
#: authentik/stages/authenticator_sms/stage.py
|
|
||||||
#: authentik/stages/authenticator_totp/stage.py
|
|
||||||
msgid "Code does not match"
|
|
||||||
msgstr "Il codice non corrisponde"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/stage.py
|
|
||||||
msgid "Invalid email"
|
|
||||||
msgstr "Email non valida"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
|
||||||
#: authentik/stages/email/templates/email/password_reset.html
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"\n"
|
|
||||||
" Hi %(username)s,\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"\n"
|
|
||||||
" Ciao %(username)s,\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
|
||||||
msgid ""
|
|
||||||
"\n"
|
|
||||||
" Email MFA code.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"\n"
|
|
||||||
" Codice MFA via e-mail.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"\n"
|
|
||||||
" If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"\n"
|
|
||||||
" Se non hai richiesto questo codice, ignora questa email. Il codice sopra riportato è valido per %(expires)s.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
|
||||||
#: authentik/stages/email/templates/email/password_reset.txt
|
|
||||||
#, python-format
|
|
||||||
msgid "Hi %(username)s,"
|
|
||||||
msgstr "Ciao %(username)s,"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
|
||||||
msgid ""
|
|
||||||
"\n"
|
|
||||||
"Email MFA code\n"
|
|
||||||
msgstr ""
|
|
||||||
"\n"
|
|
||||||
"Codice e-mail MFA\n"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"\n"
|
|
||||||
"If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
|
||||||
msgstr ""
|
|
||||||
"\n"
|
|
||||||
"Se non hai richiesto questo codice, ignora questa email. Il codice sopra riportato è valido per %(expires)s.\n"
|
|
||||||
|
|
||||||
#: authentik/stages/authenticator_sms/models.py
|
#: authentik/stages/authenticator_sms/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||||
@ -2987,6 +2768,11 @@ msgstr "Dispositivo SMS"
|
|||||||
msgid "SMS Devices"
|
msgid "SMS Devices"
|
||||||
msgstr "Dispositivi SMS"
|
msgstr "Dispositivi SMS"
|
||||||
|
|
||||||
|
#: authentik/stages/authenticator_sms/stage.py
|
||||||
|
#: authentik/stages/authenticator_totp/stage.py
|
||||||
|
msgid "Code does not match"
|
||||||
|
msgstr "Il codice non corrisponde"
|
||||||
|
|
||||||
#: authentik/stages/authenticator_sms/stage.py
|
#: authentik/stages/authenticator_sms/stage.py
|
||||||
msgid "Invalid phone number"
|
msgid "Invalid phone number"
|
||||||
msgstr "Numero di telefono non valido"
|
msgstr "Numero di telefono non valido"
|
||||||
@ -3227,10 +3013,23 @@ msgstr "Ripristino password"
|
|||||||
msgid "Account Confirmation"
|
msgid "Account Confirmation"
|
||||||
msgstr "Conferma dell'account"
|
msgstr "Conferma dell'account"
|
||||||
|
|
||||||
|
#: authentik/stages/email/models.py
|
||||||
|
msgid ""
|
||||||
|
"When enabled, global Email connection settings will be used and connection "
|
||||||
|
"settings below will be ignored."
|
||||||
|
msgstr ""
|
||||||
|
"Se abilitato, verranno utilizzate le impostazioni di connessione e-mail "
|
||||||
|
"globali e le impostazioni di connessione riportate di seguito verranno "
|
||||||
|
"ignorate."
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Activate users upon completion of stage."
|
msgid "Activate users upon completion of stage."
|
||||||
msgstr "Attiva gli utenti al completamento della fase."
|
msgstr "Attiva gli utenti al completamento della fase."
|
||||||
|
|
||||||
|
#: authentik/stages/email/models.py
|
||||||
|
msgid "Time in minutes the token sent is valid."
|
||||||
|
msgstr "Tempo in minuti in cui il token inviato è valido."
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Email Stage"
|
msgid "Email Stage"
|
||||||
msgstr "Fase email"
|
msgstr "Fase email"
|
||||||
@ -3239,6 +3038,12 @@ msgstr "Fase email"
|
|||||||
msgid "Email Stages"
|
msgid "Email Stages"
|
||||||
msgstr "Fasi Email"
|
msgstr "Fasi Email"
|
||||||
|
|
||||||
|
#: authentik/stages/email/stage.py
|
||||||
|
msgid "Exception occurred while rendering E-mail template"
|
||||||
|
msgstr ""
|
||||||
|
"Eccezione verificatasi durante la visualizzazione del modello di posta "
|
||||||
|
"elettronica"
|
||||||
|
|
||||||
#: authentik/stages/email/stage.py
|
#: authentik/stages/email/stage.py
|
||||||
msgid "Successfully verified Email."
|
msgid "Successfully verified Email."
|
||||||
msgstr "Email verificato con successo."
|
msgstr "Email verificato con successo."
|
||||||
@ -3322,6 +3127,17 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Questa email è stata inviata dal trasporto delle notifiche %(name)s.\n"
|
"Questa email è stata inviata dal trasporto delle notifiche %(name)s.\n"
|
||||||
|
|
||||||
|
#: authentik/stages/email/templates/email/password_reset.html
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Hi %(username)s,\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" Ciao %(username)s,\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: authentik/stages/email/templates/email/password_reset.html
|
#: authentik/stages/email/templates/email/password_reset.html
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
@ -3342,6 +3158,11 @@ msgstr ""
|
|||||||
" Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
|
" Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
|
#: authentik/stages/email/templates/email/password_reset.txt
|
||||||
|
#, python-format
|
||||||
|
msgid "Hi %(username)s,"
|
||||||
|
msgstr "Ciao %(username)s,"
|
||||||
|
|
||||||
#: authentik/stages/email/templates/email/password_reset.txt
|
#: authentik/stages/email/templates/email/password_reset.txt
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
@ -3671,7 +3492,6 @@ msgstr ""
|
|||||||
#: authentik/stages/redirect/api.py
|
#: authentik/stages/redirect/api.py
|
||||||
msgid "Target Flow should be present when mode is Flow."
|
msgid "Target Flow should be present when mode is Flow."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Il flusso target dovrebbe essere presente quando la modalità è Flusso."
|
|
||||||
|
|
||||||
#: authentik/stages/redirect/models.py
|
#: authentik/stages/redirect/models.py
|
||||||
msgid "Redirect Stage"
|
msgid "Redirect Stage"
|
||||||
|
Binary file not shown.
@ -14,7 +14,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
"POT-Creation-Date: 2025-03-22 00:10+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||||
"Last-Translator: deluxghost, 2025\n"
|
"Last-Translator: deluxghost, 2025\n"
|
||||||
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
|
||||||
@ -1234,20 +1234,6 @@ msgstr "信誉分数"
|
|||||||
msgid "Reputation Scores"
|
msgid "Reputation Scores"
|
||||||
msgstr "信誉分数"
|
msgstr "信誉分数"
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Waiting for authentication..."
|
|
||||||
msgstr "正在等待身份验证…"
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid ""
|
|
||||||
"You're already authenticating in another tab. This page will refresh once "
|
|
||||||
"authentication is completed."
|
|
||||||
msgstr "您正在另一个标签页中验证身份。身份验证完成后,此页面会刷新。"
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/buffer.html
|
|
||||||
msgid "Authenticate in this tab"
|
|
||||||
msgstr "在此标签页中验证身份"
|
|
||||||
|
|
||||||
#: authentik/policies/templates/policies/denied.html
|
#: authentik/policies/templates/policies/denied.html
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr "权限被拒绝"
|
msgstr "权限被拒绝"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.2.4",
|
"version": "2025.2.2",
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file TypeScript type definitions for eslint-plugin-react-hooks
|
|
||||||
*/
|
|
||||||
declare module "eslint-plugin-react-hooks" {
|
|
||||||
import { ESLint } from "eslint";
|
|
||||||
// We have to do this because ESLint aliases the namespace and class simultaneously.
|
|
||||||
type PluginInstance = ESLint.Plugin;
|
|
||||||
const Plugin: PluginInstance;
|
|
||||||
|
|
||||||
export default Plugin;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file TypeScript type definitions for eslint-plugin-react
|
|
||||||
*/
|
|
||||||
declare module "eslint-plugin-react" {
|
|
||||||
import { ESLint } from "eslint";
|
|
||||||
// We have to do this because ESLint aliases the namespace and class simultaneously.
|
|
||||||
type PluginInstance = ESLint.Plugin;
|
|
||||||
const Plugin: PluginInstance;
|
|
||||||
|
|
||||||
export default Plugin;
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2025 Authentik Security, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
||||||
portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
||||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,5 +0,0 @@
|
|||||||
# `@goauthentik/eslint-config`
|
|
||||||
|
|
||||||
This package contains the ESLint configuration used by authentik.
|
|
||||||
While it is possible to use this configuration outside of our projects,
|
|
||||||
you may find that it is not as useful as other popular configurations.
|
|
@ -1,72 +0,0 @@
|
|||||||
import eslint from "@eslint/js";
|
|
||||||
import { javaScriptConfig } from "@goauthentik/eslint-config/javascript-config";
|
|
||||||
import { reactConfig } from "@goauthentik/eslint-config/react-config";
|
|
||||||
import { typescriptConfig } from "@goauthentik/eslint-config/typescript-config";
|
|
||||||
import * as litconf from "eslint-plugin-lit";
|
|
||||||
import * as wcconf from "eslint-plugin-wc";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef ESLintPackageConfigOptions Options for creating package ESLint configuration.
|
|
||||||
* @property {string[]} [ignorePatterns] Override ignore patterns for ESLint.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {string[]} Default Ignore patterns for ESLint.
|
|
||||||
*/
|
|
||||||
export const DefaultIgnorePatterns = [
|
|
||||||
// ---
|
|
||||||
"**/*.md",
|
|
||||||
"**/out",
|
|
||||||
"**/dist",
|
|
||||||
"**/.wireit",
|
|
||||||
"website/build/**",
|
|
||||||
"website/.docusaurus/**",
|
|
||||||
"**/node_modules",
|
|
||||||
"**/coverage",
|
|
||||||
"**/storybook-static",
|
|
||||||
"**/locale-codes.ts",
|
|
||||||
"**/src/locales",
|
|
||||||
"**/gen-ts-api",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a preferred package name, creates a ESLint configuration object.
|
|
||||||
*
|
|
||||||
* @param {ESLintPackageConfigOptions} options The preferred package configuration options.
|
|
||||||
*
|
|
||||||
* @returns The ESLint configuration object.
|
|
||||||
*/
|
|
||||||
export function createESLintPackageConfig({ ignorePatterns = DefaultIgnorePatterns } = {}) {
|
|
||||||
return tseslint.config(
|
|
||||||
{
|
|
||||||
ignores: ignorePatterns,
|
|
||||||
},
|
|
||||||
|
|
||||||
eslint.configs.recommended,
|
|
||||||
javaScriptConfig,
|
|
||||||
|
|
||||||
wcconf.configs["flat/recommended"],
|
|
||||||
litconf.configs["flat/recommended"],
|
|
||||||
|
|
||||||
...tseslint.configs.recommended,
|
|
||||||
|
|
||||||
...typescriptConfig,
|
|
||||||
|
|
||||||
...reactConfig,
|
|
||||||
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
"no-console": "off",
|
|
||||||
},
|
|
||||||
files: [
|
|
||||||
// ---
|
|
||||||
"**/scripts/**/*",
|
|
||||||
"**/test/**/*",
|
|
||||||
"**/tests/**/*",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
const MAX_DEPTH = 4;
|
|
||||||
const MAX_NESTED_CALLBACKS = 4;
|
|
||||||
const MAX_PARAMS = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ESLint configuration for JavaScript authentik projects.
|
|
||||||
*/
|
|
||||||
export const javaScriptConfig = tseslint.config({
|
|
||||||
rules: {
|
|
||||||
// TODO: Clean up before enabling.
|
|
||||||
"accessor-pairs": "off",
|
|
||||||
"array-callback-return": "error",
|
|
||||||
"block-scoped-var": "error",
|
|
||||||
"consistent-return": ["error", { treatUndefinedAsUnspecified: false }],
|
|
||||||
"consistent-this": ["error", "that"],
|
|
||||||
"curly": "off",
|
|
||||||
"dot-notation": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
allowKeywords: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"eqeqeq": "error",
|
|
||||||
"func-names": ["error", "as-needed"],
|
|
||||||
"guard-for-in": "error",
|
|
||||||
"max-depth": ["error", MAX_DEPTH],
|
|
||||||
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
|
|
||||||
"max-params": ["error", MAX_PARAMS],
|
|
||||||
// TODO: Clean up before enabling.
|
|
||||||
// "new-cap": "error",
|
|
||||||
"no-alert": "error",
|
|
||||||
"no-array-constructor": "error",
|
|
||||||
"no-bitwise": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
allow: ["~"],
|
|
||||||
int32Hint: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-caller": "error",
|
|
||||||
"no-case-declarations": "error",
|
|
||||||
"no-class-assign": "error",
|
|
||||||
"no-cond-assign": "error",
|
|
||||||
"no-const-assign": "error",
|
|
||||||
"no-constant-condition": "error",
|
|
||||||
"no-control-regex": "error",
|
|
||||||
"no-debugger": "error",
|
|
||||||
"no-delete-var": "error",
|
|
||||||
"no-div-regex": "error",
|
|
||||||
"no-dupe-args": "error",
|
|
||||||
"no-dupe-keys": "error",
|
|
||||||
"no-duplicate-case": "error",
|
|
||||||
"no-else-return": "error",
|
|
||||||
"no-empty": "error",
|
|
||||||
"no-empty-character-class": "error",
|
|
||||||
"no-empty-function": ["error", { allow: ["constructors"] }],
|
|
||||||
"no-labels": "error",
|
|
||||||
"no-eq-null": "error",
|
|
||||||
"no-eval": "error",
|
|
||||||
"no-ex-assign": "error",
|
|
||||||
"no-extend-native": "error",
|
|
||||||
"no-extra-bind": "error",
|
|
||||||
"no-extra-boolean-cast": "error",
|
|
||||||
"no-extra-label": "error",
|
|
||||||
"no-fallthrough": "error",
|
|
||||||
"no-func-assign": "error",
|
|
||||||
"no-implied-eval": "error",
|
|
||||||
"no-implicit-coercion": "error",
|
|
||||||
"no-implicit-globals": "error",
|
|
||||||
"no-inner-declarations": ["error", "functions"],
|
|
||||||
"no-invalid-regexp": "error",
|
|
||||||
"no-irregular-whitespace": "error",
|
|
||||||
"no-iterator": "error",
|
|
||||||
"no-label-var": "error",
|
|
||||||
"no-lone-blocks": "error",
|
|
||||||
"no-lonely-if": "error",
|
|
||||||
"no-loop-func": "error",
|
|
||||||
"no-multi-str": "error",
|
|
||||||
// TODO: Clean up before enabling.
|
|
||||||
"no-negated-condition": "off",
|
|
||||||
"no-new": "error",
|
|
||||||
"no-new-func": "error",
|
|
||||||
"no-new-wrappers": "error",
|
|
||||||
"no-obj-calls": "error",
|
|
||||||
"no-octal": "error",
|
|
||||||
"no-octal-escape": "error",
|
|
||||||
"no-param-reassign": ["error", { props: false }],
|
|
||||||
"no-proto": "error",
|
|
||||||
"no-redeclare": "error",
|
|
||||||
"no-regex-spaces": "error",
|
|
||||||
"no-restricted-syntax": ["error", "WithStatement"],
|
|
||||||
"no-script-url": "error",
|
|
||||||
"no-self-assign": "error",
|
|
||||||
"no-self-compare": "error",
|
|
||||||
"no-sequences": "error",
|
|
||||||
// TODO: Clean up before enabling.
|
|
||||||
// "no-shadow": "error",
|
|
||||||
"no-shadow-restricted-names": "error",
|
|
||||||
"no-sparse-arrays": "error",
|
|
||||||
"no-this-before-super": "error",
|
|
||||||
"no-throw-literal": "error",
|
|
||||||
"no-trailing-spaces": "off", // Handled by Prettier.
|
|
||||||
"no-undef": "off",
|
|
||||||
"no-undef-init": "off",
|
|
||||||
"no-unexpected-multiline": "error",
|
|
||||||
"no-useless-constructor": "error",
|
|
||||||
"no-unmodified-loop-condition": "error",
|
|
||||||
"no-unneeded-ternary": "error",
|
|
||||||
"no-unreachable": "error",
|
|
||||||
"no-unused-expressions": "error",
|
|
||||||
"no-unused-labels": "error",
|
|
||||||
"no-use-before-define": "error",
|
|
||||||
"no-useless-call": "error",
|
|
||||||
"no-dupe-class-members": "error",
|
|
||||||
"no-var": "error",
|
|
||||||
"no-void": "error",
|
|
||||||
"no-with": "error",
|
|
||||||
"prefer-arrow-callback": "error",
|
|
||||||
"prefer-const": "error",
|
|
||||||
"prefer-rest-params": "error",
|
|
||||||
"prefer-spread": "error",
|
|
||||||
"prefer-template": "error",
|
|
||||||
"radix": "error",
|
|
||||||
"require-yield": "error",
|
|
||||||
"strict": ["error", "global"],
|
|
||||||
"use-isnan": "error",
|
|
||||||
"valid-typeof": "error",
|
|
||||||
"vars-on-top": "error",
|
|
||||||
"yoda": ["error", "never"],
|
|
||||||
|
|
||||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
|
||||||
// SonarJS is not yet compatible with ESLint 9. Commenting these out
|
|
||||||
// until it is.
|
|
||||||
// "sonarjs/cognitive-complexity": ["off", MAX_COGNITIVE_COMPLEXITY],
|
|
||||||
// "sonarjs/no-duplicate-string": "off",
|
|
||||||
// "sonarjs/no-nested-template-literals": "off",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default javaScriptConfig;
|
|
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@goauthentik/eslint-config",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "authentik's ESLint config",
|
|
||||||
"license": "MIT",
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": {
|
|
||||||
"import": "./index.js",
|
|
||||||
"types": "./out/index.d.ts"
|
|
||||||
},
|
|
||||||
"./react-config": {
|
|
||||||
"import": "./react-config.js",
|
|
||||||
"types": "./out/react-config.d.ts"
|
|
||||||
},
|
|
||||||
"./javascript-config": {
|
|
||||||
"import": "./javascript-config.js",
|
|
||||||
"types": "./out/javascript-config.d.ts"
|
|
||||||
},
|
|
||||||
"./typescript-config": {
|
|
||||||
"import": "./typescript-config.js",
|
|
||||||
"types": "./out/typescript-config.d.ts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"eslint": "^9.23.0",
|
|
||||||
"eslint-plugin-import": "^2.31.0",
|
|
||||||
"eslint-plugin-react": "^7.37.4",
|
|
||||||
"eslint-plugin-react-hooks": "^5.2.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@goauthentik/tsconfig": "1.0.0",
|
|
||||||
"@types/eslint": "^9.6.1",
|
|
||||||
"typescript": "^5.8.2",
|
|
||||||
"typescript-eslint": "^8.29.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5.8.2",
|
|
||||||
"typescript-eslint": "^8.29.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"react": "^18.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.11"
|
|
||||||
},
|
|
||||||
"types": "./out/index.d.ts",
|
|
||||||
"prettier": "@goauthentik/prettier-config",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
|
||||||
}
|
|
34
packages/eslint-config/react-config.js
vendored
34
packages/eslint-config/react-config.js
vendored
@ -1,34 +0,0 @@
|
|||||||
import reactPlugin from "eslint-plugin-react";
|
|
||||||
import hooksPlugin from "eslint-plugin-react-hooks";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ESLint configuration for React authentik projects.
|
|
||||||
*/
|
|
||||||
export const reactConfig = tseslint.config({
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins: {
|
|
||||||
"react": reactPlugin,
|
|
||||||
"react-hooks": hooksPlugin,
|
|
||||||
},
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
"react-hooks/rules-of-hooks": "error",
|
|
||||||
"react-hooks/exhaustive-deps": "warn",
|
|
||||||
|
|
||||||
"react/jsx-uses-react": 0,
|
|
||||||
|
|
||||||
"react/display-name": "off",
|
|
||||||
"react/jsx-curly-brace-presence": "error",
|
|
||||||
"react/jsx-no-leaked-render": "error",
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default reactConfig;
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@goauthentik/tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"checkJs": true,
|
|
||||||
"emitDeclarationOnly": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ESLint configuration for TypeScript authentik projects.
|
|
||||||
*/
|
|
||||||
export const typescriptConfig = tseslint.config({
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/ban-ts-comment": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ts-expect-error": "allow-with-description",
|
|
||||||
"ts-ignore": true,
|
|
||||||
"ts-nocheck": "allow-with-description",
|
|
||||||
"ts-check": false,
|
|
||||||
"minimumDescriptionLength": 5,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-use-before-define": "off",
|
|
||||||
"@typescript-eslint/no-use-before-define": "error",
|
|
||||||
"no-invalid-this": "off",
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-namespace": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
argsIgnorePattern: "^_",
|
|
||||||
varsIgnorePattern: "^_",
|
|
||||||
caughtErrorsIgnorePattern: "^_",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default typescriptConfig;
|
|
@ -1,18 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2025 Authentik Security, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
||||||
portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
||||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,5 +0,0 @@
|
|||||||
# `@goauthentik/monorepo`
|
|
||||||
|
|
||||||
This package contains utility scripts common to all TypeScript and JavaScript packages in the
|
|
||||||
`@goauthentik` monorepo.
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Constants for JavaScript and TypeScript files.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current Node.js environment, defaulting to "development" when not set.
|
|
||||||
*
|
|
||||||
* Note, this should only be used during the build process.
|
|
||||||
*
|
|
||||||
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
|
|
||||||
* ensure that module tree-shaking works correctly.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const NodeEnvironment = /** @type {'development' | 'production'} */ (
|
|
||||||
process.env.NODE_ENV || "development"
|
|
||||||
);
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from "./paths.js";
|
|
||||||
export * from "./constants.js";
|
|
||||||
export * from "./version.js";
|
|
||||||
export * from "./scripting.js";
|
|
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@goauthentik/monorepo",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Utilities for the authentik monorepo.",
|
|
||||||
"private": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": {
|
|
||||||
"import": "./index.js",
|
|
||||||
"types": "./out/index.d.ts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"types": "./out/index.d.ts",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.11"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { createRequire } from "node:module";
|
|
||||||
import { dirname, join, resolve } from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {'~authentik'} MonoRepoRoot
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The root of the authentik monorepo.
|
|
||||||
*/
|
|
||||||
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a package name to its location in the monorepo to the single node_modules directory.
|
|
||||||
* @param {string} packageName
|
|
||||||
* @returns {string} The resolved path to the package.
|
|
||||||
* @throws {Error} If the package cannot be resolved.
|
|
||||||
*/
|
|
||||||
export function resolvePackage(packageName) {
|
|
||||||
const packageJSONPath = require.resolve(join(packageName, "package.json"), {
|
|
||||||
paths: [MonoRepoRoot],
|
|
||||||
});
|
|
||||||
|
|
||||||
return dirname(packageJSONPath);
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import { createRequire } from "node:module";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import * as process from "node:process";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Predicate to determine if a module was run directly, i.e. not imported.
|
|
||||||
*
|
|
||||||
* @param {ImportMeta} meta The `import.meta` object of the module.
|
|
||||||
*
|
|
||||||
* @return {boolean} Whether the module was run directly.
|
|
||||||
*/
|
|
||||||
export function isMain(meta) {
|
|
||||||
// Are we not in a module context?
|
|
||||||
if (!meta) return false;
|
|
||||||
|
|
||||||
const relativeScriptPath = process.argv[1];
|
|
||||||
|
|
||||||
if (!relativeScriptPath) return false;
|
|
||||||
|
|
||||||
const require = createRequire(meta.url);
|
|
||||||
const absoluteScriptPath = require.resolve(relativeScriptPath);
|
|
||||||
|
|
||||||
const modulePath = fileURLToPath(meta.url);
|
|
||||||
|
|
||||||
const scriptExtension = path.extname(absoluteScriptPath);
|
|
||||||
|
|
||||||
if (scriptExtension) {
|
|
||||||
return modulePath === absoluteScriptPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleExtension = path.extname(modulePath);
|
|
||||||
|
|
||||||
if (moduleExtension) {
|
|
||||||
return absoluteScriptPath === modulePath.slice(0, -moduleExtension.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both are without extension, compare them directly.
|
|
||||||
return modulePath === absoluteScriptPath;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@goauthentik/tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"checkJs": true,
|
|
||||||
"emitDeclarationOnly": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import { execSync } from "node:child_process";
|
|
||||||
|
|
||||||
import PackageJSON from "../../package.json" with { type: "json" };
|
|
||||||
import { MonoRepoRoot } from "./paths.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current version of authentik in SemVer format.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const AuthentikVersion = /**@type {`${number}.${number}.${number}`} */ (PackageJSON.version);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the last commit hash from the current git repository.
|
|
||||||
*/
|
|
||||||
export function readGitBuildHash() {
|
|
||||||
try {
|
|
||||||
const commit = execSync("git rev-parse HEAD", {
|
|
||||||
encoding: "utf8",
|
|
||||||
cwd: MonoRepoRoot,
|
|
||||||
})
|
|
||||||
.toString()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
return commit;
|
|
||||||
} catch (_error) {
|
|
||||||
console.debug("Git commit could not be read.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return process.env.GIT_BUILD_HASH || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the build identifier for the current environment.
|
|
||||||
*
|
|
||||||
* This must match the behavior defined in authentik's server-side `get_full_version` function.
|
|
||||||
*
|
|
||||||
* @see {@link "authentik\_\_init\_\_.py"}
|
|
||||||
*/
|
|
||||||
export function readBuildIdentifier() {
|
|
||||||
const { GIT_BUILD_HASH } = process.env;
|
|
||||||
|
|
||||||
if (!GIT_BUILD_HASH) return AuthentikVersion;
|
|
||||||
|
|
||||||
return [AuthentikVersion, GIT_BUILD_HASH].join("+");
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2025 Authentik Security, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
||||||
portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
||||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,5 +0,0 @@
|
|||||||
# `@goauthentik/prettier-config`
|
|
||||||
|
|
||||||
This package contains the Prettier configuration used by authentik.
|
|
||||||
While it is possible to use this configuration outside of our projects,
|
|
||||||
you may find that it is not as useful as other popular configurations.
|
|
@ -1,80 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Prettier configuration for authentik.
|
|
||||||
*
|
|
||||||
* @import { Config as PrettierConfig } from "prettier";
|
|
||||||
* @import { PluginConfig as SortPluginConfig } from "@trivago/prettier-plugin-sort-imports";
|
|
||||||
*
|
|
||||||
* @typedef {object} PackageJSONPluginConfig
|
|
||||||
* @property {string[]} [packageSortOrder] Custom ordering array.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* authentik Prettier configuration.
|
|
||||||
*
|
|
||||||
* @type {PrettierConfig & SortPluginConfig & PackageJSONPluginConfig}
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export const AuthentikPrettierConfig = {
|
|
||||||
arrowParens: "always",
|
|
||||||
bracketSpacing: true,
|
|
||||||
embeddedLanguageFormatting: "auto",
|
|
||||||
htmlWhitespaceSensitivity: "css",
|
|
||||||
insertPragma: false,
|
|
||||||
jsxSingleQuote: false,
|
|
||||||
printWidth: 100,
|
|
||||||
proseWrap: "preserve",
|
|
||||||
quoteProps: "consistent",
|
|
||||||
requirePragma: false,
|
|
||||||
semi: true,
|
|
||||||
singleQuote: false,
|
|
||||||
tabWidth: 4,
|
|
||||||
trailingComma: "all",
|
|
||||||
useTabs: false,
|
|
||||||
vueIndentScriptAndStyle: false,
|
|
||||||
plugins: ["prettier-plugin-packagejson", "@trivago/prettier-plugin-sort-imports"],
|
|
||||||
importOrder: ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
|
||||||
importOrderSeparation: true,
|
|
||||||
importOrderSortSpecifiers: true,
|
|
||||||
importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: "schemas/**/*.json",
|
|
||||||
options: {
|
|
||||||
tabWidth: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: "tsconfig.json",
|
|
||||||
options: {
|
|
||||||
trailingComma: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: "package.json",
|
|
||||||
options: {
|
|
||||||
packageSortOrder: [
|
|
||||||
// ---
|
|
||||||
"name",
|
|
||||||
"version",
|
|
||||||
"description",
|
|
||||||
"license",
|
|
||||||
"private",
|
|
||||||
"author",
|
|
||||||
"authors",
|
|
||||||
"scripts",
|
|
||||||
"main",
|
|
||||||
"type",
|
|
||||||
"exports",
|
|
||||||
"imports",
|
|
||||||
"dependencies",
|
|
||||||
"devDependencies",
|
|
||||||
"peerDependencies",
|
|
||||||
"optionalDependencies",
|
|
||||||
"wireit",
|
|
||||||
"resolutions",
|
|
||||||
"engines",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import { format } from "prettier";
|
|
||||||
|
|
||||||
import { AuthentikPrettierConfig } from "./config.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format using Prettier.
|
|
||||||
*
|
|
||||||
* Defaults to using the TypeScript parser.
|
|
||||||
*
|
|
||||||
* @category Formatting
|
|
||||||
* @param {string} fileContents The contents of the file to format.
|
|
||||||
*
|
|
||||||
* @returns {Promise<string>} The formatted file contents.
|
|
||||||
*/
|
|
||||||
export function formatWithPrettier(fileContents) {
|
|
||||||
return format(fileContents, {
|
|
||||||
...AuthentikPrettierConfig,
|
|
||||||
parser: "typescript",
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import { AuthentikPrettierConfig } from "./config.js";
|
|
||||||
|
|
||||||
export * from "./config.js";
|
|
||||||
export * from "./format.js";
|
|
||||||
|
|
||||||
export default AuthentikPrettierConfig;
|
|
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@goauthentik/prettier-config",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "authentik's Prettier config",
|
|
||||||
"license": "MIT",
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": {
|
|
||||||
"import": "./index.js",
|
|
||||||
"types": "./out/index.d.ts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"types": "./out/index.d.ts",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
||||||
"prettier": "^3.5.3",
|
|
||||||
"prettier-plugin-organize-imports": "^4.1.0",
|
|
||||||
"prettier-plugin-packagejson": "^2.5.10"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.11"
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@goauthentik/tsconfig",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"checkJs": true,
|
|
||||||
"emitDeclarationOnly": true
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user