Compare commits
3 Commits
web/reques
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
c8072579c8 | |||
378a701fb9 | |||
bba793d94c |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2024.4.1
|
||||
current_version = 2024.4.0-rc1
|
||||
tag = 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*))?
|
||||
|
@ -54,9 +54,9 @@ image_main_tag = image_tags[0]
|
||||
image_tags_rendered = ",".join(image_tags)
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
|
||||
print(f"shouldBuild={should_build}", file=_output)
|
||||
print(f"sha={sha}", file=_output)
|
||||
print(f"version={version}", file=_output)
|
||||
print(f"prerelease={prerelease}", file=_output)
|
||||
print(f"imageTags={image_tags_rendered}", file=_output)
|
||||
print(f"imageMainTag={image_main_tag}", file=_output)
|
||||
print("shouldBuild=%s" % should_build, file=_output)
|
||||
print("sha=%s" % sha, file=_output)
|
||||
print("version=%s" % version, file=_output)
|
||||
print("prerelease=%s" % prerelease, file=_output)
|
||||
print("imageTags=%s" % image_tags_rendered, file=_output)
|
||||
print("imageMainTag=%s" % image_main_tag, file=_output)
|
||||
|
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -130,7 +130,7 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1.10.0
|
||||
uses: helm/kind-action@v1.9.0
|
||||
- name: run integration
|
||||
run: |
|
||||
poetry run coverage run manage.py test tests/integration
|
||||
|
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
- name: Generate API
|
||||
run: make gen-client-go
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v5
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
version: v1.54.2
|
||||
args: --timeout 5000s --verbose
|
||||
|
8
.github/workflows/ci-web.yml
vendored
8
.github/workflows/ci-web.yml
vendored
@ -34,13 +34,6 @@ jobs:
|
||||
- name: Eslint
|
||||
working-directory: ${{ matrix.project }}/
|
||||
run: npm run lint
|
||||
lint-lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- working-directory: web/
|
||||
run: |
|
||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
|
||||
lint-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -102,7 +95,6 @@ jobs:
|
||||
run: npm run lit-analyse
|
||||
ci-web-mark:
|
||||
needs:
|
||||
- lint-lockfile
|
||||
- lint-eslint
|
||||
- lint-prettier
|
||||
- lint-lit-analyse
|
||||
|
8
.github/workflows/ci-website.yml
vendored
8
.github/workflows/ci-website.yml
vendored
@ -12,13 +12,6 @@ on:
|
||||
- version-*
|
||||
|
||||
jobs:
|
||||
lint-lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- working-directory: website/
|
||||
run: |
|
||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
|
||||
lint-prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -69,7 +62,6 @@ jobs:
|
||||
run: npm run ${{ matrix.job }}
|
||||
ci-website-mark:
|
||||
needs:
|
||||
- lint-lockfile
|
||||
- lint-prettier
|
||||
- test
|
||||
- build
|
||||
|
4
.github/workflows/release-publish.yml
vendored
4
.github/workflows/release-publish.yml
vendored
@ -155,8 +155,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run test suite in final docker images
|
||||
run: |
|
||||
echo "PG_PASS=$(openssl rand 32 | base64)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env
|
||||
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
|
||||
docker compose pull -q
|
||||
docker compose up --no-start
|
||||
docker compose start postgresql redis
|
||||
|
4
.github/workflows/release-tag.yml
vendored
4
.github/workflows/release-tag.yml
vendored
@ -14,8 +14,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Pre-release test
|
||||
run: |
|
||||
echo "PG_PASS=$(openssl rand 32 | base64)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env
|
||||
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
|
||||
docker buildx install
|
||||
mkdir -p ./gen-ts-api
|
||||
docker build -t testing:latest .
|
||||
|
@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Build website
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:22 as website-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
@ -20,7 +20,7 @@ COPY ./SECURITY.md /work/
|
||||
RUN npm run build-bundled
|
||||
|
||||
# Stage 2: Build webui
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:22 as web-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
|
4
Makefile
4
Makefile
@ -46,8 +46,8 @@ test-go:
|
||||
go test -timeout 0 -v -race -cover ./...
|
||||
|
||||
test-docker: ## Run all tests in a docker-compose
|
||||
echo "PG_PASS=$(shell openssl rand 32 | base64)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(shell openssl rand 32 | base64)" >> .env
|
||||
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
|
||||
docker compose pull -q
|
||||
docker compose up --no-start
|
||||
docker compose start postgresql redis
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2024.4.1"
|
||||
__version__ = "2024.4.0"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -154,18 +154,12 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
pk = IntegerField(required=True)
|
||||
|
||||
queryset = Group.objects.none()
|
||||
queryset = Group.objects.all().select_related("parent").prefetch_related("users")
|
||||
serializer_class = GroupSerializer
|
||||
search_fields = ["name", "is_superuser"]
|
||||
filterset_class = GroupFilter
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
base_qs = Group.objects.all().select_related("parent").prefetch_related("roles")
|
||||
if self.serializer_class(context={"request": self.request})._should_include_users:
|
||||
base_qs = base_qs.prefetch_related("users")
|
||||
return base_qs
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter("include_users", bool, default=True),
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from django.utils.timezone import now
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||
from guardian.shortcuts import assign_perm, get_anonymous_user
|
||||
@ -28,6 +27,7 @@ from authentik.core.models import (
|
||||
TokenIntents,
|
||||
User,
|
||||
default_token_duration,
|
||||
token_expires_from_timedelta,
|
||||
)
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.events.utils import model_to_dict
|
||||
@ -68,17 +68,15 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
|
||||
max_token_lifetime_dt = default_token_duration()
|
||||
if max_token_lifetime is not None:
|
||||
try:
|
||||
max_token_lifetime_dt = now() + timedelta_from_string(max_token_lifetime)
|
||||
max_token_lifetime_dt = timedelta_from_string(max_token_lifetime)
|
||||
except ValueError:
|
||||
pass
|
||||
max_token_lifetime_dt = default_token_duration()
|
||||
|
||||
if "expires" in attrs and attrs.get("expires") > max_token_lifetime_dt:
|
||||
if "expires" in attrs and attrs.get("expires") > token_expires_from_timedelta(
|
||||
max_token_lifetime_dt
|
||||
):
|
||||
raise ValidationError(
|
||||
{
|
||||
"expires": (
|
||||
f"Token expires exceeds maximum lifetime ({max_token_lifetime_dt} UTC)."
|
||||
)
|
||||
}
|
||||
{"expires": f"Token expires exceeds maximum lifetime ({max_token_lifetime})."}
|
||||
)
|
||||
elif attrs.get("intent") == TokenIntents.INTENT_API:
|
||||
# For API tokens, expires cannot be overridden
|
||||
|
@ -407,11 +407,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
search_fields = ["username", "name", "is_active", "email", "uuid"]
|
||||
filterset_class = UsersFilter
|
||||
|
||||
def get_queryset(self):
|
||||
base_qs = User.objects.all().exclude_anonymous()
|
||||
if self.serializer_class(context={"request": self.request})._should_include_groups:
|
||||
base_qs = base_qs.prefetch_related("ak_groups")
|
||||
return base_qs
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return User.objects.all().exclude_anonymous().prefetch_related("ak_groups")
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""authentik core models"""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from hashlib import sha256
|
||||
from typing import Any, Optional, Self
|
||||
from uuid import uuid4
|
||||
@ -68,6 +68,11 @@ def default_token_duration() -> datetime:
|
||||
return now() + timedelta_from_string(token_duration)
|
||||
|
||||
|
||||
def token_expires_from_timedelta(dt: timedelta) -> datetime:
|
||||
"""Return a `datetime.datetime` object with the duration of the Token"""
|
||||
return now() + dt
|
||||
|
||||
|
||||
def default_token_key() -> str:
|
||||
"""Default token key"""
|
||||
current_tenant = get_current_tenant()
|
||||
@ -632,7 +637,7 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"User-source connection (user={self.user_id}, source={self.source_id})"
|
||||
return f"User-source connection (user={self.user.username}, source={self.source.slug})"
|
||||
|
||||
class Meta:
|
||||
unique_together = (("user", "source"),)
|
||||
|
@ -5,7 +5,7 @@ from guardian.shortcuts import assign_perm
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_user
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
|
||||
@ -16,13 +16,6 @@ class TestGroupsAPI(APITestCase):
|
||||
self.login_user = create_test_user()
|
||||
self.user = User.objects.create(username="test-user")
|
||||
|
||||
def test_list_with_users(self):
|
||||
"""Test listing with users"""
|
||||
admin = create_test_admin_user()
|
||||
self.client.force_login(admin)
|
||||
response = self.client.get(reverse("authentik_api:group-list"), {"include_users": "true"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_add_user(self):
|
||||
"""Test add_user"""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
|
@ -66,11 +66,14 @@ class TestPropertyMappings(TestCase):
|
||||
expression="return request.http_request.path",
|
||||
)
|
||||
http_request = self.factory.get("/")
|
||||
tmpl = f"""
|
||||
res = ak_call_policy('{expr.name}')
|
||||
tmpl = (
|
||||
"""
|
||||
res = ak_call_policy('%s')
|
||||
result = [request.http_request.path, res.raw_result]
|
||||
return result
|
||||
"""
|
||||
% expr.name
|
||||
)
|
||||
evaluator = PropertyMapping(expression=tmpl, name=generate_id())
|
||||
res = evaluator.evaluate(self.user, http_request)
|
||||
self.assertEqual(res, ["/", "/"])
|
||||
|
@ -41,12 +41,6 @@ class TestUsersAPI(APITestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_list_with_groups(self):
|
||||
"""Test listing with groups"""
|
||||
self.client.force_login(self.admin)
|
||||
response = self.client.get(reverse("authentik_api:user-list"), {"include_groups": "true"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_metrics(self):
|
||||
"""Test user's metrics"""
|
||||
self.client.force_login(self.admin)
|
||||
|
@ -8,6 +8,7 @@ from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.utils import get_current_tenant
|
||||
|
||||
|
||||
@ -24,6 +25,7 @@ class TestUsersAvatars(APITestCase):
|
||||
tenant.avatars = mode
|
||||
tenant.save()
|
||||
|
||||
@CONFIG.patch("avatars", "none")
|
||||
def test_avatars_none(self):
|
||||
"""Test avatars none"""
|
||||
self.set_avatar_mode("none")
|
||||
|
@ -4,7 +4,7 @@ from django.utils.text import slugify
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.lib.generators import generate_id
|
||||
@ -50,10 +50,12 @@ def create_test_brand(**kwargs) -> Brand:
|
||||
return Brand.objects.create(domain=uid, default=True, **kwargs)
|
||||
|
||||
|
||||
def create_test_cert(alg=PrivateKeyAlg.RSA) -> CertificateKeyPair:
|
||||
def create_test_cert(use_ec_private_key=False) -> CertificateKeyPair:
|
||||
"""Generate a certificate for testing"""
|
||||
builder = CertificateBuilder(f"{generate_id()}.self-signed.goauthentik.io")
|
||||
builder.alg = alg
|
||||
builder = CertificateBuilder(
|
||||
name=f"{generate_id()}.self-signed.goauthentik.io",
|
||||
use_ec_private_key=use_ec_private_key,
|
||||
)
|
||||
builder.build(
|
||||
subject_alt_names=[f"{generate_id()}.self-signed.goauthentik.io"],
|
||||
validity_days=360,
|
||||
|
@ -14,13 +14,7 @@ from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import (
|
||||
CharField,
|
||||
ChoiceField,
|
||||
DateTimeField,
|
||||
IntegerField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.fields import CharField, DateTimeField, IntegerField, SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -32,7 +26,7 @@ from authentik.api.authorization import SecretKeyFilter
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.crypto.apps import MANAGED_KEY
|
||||
from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.rbac.decorators import permission_required
|
||||
@ -184,7 +178,6 @@ class CertificateGenerationSerializer(PassiveSerializer):
|
||||
common_name = CharField()
|
||||
subject_alt_name = CharField(required=False, allow_blank=True, label=_("Subject-alt name"))
|
||||
validity_days = IntegerField(initial=365)
|
||||
alg = ChoiceField(default=PrivateKeyAlg.RSA, choices=PrivateKeyAlg.choices)
|
||||
|
||||
|
||||
class CertificateKeyPairFilter(FilterSet):
|
||||
@ -247,7 +240,6 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
raw_san = data.validated_data.get("subject_alt_name", "")
|
||||
sans = raw_san.split(",") if raw_san != "" else []
|
||||
builder = CertificateBuilder(data.validated_data["common_name"])
|
||||
builder.alg = data.validated_data["alg"]
|
||||
builder.build(
|
||||
subject_alt_names=sans,
|
||||
validity_days=int(data.validated_data["validity_days"]),
|
||||
|
@ -9,28 +9,20 @@ from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
|
||||
from cryptography.x509.oid import NameOID
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
|
||||
class PrivateKeyAlg(models.TextChoices):
|
||||
"""Algorithm to create private key with"""
|
||||
|
||||
RSA = "rsa", _("rsa")
|
||||
ECDSA = "ecdsa", _("ecdsa")
|
||||
|
||||
|
||||
class CertificateBuilder:
|
||||
"""Build self-signed certificates"""
|
||||
|
||||
common_name: str
|
||||
alg: PrivateKeyAlg
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.alg = PrivateKeyAlg.RSA
|
||||
_use_ec_private_key: bool
|
||||
|
||||
def __init__(self, name: str, use_ec_private_key=False):
|
||||
self._use_ec_private_key = use_ec_private_key
|
||||
self.__public_key = None
|
||||
self.__private_key = None
|
||||
self.__builder = None
|
||||
@ -50,13 +42,11 @@ class CertificateBuilder:
|
||||
|
||||
def generate_private_key(self) -> PrivateKeyTypes:
|
||||
"""Generate private key"""
|
||||
if self.alg == PrivateKeyAlg.ECDSA:
|
||||
if self._use_ec_private_key:
|
||||
return ec.generate_private_key(curve=ec.SECP256R1())
|
||||
if self.alg == PrivateKeyAlg.RSA:
|
||||
return rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=4096, backend=default_backend()
|
||||
)
|
||||
raise ValueError(f"Invalid alg: {self.alg}")
|
||||
return rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=4096, backend=default_backend()
|
||||
)
|
||||
|
||||
def build(
|
||||
self,
|
||||
|
@ -201,7 +201,10 @@ class ConnectionToken(ExpiringModel):
|
||||
return settings
|
||||
|
||||
def __str__(self):
|
||||
return f"RAC Connection token {self.session_id} to {self.provider_id}/{self.endpoint_id}"
|
||||
return (
|
||||
f"RAC Connection token {self.session.user} to "
|
||||
f"{self.endpoint.provider.name}/{self.endpoint.name}"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("RAC Connection token")
|
||||
|
@ -116,12 +116,12 @@ class AuditMiddleware:
|
||||
return user
|
||||
user = getattr(request, "user", self.anonymous_user)
|
||||
if not user.is_authenticated:
|
||||
self._ensure_fallback_user()
|
||||
return self.anonymous_user
|
||||
return user
|
||||
|
||||
def connect(self, request: HttpRequest):
|
||||
"""Connect signal for automatic logging"""
|
||||
self._ensure_fallback_user()
|
||||
if not hasattr(request, "request_id"):
|
||||
return
|
||||
post_save.connect(
|
||||
|
@ -556,7 +556,7 @@ class Notification(SerializerModel):
|
||||
if len(self.body) > NOTIFICATION_SUMMARY_LENGTH
|
||||
else self.body
|
||||
)
|
||||
return f"Notification for user {self.user_id}: {body_trunc}"
|
||||
return f"Notification for user {self.user}: {body_trunc}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Notification")
|
||||
|
@ -1,35 +0,0 @@
|
||||
"""authentik event models tests"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from django.db.models import Model
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.core.models import default_token_key
|
||||
from authentik.lib.utils.reflection import get_apps
|
||||
|
||||
|
||||
class TestModels(TestCase):
|
||||
"""Test Models"""
|
||||
|
||||
|
||||
def model_tester_factory(test_model: type[Model]) -> Callable:
|
||||
"""Test models' __str__ and __repr__"""
|
||||
|
||||
def tester(self: TestModels):
|
||||
allowed = 0
|
||||
# Token-like objects need to lookup the current tenant to get the default token length
|
||||
for field in test_model._meta.fields:
|
||||
if field.default == default_token_key:
|
||||
allowed += 1
|
||||
with self.assertNumQueries(allowed):
|
||||
str(test_model())
|
||||
with self.assertNumQueries(allowed):
|
||||
repr(test_model())
|
||||
|
||||
return tester
|
||||
|
||||
|
||||
for app in get_apps():
|
||||
for model in app.get_models():
|
||||
setattr(TestModels, f"test_{app.label}_{model.__name__}", model_tester_factory(model))
|
@ -278,7 +278,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
|
||||
},
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[])
|
||||
def execute(self, request: Request, slug: str):
|
||||
def execute(self, request: Request, _slug: str):
|
||||
"""Execute flow for current user"""
|
||||
# Because we pre-plan the flow here, and not in the planner, we need to manually clear
|
||||
# the history of the inspector
|
||||
|
@ -6,7 +6,6 @@ from rest_framework.test import APITestCase
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.flows.api.stages import StageSerializer, StageViewSet
|
||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.stages.dummy.models import DummyStage
|
||||
@ -102,21 +101,3 @@ class TestFlowsAPI(APITestCase):
|
||||
reverse("authentik_api:stage-types"),
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_execute(self):
|
||||
"""Test execute endpoint"""
|
||||
user = create_test_admin_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
flow = Flow.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
designation=FlowDesignation.AUTHENTICATION,
|
||||
)
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-execute", kwargs={"slug": flow.slug})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -96,13 +96,16 @@ class TestEvaluator(TestCase):
|
||||
execution_logging=True,
|
||||
expression="ak_message(request.http_request.path)\nreturn True",
|
||||
)
|
||||
tmpl = f"""
|
||||
tmpl = (
|
||||
"""
|
||||
ak_message(request.http_request.path)
|
||||
res = ak_call_policy('{expr.name}')
|
||||
res = ak_call_policy('%s')
|
||||
ak_message(request.http_request.path)
|
||||
for msg in res.messages:
|
||||
ak_message(msg)
|
||||
"""
|
||||
% expr.name
|
||||
)
|
||||
evaluator = PolicyEvaluator("test")
|
||||
evaluator.set_policy_request(self.request)
|
||||
res = evaluator.evaluate(tmpl)
|
||||
|
@ -326,7 +326,7 @@ class AuthorizationCode(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
verbose_name_plural = _("Authorization Codes")
|
||||
|
||||
def __str__(self):
|
||||
return f"Authorization code for {self.provider_id} for user {self.user_id}"
|
||||
return f"Authorization code for {self.provider} for user {self.user}"
|
||||
|
||||
@property
|
||||
def serializer(self) -> Serializer:
|
||||
@ -356,7 +356,7 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
verbose_name_plural = _("OAuth2 Access Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return f"Access Token for {self.provider_id} for user {self.user_id}"
|
||||
return f"Access Token for {self.provider} for user {self.user}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> IDToken:
|
||||
@ -399,7 +399,7 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
verbose_name_plural = _("OAuth2 Refresh Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return f"Refresh Token for {self.provider_id} for user {self.user_id}"
|
||||
return f"Refresh Token for {self.provider} for user {self.user}"
|
||||
|
||||
@property
|
||||
def id_token(self) -> IDToken:
|
||||
@ -443,4 +443,4 @@ class DeviceToken(ExpiringModel):
|
||||
verbose_name_plural = _("Device Tokens")
|
||||
|
||||
def __str__(self):
|
||||
return f"Device Token for {self.provider_id}"
|
||||
return f"Device Token for {self.provider}"
|
||||
|
@ -10,7 +10,6 @@ from jwt import PyJWKSet
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.crypto.builder import PrivateKeyAlg
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
@ -83,7 +82,7 @@ class TestJWKS(OAuthTestCase):
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
signing_key=create_test_cert(PrivateKeyAlg.ECDSA),
|
||||
signing_key=create_test_cert(use_ec_private_key=True),
|
||||
)
|
||||
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||
response = self.client.get(
|
||||
|
@ -1,44 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-05-01 15:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_saml", "0013_samlprovider_default_relay_state"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="samlprovider",
|
||||
name="digest_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#sha1", "SHA1"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha256", "SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#sha384", "SHA384"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha512", "SHA512"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlprovider",
|
||||
name="signature_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "RSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RSA-SHA512"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1", "ECDSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", "ECDSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", "ECDSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", "ECDSA-SHA512"),
|
||||
("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "DSA-SHA1"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
),
|
||||
),
|
||||
]
|
@ -11,10 +11,6 @@ from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
DSA_SHA1,
|
||||
ECDSA_SHA1,
|
||||
ECDSA_SHA256,
|
||||
ECDSA_SHA384,
|
||||
ECDSA_SHA512,
|
||||
RSA_SHA1,
|
||||
RSA_SHA256,
|
||||
RSA_SHA384,
|
||||
@ -96,7 +92,8 @@ class SAMLProvider(Provider):
|
||||
),
|
||||
)
|
||||
|
||||
digest_algorithm = models.TextField(
|
||||
digest_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
choices=(
|
||||
(SHA1, _("SHA1")),
|
||||
(SHA256, _("SHA256")),
|
||||
@ -105,16 +102,13 @@ class SAMLProvider(Provider):
|
||||
),
|
||||
default=SHA256,
|
||||
)
|
||||
signature_algorithm = models.TextField(
|
||||
signature_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
choices=(
|
||||
(RSA_SHA1, _("RSA-SHA1")),
|
||||
(RSA_SHA256, _("RSA-SHA256")),
|
||||
(RSA_SHA384, _("RSA-SHA384")),
|
||||
(RSA_SHA512, _("RSA-SHA512")),
|
||||
(ECDSA_SHA1, _("ECDSA-SHA1")),
|
||||
(ECDSA_SHA256, _("ECDSA-SHA256")),
|
||||
(ECDSA_SHA384, _("ECDSA-SHA384")),
|
||||
(ECDSA_SHA512, _("ECDSA-SHA512")),
|
||||
(DSA_SHA1, _("DSA-SHA1")),
|
||||
),
|
||||
default=RSA_SHA256,
|
||||
|
@ -7,14 +7,13 @@ from lxml import etree # nosec
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.crypto.builder import PrivateKeyAlg
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import load_fixture
|
||||
from authentik.lib.xml import lxml_from_string
|
||||
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
|
||||
from authentik.providers.saml.processors.metadata import MetadataProcessor
|
||||
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
|
||||
from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA
|
||||
from authentik.sources.saml.processors.constants import NS_MAP, NS_SAML_METADATA
|
||||
|
||||
|
||||
class TestServiceProviderMetadataParser(TestCase):
|
||||
@ -108,41 +107,12 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "")
|
||||
)
|
||||
|
||||
def test_signature_rsa(self):
|
||||
"""Test signature validation (RSA)"""
|
||||
def test_signature(self):
|
||||
"""Test signature validation"""
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=self.flow,
|
||||
signing_kp=create_test_cert(PrivateKeyAlg.RSA),
|
||||
)
|
||||
Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
provider=provider,
|
||||
)
|
||||
request = self.factory.get("/")
|
||||
metadata = MetadataProcessor(provider, request).build_entity_descriptor()
|
||||
|
||||
root = fromstring(metadata.encode())
|
||||
xmlsec.tree.add_ids(root, ["ID"])
|
||||
signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP)
|
||||
signature_node = signature_nodes[0]
|
||||
ctx = xmlsec.SignatureContext()
|
||||
key = xmlsec.Key.from_memory(
|
||||
provider.signing_kp.certificate_data,
|
||||
xmlsec.constants.KeyDataFormatCertPem,
|
||||
None,
|
||||
)
|
||||
ctx.key = key
|
||||
ctx.verify(signature_node)
|
||||
|
||||
def test_signature_ecdsa(self):
|
||||
"""Test signature validation (ECDSA)"""
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=self.flow,
|
||||
signing_kp=create_test_cert(PrivateKeyAlg.ECDSA),
|
||||
signature_algorithm=ECDSA_SHA256,
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
Application.objects.create(
|
||||
name=generate_id(),
|
||||
|
@ -3,7 +3,7 @@
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.providers.scim.models import SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_task_wrapper
|
||||
from authentik.providers.scim.tasks import scim_sync
|
||||
from authentik.tenants.management import TenantCommand
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -21,4 +21,4 @@ class Command(TenantCommand):
|
||||
if not provider:
|
||||
LOGGER.warning("Provider does not exist", name=provider_name)
|
||||
continue
|
||||
scim_task_wrapper(provider.pk).get()
|
||||
scim_sync.delay(provider.pk).get()
|
||||
|
@ -105,7 +105,7 @@ class SCIMUser(models.Model):
|
||||
unique_together = (("id", "user", "provider"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM User {self.user_id} to {self.provider_id}"
|
||||
return f"SCIM User {self.user.username} to {self.provider.name}"
|
||||
|
||||
|
||||
class SCIMGroup(models.Model):
|
||||
@ -119,4 +119,4 @@ class SCIMGroup(models.Model):
|
||||
unique_together = (("id", "group", "provider"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM Group {self.group_id} to {self.provider_id}"
|
||||
return f"SCIM Group {self.group.name} to {self.provider.name}"
|
||||
|
@ -9,7 +9,7 @@ from structlog.stdlib import get_logger
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.providers.scim.models import SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_signal_direct, scim_signal_m2m, scim_task_wrapper
|
||||
from authentik.providers.scim.tasks import scim_signal_direct, scim_signal_m2m, scim_sync
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -17,7 +17,7 @@ LOGGER = get_logger()
|
||||
@receiver(post_save, sender=SCIMProvider)
|
||||
def post_save_provider(sender: type[Model], instance, created: bool, **_):
|
||||
"""Trigger sync when SCIM provider is saved"""
|
||||
scim_task_wrapper(instance.pk)
|
||||
scim_sync.delay(instance.pk)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
|
@ -38,23 +38,7 @@ def client_for_model(provider: SCIMProvider, model: Model) -> SCIMClient:
|
||||
def scim_sync_all():
|
||||
"""Run sync for all providers"""
|
||||
for provider in SCIMProvider.objects.filter(backchannel_application__isnull=False):
|
||||
scim_task_wrapper(provider.pk)
|
||||
|
||||
|
||||
def scim_task_wrapper(provider_pk: int):
|
||||
"""Wrap scim_sync to set the correct timeouts"""
|
||||
provider: SCIMProvider = SCIMProvider.objects.filter(
|
||||
pk=provider_pk, backchannel_application__isnull=False
|
||||
).first()
|
||||
if not provider:
|
||||
return
|
||||
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
|
||||
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
|
||||
soft_time_limit = (users_paginator.num_pages + groups_paginator.num_pages) * PAGE_TIMEOUT
|
||||
time_limit = soft_time_limit * 1.5
|
||||
return scim_sync.apply_async(
|
||||
(provider.pk,), time_limit=int(time_limit), soft_time_limit=int(soft_time_limit)
|
||||
)
|
||||
scim_sync.delay(provider.pk)
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=SystemTask)
|
||||
@ -76,7 +60,7 @@ def scim_sync(self: SystemTask, provider_pk: int) -> None:
|
||||
users_paginator = Paginator(provider.get_user_qs(), PAGE_SIZE)
|
||||
groups_paginator = Paginator(provider.get_group_qs(), PAGE_SIZE)
|
||||
self.soft_time_limit = self.time_limit = (
|
||||
users_paginator.num_pages + groups_paginator.num_pages
|
||||
users_paginator.count + groups_paginator.count
|
||||
) * PAGE_TIMEOUT
|
||||
with allow_join_result():
|
||||
try:
|
||||
|
@ -8,7 +8,7 @@ from authentik.core.models import Application, Group, User
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.clients.schema import ServiceProviderConfiguration
|
||||
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_task_wrapper
|
||||
from authentik.providers.scim.tasks import scim_sync
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ class SCIMMembershipTests(TestCase):
|
||||
)
|
||||
|
||||
self.configure()
|
||||
scim_task_wrapper(self.provider.pk).get()
|
||||
scim_sync.delay(self.provider.pk).get()
|
||||
|
||||
self.assertEqual(mocker.call_count, 6)
|
||||
self.assertEqual(mocker.request_history[0].method, "GET")
|
||||
@ -169,7 +169,7 @@ class SCIMMembershipTests(TestCase):
|
||||
)
|
||||
|
||||
self.configure()
|
||||
scim_task_wrapper(self.provider.pk).get()
|
||||
scim_sync.delay(self.provider.pk).get()
|
||||
|
||||
self.assertEqual(mocker.call_count, 6)
|
||||
self.assertEqual(mocker.request_history[0].method, "GET")
|
||||
|
@ -10,7 +10,7 @@ from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import Application, Group, User
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_task_wrapper
|
||||
from authentik.providers.scim.tasks import scim_sync
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
@ -236,7 +236,7 @@ class SCIMUserTests(TestCase):
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
|
||||
scim_task_wrapper(self.provider.pk).get()
|
||||
scim_sync.delay(self.provider.pk).get()
|
||||
|
||||
self.assertEqual(mock.call_count, 5)
|
||||
self.assertEqual(mock.request_history[0].method, "GET")
|
||||
|
@ -76,7 +76,7 @@ class S3Storage(BaseS3Storage):
|
||||
|
||||
return safe_join(self.location, connection.schema_name, name)
|
||||
except ValueError:
|
||||
raise SuspiciousOperation(f"Attempted access to '{name}' denied.") from None
|
||||
raise SuspiciousOperation("Attempted access to '%s' denied." % name) from None
|
||||
|
||||
# This is a fix for https://github.com/jschneier/django-storages/pull/839
|
||||
def url(self, name, parameters=None, expire=None, http_method=None):
|
||||
|
@ -80,7 +80,7 @@ class OAuth2Client(BaseOAuthClient):
|
||||
access_token_url = self.source.source_type.access_token_url or ""
|
||||
if self.source.source_type.urls_customizable and self.source.access_token_url:
|
||||
access_token_url = self.source.access_token_url
|
||||
response = self.do_request(
|
||||
response = self.session.request(
|
||||
"post", access_token_url, data=args, headers=self._default_headers, **request_kwargs
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
@ -1,37 +0,0 @@
|
||||
"""Apple Type tests"""
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import dummy_get_response
|
||||
from authentik.root.middleware import SessionMiddleware
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.registry import registry
|
||||
|
||||
|
||||
class TestTypeApple(TestCase):
|
||||
"""OAuth Source tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.source = OAuthSource.objects.create(
|
||||
name="test",
|
||||
slug="test",
|
||||
provider_type="apple",
|
||||
authorization_url="",
|
||||
profile_url="",
|
||||
consumer_key=generate_id(),
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_login_challenge(self):
|
||||
"""Test login_challenge"""
|
||||
request = self.factory.get("/")
|
||||
request.user = get_anonymous_user()
|
||||
|
||||
middleware = SessionMiddleware(dummy_get_response)
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
oauth_type = registry.find_type("apple")
|
||||
challenge = oauth_type().login_challenge(self.source, request)
|
||||
self.assertTrue(challenge.is_valid(raise_exception=True))
|
@ -125,7 +125,7 @@ class AppleType(SourceType):
|
||||
)
|
||||
args = apple_client.get_redirect_args()
|
||||
return AppleLoginChallenge(
|
||||
data={
|
||||
instance={
|
||||
"client_id": apple_client.get_client_id(),
|
||||
"scope": "name email",
|
||||
"redirect_uri": args["redirect_uri"],
|
||||
|
@ -66,7 +66,7 @@ class PlexSource(Source):
|
||||
icon = static("authentik/sources/plex.svg")
|
||||
return UILoginButton(
|
||||
challenge=PlexAuthenticationChallenge(
|
||||
data={
|
||||
{
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
"component": "ak-source-plex",
|
||||
"client_id": self.client_id,
|
||||
|
@ -40,11 +40,6 @@ class TestPlexSource(TestCase):
|
||||
slug="test",
|
||||
)
|
||||
|
||||
def test_login_challenge(self):
|
||||
"""Test login_challenge"""
|
||||
ui_login_button = self.source.ui_login_button(None)
|
||||
self.assertTrue(ui_login_button.challenge.is_valid(raise_exception=True))
|
||||
|
||||
def test_get_user_info(self):
|
||||
"""Test get_user_info"""
|
||||
token = generate_key()
|
||||
|
@ -1,44 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-05-01 15:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_saml", "0013_samlsource_verification_kp_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="samlsource",
|
||||
name="digest_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#sha1", "SHA1"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha256", "SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#sha384", "SHA384"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha512", "SHA512"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlsource",
|
||||
name="signature_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "RSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RSA-SHA512"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1", "ECDSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", "ECDSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", "ECDSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", "ECDSA-SHA512"),
|
||||
("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "DSA-SHA1"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
),
|
||||
),
|
||||
]
|
@ -15,10 +15,6 @@ from authentik.flows.models import Flow
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
DSA_SHA1,
|
||||
ECDSA_SHA1,
|
||||
ECDSA_SHA256,
|
||||
ECDSA_SHA384,
|
||||
ECDSA_SHA512,
|
||||
RSA_SHA1,
|
||||
RSA_SHA256,
|
||||
RSA_SHA384,
|
||||
@ -147,7 +143,8 @@ class SAMLSource(Source):
|
||||
verbose_name=_("Signing Keypair"),
|
||||
)
|
||||
|
||||
digest_algorithm = models.TextField(
|
||||
digest_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
choices=(
|
||||
(SHA1, _("SHA1")),
|
||||
(SHA256, _("SHA256")),
|
||||
@ -156,16 +153,13 @@ class SAMLSource(Source):
|
||||
),
|
||||
default=SHA256,
|
||||
)
|
||||
signature_algorithm = models.TextField(
|
||||
signature_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
choices=(
|
||||
(RSA_SHA1, _("RSA-SHA1")),
|
||||
(RSA_SHA256, _("RSA-SHA256")),
|
||||
(RSA_SHA384, _("RSA-SHA384")),
|
||||
(RSA_SHA512, _("RSA-SHA512")),
|
||||
(ECDSA_SHA1, _("ECDSA-SHA1")),
|
||||
(ECDSA_SHA256, _("ECDSA-SHA256")),
|
||||
(ECDSA_SHA384, _("ECDSA-SHA384")),
|
||||
(ECDSA_SHA512, _("ECDSA-SHA512")),
|
||||
(DSA_SHA1, _("DSA-SHA1")),
|
||||
),
|
||||
default=RSA_SHA256,
|
||||
|
@ -26,16 +26,9 @@ SAML_BINDING_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
|
||||
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.2
|
||||
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
||||
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
||||
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
||||
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.6
|
||||
ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
|
||||
ECDSA_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"
|
||||
ECDSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
|
||||
ECDSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
|
||||
ECDSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"
|
||||
|
||||
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||
SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
|
||||
@ -48,11 +41,6 @@ SIGN_ALGORITHM_TRANSFORM_MAP = {
|
||||
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
|
||||
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
|
||||
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
|
||||
ECDSA_SHA1: xmlsec.constants.TransformEcdsaSha1,
|
||||
ECDSA_SHA224: xmlsec.constants.TransformEcdsaSha224,
|
||||
ECDSA_SHA256: xmlsec.constants.TransformEcdsaSha256,
|
||||
ECDSA_SHA384: xmlsec.constants.TransformEcdsaSha384,
|
||||
ECDSA_SHA512: xmlsec.constants.TransformEcdsaSha512,
|
||||
}
|
||||
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP = {
|
||||
|
@ -60,7 +60,7 @@ class SCIMSourceUser(SerializerModel):
|
||||
unique_together = (("id", "user", "source"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM User {self.user_id} to {self.source_id}"
|
||||
return f"SCIM User {self.user.username} to {self.source.name}"
|
||||
|
||||
|
||||
class SCIMSourceGroup(SerializerModel):
|
||||
@ -81,4 +81,4 @@ class SCIMSourceGroup(SerializerModel):
|
||||
unique_together = (("id", "group", "source"),)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SCIM Group {self.group_id} to {self.source_id}"
|
||||
return f"SCIM Group {self.group.name} to {self.source.name}"
|
||||
|
@ -2,11 +2,9 @@ from django.db.models import Model
|
||||
from django.db.models.signals import pre_delete, pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from authentik.core.models import USER_PATH_SYSTEM_PREFIX, Token, TokenIntents, User, UserTypes
|
||||
from authentik.core.models import Token, TokenIntents, User, UserTypes
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
USER_PATH_SOURCE_SCIM = USER_PATH_SYSTEM_PREFIX + "/sources/scim"
|
||||
|
||||
|
||||
@receiver(pre_save, sender=SCIMSource)
|
||||
def scim_source_pre_save(sender: type[Model], instance: SCIMSource, **_):
|
||||
@ -18,7 +16,6 @@ def scim_source_pre_save(sender: type[Model], instance: SCIMSource, **_):
|
||||
username=identifier,
|
||||
name=f"SCIM Source {instance.name} Service-Account",
|
||||
type=UserTypes.INTERNAL_SERVICE_ACCOUNT,
|
||||
path=USER_PATH_SOURCE_SCIM,
|
||||
)
|
||||
token = Token.objects.create(
|
||||
user=user,
|
||||
|
@ -96,7 +96,7 @@ class DuoDevice(SerializerModel, Device):
|
||||
return DuoDeviceSerializer
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) or str(self.user_id)
|
||||
return str(self.name) or str(self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Duo Device")
|
||||
|
@ -221,7 +221,7 @@ class SMSDevice(SerializerModel, SideChannelDevice):
|
||||
return valid
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) or str(self.user_id)
|
||||
return str(self.name) or str(self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("SMS Device")
|
||||
|
File diff suppressed because one or more lines are too long
@ -155,7 +155,7 @@ class WebAuthnDevice(SerializerModel, Device):
|
||||
return WebAuthnDeviceSerializer
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name) or str(self.user_id)
|
||||
return str(self.name) or str(self.user)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("WebAuthn Device")
|
||||
|
@ -65,7 +65,7 @@ class UserConsent(SerializerModel, ExpiringModel):
|
||||
return UserConsentSerializer
|
||||
|
||||
def __str__(self):
|
||||
return f"User Consent {self.application_id} by {self.user_id}"
|
||||
return f"User Consent {self.application} by {self.user}"
|
||||
|
||||
class Meta:
|
||||
unique_together = (("user", "application", "permissions"),)
|
||||
|
@ -79,7 +79,7 @@ class Invitation(SerializerModel, ExpiringModel):
|
||||
return InvitationSerializer
|
||||
|
||||
def __str__(self):
|
||||
return f"Invitation {str(self.invite_uuid)} created by {self.created_by_id}"
|
||||
return f"Invitation {str(self.invite_uuid)} created by {self.created_by}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Invitation")
|
||||
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-05-01 15:32
|
||||
|
||||
import authentik.lib.utils.time
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_tenants", "0002_tenant_default_token_duration_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="tenant",
|
||||
name="default_token_duration",
|
||||
field=models.TextField(
|
||||
default="days=1",
|
||||
help_text="Default token duration",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
]
|
@ -23,7 +23,7 @@ LOGGER = get_logger()
|
||||
|
||||
VALID_SCHEMA_NAME = re.compile(r"^t_[a-z0-9]{1,61}$")
|
||||
|
||||
DEFAULT_TOKEN_DURATION = "days=1" # nosec
|
||||
DEFAULT_TOKEN_DURATION = "minutes=30" # nosec
|
||||
DEFAULT_TOKEN_LENGTH = 60
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2024.4.1 Blueprint schema",
|
||||
"title": "authentik 2024.4.0 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
@ -4131,10 +4131,6 @@
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
|
||||
"http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
],
|
||||
"title": "Signature algorithm"
|
||||
@ -4939,10 +4935,6 @@
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
|
||||
"http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
],
|
||||
"title": "Signature algorithm"
|
||||
|
@ -32,7 +32,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.0}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -53,7 +53,7 @@ services:
|
||||
- postgresql
|
||||
- redis
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.0}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
2
go.mod
2
go.mod
@ -28,7 +28,7 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2024041.2
|
||||
goauthentik.io/api/v3 v3.2024023.2
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/sync v0.7.0
|
||||
|
4
go.sum
4
go.sum
@ -294,8 +294,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.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
goauthentik.io/api/v3 v3.2024041.2 h1:gbquIA8RU+9jJbFdGckQTtJzOfWVp2+QdF4LuNVTAWM=
|
||||
goauthentik.io/api/v3 v3.2024041.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2024023.2 h1:lSVaZAKTpsDhtw11wnkGjPalkDzv9H2VKEJllBi2aXs=
|
||||
goauthentik.io/api/v3 v3.2024023.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2024.4.1"
|
||||
const VERSION = "2024.4.0"
|
||||
|
@ -117,8 +117,6 @@ def run_migrations():
|
||||
)
|
||||
finally:
|
||||
release_lock(curr)
|
||||
curr.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -3,6 +3,7 @@
|
||||
import authentik. This is done by the dockerfile."""
|
||||
from sys import exit as sysexit
|
||||
from time import sleep
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from psycopg import OperationalError, connect
|
||||
from redis import Redis
|
||||
@ -34,7 +35,7 @@ def check_postgres():
|
||||
|
||||
|
||||
def check_redis():
|
||||
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
|
||||
url = redis_url(CONFIG.get("redis.db"))
|
||||
while True:
|
||||
try:
|
||||
redis = Redis.from_url(url)
|
||||
@ -42,7 +43,10 @@ def check_redis():
|
||||
break
|
||||
except RedisError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})")
|
||||
sanitized_url = url.replace(quote_plus(CONFIG.get("redis.password")), "******")
|
||||
CONFIG.log(
|
||||
"info", f"Redis Connection failed, retrying... ({exc})", redis_url=sanitized_url
|
||||
)
|
||||
CONFIG.log("info", "Redis Connection successful")
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-05-03 00:08+0000\n"
|
||||
"POT-Creation-Date: 2024-04-16 00:07+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -363,14 +363,6 @@ msgstr ""
|
||||
msgid "Subject-alt name"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/crypto/builder.py
|
||||
msgid "rsa"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/crypto/builder.py
|
||||
msgid "ecdsa"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/crypto/models.py
|
||||
msgid "PEM-encoded Certificate data"
|
||||
msgstr ""
|
||||
@ -1557,22 +1549,6 @@ msgstr ""
|
||||
msgid "RSA-SHA512"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
|
||||
msgid "ECDSA-SHA1"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
|
||||
msgid "ECDSA-SHA256"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
|
||||
msgid "ECDSA-SHA384"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
|
||||
msgid "ECDSA-SHA512"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/saml/models.py authentik/sources/saml/models.py
|
||||
msgid "DSA-SHA1"
|
||||
msgstr ""
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
2316
poetry.lock
generated
2316
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Build website
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:22 as web-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder
|
||||
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /static
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2024.4.1"
|
||||
version = "2024.4.0"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
@ -89,7 +89,6 @@ channels = { version = "*", extras = ["daphne"] }
|
||||
channels-redis = "*"
|
||||
codespell = "*"
|
||||
colorama = "*"
|
||||
cryptography = "*"
|
||||
dacite = "*"
|
||||
deepmerge = "*"
|
||||
defusedxml = "*"
|
||||
@ -102,7 +101,7 @@ django-redis = "*"
|
||||
django-storages = { extras = ["s3"], version = "*" }
|
||||
# See https://github.com/django-tenants/django-tenants/pull/997
|
||||
django-tenants = { git = "https://github.com/rissson/django-tenants.git", branch="authentik-fixes" }
|
||||
djangorestframework = "3.14.0"
|
||||
djangorestframework = "*"
|
||||
djangorestframework-guardian = "*"
|
||||
docker = "*"
|
||||
drf-spectacular = "*"
|
||||
@ -116,11 +115,17 @@ gunicorn = "*"
|
||||
jsonpatch = "*"
|
||||
kubernetes = "*"
|
||||
ldap3 = "*"
|
||||
lxml = "*"
|
||||
lxml = [
|
||||
# 5.0.0 works with libxml2 2.11.x, which is standard on brew
|
||||
{ version = "5.0.0", platform = "darwin" },
|
||||
# 4.9.x works with previous libxml2 versions, which is what we get on linux
|
||||
{ version = "4.9.4", platform = "linux" },
|
||||
]
|
||||
opencontainers = { extras = ["reggie"], version = "*" }
|
||||
packaging = "*"
|
||||
paramiko = "*"
|
||||
psycopg = { extras = ["c"], version = "*" }
|
||||
pycryptodome = "*"
|
||||
pydantic = "*"
|
||||
pydantic-scim = "*"
|
||||
pyjwt = "*"
|
||||
|
@ -18,7 +18,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
||||
go build -o /go/rac ./cmd/rac
|
||||
|
||||
# Stage 2: Run
|
||||
FROM ghcr.io/beryju/guacd:1.5.5
|
||||
FROM ghcr.io/beryju/guacd:1.5.3
|
||||
|
||||
ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
23
schema.yml
23
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2024.4.1
|
||||
version: 2024.4.0
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@ -17051,10 +17051,6 @@ paths:
|
||||
enum:
|
||||
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
|
||||
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
|
||||
@ -20914,10 +20910,6 @@ paths:
|
||||
enum:
|
||||
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
|
||||
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
|
||||
@ -30458,11 +30450,6 @@ components:
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AlgEnum:
|
||||
enum:
|
||||
- rsa
|
||||
- ecdsa
|
||||
type: string
|
||||
App:
|
||||
type: object
|
||||
description: Serialize Application info
|
||||
@ -32120,10 +32107,6 @@ components:
|
||||
type: string
|
||||
validity_days:
|
||||
type: integer
|
||||
alg:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AlgEnum'
|
||||
default: rsa
|
||||
required:
|
||||
- common_name
|
||||
- validity_days
|
||||
@ -43675,10 +43658,6 @@ components:
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
|
||||
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
|
||||
type: string
|
||||
Source:
|
||||
|
2593
tests/wdio/package-lock.json
generated
2593
tests/wdio/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,10 @@
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
||||
"@typescript-eslint/parser": "^7.5.0",
|
||||
"@wdio/cli": "^8.36.1",
|
||||
"@wdio/local-runner": "^8.36.1",
|
||||
"@wdio/mocha-framework": "^8.36.1",
|
||||
"@wdio/spec-reporter": "^8.36.1",
|
||||
"@wdio/cli": "^8.36.0",
|
||||
"@wdio/local-runner": "^8.36.0",
|
||||
"@wdio/mocha-framework": "^8.36.0",
|
||||
"@wdio/spec-reporter": "^8.36.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-sonarjs": "^0.25.1",
|
||||
@ -32,6 +32,6 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"dependencies": {
|
||||
"chromedriver": "^124.0.1"
|
||||
"chromedriver": "^123.0.4"
|
||||
}
|
||||
}
|
||||
|
4581
web/package-lock.json
generated
4581
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,8 +14,8 @@
|
||||
"build": "run-s build-locales esbuild:build",
|
||||
"build-proxy": "run-s build-locales esbuild:build-proxy",
|
||||
"watch": "run-s build-locales esbuild:watch",
|
||||
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix",
|
||||
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=65536' node scripts/eslint-precommit.mjs",
|
||||
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=16384' eslint . --max-warnings 0 --fix",
|
||||
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=16384' node scripts/eslint-precommit.mjs",
|
||||
"lint:spelling": "node scripts/check-spelling.mjs",
|
||||
"lit-analyse": "lit-analyzer src",
|
||||
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
|
||||
@ -32,13 +32,13 @@
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-python": "^6.1.6",
|
||||
"@codemirror/lang-python": "^6.1.5",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/legacy-modes": "^6.4.0",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.5.5",
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@goauthentik/api": "^2024.4.1-1714655911",
|
||||
"@goauthentik/api": "^2024.2.3-1713441634",
|
||||
"@lit-labs/task": "^3.1.0",
|
||||
"@lit/context": "^1.1.1",
|
||||
"@lit/localize": "^0.12.1",
|
||||
@ -46,7 +46,7 @@
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^3.0.1",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^7.113.0",
|
||||
"@sentry/browser": "^7.111.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"chart.js": "^4.4.2",
|
||||
@ -65,29 +65,29 @@
|
||||
"style-mod": "^4.1.2",
|
||||
"ts-pattern": "^5.1.1",
|
||||
"webcomponent-qr-code": "^1.2.0",
|
||||
"yaml": "^2.4.2"
|
||||
"yaml": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.5",
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.24.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.24.5",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
|
||||
"@babel/plugin-transform-runtime": "^7.24.3",
|
||||
"@babel/preset-env": "^7.24.5",
|
||||
"@babel/preset-env": "^7.24.4",
|
||||
"@babel/preset-typescript": "^7.24.1",
|
||||
"@hcaptcha/types": "^1.0.3",
|
||||
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
|
||||
"@lit/localize-tools": "^0.7.2",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@spotlightjs/spotlight": "^1.2.17",
|
||||
"@storybook/addon-essentials": "^8.0.9",
|
||||
"@storybook/addon-links": "^8.0.9",
|
||||
"@storybook/addon-essentials": "^8.0.8",
|
||||
"@storybook/addon-links": "^8.0.8",
|
||||
"@storybook/api": "^7.6.17",
|
||||
"@storybook/blocks": "^8.0.8",
|
||||
"@storybook/manager-api": "^8.0.9",
|
||||
"@storybook/web-components": "^8.0.9",
|
||||
"@storybook/web-components-vite": "^8.0.9",
|
||||
"@storybook/manager-api": "^8.0.8",
|
||||
"@storybook/web-components": "^8.0.8",
|
||||
"@storybook/web-components-vite": "^8.0.8",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/codemirror": "5.60.15",
|
||||
@ -114,10 +114,10 @@
|
||||
"prettier": "^3.2.5",
|
||||
"pseudolocale": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"rollup-plugin-modify": "^3.0.0",
|
||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||
"storybook": "^8.0.9",
|
||||
"storybook": "^8.0.8",
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"ts-lit-plugin": "^2.0.2",
|
||||
"tslib": "^2.6.2",
|
||||
@ -129,9 +129,9 @@
|
||||
"@esbuild/darwin-arm64": "^0.20.1",
|
||||
"@esbuild/linux-amd64": "^0.18.11",
|
||||
"@esbuild/linux-arm64": "^0.20.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.17.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.17.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.17.2"
|
||||
"@rollup/rollup-darwin-arm64": "4.14.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.14.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.14.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
@ -29,9 +29,5 @@ export const signatureAlgorithmOptions = toOptions([
|
||||
["RSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMorersaSha256, true],
|
||||
["RSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMorersaSha384],
|
||||
["RSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMorersaSha512],
|
||||
["ECDSA-SHA1", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha1],
|
||||
["ECDSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha256],
|
||||
["ECDSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha384],
|
||||
["ECDSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha512],
|
||||
["DSA-SHA1", SignatureAlgorithmEnum._200009XmldsigdsaSha1],
|
||||
]);
|
||||
|
@ -6,12 +6,7 @@ import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import {
|
||||
AlgEnum,
|
||||
CertificateGenerationRequest,
|
||||
CertificateKeyPair,
|
||||
CryptoApi,
|
||||
} from "@goauthentik/api";
|
||||
import { CertificateGenerationRequest, CertificateKeyPair, CryptoApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-crypto-certificate-generate-form")
|
||||
export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
@ -45,29 +40,6 @@ export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
?required=${true}
|
||||
>
|
||||
<input class="pf-c-form-control" type="number" value="365" />
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Private key Algorithm")}
|
||||
?required=${true}
|
||||
name="alg"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("RSA"),
|
||||
value: AlgEnum.Rsa,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg("ECDSA"),
|
||||
value: AlgEnum.Ecdsa,
|
||||
},
|
||||
]}
|
||||
>
|
||||
</ak-radio>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Algorithm used to generate the private key.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal> `;
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
}
|
||||
|
@ -214,23 +214,28 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
||||
name="sources"
|
||||
>
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${this.sources?.results
|
||||
.filter((source) => {
|
||||
return source.component !== "";
|
||||
})
|
||||
.map((source) => {
|
||||
const selected = Array.from(this.instance?.sources || []).some(
|
||||
(su) => {
|
||||
return su == source.pk;
|
||||
},
|
||||
);
|
||||
return html`<option
|
||||
value=${ifDefined(source.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${source.name}
|
||||
</option>`;
|
||||
})}
|
||||
${this.sources?.results.map((source) => {
|
||||
let selected = Array.from(this.instance?.sources || []).some(
|
||||
(su) => {
|
||||
return su == source.pk;
|
||||
},
|
||||
);
|
||||
// Creating a new instance, auto-select built-in source
|
||||
// Only when no other sources exist
|
||||
if (
|
||||
!this.instance &&
|
||||
source.component === "" &&
|
||||
(this.sources?.results || []).length < 2
|
||||
) {
|
||||
selected = true;
|
||||
}
|
||||
return html`<option
|
||||
value=${ifDefined(source.pk)}
|
||||
?selected=${selected}
|
||||
>
|
||||
${source.name}
|
||||
</option>`;
|
||||
})}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
|
@ -128,14 +128,6 @@ export class UserForm extends ModelForm<User, number> {
|
||||
"Service accounts should be used for machine-to-machine authentication or other automations.",
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
label: "Internal Service account",
|
||||
value: UserTypeEnum.InternalServiceAccount,
|
||||
disabled: true,
|
||||
description: html`${msg(
|
||||
"Internal Service accounts are created and managed by authentik and cannot be created manually.",
|
||||
)}`,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.type}
|
||||
>
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2024.4.1";
|
||||
export const VERSION = "2024.4.0";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
@ -187,9 +187,6 @@ input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
.pf-c-select__menu-item.pf-m-focus {
|
||||
--pf-c-select__menu-item--focus--BackgroundColor: var(--ak-dark-background-light-ish);
|
||||
}
|
||||
.pf-c-button:disabled {
|
||||
color: var(--ak-dark-background-lighter);
|
||||
}
|
||||
.pf-c-button.pf-m-plain:hover {
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export function me(): Promise<SessionUser> {
|
||||
if (!user.user.settings || !("locale" in user.user.settings)) {
|
||||
return user;
|
||||
}
|
||||
const locale: string | undefined = user.user.settings.locale;
|
||||
const locale = user.user.settings.locale;
|
||||
if (locale && locale !== "") {
|
||||
console.debug(
|
||||
`authentik/locale: Activating user's configured locale '${locale}'`,
|
||||
|
@ -111,21 +111,6 @@ export function dateTimeLocal(date: Date): string {
|
||||
return `${parts[0]}:${parts[1]}`;
|
||||
}
|
||||
|
||||
export function dateToUTC(date: Date): Date {
|
||||
// Sigh...so our API is UTC/can take TZ info in the ISO format as it should.
|
||||
// datetime-local fields (which is almost the only date-time input we use)
|
||||
// can return its value as a UTC timestamp...however the generated API client
|
||||
// _requires_ a Date object, only to then convert it to an ISO string anyways
|
||||
// JS Dates don't include timezone info in the ISO string, so that just sends
|
||||
// the local time as UTC...which is wrong
|
||||
// Instead we have to do this, convert the given date to a UTC timestamp,
|
||||
// then subtract the timezone offset to create an "invalid" date (correct time&date)
|
||||
// but it still "thinks" it's in local TZ
|
||||
const timestamp = date.getTime();
|
||||
const offset = -1 * (new Date().getTimezoneOffset() * 60000);
|
||||
return new Date(timestamp - offset);
|
||||
}
|
||||
|
||||
// Lit is extremely well-typed with regard to CSS, and Storybook's `build` does not currently have a
|
||||
// coherent way of importing CSS-as-text into CSSStyleSheet. It works well when Storybook is running
|
||||
// in `dev,` but in `build` it fails. Storied components will have to map their textual CSS imports
|
||||
|
@ -12,8 +12,4 @@ export const authentikEnterpriseContext = createContext<LicenseSummary>(
|
||||
|
||||
export const authentikBrandContext = createContext<CurrentBrand>(Symbol("authentik-brand-context"));
|
||||
|
||||
export const authentikLocalStoreContext = createContext<unknown>(
|
||||
Symbol("authentik-local-store-context"),
|
||||
);
|
||||
|
||||
export default authentikConfigContext;
|
||||
|
@ -1,22 +1,23 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import type { ReactiveController } from "lit";
|
||||
import { ReactiveController, ReactiveControllerHost } from "lit";
|
||||
|
||||
import type { CurrentBrand } from "@goauthentik/api";
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;
|
||||
|
||||
export class BrandContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<AkInterface>;
|
||||
host!: ReactiveElementHost;
|
||||
|
||||
context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<AkInterface>) {
|
||||
constructor(host: ReactiveElementHost) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikBrandContext,
|
||||
|
@ -2,22 +2,23 @@ import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import type { ReactiveController } from "lit";
|
||||
import { ReactiveController, ReactiveControllerHost } from "lit";
|
||||
|
||||
import type { Config } from "@goauthentik/api";
|
||||
import { RootApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkInterface } from "./Interface";
|
||||
|
||||
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;
|
||||
|
||||
export class ConfigContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<AkInterface>;
|
||||
host!: ReactiveElementHost;
|
||||
|
||||
context!: ContextProvider<{ __context__: Config | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<AkInterface>) {
|
||||
constructor(host: ReactiveElementHost) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikConfigContext,
|
||||
|
@ -1,22 +1,23 @@
|
||||
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import type { ReactiveController } from "lit";
|
||||
import { ReactiveController, ReactiveControllerHost } from "lit";
|
||||
|
||||
import type { LicenseSummary } from "@goauthentik/api";
|
||||
import { EnterpriseApi } from "@goauthentik/api";
|
||||
|
||||
import type { AkEnterpriseInterface } from "./Interface";
|
||||
|
||||
type ReactiveElementHost = Partial<ReactiveControllerHost> & AkEnterpriseInterface;
|
||||
|
||||
export class EnterpriseContextController implements ReactiveController {
|
||||
host!: ReactiveElementHost<AkEnterpriseInterface>;
|
||||
host!: ReactiveElementHost;
|
||||
|
||||
context!: ContextProvider<{ __context__: LicenseSummary | undefined }>;
|
||||
|
||||
constructor(host: ReactiveElementHost<AkEnterpriseInterface>) {
|
||||
constructor(host: ReactiveElementHost) {
|
||||
this.host = host;
|
||||
this.context = new ContextProvider(this.host, {
|
||||
context: authentikEnterpriseContext,
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { Constructor } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { consume } from "@lit/context";
|
||||
import type { LitElement } from "lit";
|
||||
|
||||
import type { Config } from "@goauthentik/api";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Constructor<T = object> = new (...args: any[]) => T;
|
||||
|
||||
export function WithAuthentikConfig<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
subscribe = true,
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { AbstractConstructor } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { consume } from "@lit/context";
|
||||
import type { LitElement } from "lit";
|
||||
|
||||
import type { CurrentBrand } from "@goauthentik/api";
|
||||
|
||||
export function WithBrandConfig<T extends AbstractConstructor<LitElement>>(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Constructor<T = object> = abstract new (...args: any[]) => T;
|
||||
|
||||
export function WithBrandConfig<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
subscribe = true,
|
||||
) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { AbstractConstructor } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { consume } from "@lit/context";
|
||||
import type { LitElement } from "lit";
|
||||
@ -7,6 +6,9 @@ import type { LitElement } from "lit";
|
||||
import { CapabilitiesEnum } from "@goauthentik/api";
|
||||
import { Config } from "@goauthentik/api";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Constructor<T = object> = abstract new (...args: any[]) => T;
|
||||
|
||||
// Using a unique, lexically scoped, and locally static symbol as the field name for the context
|
||||
// means that it's inaccessible to any child class looking for it. It's one of the strongest privacy
|
||||
// guarantees in JavaScript.
|
||||
@ -43,7 +45,7 @@ class WCC {
|
||||
*
|
||||
*/
|
||||
|
||||
export function WithCapabilitiesConfig<T extends AbstractConstructor<LitElement>>(
|
||||
export function WithCapabilitiesConfig<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
subscribe = true,
|
||||
) {
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import { Constructor } from "@goauthentik/elements/types.js";
|
||||
|
||||
import { consume } from "@lit/context";
|
||||
import type { LitElement } from "lit";
|
||||
|
||||
import type { LicenseSummary } from "@goauthentik/api";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Constructor<T = object> = abstract new (...args: any[]) => T;
|
||||
|
||||
export function WithLicenseSummary<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
subscribe = true,
|
||||
|
@ -87,7 +87,7 @@ export class Markdown extends AKElement {
|
||||
const parsedContent = matter(this.md);
|
||||
const parsedHTML = this.converter.makeHtml(parsedContent.content);
|
||||
const replacers = [...this.defaultReplacers, ...this.replacers];
|
||||
this.docTitle = parsedContent?.data?.title ?? "";
|
||||
this.docTitle = parsedContent.data["title"] ?? "";
|
||||
this.docHtml = replacers.reduce(
|
||||
(html, replacer) => replacer(html, { path: this.meta }),
|
||||
parsedHTML,
|
||||
|
@ -13,7 +13,7 @@ import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, PropertyValues, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
@ -107,23 +107,21 @@ export class PageHeader extends WithBrandConfig(AKElement) {
|
||||
});
|
||||
}
|
||||
|
||||
setTitle(header?: string) {
|
||||
setTitle(value: string) {
|
||||
const currentIf = currentInterface();
|
||||
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||
if (currentIf === "admin") {
|
||||
title = `${msg("Admin")} - ${title}`;
|
||||
}
|
||||
// Prepend the header to the title
|
||||
if (header !== undefined && header !== "") {
|
||||
title = `${header} - ${title}`;
|
||||
}
|
||||
document.title = title;
|
||||
const title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||
document.title =
|
||||
currentIf === "admin"
|
||||
? `${msg("Admin")} - ${title}`
|
||||
: value !== ""
|
||||
? `${value} - ${title}`
|
||||
: title;
|
||||
}
|
||||
|
||||
willUpdate() {
|
||||
// Always update title, even if there's no header value set,
|
||||
// as in that case we still need to return to the generic title
|
||||
this.setTitle(this.header);
|
||||
willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("header") && this.header) {
|
||||
this.setTitle(this.header);
|
||||
}
|
||||
}
|
||||
|
||||
renderIcon(): TemplateResult {
|
||||
|
@ -2,7 +2,7 @@ import { EVENT_LOCALE_CHANGE, EVENT_LOCALE_REQUEST } from "@goauthentik/common/c
|
||||
import { customEvent } from "@goauthentik/elements/utils/customEvents";
|
||||
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import { WithBrandConfig } from "../Interface/brandProvider";
|
||||
import { initializeLocalization } from "./configureLocale";
|
||||
@ -38,6 +38,9 @@ export class LocaleContext extends LocaleContextBase {
|
||||
|
||||
setLocale: LocaleSetter;
|
||||
|
||||
@state()
|
||||
userLocale = "";
|
||||
|
||||
constructor(code = DEFAULT_LOCALE) {
|
||||
super();
|
||||
this.notifyApplication = this.notifyApplication.bind(this);
|
||||
@ -56,22 +59,30 @@ export class LocaleContext extends LocaleContextBase {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Commenting out until we can come up with a better way of separating the
|
||||
// "request user identity" with the session expiration heartbeat.
|
||||
/*
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersMeRetrieve()
|
||||
.then((user) => (this.userLocale = user?.user?.settings?.locale ?? ""))
|
||||
.catch(() => {});
|
||||
*/
|
||||
this.updateLocale();
|
||||
window.addEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler as EventListener);
|
||||
window.addEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler as EventListener);
|
||||
window.removeEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
updateLocaleHandler(ev: CustomEvent<{ locale: string }>) {
|
||||
updateLocaleHandler(_ev: Event) {
|
||||
console.debug("authentik/locale: Locale update request received.");
|
||||
this.updateLocale(ev.detail.locale);
|
||||
this.updateLocale();
|
||||
}
|
||||
|
||||
updateLocale(requestedLocale: string | undefined = undefined) {
|
||||
const localeRequest = autoDetectLanguage(requestedLocale, this.brand?.defaultLocale);
|
||||
updateLocale() {
|
||||
const localeRequest = autoDetectLanguage(this.userLocale, this.brand?.defaultLocale);
|
||||
const locale = getBestMatchLocale(localeRequest);
|
||||
if (!locale) {
|
||||
console.warn(`authentik/locale: failed to find locale for code ${localeRequest}`);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { camelToSnake, convertToSlug, dateToUTC } from "@goauthentik/common/utils";
|
||||
import { camelToSnake, convertToSlug } from "@goauthentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
|
||||
@ -104,7 +104,7 @@ export function serializeForm<T extends KeyUnknown>(
|
||||
inputElement.tagName.toLowerCase() === "input" &&
|
||||
inputElement.type === "datetime-local"
|
||||
) {
|
||||
assignValue(inputElement, dateToUTC(new Date(inputElement.valueAsNumber)), json);
|
||||
assignValue(inputElement, new Date(inputElement.valueAsNumber), json);
|
||||
} else if (
|
||||
inputElement.tagName.toLowerCase() === "input" &&
|
||||
"type" in inputElement.dataset &&
|
||||
@ -112,7 +112,7 @@ export function serializeForm<T extends KeyUnknown>(
|
||||
) {
|
||||
// Workaround for Firefox <93, since 92 and older don't support
|
||||
// datetime-local fields
|
||||
assignValue(inputElement, dateToUTC(new Date(inputElement.value)), json);
|
||||
assignValue(inputElement, new Date(inputElement.value), json);
|
||||
} else if (
|
||||
inputElement.tagName.toLowerCase() === "input" &&
|
||||
inputElement.type === "checkbox"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user