Compare commits
60 Commits
sdko/remov
...
core/objec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b96c9cf1d6 | ||
|
|
a3c50ae92a | ||
|
|
3ef36b9e9e | ||
|
|
691e173cad | ||
|
|
68a6b04749 | ||
|
|
046dbdabe2 | ||
|
|
aae1b32c61 | ||
|
|
87a95eddea | ||
|
|
71025a83ad | ||
|
|
00f0cfe6e4 | ||
|
|
b19f43c8e1 | ||
|
|
5053167a05 | ||
|
|
f4e868210d | ||
|
|
ee954d64f8 | ||
|
|
69facf209f | ||
|
|
561cd8c97b | ||
|
|
d14afe242d | ||
|
|
349a97b1df | ||
|
|
31d8ddc887 | ||
|
|
78f5d85a8b | ||
|
|
c2636d72a4 | ||
|
|
f4d6ebf024 | ||
|
|
75a62b7dca | ||
|
|
9581b90961 | ||
|
|
7dbc01c051 | ||
|
|
e188ddc2ab | ||
|
|
ae073544fe | ||
|
|
a4e0ae9ecd | ||
|
|
086510230d | ||
|
|
8d32228c90 | ||
|
|
1295e2d595 | ||
|
|
008c9fb723 | ||
|
|
9be1b618a5 | ||
|
|
a555570418 | ||
|
|
aa8463a6a8 | ||
|
|
e616cb8bac | ||
|
|
ddadbba685 | ||
|
|
b70dfe1cf0 | ||
|
|
aba6932a2d | ||
|
|
4b66289798 | ||
|
|
ba6060be77 | ||
|
|
e835418e76 | ||
|
|
e67c78ea85 | ||
|
|
5bbe099528 | ||
|
|
949b5d671a | ||
|
|
4eef34e223 | ||
|
|
e58cfd3b70 | ||
|
|
4ea9451e5f | ||
|
|
d6867895aa | ||
|
|
4727a0a69a | ||
|
|
1c226196b4 | ||
|
|
74f0def068 | ||
|
|
59afc1c7d9 | ||
|
|
6fda71763a | ||
|
|
059acf477e | ||
|
|
d5b9071fa7 | ||
|
|
607b4d6a7c | ||
|
|
09cb76bf7c | ||
|
|
a4e18ba849 | ||
|
|
d70bdc68ec |
2
.github/actions/setup/action.yml
vendored
@@ -64,7 +64,7 @@ runs:
|
||||
rustflags: ""
|
||||
- name: Setup rust dependencies
|
||||
if: ${{ contains(inputs.dependencies, 'rust') }}
|
||||
uses: taiki-e/install-action@3fa6878dc4ae603f73960271565a082bf196ab96 # v2
|
||||
uses: taiki-e/install-action@ec28e287910af896fd98e04056d31fa68607e7ad # v2
|
||||
with:
|
||||
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
|
||||
- name: Setup node (web)
|
||||
|
||||
6
.github/workflows/qa-codeql.yml
vendored
@@ -28,10 +28,10 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4.35.3
|
||||
uses: github/codeql-action/init@v4.35.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4.35.3
|
||||
uses: github/codeql-action/autobuild@v4.35.4
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4.35.3
|
||||
uses: github/codeql-action/analyze@v4.35.4
|
||||
|
||||
4
Cargo.lock
generated
@@ -3924,9 +3924,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.2"
|
||||
version = "1.52.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386"
|
||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
|
||||
@@ -97,7 +97,7 @@ sqlx = { version = "= 0.8.6", default-features = false, features = [
|
||||
tempfile = "= 3.27.0"
|
||||
thiserror = "= 2.0.18"
|
||||
time = { version = "= 0.3.47", features = ["macros"] }
|
||||
tokio = { version = "= 1.52.2", features = ["full", "tracing"] }
|
||||
tokio = { version = "= 1.52.3", features = ["full", "tracing"] }
|
||||
tokio-retry2 = "= 0.9.1"
|
||||
tokio-rustls = "= 0.26.4"
|
||||
tokio-util = { version = "= 0.7.18", features = ["full"] }
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
"""Meta API"""
|
||||
|
||||
from django.apps import apps
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from authentik.api.validation import validate
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.models import AttributesMixin
|
||||
from authentik.lib.api import Models
|
||||
from authentik.lib.utils.reflection import get_apps
|
||||
|
||||
@@ -36,12 +39,19 @@ class AppsViewSet(ViewSet):
|
||||
class ModelViewSet(ViewSet):
|
||||
"""Read-only view list all installed models"""
|
||||
|
||||
class ModelFilterSerializer(PassiveSerializer):
|
||||
filter_has_attributes = BooleanField(allow_null=True, default=None)
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@extend_schema(responses={200: AppSerializer(many=True)})
|
||||
def list(self, request: Request) -> Response:
|
||||
@extend_schema(responses={200: AppSerializer(many=True)}, parameters=[ModelFilterSerializer])
|
||||
@validate(ModelFilterSerializer, "query")
|
||||
def list(self, request: Request, query: ModelFilterSerializer) -> Response:
|
||||
"""Read-only view list all installed models"""
|
||||
data = []
|
||||
for name, label in Models.choices:
|
||||
if query.validated_data["filter_has_attributes"]:
|
||||
if not issubclass(apps.get_model(name), AttributesMixin):
|
||||
continue
|
||||
data.append({"name": name, "label": label})
|
||||
return Response(AppSerializer(data, many=True).data)
|
||||
|
||||
@@ -42,11 +42,29 @@ def validate_auth(header: bytes, format="bearer") -> str | None:
|
||||
return auth_credentials
|
||||
|
||||
|
||||
class IPCUser(AnonymousUser):
|
||||
class VirtualUser(AnonymousUser):
|
||||
is_active = True
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||
|
||||
@property
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def all_roles(self):
|
||||
return []
|
||||
|
||||
|
||||
class IPCUser(VirtualUser):
|
||||
"""'Virtual' user for IPC communication between authentik core and the authentik router"""
|
||||
|
||||
username = "authentik:system"
|
||||
is_active = True
|
||||
is_superuser = True
|
||||
|
||||
@property
|
||||
@@ -62,17 +80,6 @@ class IPCUser(AnonymousUser):
|
||||
def has_module_perms(self, module):
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def all_roles(self):
|
||||
return []
|
||||
|
||||
|
||||
class TokenAuthentication(BaseAuthentication):
|
||||
"""Token-based authentication using HTTP Bearer authentication"""
|
||||
|
||||
@@ -31,7 +31,7 @@ entries:
|
||||
slug: "%(uid)s-source"
|
||||
attrs:
|
||||
name: "%(uid)s-source"
|
||||
provider_type: entraid
|
||||
provider_type: azuread
|
||||
consumer_key: "%(uid)s"
|
||||
consumer_secret: "%(uid)s"
|
||||
icon: https://goauthentik.io/img/icon.png
|
||||
|
||||
@@ -6,6 +6,7 @@ from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.core.api.object_attributes import AttributesMixinSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.core.models import (
|
||||
@@ -14,7 +15,7 @@ from authentik.core.models import (
|
||||
)
|
||||
|
||||
|
||||
class ApplicationEntitlementSerializer(ModelSerializer):
|
||||
class ApplicationEntitlementSerializer(AttributesMixinSerializer, ModelSerializer):
|
||||
"""ApplicationEntitlement Serializer"""
|
||||
|
||||
def validate_app(self, app: Application) -> Application:
|
||||
|
||||
@@ -30,6 +30,7 @@ from authentik.api.search.fields import (
|
||||
JSONSearchField,
|
||||
)
|
||||
from authentik.api.validation import validate
|
||||
from authentik.core.api.object_attributes import AttributesMixinSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
|
||||
from authentik.core.models import Group, User
|
||||
@@ -146,7 +147,7 @@ class RelatedGroupSerializer(ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class GroupSerializer(ModelSerializer):
|
||||
class GroupSerializer(AttributesMixinSerializer, ModelSerializer):
|
||||
"""Group Serializer"""
|
||||
|
||||
attributes = JSONDictField(required=False)
|
||||
@@ -246,6 +247,25 @@ class GroupSerializer(ModelSerializer):
|
||||
)
|
||||
return superuser
|
||||
|
||||
def validate_users(self, users: list) -> list:
|
||||
"""Require add_user_to_group permission when adding new members via group PATCH."""
|
||||
request: Request = self.context.get("request", None)
|
||||
if not request:
|
||||
return users
|
||||
if not self.instance:
|
||||
return users
|
||||
# BulkManyRelatedField returns raw PKs, not model instances
|
||||
current_user_pks = set(self.instance.users.values_list("pk", flat=True))
|
||||
new_users = [u for u in users if u not in current_user_pks]
|
||||
if not new_users:
|
||||
return users
|
||||
has_perm = request.user.has_perm(
|
||||
"authentik_core.add_user_to_group"
|
||||
) or request.user.has_perm("authentik_core.add_user_to_group", self.instance)
|
||||
if not has_perm:
|
||||
raise ValidationError(_("User does not have permission to add members to this group."))
|
||||
return users
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = [
|
||||
|
||||
94
authentik/core/api/object_attributes.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField, SerializerMethodField
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.core.models import AttributesMixin, ObjectAttribute
|
||||
from authentik.lib.utils.dict import get_path_from_dict
|
||||
|
||||
|
||||
class AttributesMixinSerializer(ModelSerializer):
|
||||
|
||||
def validate(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
model = self.Meta.model
|
||||
attrs = data.get("attributes", {})
|
||||
attributes = ObjectAttribute.objects.filter(
|
||||
object_type=ContentType.objects.get_for_model(model),
|
||||
enabled=True,
|
||||
)
|
||||
for attr in attributes:
|
||||
value = get_path_from_dict(attrs, attr.key)
|
||||
attr.run_validation(value)
|
||||
return data
|
||||
|
||||
|
||||
class ContentTypeSerializer(ModelSerializer):
|
||||
app_label = CharField(read_only=True)
|
||||
model = CharField(read_only=True)
|
||||
verbose_name_plural = SerializerMethodField()
|
||||
fully_qualified_model = SerializerMethodField()
|
||||
|
||||
def get_fully_qualified_model(self, ct: ContentType) -> str:
|
||||
return f"{ct.app_label}.{ct.model}"
|
||||
|
||||
def get_verbose_name_plural(self, ct: ContentType) -> str:
|
||||
return ct.model_class()._meta.verbose_name_plural
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ("id", "app_label", "model", "verbose_name_plural", "fully_qualified_model")
|
||||
|
||||
|
||||
class ObjectAttributeSerializer(ModelSerializer):
|
||||
|
||||
object_type = CharField()
|
||||
object_type_obj = ContentTypeSerializer(read_only=True, source="object_type")
|
||||
|
||||
def validate_object_type(self, fqm: str) -> ContentType:
|
||||
app_label, _, model = fqm.partition(".")
|
||||
ct = ContentType.objects.filter(app_label=app_label, model=model).first()
|
||||
if not ct or not issubclass(ct.model_class(), AttributesMixin):
|
||||
raise ValidationError("Invalid object type")
|
||||
return ct
|
||||
|
||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
if attrs.get("is_unique") and attrs.get("is_array"):
|
||||
raise ValidationError(_("Unique cannot be enabled for arrays."))
|
||||
return super().validate(attrs)
|
||||
|
||||
class Meta:
|
||||
model = ObjectAttribute
|
||||
fields = [
|
||||
"pk",
|
||||
"object_type",
|
||||
"object_type_obj",
|
||||
"enabled",
|
||||
"created",
|
||||
"key",
|
||||
"label",
|
||||
"last_updated",
|
||||
"regex",
|
||||
"type",
|
||||
"group",
|
||||
"managed",
|
||||
"is_unique",
|
||||
"is_required",
|
||||
"is_array",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"last_updated": {"read_only": True},
|
||||
"created": {"read_only": True},
|
||||
"pk": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
class ObjectAttributeViewSet(ModelViewSet):
|
||||
serializer_class = ObjectAttributeSerializer
|
||||
queryset = ObjectAttribute.objects.all()
|
||||
filterset_fields = ["object_type__model", "object_type__app_label", "enabled"]
|
||||
search_fields = ["key", "label", "group", "object_type__model", "object_type__app_label"]
|
||||
ordering = ["key"]
|
||||
@@ -65,6 +65,7 @@ from authentik.api.search.fields import (
|
||||
from authentik.api.validation import validate
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.api.object_attributes import AttributesMixinSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import (
|
||||
JSONDictField,
|
||||
@@ -134,7 +135,7 @@ class PartialGroupSerializer(ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class UserSerializer(ModelSerializer):
|
||||
class UserSerializer(AttributesMixinSerializer, ModelSerializer):
|
||||
"""User Serializer"""
|
||||
|
||||
is_superuser = SerializerMethodField()
|
||||
@@ -297,6 +298,36 @@ class UserSerializer(ModelSerializer):
|
||||
raise ValidationError(_("Setting a user to internal service account is not allowed."))
|
||||
return user_type
|
||||
|
||||
def validate_groups(self, groups: list) -> list:
|
||||
"""Require enable_group_superuser permission when adding a user to a superuser group."""
|
||||
request: Request = self.context.get("request", None)
|
||||
if not request:
|
||||
return groups
|
||||
current_groups = set(self.instance.groups.all()) if self.instance else set()
|
||||
for group in groups:
|
||||
if not group.is_superuser:
|
||||
continue
|
||||
if group in current_groups:
|
||||
continue
|
||||
if not request.user.has_perm("authentik_core.enable_group_superuser"):
|
||||
raise ValidationError(
|
||||
_("User does not have permission to add members to a superuser group.")
|
||||
)
|
||||
return groups
|
||||
|
||||
def validate_roles(self, roles: list) -> list:
|
||||
"""Require change_role permission when assigning new roles to a user."""
|
||||
request: Request = self.context.get("request", None)
|
||||
if not request:
|
||||
return roles
|
||||
current_roles = set(self.instance.roles.all()) if self.instance else set()
|
||||
new_roles = [r for r in roles if r not in current_roles]
|
||||
if not new_roles:
|
||||
return roles
|
||||
if not request.user.has_perm("authentik_rbac.change_role"):
|
||||
raise ValidationError(_("User does not have permission to assign roles."))
|
||||
return roles
|
||||
|
||||
def validate(self, attrs: dict) -> dict:
|
||||
if self.instance and self.instance.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
|
||||
raise ValidationError(_("Can't modify internal service account users"))
|
||||
|
||||
62
authentik/core/migrations/0058_objectattribute.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# Generated by Django 5.2.13 on 2026-04-11 18:35
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0057_remove_user_groups_remove_user_user_permissions_and_more"),
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ObjectAttribute",
|
||||
fields=[
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("last_updated", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"managed",
|
||||
models.TextField(
|
||||
default=None,
|
||||
help_text="Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
|
||||
null=True,
|
||||
unique=True,
|
||||
verbose_name="Managed by authentik",
|
||||
),
|
||||
),
|
||||
(
|
||||
"attribute_id",
|
||||
models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
|
||||
),
|
||||
("enabled", models.BooleanField(default=True)),
|
||||
("label", models.TextField()),
|
||||
("group", models.TextField(blank=True)),
|
||||
("key", models.TextField()),
|
||||
(
|
||||
"type",
|
||||
models.TextField(
|
||||
choices=[("text", "Text"), ("number", "Number"), ("boolean", "Boolean")]
|
||||
),
|
||||
),
|
||||
("is_unique", models.BooleanField(default=False)),
|
||||
("is_required", models.BooleanField(default=False)),
|
||||
("regex", models.TextField(blank=True)),
|
||||
("is_array", models.BooleanField(default=False)),
|
||||
(
|
||||
"object_type",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Object Attribute",
|
||||
"verbose_name_plural": "Object Attributes",
|
||||
"unique_together": {("object_type", "key", "enabled")},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -13,6 +13,7 @@ from deepmerge import always_merger
|
||||
from django.contrib.auth.hashers import check_password, identify_hasher
|
||||
from django.contrib.auth.models import AbstractUser, Permission
|
||||
from django.contrib.auth.models import UserManager as DjangoUserManager
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.sessions.base_session import AbstractBaseSession
|
||||
from django.core.validators import validate_slug
|
||||
from django.db import models
|
||||
@@ -26,6 +27,7 @@ from guardian.models import RoleModelPermission, RoleObjectPermission
|
||||
from model_utils.managers import InheritanceManager
|
||||
from psqlextra.indexes import UniqueIndex
|
||||
from psqlextra.models import PostgresMaterializedViewModel
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.serializers import Serializer
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
@@ -1392,3 +1394,61 @@ class AuthenticatedSession(SerializerModel):
|
||||
session=Session.objects.filter(session_key=request.session.session_key).first(),
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
class ObjectAttribute(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
||||
"""User-defined schema for models' `attributes` JSON field."""
|
||||
|
||||
class AttributeType(models.TextChoices):
|
||||
TEXT = "text"
|
||||
NUMBER = "number"
|
||||
BOOLEAN = "boolean"
|
||||
|
||||
attribute_id = models.UUIDField(default=uuid4, primary_key=True)
|
||||
|
||||
enabled = models.BooleanField(default=True)
|
||||
|
||||
object_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
label = models.TextField()
|
||||
group = models.TextField(blank=True)
|
||||
key = models.TextField()
|
||||
|
||||
type = models.TextField(choices=AttributeType.choices)
|
||||
is_unique = models.BooleanField(default=False)
|
||||
is_required = models.BooleanField(default=False)
|
||||
regex = models.TextField(blank=True)
|
||||
is_array = models.BooleanField(default=False)
|
||||
|
||||
def run_validation(self, value: Any) -> None:
|
||||
err_key = f"attributes_{self.key.replace(".", "_")}"
|
||||
if self.is_required and value is None:
|
||||
raise ValidationError({err_key: _("This field is required")})
|
||||
if self.is_array:
|
||||
if not isinstance(value, (list, tuple)):
|
||||
raise ValidationError({err_key: _("Value must be an array.")})
|
||||
if self.regex != "":
|
||||
if not all(re.fullmatch(self.regex, v) for v in value):
|
||||
raise ValidationError({err_key: _("Value does not match configured pattern.")})
|
||||
else:
|
||||
if self.is_unique:
|
||||
model: type[models.Model] = self.object_type.model_class()
|
||||
lookup_key = f"attributes__{self.key.replace(".", "__")}"
|
||||
if model.objects.filter(**{lookup_key: value}).exists():
|
||||
raise ValidationError({err_key: _("Value is not unique.")})
|
||||
if self.regex != "":
|
||||
if not re.fullmatch(self.regex, value):
|
||||
raise ValidationError({err_key: _("Value does not match configured pattern.")})
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
from authentik.core.api.object_attributes import ObjectAttributeSerializer
|
||||
|
||||
return ObjectAttributeSerializer
|
||||
|
||||
def __str__(self):
|
||||
return f"Object attribute '{self.key}' for content type {self.object_type_id}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Object Attribute")
|
||||
verbose_name_plural = _("Object Attributes")
|
||||
unique_together = (("object_type", "key", "enabled"),)
|
||||
|
||||
@@ -158,3 +158,58 @@ class TestGroupsAPI(APITestCase):
|
||||
data={"name": generate_id(), "is_superuser": True},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
||||
def test_patch_users_no_perm(self):
|
||||
"""PATCH group with new users without add_user_to_group must be rejected."""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"users": [self.user.pk]},
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
|
||||
def test_patch_users_with_global_perm(self):
|
||||
"""PATCH group with new users with global add_user_to_group must succeed."""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.add_user_to_group")
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"users": [self.user.pk]},
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_patch_users_with_obj_perm(self):
|
||||
"""PATCH group with new users with object-level add_user_to_group must succeed."""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.add_user_to_group", group)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"users": [self.user.pk]},
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_patch_existing_users_no_perm(self):
|
||||
"""PATCH group keeping existing membership without add_user_to_group must succeed."""
|
||||
group = Group.objects.create(name=generate_id())
|
||||
group.users.add(self.user)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.view_group", group)
|
||||
self.login_user.assign_perms_to_managed_role("authentik_core.change_group", group)
|
||||
self.client.force_login(self.login_user)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:group-detail", kwargs={"pk": group.pk}),
|
||||
data={"users": [self.user.pk]},
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
198
authentik/core/tests/test_object_attributes_api.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""Test object attributes API"""
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.api.object_attributes import ContentType
|
||||
from authentik.core.models import ObjectAttribute, User
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_user
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
|
||||
class TestObjectAttributesAPI(APITestCase):
|
||||
"""Test object attributes API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_create(self):
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:objectattribute-list"),
|
||||
data={
|
||||
"object_type": "authentik_core.user",
|
||||
"enabled": False,
|
||||
"key": "employeeNumber",
|
||||
"label": "Employee Number",
|
||||
"type": "text",
|
||||
"group": "Employee",
|
||||
"is_unique": False,
|
||||
"is_required": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
attr = ObjectAttribute.objects.filter(key="employeeNumber").first()
|
||||
self.assertIsNotNone(attr)
|
||||
|
||||
def test_create_invalid(self):
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:objectattribute-list"),
|
||||
data={
|
||||
"object_type": "authentik_core.objectattribute",
|
||||
"enabled": False,
|
||||
"key": "employeeNumber",
|
||||
"label": "Employee Number",
|
||||
"type": "text",
|
||||
"group": "Employee",
|
||||
"is_unique": False,
|
||||
"is_required": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(res.content, {"object_type": ["Invalid object type"]})
|
||||
|
||||
def test_create_invalid_array_unique(self):
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:objectattribute-list"),
|
||||
data={
|
||||
"object_type": "authentik_core.user",
|
||||
"enabled": False,
|
||||
"key": "employeeNumber",
|
||||
"label": "Employee Number",
|
||||
"type": "text",
|
||||
"group": "Employee",
|
||||
"is_unique": True,
|
||||
"is_required": False,
|
||||
"is_array": True,
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content, {"non_field_errors": ["Unique cannot be enabled for arrays."]}
|
||||
)
|
||||
|
||||
def test_update(self):
|
||||
attr = ObjectAttribute.objects.create(
|
||||
object_type=ContentType.objects.get_for_model(User),
|
||||
label="foo",
|
||||
key=generate_id(),
|
||||
type=ObjectAttribute.AttributeType.TEXT,
|
||||
)
|
||||
res = self.client.put(
|
||||
reverse("authentik_api:objectattribute-detail", kwargs={"pk": attr.pk}),
|
||||
data={
|
||||
"object_type": "authentik_core.user",
|
||||
"enabled": False,
|
||||
"key": attr.key,
|
||||
"label": "Employee Number",
|
||||
"type": "text",
|
||||
"group": "Employee",
|
||||
"is_unique": False,
|
||||
"is_required": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
attr.refresh_from_db()
|
||||
self.assertEqual(attr.label, "Employee Number")
|
||||
|
||||
def test_user_attrib_validation_required(self):
|
||||
attr = ObjectAttribute.objects.create(
|
||||
object_type=ContentType.objects.get_for_model(User),
|
||||
label="foo",
|
||||
key=generate_id(),
|
||||
type=ObjectAttribute.AttributeType.TEXT,
|
||||
is_required=True,
|
||||
)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.user.pk}),
|
||||
data={
|
||||
"attributes": {},
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(res.content, {f"attributes_{attr.key}": ["This field is required"]})
|
||||
|
||||
def test_user_attrib_validation_unique(self):
|
||||
attr = ObjectAttribute.objects.create(
|
||||
object_type=ContentType.objects.get_for_model(User),
|
||||
label="foo",
|
||||
key=generate_id(),
|
||||
type=ObjectAttribute.AttributeType.TEXT,
|
||||
is_unique=True,
|
||||
)
|
||||
other_user = create_test_user()
|
||||
other_user.attributes[attr.key] = "foo"
|
||||
other_user.save()
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.user.pk}),
|
||||
data={
|
||||
"attributes": {attr.key: "foo"},
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(res.content, {f"attributes_{attr.key}": ["Value is not unique."]})
|
||||
|
||||
def test_user_attrib_validation_regex(self):
|
||||
attr = ObjectAttribute.objects.create(
|
||||
object_type=ContentType.objects.get_for_model(User),
|
||||
label="foo",
|
||||
key=generate_id(),
|
||||
type=ObjectAttribute.AttributeType.TEXT,
|
||||
regex="bar",
|
||||
)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.user.pk}),
|
||||
data={
|
||||
"attributes": {attr.key: "foo"},
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content, {f"attributes_{attr.key}": ["Value does not match configured pattern."]}
|
||||
)
|
||||
|
||||
def test_user_attrib_validation_array(self):
|
||||
attr = ObjectAttribute.objects.create(
|
||||
object_type=ContentType.objects.get_for_model(User),
|
||||
label="foo",
|
||||
key=generate_id(),
|
||||
type=ObjectAttribute.AttributeType.TEXT,
|
||||
is_array=True,
|
||||
)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.user.pk}),
|
||||
data={
|
||||
"attributes": {attr.key: "foo"},
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(res.content, {f"attributes_{attr.key}": ["Value must be an array."]})
|
||||
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.user.pk}),
|
||||
data={
|
||||
"attributes": {attr.key: ["foo"]},
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_user_attrib_validation_array_regex(self):
|
||||
attr = ObjectAttribute.objects.create(
|
||||
object_type=ContentType.objects.get_for_model(User),
|
||||
label="foo",
|
||||
key=generate_id(),
|
||||
type=ObjectAttribute.AttributeType.TEXT,
|
||||
is_array=True,
|
||||
regex="bar",
|
||||
)
|
||||
res = self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.user.pk}),
|
||||
data={
|
||||
"attributes": {attr.key: ["foo"]},
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content, {f"attributes_{attr.key}": ["Value does not match configured pattern."]}
|
||||
)
|
||||
@@ -12,6 +12,7 @@ from authentik.brands.models import Brand
|
||||
from authentik.core.models import (
|
||||
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
||||
AuthenticatedSession,
|
||||
Group,
|
||||
Session,
|
||||
Token,
|
||||
User,
|
||||
@@ -25,6 +26,7 @@ from authentik.core.tests.utils import (
|
||||
)
|
||||
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.rbac.models import Role
|
||||
from authentik.stages.email.models import EmailStage
|
||||
|
||||
INVALID_PASSWORD_HASH = "not-a-valid-hash"
|
||||
@@ -939,3 +941,79 @@ class TestUsersAPI(APITestCase):
|
||||
self.assertIn(user2.pk, pks)
|
||||
# Verify user2 comes before user1 in descending order
|
||||
self.assertLess(pks.index(user2.pk), pks.index(user1.pk))
|
||||
|
||||
|
||||
class TestUsersAPIGroupRoleValidation(APITestCase):
|
||||
"""Test that PATCH /api/v3/core/users/{pk}/ enforces group and role permission checks."""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.actor = create_test_user()
|
||||
self.target = create_test_user()
|
||||
|
||||
def _patch(self, data: dict):
|
||||
self.client.force_login(self.actor)
|
||||
return self.client.patch(
|
||||
reverse("authentik_api:user-detail", kwargs={"pk": self.target.pk}),
|
||||
data=data,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
def test_patch_superuser_group_no_perm(self):
|
||||
"""Assigning a superuser group without enable_group_superuser must be rejected."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||
res = self._patch({"groups": [str(group.pk)]})
|
||||
self.assertEqual(res.status_code, 400)
|
||||
|
||||
def test_patch_superuser_group_with_perm(self):
|
||||
"""Assigning a superuser group with enable_group_superuser must succeed."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.enable_group_superuser")
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||
res = self._patch({"groups": [str(group.pk)]})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_patch_non_superuser_group_no_perm(self):
|
||||
"""Assigning a non-superuser group without special permission must succeed."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=False)
|
||||
res = self._patch({"groups": [str(group.pk)]})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_patch_existing_superuser_group_no_perm(self):
|
||||
"""Keeping an existing superuser group membership without the permission must succeed."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
group = Group.objects.create(name=generate_id(), is_superuser=True)
|
||||
self.target.groups.add(group)
|
||||
res = self._patch({"groups": [str(group.pk)]})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_patch_role_no_perm(self):
|
||||
"""Assigning a new role without change_role must be rejected."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
role = Role.objects.create(name=generate_id())
|
||||
res = self._patch({"roles": [str(role.pk)]})
|
||||
self.assertEqual(res.status_code, 400)
|
||||
|
||||
def test_patch_role_with_perm(self):
|
||||
"""Assigning a new role with change_role must succeed."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
self.actor.assign_perms_to_managed_role("authentik_rbac.change_role")
|
||||
role = Role.objects.create(name=generate_id())
|
||||
res = self._patch({"roles": [str(role.pk)]})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_patch_existing_role_no_perm(self):
|
||||
"""Keeping an existing role without change_role must succeed."""
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.view_user")
|
||||
self.actor.assign_perms_to_managed_role("authentik_core.change_user", self.target)
|
||||
role = Role.objects.create(name=generate_id())
|
||||
self.target.roles.add(role)
|
||||
res = self._patch({"roles": [str(role.pk)]})
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
@@ -8,6 +8,7 @@ from authentik.core.api.applications import ApplicationViewSet
|
||||
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
||||
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
||||
from authentik.core.api.groups import GroupViewSet
|
||||
from authentik.core.api.object_attributes import ObjectAttributeViewSet
|
||||
from authentik.core.api.property_mappings import PropertyMappingViewSet
|
||||
from authentik.core.api.providers import ProviderViewSet
|
||||
from authentik.core.api.sources import (
|
||||
@@ -87,6 +88,7 @@ api_urlpatterns = [
|
||||
("core/groups", GroupViewSet),
|
||||
("core/users", UserViewSet),
|
||||
("core/tokens", TokenViewSet),
|
||||
("core/object_attributes", ObjectAttributeViewSet),
|
||||
("sources/all", SourceViewSet),
|
||||
("sources/user_connections/all", UserSourceConnectionViewSet),
|
||||
("sources/group_connections/all", GroupSourceConnectionViewSet),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.object_attributes import AttributesMixinSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.endpoints.models import DeviceAccessGroup
|
||||
|
||||
|
||||
class DeviceAccessGroupSerializer(ModelSerializer):
|
||||
class DeviceAccessGroupSerializer(AttributesMixinSerializer, ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = DeviceAccessGroup
|
||||
|
||||
@@ -7,7 +7,7 @@ from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_sche
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.fields import ChoiceField
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.relations import PrimaryKeyRelatedField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@@ -44,7 +44,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_ME
|
||||
|
||||
|
||||
class AgentConnectorSerializer(ConnectorSerializer):
|
||||
|
||||
class Meta(ConnectorSerializer.Meta):
|
||||
model = AgentConnector
|
||||
fields = ConnectorSerializer.Meta.fields + [
|
||||
@@ -63,7 +62,6 @@ class AgentConnectorSerializer(ConnectorSerializer):
|
||||
|
||||
|
||||
class MDMConfigSerializer(PassiveSerializer):
|
||||
|
||||
platform = ChoiceField(choices=OSFamily.choices)
|
||||
enrollment_token = PrimaryKeyRelatedField(
|
||||
queryset=EnrollmentToken.objects.including_expired().all()
|
||||
@@ -89,7 +87,6 @@ class AgentConnectorViewSet(
|
||||
UsedByMixin,
|
||||
ModelViewSet,
|
||||
):
|
||||
|
||||
queryset = AgentConnector.objects.all()
|
||||
serializer_class = AgentConnectorSerializer
|
||||
search_fields = ["name"]
|
||||
@@ -121,6 +118,8 @@ class AgentConnectorViewSet(
|
||||
methods=["POST"],
|
||||
detail=False,
|
||||
authentication_classes=[AgentEnrollmentAuth],
|
||||
# Permissions are handled via AgentEnrollmentAuth
|
||||
permission_classes=[AllowAny],
|
||||
)
|
||||
def enroll(self, request: Request):
|
||||
token: EnrollmentToken = request.auth
|
||||
@@ -151,7 +150,13 @@ class AgentConnectorViewSet(
|
||||
request=OpenApiTypes.NONE,
|
||||
responses=AgentConfigSerializer(),
|
||||
)
|
||||
@action(methods=["GET"], detail=False, authentication_classes=[AgentAuth])
|
||||
@action(
|
||||
methods=["GET"],
|
||||
detail=False,
|
||||
authentication_classes=[AgentAuth],
|
||||
# Permissions are handled via AgentAuth
|
||||
permission_classes=[AllowAny],
|
||||
)
|
||||
def agent_config(self, request: Request):
|
||||
token: DeviceToken = request.auth
|
||||
connector: AgentConnector = token.device.connector.agentconnector
|
||||
@@ -165,7 +170,13 @@ class AgentConnectorViewSet(
|
||||
request=DeviceFacts(),
|
||||
responses={204: OpenApiResponse(description="Successfully checked in")},
|
||||
)
|
||||
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
|
||||
@action(
|
||||
methods=["POST"],
|
||||
detail=False,
|
||||
authentication_classes=[AgentAuth],
|
||||
# Permissions are handled via AgentAuth
|
||||
permission_classes=[AllowAny],
|
||||
)
|
||||
def check_in(self, request: Request):
|
||||
token: DeviceToken = request.auth
|
||||
data = DeviceFacts(data=request.data)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from django.db.models import Model
|
||||
from django.http import HttpRequest
|
||||
from django.utils.timezone import now
|
||||
from drf_spectacular.extensions import OpenApiAuthenticationExtension
|
||||
@@ -9,7 +10,7 @@ from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.request import Request
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.authentication import IPCUser, validate_auth
|
||||
from authentik.api.authentication import VirtualUser, validate_auth
|
||||
from authentik.core.middleware import CTX_AUTH_VIA
|
||||
from authentik.core.models import User
|
||||
from authentik.crypto.apps import MANAGED_KEY
|
||||
@@ -25,9 +26,18 @@ LOGGER = get_logger()
|
||||
PLATFORM_ISSUER = "goauthentik.io/platform"
|
||||
|
||||
|
||||
class DeviceUser(IPCUser):
|
||||
class DeviceUser(VirtualUser):
|
||||
|
||||
username = "authentik:endpoints:device"
|
||||
|
||||
def has_perm(self, perm: str, obj: Model | None = None) -> bool:
|
||||
if perm in [
|
||||
"authentik_core.view_user",
|
||||
"authentik_core.view_group",
|
||||
]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class AgentEnrollmentAuth(BaseAuthentication):
|
||||
|
||||
|
||||
@@ -223,3 +223,17 @@ class TestAgentAPI(APITestCase):
|
||||
data={"platform": OSFamily.macOS, "enrollment_token": self.token.pk},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_users_list(self):
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:user-list"),
|
||||
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_other_api_forbidden(self):
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:application-list"),
|
||||
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.urls import reverse
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from structlog.stdlib import get_logger
|
||||
@@ -25,7 +26,13 @@ class AgentConnectorViewSetMixin:
|
||||
request=OpenApiTypes.NONE,
|
||||
responses=AgentAuthenticationResponse(),
|
||||
)
|
||||
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
|
||||
@action(
|
||||
methods=["POST"],
|
||||
detail=False,
|
||||
authentication_classes=[AgentAuth],
|
||||
# Permissions are handled via AgentAuth
|
||||
permission_classes=[AllowAny],
|
||||
)
|
||||
@enterprise_action
|
||||
def auth_ia(self, request: Request) -> Response:
|
||||
token: DeviceToken = request.auth
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -55,7 +56,9 @@ class SignInRequest:
|
||||
_, provider = req.get_app_provider()
|
||||
if not req.wreply:
|
||||
req.wreply = provider.acs_url
|
||||
if not req.wreply.startswith(provider.acs_url):
|
||||
reply = urlparse(req.wreply)
|
||||
configured = urlparse(provider.acs_url)
|
||||
if not (reply[:2] == configured[:2] and reply.path.startswith(configured.path)):
|
||||
raise ValueError("Invalid wreply")
|
||||
return req
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -32,7 +33,9 @@ class SignOutRequest:
|
||||
_, provider = req.get_app_provider()
|
||||
if not req.wreply:
|
||||
req.wreply = provider.acs_url
|
||||
if not req.wreply.startswith(provider.acs_url):
|
||||
reply = urlparse(req.wreply)
|
||||
configured = urlparse(provider.acs_url)
|
||||
if not (reply[:2] == configured[:2] and reply.path.startswith(configured.path)):
|
||||
raise ValueError("Invalid wreply")
|
||||
return req
|
||||
|
||||
|
||||
@@ -27,12 +27,27 @@ class TestWSFedSignIn(TestCase):
|
||||
name=generate_id(),
|
||||
authorization_flow=self.flow,
|
||||
signing_kp=self.cert,
|
||||
acs_url="https://t.goauthentik.io",
|
||||
audience="foo",
|
||||
)
|
||||
self.app = Application.objects.create(
|
||||
name=generate_id(), slug=generate_id(), provider=self.provider
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_wreply(self):
|
||||
request = self.factory.get(
|
||||
"/?wreply=https://t.goauthentik.io/foo&wa=wsignin1.0&wtrealm=foo",
|
||||
user=get_anonymous_user(),
|
||||
)
|
||||
SignInRequest.parse(request)
|
||||
with self.assertRaises(ValueError):
|
||||
request = self.factory.get(
|
||||
"/?wreply=https://t.goauthentik.io.invalid.com&wa=wsignin1.0&wtrealm=foo",
|
||||
user=get_anonymous_user(),
|
||||
)
|
||||
SignInRequest.parse(request)
|
||||
|
||||
def test_token_gen(self):
|
||||
request = self.factory.get("/", user=get_anonymous_user())
|
||||
proc = SignInProcessor(
|
||||
|
||||
@@ -4,13 +4,13 @@ from django.urls import reverse
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import mixins
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, SerializerMethodField
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.core.api.groups import PartialUserSerializer
|
||||
from authentik.core.api.object_attributes import ContentTypeSerializer
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||
from authentik.enterprise.reports.models import DataExport
|
||||
@@ -18,19 +18,6 @@ from authentik.enterprise.reports.tasks import generate_export
|
||||
from authentik.rbac.permissions import HasPermission
|
||||
|
||||
|
||||
class ContentTypeSerializer(ModelSerializer):
|
||||
app_label = CharField(read_only=True)
|
||||
model = CharField(read_only=True)
|
||||
verbose_name_plural = SerializerMethodField()
|
||||
|
||||
def get_verbose_name_plural(self, ct: ContentType) -> str:
|
||||
return ct.model_class()._meta.verbose_name_plural
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ("id", "app_label", "model", "verbose_name_plural")
|
||||
|
||||
|
||||
class DataExportSerializer(EnterpriseRequiredMixin, ModelSerializer):
|
||||
requested_by = PartialUserSerializer(read_only=True)
|
||||
content_type = ContentTypeSerializer(read_only=True)
|
||||
|
||||
@@ -7,6 +7,13 @@ from authentik.lib.config import CONFIG, ENV_PREFIX
|
||||
from authentik.lib.utils.time import fqdn_rand
|
||||
from authentik.tasks.schedules.common import ScheduleSpec
|
||||
|
||||
# TODO: Deprecated metric - remove in 2024.2 or later
|
||||
GAUGE_TASKS = Gauge(
|
||||
"authentik_system_tasks",
|
||||
"System tasks and their status",
|
||||
["tenant", "task_name", "task_uid", "status"],
|
||||
)
|
||||
|
||||
SYSTEM_TASK_TIME = Histogram(
|
||||
"authentik_system_tasks_time_seconds",
|
||||
"Runtime of system tasks",
|
||||
|
||||
@@ -49,6 +49,15 @@ class LogEventSerializer(PassiveSerializer):
|
||||
event = CharField()
|
||||
attributes = DictField()
|
||||
|
||||
# TODO(2024.6?): This is a migration helper to return a correct API response for logs that
|
||||
# have been saved in an older format (mostly just list[str] with just the messages)
|
||||
def to_representation(self, instance):
|
||||
if isinstance(instance, str):
|
||||
instance = LogEvent(instance, "", "")
|
||||
elif isinstance(instance, list):
|
||||
instance = [LogEvent(x, "", "") for x in instance]
|
||||
return super().to_representation(instance)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def capture_logs(log_default_output=True) -> Generator[list[LogEvent]]:
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from cachetools import TLRUCache, cached
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models import Model
|
||||
from django.http import HttpRequest
|
||||
from django.utils.text import slugify
|
||||
from django.utils.timezone import now
|
||||
@@ -22,6 +23,7 @@ from structlog.stdlib import get_logger
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import Event
|
||||
from authentik.lib.expression.exceptions import ControlFlowException
|
||||
from authentik.lib.utils.dict import get_path_from_dict
|
||||
from authentik.lib.utils.email import normalize_addresses
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
@@ -70,6 +72,7 @@ class BaseEvaluator:
|
||||
"ak_send_email": self.expr_send_email,
|
||||
"ak_user_by": BaseEvaluator.expr_user_by,
|
||||
"ak_user_has_authenticator": BaseEvaluator.expr_func_user_has_authenticator,
|
||||
"ak_obj_attr": BaseEvaluator.expr_obj_attr,
|
||||
"ip_address": ip_address,
|
||||
"ip_network": ip_network,
|
||||
"list_flatten": BaseEvaluator.expr_flatten,
|
||||
@@ -162,6 +165,16 @@ class BaseEvaluator:
|
||||
return False
|
||||
return len(list(user_devices)) > 0
|
||||
|
||||
@staticmethod
|
||||
def expr_obj_attr(obj: Model, attr_key: str, fallback: str) -> Any:
|
||||
"""Get an attribute of the given object if set by its dotted path, otherwise
|
||||
return fallback value."""
|
||||
attrs = getattr(obj, "attributes", {})
|
||||
value = get_path_from_dict(attrs, attr_key)
|
||||
if value is None and fallback:
|
||||
return getattr(obj, fallback)
|
||||
return value
|
||||
|
||||
def expr_event_create(self, action: str, **kwargs):
|
||||
"""Create event with supplied data and try to extract as much relevant data
|
||||
from the context"""
|
||||
|
||||
@@ -9,10 +9,10 @@ from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.api.utils import MetaNameSerializer, ModelSerializer
|
||||
from authentik.providers.oauth2.api.providers import OAuth2ProviderSerializer
|
||||
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class ExpiringBaseGrantModelSerializer(ModelSerializer, MetaNameSerializer):
|
||||
"""Serializer for BaseGrantModel and ExpiringBaseGrant"""
|
||||
|
||||
user = UserSerializer()
|
||||
provider = OAuth2ProviderSerializer()
|
||||
provider = ProviderSerializer()
|
||||
scope = ListField(child=CharField())
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -10,6 +10,7 @@ LOGGER = get_logger()
|
||||
|
||||
AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
||||
"authentik.sources.oauth.types.apple",
|
||||
"authentik.sources.oauth.types.azure_ad",
|
||||
"authentik.sources.oauth.types.discord",
|
||||
"authentik.sources.oauth.types.entra_id",
|
||||
"authentik.sources.oauth.types.facebook",
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.2.14 on 2026-05-09 19:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_azuread_to_entraid(apps, schema_editor):
|
||||
OAuthSource = apps.get_model("authentik_sources_oauth", "OAuthSource")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
OAuthSource.objects.using(db_alias).filter(provider_type="azuread").update(
|
||||
provider_type="entraid"
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_oauth", "0013_useroauthsourceconnection_refresh_token"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_azuread_to_entraid, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -251,6 +251,17 @@ class GoogleOAuthSource(CreatableType, OAuthSource):
|
||||
verbose_name_plural = _("Google OAuth Sources")
|
||||
|
||||
|
||||
class AzureADOAuthSource(CreatableType, OAuthSource):
|
||||
"""(Deprecated) Social Login using Azure AD."""
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _("Azure AD OAuth Source")
|
||||
verbose_name_plural = _("Azure AD OAuth Sources")
|
||||
|
||||
|
||||
# TODO: When removing this, add a migration for OAuthSource that sets
|
||||
# provider_type to `entraid` if it is currently `azuread`
|
||||
class EntraIDOAuthSource(CreatableType, OAuthSource):
|
||||
"""Social Login using Entra ID."""
|
||||
|
||||
|
||||
17
authentik/sources/oauth/types/azure_ad.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""AzureAD OAuth2 Views"""
|
||||
|
||||
from authentik.sources.oauth.types.entra_id import EntraIDType
|
||||
from authentik.sources.oauth.types.registry import registry
|
||||
|
||||
# TODO: When removing this, add a migration for OAuthSource that sets
|
||||
# provider_type to `entraid` if it is currently `azuread`
|
||||
|
||||
|
||||
@registry.register()
|
||||
class AzureADType(EntraIDType):
|
||||
"""Azure AD Type definition"""
|
||||
|
||||
verbose_name = "Azure AD"
|
||||
name = "azuread"
|
||||
|
||||
urls_customizable = True
|
||||
@@ -1,6 +1,7 @@
|
||||
"""authentik saml source processor"""
|
||||
|
||||
from base64 import b64decode
|
||||
from datetime import UTC, datetime
|
||||
from time import mktime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -40,6 +41,7 @@ from authentik.sources.saml.exceptions import (
|
||||
InvalidSignature,
|
||||
MismatchedRequestID,
|
||||
MissingSAMLResponse,
|
||||
SAMLException,
|
||||
UnsupportedNameIDFormat,
|
||||
)
|
||||
from authentik.sources.saml.models import (
|
||||
@@ -95,6 +97,7 @@ class ResponseProcessor:
|
||||
|
||||
self._verify_request_id()
|
||||
self._verify_status()
|
||||
self._verify_conditions()
|
||||
|
||||
def _decrypt_response(self):
|
||||
"""Decrypt SAMLResponse EncryptedAssertion Element"""
|
||||
@@ -126,6 +129,20 @@ class ResponseProcessor:
|
||||
)
|
||||
self._assertion = decrypted_assertion
|
||||
|
||||
def _verify_conditions(self):
|
||||
conditions = self.get_assertion().find(f"{{{NS_SAML_ASSERTION}}}Conditions")
|
||||
if conditions is None:
|
||||
return
|
||||
_now = now()
|
||||
before = conditions.attrib.get("NotBefore")
|
||||
if before:
|
||||
if datetime.fromisoformat(before).replace(tzinfo=UTC) > _now:
|
||||
raise SAMLException("Assertion is not valid yet or expired.")
|
||||
on_or_after = conditions.attrib.get("NotOnOrAfter")
|
||||
if on_or_after:
|
||||
if datetime.fromisoformat(on_or_after).replace(tzinfo=UTC) < _now:
|
||||
raise SAMLException("Assertion is not valid yet or expired.")
|
||||
|
||||
def _verify_signature(self, signature_node: _Element):
|
||||
"""Verify a single signature node"""
|
||||
xmlsec.tree.add_ids(self._root, ["ID"])
|
||||
@@ -215,10 +232,9 @@ class ResponseProcessor:
|
||||
user has an attribute that refers to our Source for cleanup. The user is also deleted
|
||||
on logout and periodically."""
|
||||
# Create a temporary User
|
||||
name_id = self._get_name_id()
|
||||
username = name_id.text
|
||||
name_id_el, name_id = self._get_name_id()
|
||||
# trim username to ensure it is max 150 chars
|
||||
username = f"ak-{username[: USERNAME_MAX_LENGTH - 14]}-transient"
|
||||
username = f"ak-{name_id[: USERNAME_MAX_LENGTH - 14]}-transient"
|
||||
expiry = mktime(
|
||||
(now() + timedelta_from_string(self._source.temporary_user_delete_after)).timetuple()
|
||||
)
|
||||
@@ -234,20 +250,18 @@ class ResponseProcessor:
|
||||
},
|
||||
path=self._source.get_user_path(),
|
||||
)
|
||||
LOGGER.debug("Created temporary user for NameID Transient", username=name_id.text)
|
||||
LOGGER.debug("Created temporary user for NameID Transient", username=name_id)
|
||||
user.set_unusable_password()
|
||||
user.save()
|
||||
UserSAMLSourceConnection.objects.create(
|
||||
source=self._source, user=user, identifier=name_id.text
|
||||
)
|
||||
UserSAMLSourceConnection.objects.create(source=self._source, user=user, identifier=name_id)
|
||||
return SAMLSourceFlowManager(
|
||||
source=self._source,
|
||||
request=self._http_request,
|
||||
identifier=str(name_id.text),
|
||||
identifier=str(name_id),
|
||||
user_info={
|
||||
"root": self._root,
|
||||
"assertion": self.get_assertion(),
|
||||
"name_id": name_id,
|
||||
"name_id": name_id_el,
|
||||
},
|
||||
policy_context={},
|
||||
)
|
||||
@@ -258,7 +272,7 @@ class ResponseProcessor:
|
||||
return self._assertion
|
||||
return self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
|
||||
|
||||
def _get_name_id(self) -> Element:
|
||||
def _get_name_id(self) -> tuple[Element, str]:
|
||||
"""Get NameID Element"""
|
||||
assertion = self.get_assertion()
|
||||
if assertion is None:
|
||||
@@ -269,12 +283,11 @@ class ResponseProcessor:
|
||||
name_id = subject.find(f"{{{NS_SAML_ASSERTION}}}NameID")
|
||||
if name_id is None:
|
||||
raise ValueError("NameID element not found")
|
||||
return name_id
|
||||
return name_id, "".join(name_id.itertext())
|
||||
|
||||
def _get_name_id_filter(self) -> dict[str, str]:
|
||||
"""Returns the subject's NameID as a Filter for the `User`"""
|
||||
name_id_el = self._get_name_id()
|
||||
name_id = name_id_el.text
|
||||
name_id_el, name_id = self._get_name_id()
|
||||
if not name_id:
|
||||
raise UnsupportedNameIDFormat("Subject's NameID is empty.")
|
||||
_format = name_id_el.attrib["Format"]
|
||||
@@ -295,26 +308,26 @@ class ResponseProcessor:
|
||||
|
||||
def prepare_flow_manager(self) -> SourceFlowManager:
|
||||
"""Prepare flow plan depending on whether or not the user exists"""
|
||||
name_id = self._get_name_id()
|
||||
name_id_el, name_id = self._get_name_id()
|
||||
# Sanity check, show a warning if NameIDPolicy doesn't match what we go
|
||||
if self._source.name_id_policy != name_id.attrib["Format"]:
|
||||
if self._source.name_id_policy != name_id_el.attrib["Format"]:
|
||||
LOGGER.warning(
|
||||
"NameID from IdP doesn't match our policy",
|
||||
expected=self._source.name_id_policy,
|
||||
got=name_id.attrib["Format"],
|
||||
got=name_id_el.attrib["Format"],
|
||||
)
|
||||
# transient NameIDs are handled separately as they don't have to go through flows.
|
||||
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
|
||||
if name_id_el.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
|
||||
return self._handle_name_id_transient()
|
||||
|
||||
return SAMLSourceFlowManager(
|
||||
source=self._source,
|
||||
request=self._http_request,
|
||||
identifier=str(name_id.text),
|
||||
identifier=str(name_id),
|
||||
user_info={
|
||||
"root": self._root,
|
||||
"assertion": self.get_assertion(),
|
||||
"name_id": name_id,
|
||||
"name_id": name_id_el,
|
||||
},
|
||||
policy_context={
|
||||
"saml_response": etree.tostring(self._root),
|
||||
|
||||
@@ -4,6 +4,7 @@ from base64 import b64encode
|
||||
|
||||
from defusedxml.lxml import fromstring
|
||||
from django.test import TestCase
|
||||
from freezegun import freeze_time
|
||||
|
||||
from authentik.common.saml.constants import NS_SAML_ASSERTION
|
||||
from authentik.core.tests.utils import RequestFactory, create_test_flow
|
||||
@@ -34,6 +35,7 @@ class TestPropertyMappings(TestCase):
|
||||
pre_authentication_flow=create_test_flow(),
|
||||
)
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_user_base_properties(self):
|
||||
"""Test user base properties"""
|
||||
properties = self.source.get_base_user_properties(
|
||||
@@ -61,6 +63,7 @@ class TestPropertyMappings(TestCase):
|
||||
properties = self.source.get_base_group_properties(root=ROOT, group_id=group_id)
|
||||
self.assertEqual(properties, {"name": group_id})
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_user_property_mappings(self):
|
||||
"""Test user property mappings"""
|
||||
self.source.user_property_mappings.add(
|
||||
@@ -94,6 +97,7 @@ class TestPropertyMappings(TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_group_property_mappings(self):
|
||||
"""Test group property mappings"""
|
||||
self.source.group_property_mappings.add(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from base64 import b64encode
|
||||
|
||||
from django.test import TestCase
|
||||
from freezegun import freeze_time
|
||||
|
||||
from authentik.core.tests.utils import RequestFactory, create_test_cert, create_test_flow
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
@@ -46,6 +47,7 @@ class TestResponseProcessor(TestCase):
|
||||
):
|
||||
ResponseProcessor(self.source, request).parse()
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_success(self):
|
||||
"""Test success"""
|
||||
request = self.factory.post(
|
||||
@@ -72,6 +74,7 @@ class TestResponseProcessor(TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
@freeze_time("2022-10-14T14:16:40Z")
|
||||
def test_success_with_status_message_and_detail(self):
|
||||
"""Test success with StatusMessage and StatusDetail present (should not raise error)"""
|
||||
request = self.factory.post(
|
||||
@@ -88,6 +91,7 @@ class TestResponseProcessor(TestCase):
|
||||
sfm = parser.prepare_flow_manager()
|
||||
self.assertEqual(sfm.user_properties["username"], "jens@goauthentik.io")
|
||||
|
||||
@freeze_time("2022-10-14T14:16:40Z")
|
||||
def test_error_with_message_and_detail(self):
|
||||
"""Test error status with StatusMessage and StatusDetail includes both in error"""
|
||||
request = self.factory.post(
|
||||
@@ -105,6 +109,7 @@ class TestResponseProcessor(TestCase):
|
||||
self.assertIn("User account is disabled", str(ctx.exception))
|
||||
self.assertIn("Authentication failed", str(ctx.exception))
|
||||
|
||||
@freeze_time("2024-08-07T15:48:09.325Z")
|
||||
def test_encrypted_correct(self):
|
||||
"""Test encrypted"""
|
||||
key = load_fixture("fixtures/encrypted-key.pem")
|
||||
@@ -142,6 +147,7 @@ class TestResponseProcessor(TestCase):
|
||||
with self.assertRaises(InvalidEncryption):
|
||||
parser.parse()
|
||||
|
||||
@freeze_time("2022-10-14T14:16:40Z")
|
||||
def test_verification_assertion(self):
|
||||
"""Test verifying signature inside assertion"""
|
||||
key = load_fixture("fixtures/signature_cert.pem")
|
||||
@@ -164,6 +170,7 @@ class TestResponseProcessor(TestCase):
|
||||
parser = ResponseProcessor(self.source, request)
|
||||
parser.parse()
|
||||
|
||||
@freeze_time("2014-07-17T01:02:18Z")
|
||||
def test_verification_assertion_duplicate(self):
|
||||
"""Test verifying signature inside assertion, where the response has another assertion
|
||||
before our signed assertion"""
|
||||
@@ -186,9 +193,35 @@ class TestResponseProcessor(TestCase):
|
||||
|
||||
parser = ResponseProcessor(self.source, request)
|
||||
parser.parse()
|
||||
self.assertNotEqual(parser._get_name_id().text, "bad")
|
||||
self.assertEqual(parser._get_name_id().text, "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
|
||||
self.assertNotEqual(parser._get_name_id()[1], "bad")
|
||||
self.assertEqual(parser._get_name_id()[1], "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_name_id_comment(self):
|
||||
"""Test comment in name ID"""
|
||||
fixture = load_fixture("fixtures/response_signed_assertion_dup.xml")
|
||||
fixture = fixture.replace(
|
||||
"_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7",
|
||||
"_ce3d2948b4cf20146dee0a0b3dd6f<!--x-->69b6cf86f62d7",
|
||||
)
|
||||
key = load_fixture("fixtures/signature_cert.pem")
|
||||
kp = CertificateKeyPair.objects.create(
|
||||
name=generate_id(),
|
||||
certificate_data=key,
|
||||
)
|
||||
self.source.verification_kp = kp
|
||||
self.source.signed_assertion = True
|
||||
self.source.signed_response = False
|
||||
request = self.factory.post(
|
||||
"/",
|
||||
data={"SAMLResponse": b64encode(fixture.encode()).decode()},
|
||||
)
|
||||
|
||||
parser = ResponseProcessor(self.source, request)
|
||||
parser.parse()
|
||||
self.assertEqual(parser._get_name_id()[1], "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
|
||||
|
||||
@freeze_time("2014-07-17T01:02:18Z")
|
||||
def test_verification_response(self):
|
||||
"""Test verifying signature inside response"""
|
||||
key = load_fixture("fixtures/signature_cert.pem")
|
||||
@@ -211,6 +244,7 @@ class TestResponseProcessor(TestCase):
|
||||
parser = ResponseProcessor(self.source, request)
|
||||
parser.parse()
|
||||
|
||||
@freeze_time("2024-01-18T06:20:48Z")
|
||||
def test_verification_response_and_assertion(self):
|
||||
"""Test verifying signature inside response and assertion"""
|
||||
key = load_fixture("fixtures/signature_cert.pem")
|
||||
@@ -257,6 +291,7 @@ class TestResponseProcessor(TestCase):
|
||||
with self.assertRaisesMessage(InvalidSignature, ""):
|
||||
parser.parse()
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_verification_no_signature(self):
|
||||
"""Test rejecting response without signature when signed_assertion is True"""
|
||||
key = load_fixture("fixtures/signature_cert.pem")
|
||||
@@ -303,6 +338,7 @@ class TestResponseProcessor(TestCase):
|
||||
with self.assertRaisesMessage(InvalidSignature, ""):
|
||||
parser.parse()
|
||||
|
||||
@freeze_time("2025-10-30T05:45:47.619Z")
|
||||
def test_signed_encrypted_response(self):
|
||||
"""Test signed & encrypted response"""
|
||||
verification_key = load_fixture("fixtures/signature_cert2.pem")
|
||||
@@ -330,6 +366,7 @@ class TestResponseProcessor(TestCase):
|
||||
parser = ResponseProcessor(self.source, request)
|
||||
parser.parse()
|
||||
|
||||
@freeze_time("2026-01-21T14:23")
|
||||
def test_transient(self):
|
||||
"""Test SAML transient NameID"""
|
||||
verification_key = load_fixture("fixtures/signature_cert2.pem")
|
||||
|
||||
@@ -4,6 +4,7 @@ from base64 import b64encode
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
from freezegun import freeze_time
|
||||
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.flows.planner import PLAN_CONTEXT_REDIRECT, FlowPlan
|
||||
@@ -26,6 +27,7 @@ class TestViews(TestCase):
|
||||
pre_authentication_flow=create_test_flow(),
|
||||
)
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_enroll(self):
|
||||
"""Enroll"""
|
||||
flow = create_test_flow()
|
||||
@@ -52,6 +54,7 @@ class TestViews(TestCase):
|
||||
plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN)
|
||||
self.assertIsNotNone(plan)
|
||||
|
||||
@freeze_time("2022-10-14T14:15:00")
|
||||
def test_enroll_redirect(self):
|
||||
"""Enroll when attempting to access a provider"""
|
||||
initial_redirect = f"http://{generate_id()}"
|
||||
|
||||
@@ -296,6 +296,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"identifiers"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"const": "authentik_core.objectattribute"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"absent",
|
||||
"created",
|
||||
"must_created",
|
||||
"present"
|
||||
],
|
||||
"default": "present"
|
||||
},
|
||||
"conditions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/$defs/model_authentik_core.objectattribute_permissions"
|
||||
},
|
||||
"attrs": {
|
||||
"$ref": "#/$defs/model_authentik_core.objectattribute"
|
||||
},
|
||||
"identifiers": {
|
||||
"$ref": "#/$defs/model_authentik_core.objectattribute"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -5420,6 +5460,95 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_authentik_core.objectattribute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_type": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Object type"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"title": "Enabled"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Key"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Label"
|
||||
},
|
||||
"regex": {
|
||||
"type": "string",
|
||||
"title": "Regex"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"text",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"title": "Type"
|
||||
},
|
||||
"group": {
|
||||
"type": "string",
|
||||
"title": "Group"
|
||||
},
|
||||
"managed": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"minLength": 1,
|
||||
"title": "Managed by authentik",
|
||||
"description": "Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update."
|
||||
},
|
||||
"is_unique": {
|
||||
"type": "boolean",
|
||||
"title": "Is unique"
|
||||
},
|
||||
"is_required": {
|
||||
"type": "boolean",
|
||||
"title": "Is required"
|
||||
},
|
||||
"is_array": {
|
||||
"type": "boolean",
|
||||
"title": "Is array"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"model_authentik_core.objectattribute_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permission"
|
||||
],
|
||||
"properties": {
|
||||
"permission": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"add_objectattribute",
|
||||
"change_objectattribute",
|
||||
"delete_objectattribute",
|
||||
"view_objectattribute"
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_authentik_core.token": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5610,6 +5739,7 @@
|
||||
"authentik_core.add_groupancestrynode",
|
||||
"authentik_core.add_groupparentagenode",
|
||||
"authentik_core.add_groupsourceconnection",
|
||||
"authentik_core.add_objectattribute",
|
||||
"authentik_core.add_propertymapping",
|
||||
"authentik_core.add_provider",
|
||||
"authentik_core.add_source",
|
||||
@@ -5624,6 +5754,7 @@
|
||||
"authentik_core.change_groupancestrynode",
|
||||
"authentik_core.change_groupparentagenode",
|
||||
"authentik_core.change_groupsourceconnection",
|
||||
"authentik_core.change_objectattribute",
|
||||
"authentik_core.change_propertymapping",
|
||||
"authentik_core.change_provider",
|
||||
"authentik_core.change_source",
|
||||
@@ -5637,6 +5768,7 @@
|
||||
"authentik_core.delete_groupancestrynode",
|
||||
"authentik_core.delete_groupparentagenode",
|
||||
"authentik_core.delete_groupsourceconnection",
|
||||
"authentik_core.delete_objectattribute",
|
||||
"authentik_core.delete_propertymapping",
|
||||
"authentik_core.delete_provider",
|
||||
"authentik_core.delete_source",
|
||||
@@ -5657,6 +5789,7 @@
|
||||
"authentik_core.view_groupancestrynode",
|
||||
"authentik_core.view_groupparentagenode",
|
||||
"authentik_core.view_groupsourceconnection",
|
||||
"authentik_core.view_objectattribute",
|
||||
"authentik_core.view_propertymapping",
|
||||
"authentik_core.view_provider",
|
||||
"authentik_core.view_source",
|
||||
@@ -9085,6 +9218,7 @@
|
||||
"authentik_core.application",
|
||||
"authentik_core.applicationentitlement",
|
||||
"authentik_core.token",
|
||||
"authentik_core.objectattribute",
|
||||
"authentik_crypto.certificatekeypair",
|
||||
"authentik_endpoints.deviceuserbinding",
|
||||
"authentik_endpoints.deviceaccessgroup",
|
||||
@@ -11376,6 +11510,7 @@
|
||||
"authentik_core.add_groupancestrynode",
|
||||
"authentik_core.add_groupparentagenode",
|
||||
"authentik_core.add_groupsourceconnection",
|
||||
"authentik_core.add_objectattribute",
|
||||
"authentik_core.add_propertymapping",
|
||||
"authentik_core.add_provider",
|
||||
"authentik_core.add_source",
|
||||
@@ -11390,6 +11525,7 @@
|
||||
"authentik_core.change_groupancestrynode",
|
||||
"authentik_core.change_groupparentagenode",
|
||||
"authentik_core.change_groupsourceconnection",
|
||||
"authentik_core.change_objectattribute",
|
||||
"authentik_core.change_propertymapping",
|
||||
"authentik_core.change_provider",
|
||||
"authentik_core.change_source",
|
||||
@@ -11403,6 +11539,7 @@
|
||||
"authentik_core.delete_groupancestrynode",
|
||||
"authentik_core.delete_groupparentagenode",
|
||||
"authentik_core.delete_groupsourceconnection",
|
||||
"authentik_core.delete_objectattribute",
|
||||
"authentik_core.delete_propertymapping",
|
||||
"authentik_core.delete_provider",
|
||||
"authentik_core.delete_source",
|
||||
@@ -11423,6 +11560,7 @@
|
||||
"authentik_core.view_groupancestrynode",
|
||||
"authentik_core.view_groupparentagenode",
|
||||
"authentik_core.view_groupsourceconnection",
|
||||
"authentik_core.view_objectattribute",
|
||||
"authentik_core.view_propertymapping",
|
||||
"authentik_core.view_provider",
|
||||
"authentik_core.view_source",
|
||||
|
||||
145
blueprints/system/object-attributes-user.yaml
Normal file
@@ -0,0 +1,145 @@
|
||||
version: 1
|
||||
metadata:
|
||||
labels:
|
||||
blueprints.goauthentik.io/system: "true"
|
||||
name: System - Object Attributes - User
|
||||
entries:
|
||||
identity:
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: givenName
|
||||
managed: goauthentik.io/object-attrs/user/identity/givenName
|
||||
attrs:
|
||||
group: Identity
|
||||
object_type: authentik_core.user
|
||||
label: Given Name
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: familyName
|
||||
managed: goauthentik.io/object-attrs/user/identity/familyName
|
||||
attrs:
|
||||
group: Identity
|
||||
object_type: authentik_core.user
|
||||
label: Family Name
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
ak:
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: settings.locale
|
||||
managed: goauthentik.io/object-attrs/user/settings/locale
|
||||
attrs:
|
||||
group: Settings
|
||||
object_type: authentik_core.user
|
||||
label: Locale
|
||||
enabled: true
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
address:
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: street
|
||||
managed: goauthentik.io/object-attrs/user/address/street
|
||||
attrs:
|
||||
group: Address
|
||||
object_type: authentik_core.user
|
||||
label: Street
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: state
|
||||
managed: goauthentik.io/object-attrs/user/address/state
|
||||
attrs:
|
||||
group: Address
|
||||
object_type: authentik_core.user
|
||||
label: State
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: location
|
||||
managed: goauthentik.io/object-attrs/user/address/location
|
||||
attrs:
|
||||
group: Address
|
||||
object_type: authentik_core.user
|
||||
label: Location
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: postalCode
|
||||
managed: goauthentik.io/object-attrs/user/address/postal-code
|
||||
attrs:
|
||||
group: Address
|
||||
object_type: authentik_core.user
|
||||
label: Postal code
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
contact:
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: phoneNumber
|
||||
managed: goauthentik.io/object-attrs/user/contact/phone-number
|
||||
attrs:
|
||||
group: Contact
|
||||
object_type: authentik_core.user
|
||||
label: Phone number(s)
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
is_array: true
|
||||
unix:
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: shell
|
||||
managed: goauthentik.io/object-attrs/user/unix/shell
|
||||
attrs:
|
||||
group: Unix
|
||||
object_type: authentik_core.user
|
||||
label: Shell
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
employee:
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: employeeNumber
|
||||
managed: goauthentik.io/object-attrs/user/employee/number
|
||||
attrs:
|
||||
group: Employee
|
||||
object_type: authentik_core.user
|
||||
label: Employee Number
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
- model: authentik_core.objectattribute
|
||||
identifiers:
|
||||
key: jobTitle
|
||||
managed: goauthentik.io/object-attrs/user/employee/title
|
||||
attrs:
|
||||
group: Employee
|
||||
object_type: authentik_core.user
|
||||
label: Title
|
||||
enabled: false
|
||||
is_required: false
|
||||
is_unique: false
|
||||
type: text
|
||||
@@ -35,14 +35,13 @@ entries:
|
||||
description: "General Profile Information"
|
||||
expression: |
|
||||
return {
|
||||
# Because authentik only saves the user's full name, and has no concept of first and last names,
|
||||
# the full name is used as given name.
|
||||
# You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
|
||||
"name": request.user.name,
|
||||
"given_name": request.user.name,
|
||||
"given_name": ak_obj_attr(request.user, "givenName", "name"),
|
||||
"family_name": ak_obj_attr(request.user, "familyName"),
|
||||
"preferred_username": request.user.username,
|
||||
"nickname": request.user.username,
|
||||
"groups": [group.name for group in request.user.groups.all()],
|
||||
"picture": request.user.avatar,
|
||||
}
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/scope-entitlements
|
||||
|
||||
@@ -42,8 +42,8 @@ entries:
|
||||
"userName": request.user.username,
|
||||
"name": {
|
||||
"formatted": formatted,
|
||||
"givenName": givenName,
|
||||
"familyName": familyName,
|
||||
"givenName": ak_obj_attr(request.user, "givenName") or givenName,
|
||||
"familyName": ak_obj_attr(request.user, "familyName") or familyName,
|
||||
},
|
||||
"displayName": request.user.name,
|
||||
"photos": photos,
|
||||
|
||||
@@ -110,17 +110,6 @@ func (a *Application) getTraefikForwardUrl(r *http.Request) (*url.URL, error) {
|
||||
|
||||
// getNginxForwardUrl See https://github.com/kubernetes/ingress-nginx/blob/main/rootfs/etc/nginx/template/nginx.tmpl
|
||||
func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) {
|
||||
ou := r.Header.Get("X-Original-URI")
|
||||
if ou != "" {
|
||||
// Turn this full URL into a relative URL
|
||||
u := &url.URL{
|
||||
Host: "",
|
||||
Scheme: "",
|
||||
Path: ou,
|
||||
}
|
||||
a.log.WithField("url", u.String()).Info("building forward URL from X-Original-URI")
|
||||
return u, nil
|
||||
}
|
||||
h := r.Header.Get("X-Original-URL")
|
||||
if len(h) < 1 {
|
||||
return nil, errors.New("no forward URL found")
|
||||
|
||||
@@ -5,10 +5,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||
"goauthentik.io/internal/outpost/proxyv2/types"
|
||||
api "goauthentik.io/packages/client-go"
|
||||
)
|
||||
|
||||
@@ -47,67 +45,6 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "https://foo.bar/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URI", "/app")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
|
||||
s, _ := a.sessions.Get(req, a.SessionName())
|
||||
assert.Equal(t, "/app", s.Values[constants.SessionRedirect])
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Single_Claims(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
|
||||
req.Header.Set("X-Original-URI", "/")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
s, _ := a.sessions.Get(req, a.SessionName())
|
||||
s.ID = uuid.New().String()
|
||||
s.Options.MaxAge = 86400
|
||||
s.Values[constants.SessionClaims] = types.Claims{
|
||||
Sub: "foo",
|
||||
Proxy: &types.ProxyClaims{
|
||||
UserAttributes: map[string]any{
|
||||
"username": "foo",
|
||||
"password": "bar",
|
||||
"additionalHeaders": map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := a.sessions.Save(req, rr, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
a.forwardHandleNginx(rr, req)
|
||||
|
||||
h := rr.Result().Header
|
||||
|
||||
assert.Equal(t, []string{"Basic Zm9vOmJhcg=="}, h["Authorization"])
|
||||
assert.Equal(t, []string{"bar"}, h["Foo"])
|
||||
assert.Equal(t, []string{""}, h["User-Agent"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Email"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Groups"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Jwt"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-App"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Jwks"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Meta-Outpost"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Name"])
|
||||
assert.Equal(t, []string{"foo"}, h["X-Authentik-Uid"])
|
||||
assert.Equal(t, []string{""}, h["X-Authentik-Username"])
|
||||
}
|
||||
|
||||
func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
|
||||
a := newTestApplication()
|
||||
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-06 00:27+0000\n"
|
||||
"POT-Creation-Date: 2026-05-13 05:39+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"
|
||||
@@ -226,6 +226,10 @@ msgstr ""
|
||||
msgid "The slug '{slug}' is reserved and cannot be used for applications."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "User does not have permission to add members to this group."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"When not set all providers are returned. When set to true, only backchannel "
|
||||
@@ -256,6 +260,14 @@ msgstr ""
|
||||
msgid "Setting a user to internal service account is not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py
|
||||
msgid "User does not have permission to add members to a superuser group."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py
|
||||
msgid "User does not have permission to assign roles."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/api/users.py
|
||||
msgid "Can't modify internal service account users"
|
||||
msgstr ""
|
||||
|
||||
@@ -11,3 +11,4 @@ Naur
|
||||
Wärting
|
||||
Aadit
|
||||
Kilby
|
||||
Kahmen
|
||||
|
||||
@@ -164,3 +164,4 @@ yamltags
|
||||
zxcvbn
|
||||
~uuid
|
||||
~uuids
|
||||
wreply
|
||||
|
||||
18
packages/client-ts/src/apis/AdminApi.ts
generated
@@ -58,6 +58,10 @@ export interface AdminFileUsedByListRequest {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface AdminModelsListRequest {
|
||||
filterHasAttributes?: boolean | null;
|
||||
}
|
||||
|
||||
export interface AdminSettingsPartialUpdateRequest {
|
||||
patchedSettingsRequest?: PatchedSettingsRequest;
|
||||
}
|
||||
@@ -400,9 +404,15 @@ export class AdminApi extends runtime.BaseAPI {
|
||||
/**
|
||||
* Creates request options for adminModelsList without sending the request
|
||||
*/
|
||||
async adminModelsListRequestOpts(): Promise<runtime.RequestOpts> {
|
||||
async adminModelsListRequestOpts(
|
||||
requestParameters: AdminModelsListRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters["filterHasAttributes"] != null) {
|
||||
queryParameters["filter_has_attributes"] = requestParameters["filterHasAttributes"];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
@@ -428,9 +438,10 @@ export class AdminApi extends runtime.BaseAPI {
|
||||
* Read-only view list all installed models
|
||||
*/
|
||||
async adminModelsListRaw(
|
||||
requestParameters: AdminModelsListRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<Array<App>>> {
|
||||
const requestOptions = await this.adminModelsListRequestOpts();
|
||||
const requestOptions = await this.adminModelsListRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(AppFromJSON));
|
||||
@@ -440,9 +451,10 @@ export class AdminApi extends runtime.BaseAPI {
|
||||
* Read-only view list all installed models
|
||||
*/
|
||||
async adminModelsList(
|
||||
requestParameters: AdminModelsListRequest = {},
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<Array<App>> {
|
||||
const response = await this.adminModelsListRaw(initOverrides);
|
||||
const response = await this.adminModelsListRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
|
||||
460
packages/client-ts/src/apis/CoreApi.ts
generated
@@ -28,11 +28,14 @@ import type {
|
||||
ImpersonationRequest,
|
||||
IntentEnum,
|
||||
Link,
|
||||
ObjectAttribute,
|
||||
ObjectAttributeRequest,
|
||||
PaginatedApplicationEntitlementList,
|
||||
PaginatedApplicationList,
|
||||
PaginatedAuthenticatedSessionList,
|
||||
PaginatedBrandList,
|
||||
PaginatedGroupList,
|
||||
PaginatedObjectAttributeList,
|
||||
PaginatedTokenList,
|
||||
PaginatedUserConsentList,
|
||||
PaginatedUserList,
|
||||
@@ -40,6 +43,7 @@ import type {
|
||||
PatchedApplicationRequest,
|
||||
PatchedBrandRequest,
|
||||
PatchedGroupRequest,
|
||||
PatchedObjectAttributeRequest,
|
||||
PatchedTokenRequest,
|
||||
PatchedUserRequest,
|
||||
PolicyTestResult,
|
||||
@@ -80,11 +84,14 @@ import {
|
||||
GroupRequestToJSON,
|
||||
ImpersonationRequestToJSON,
|
||||
LinkFromJSON,
|
||||
ObjectAttributeFromJSON,
|
||||
ObjectAttributeRequestToJSON,
|
||||
PaginatedApplicationEntitlementListFromJSON,
|
||||
PaginatedApplicationListFromJSON,
|
||||
PaginatedAuthenticatedSessionListFromJSON,
|
||||
PaginatedBrandListFromJSON,
|
||||
PaginatedGroupListFromJSON,
|
||||
PaginatedObjectAttributeListFromJSON,
|
||||
PaginatedTokenListFromJSON,
|
||||
PaginatedUserConsentListFromJSON,
|
||||
PaginatedUserListFromJSON,
|
||||
@@ -92,6 +99,7 @@ import {
|
||||
PatchedApplicationRequestToJSON,
|
||||
PatchedBrandRequestToJSON,
|
||||
PatchedGroupRequestToJSON,
|
||||
PatchedObjectAttributeRequestToJSON,
|
||||
PatchedTokenRequestToJSON,
|
||||
PatchedUserRequestToJSON,
|
||||
PolicyTestResultFromJSON,
|
||||
@@ -332,6 +340,38 @@ export interface CoreGroupsUsedByListRequest {
|
||||
groupUuid: string;
|
||||
}
|
||||
|
||||
export interface CoreObjectAttributesCreateRequest {
|
||||
objectAttributeRequest: ObjectAttributeRequest;
|
||||
}
|
||||
|
||||
export interface CoreObjectAttributesDestroyRequest {
|
||||
attributeId: string;
|
||||
}
|
||||
|
||||
export interface CoreObjectAttributesListRequest {
|
||||
enabled?: boolean;
|
||||
objectTypeAppLabel?: string;
|
||||
objectTypeModel?: string;
|
||||
ordering?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface CoreObjectAttributesPartialUpdateRequest {
|
||||
attributeId: string;
|
||||
patchedObjectAttributeRequest?: PatchedObjectAttributeRequest;
|
||||
}
|
||||
|
||||
export interface CoreObjectAttributesRetrieveRequest {
|
||||
attributeId: string;
|
||||
}
|
||||
|
||||
export interface CoreObjectAttributesUpdateRequest {
|
||||
attributeId: string;
|
||||
objectAttributeRequest: ObjectAttributeRequest;
|
||||
}
|
||||
|
||||
export interface CoreTokensCreateRequest {
|
||||
tokenRequest: TokenRequest;
|
||||
}
|
||||
@@ -3237,6 +3277,426 @@ export class CoreApi extends runtime.BaseAPI {
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreObjectAttributesCreate without sending the request
|
||||
*/
|
||||
async coreObjectAttributesCreateRequestOpts(
|
||||
requestParameters: CoreObjectAttributesCreateRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
if (requestParameters["objectAttributeRequest"] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
"objectAttributeRequest",
|
||||
'Required parameter "objectAttributeRequest" was null or undefined when calling coreObjectAttributesCreate().',
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters["Content-Type"] = "application/json";
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/object_attributes/`;
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "POST",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: ObjectAttributeRequestToJSON(requestParameters["objectAttributeRequest"]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesCreateRaw(
|
||||
requestParameters: CoreObjectAttributesCreateRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<ObjectAttribute>> {
|
||||
const requestOptions = await this.coreObjectAttributesCreateRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) =>
|
||||
ObjectAttributeFromJSON(jsonValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesCreate(
|
||||
requestParameters: CoreObjectAttributesCreateRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<ObjectAttribute> {
|
||||
const response = await this.coreObjectAttributesCreateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreObjectAttributesDestroy without sending the request
|
||||
*/
|
||||
async coreObjectAttributesDestroyRequestOpts(
|
||||
requestParameters: CoreObjectAttributesDestroyRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
if (requestParameters["attributeId"] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
"attributeId",
|
||||
'Required parameter "attributeId" was null or undefined when calling coreObjectAttributesDestroy().',
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/object_attributes/{attribute_id}/`;
|
||||
urlPath = urlPath.replace(
|
||||
`{${"attribute_id"}}`,
|
||||
encodeURIComponent(String(requestParameters["attributeId"])),
|
||||
);
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "DELETE",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesDestroyRaw(
|
||||
requestParameters: CoreObjectAttributesDestroyRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<void>> {
|
||||
const requestOptions = await this.coreObjectAttributesDestroyRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesDestroy(
|
||||
requestParameters: CoreObjectAttributesDestroyRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<void> {
|
||||
await this.coreObjectAttributesDestroyRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreObjectAttributesList without sending the request
|
||||
*/
|
||||
async coreObjectAttributesListRequestOpts(
|
||||
requestParameters: CoreObjectAttributesListRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters["enabled"] != null) {
|
||||
queryParameters["enabled"] = requestParameters["enabled"];
|
||||
}
|
||||
|
||||
if (requestParameters["objectTypeAppLabel"] != null) {
|
||||
queryParameters["object_type__app_label"] = requestParameters["objectTypeAppLabel"];
|
||||
}
|
||||
|
||||
if (requestParameters["objectTypeModel"] != null) {
|
||||
queryParameters["object_type__model"] = requestParameters["objectTypeModel"];
|
||||
}
|
||||
|
||||
if (requestParameters["ordering"] != null) {
|
||||
queryParameters["ordering"] = requestParameters["ordering"];
|
||||
}
|
||||
|
||||
if (requestParameters["page"] != null) {
|
||||
queryParameters["page"] = requestParameters["page"];
|
||||
}
|
||||
|
||||
if (requestParameters["pageSize"] != null) {
|
||||
queryParameters["page_size"] = requestParameters["pageSize"];
|
||||
}
|
||||
|
||||
if (requestParameters["search"] != null) {
|
||||
queryParameters["search"] = requestParameters["search"];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/object_attributes/`;
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "GET",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesListRaw(
|
||||
requestParameters: CoreObjectAttributesListRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<PaginatedObjectAttributeList>> {
|
||||
const requestOptions = await this.coreObjectAttributesListRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) =>
|
||||
PaginatedObjectAttributeListFromJSON(jsonValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesList(
|
||||
requestParameters: CoreObjectAttributesListRequest = {},
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<PaginatedObjectAttributeList> {
|
||||
const response = await this.coreObjectAttributesListRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreObjectAttributesPartialUpdate without sending the request
|
||||
*/
|
||||
async coreObjectAttributesPartialUpdateRequestOpts(
|
||||
requestParameters: CoreObjectAttributesPartialUpdateRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
if (requestParameters["attributeId"] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
"attributeId",
|
||||
'Required parameter "attributeId" was null or undefined when calling coreObjectAttributesPartialUpdate().',
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters["Content-Type"] = "application/json";
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/object_attributes/{attribute_id}/`;
|
||||
urlPath = urlPath.replace(
|
||||
`{${"attribute_id"}}`,
|
||||
encodeURIComponent(String(requestParameters["attributeId"])),
|
||||
);
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "PATCH",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: PatchedObjectAttributeRequestToJSON(
|
||||
requestParameters["patchedObjectAttributeRequest"],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesPartialUpdateRaw(
|
||||
requestParameters: CoreObjectAttributesPartialUpdateRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<ObjectAttribute>> {
|
||||
const requestOptions =
|
||||
await this.coreObjectAttributesPartialUpdateRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) =>
|
||||
ObjectAttributeFromJSON(jsonValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesPartialUpdate(
|
||||
requestParameters: CoreObjectAttributesPartialUpdateRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<ObjectAttribute> {
|
||||
const response = await this.coreObjectAttributesPartialUpdateRaw(
|
||||
requestParameters,
|
||||
initOverrides,
|
||||
);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreObjectAttributesRetrieve without sending the request
|
||||
*/
|
||||
async coreObjectAttributesRetrieveRequestOpts(
|
||||
requestParameters: CoreObjectAttributesRetrieveRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
if (requestParameters["attributeId"] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
"attributeId",
|
||||
'Required parameter "attributeId" was null or undefined when calling coreObjectAttributesRetrieve().',
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/object_attributes/{attribute_id}/`;
|
||||
urlPath = urlPath.replace(
|
||||
`{${"attribute_id"}}`,
|
||||
encodeURIComponent(String(requestParameters["attributeId"])),
|
||||
);
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "GET",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesRetrieveRaw(
|
||||
requestParameters: CoreObjectAttributesRetrieveRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<ObjectAttribute>> {
|
||||
const requestOptions =
|
||||
await this.coreObjectAttributesRetrieveRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) =>
|
||||
ObjectAttributeFromJSON(jsonValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesRetrieve(
|
||||
requestParameters: CoreObjectAttributesRetrieveRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<ObjectAttribute> {
|
||||
const response = await this.coreObjectAttributesRetrieveRaw(
|
||||
requestParameters,
|
||||
initOverrides,
|
||||
);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreObjectAttributesUpdate without sending the request
|
||||
*/
|
||||
async coreObjectAttributesUpdateRequestOpts(
|
||||
requestParameters: CoreObjectAttributesUpdateRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
if (requestParameters["attributeId"] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
"attributeId",
|
||||
'Required parameter "attributeId" was null or undefined when calling coreObjectAttributesUpdate().',
|
||||
);
|
||||
}
|
||||
|
||||
if (requestParameters["objectAttributeRequest"] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
"objectAttributeRequest",
|
||||
'Required parameter "objectAttributeRequest" was null or undefined when calling coreObjectAttributesUpdate().',
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters["Content-Type"] = "application/json";
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/object_attributes/{attribute_id}/`;
|
||||
urlPath = urlPath.replace(
|
||||
`{${"attribute_id"}}`,
|
||||
encodeURIComponent(String(requestParameters["attributeId"])),
|
||||
);
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "PUT",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: ObjectAttributeRequestToJSON(requestParameters["objectAttributeRequest"]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesUpdateRaw(
|
||||
requestParameters: CoreObjectAttributesUpdateRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<ObjectAttribute>> {
|
||||
const requestOptions = await this.coreObjectAttributesUpdateRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) =>
|
||||
ObjectAttributeFromJSON(jsonValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async coreObjectAttributesUpdate(
|
||||
requestParameters: CoreObjectAttributesUpdateRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<ObjectAttribute> {
|
||||
const response = await this.coreObjectAttributesUpdateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreTokensCreate without sending the request
|
||||
*/
|
||||
|
||||
14
packages/client-ts/src/models/ContentType.ts
generated
@@ -42,6 +42,12 @@ export interface ContentType {
|
||||
* @memberof ContentType
|
||||
*/
|
||||
readonly verboseNamePlural: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ContentType
|
||||
*/
|
||||
readonly fullyQualifiedModel: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,6 +58,8 @@ export function instanceOfContentType(value: object): value is ContentType {
|
||||
if (!("appLabel" in value) || value["appLabel"] === undefined) return false;
|
||||
if (!("model" in value) || value["model"] === undefined) return false;
|
||||
if (!("verboseNamePlural" in value) || value["verboseNamePlural"] === undefined) return false;
|
||||
if (!("fullyQualifiedModel" in value) || value["fullyQualifiedModel"] === undefined)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,6 +76,7 @@ export function ContentTypeFromJSONTyped(json: any, ignoreDiscriminator: boolean
|
||||
appLabel: json["app_label"],
|
||||
model: json["model"],
|
||||
verboseNamePlural: json["verbose_name_plural"],
|
||||
fullyQualifiedModel: json["fully_qualified_model"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,7 +85,10 @@ export function ContentTypeToJSON(json: any): ContentType {
|
||||
}
|
||||
|
||||
export function ContentTypeToJSONTyped(
|
||||
value?: Omit<ContentType, "id" | "app_label" | "model" | "verbose_name_plural"> | null,
|
||||
value?: Omit<
|
||||
ContentType,
|
||||
"id" | "app_label" | "model" | "verbose_name_plural" | "fully_qualified_model"
|
||||
> | null,
|
||||
ignoreDiscriminator: boolean = false,
|
||||
): any {
|
||||
if (value == null) {
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import type { OAuth2Provider } from "./OAuth2Provider";
|
||||
import { OAuth2ProviderFromJSON, OAuth2ProviderToJSON } from "./OAuth2Provider";
|
||||
import type { Provider } from "./Provider";
|
||||
import { ProviderFromJSON, ProviderToJSON } from "./Provider";
|
||||
import type { User } from "./User";
|
||||
import { UserFromJSON, UserToJSON } from "./User";
|
||||
|
||||
@@ -31,10 +31,10 @@ export interface ExpiringBaseGrantModel {
|
||||
readonly pk: number;
|
||||
/**
|
||||
*
|
||||
* @type {OAuth2Provider}
|
||||
* @type {Provider}
|
||||
* @memberof ExpiringBaseGrantModel
|
||||
*/
|
||||
provider: OAuth2Provider;
|
||||
provider: Provider;
|
||||
/**
|
||||
*
|
||||
* @type {User}
|
||||
@@ -86,7 +86,7 @@ export function ExpiringBaseGrantModelFromJSONTyped(
|
||||
}
|
||||
return {
|
||||
pk: json["pk"],
|
||||
provider: OAuth2ProviderFromJSON(json["provider"]),
|
||||
provider: ProviderFromJSON(json["provider"]),
|
||||
user: UserFromJSON(json["user"]),
|
||||
isExpired: json["is_expired"],
|
||||
expires: json["expires"] == null ? undefined : new Date(json["expires"]),
|
||||
@@ -107,7 +107,7 @@ export function ExpiringBaseGrantModelToJSONTyped(
|
||||
}
|
||||
|
||||
return {
|
||||
provider: OAuth2ProviderToJSON(value["provider"]),
|
||||
provider: ProviderToJSON(value["provider"]),
|
||||
user: UserToJSON(value["user"]),
|
||||
expires: value["expires"] == null ? value["expires"] : value["expires"].toISOString(),
|
||||
scope: value["scope"],
|
||||
|
||||
1
packages/client-ts/src/models/ModelEnum.ts
generated
@@ -23,6 +23,7 @@ export const ModelEnum = {
|
||||
AuthentikCoreApplication: "authentik_core.application",
|
||||
AuthentikCoreApplicationentitlement: "authentik_core.applicationentitlement",
|
||||
AuthentikCoreToken: "authentik_core.token",
|
||||
AuthentikCoreObjectattribute: "authentik_core.objectattribute",
|
||||
AuthentikCryptoCertificatekeypair: "authentik_crypto.certificatekeypair",
|
||||
AuthentikEndpointsDeviceuserbinding: "authentik_endpoints.deviceuserbinding",
|
||||
AuthentikEndpointsDeviceaccessgroup: "authentik_endpoints.deviceaccessgroup",
|
||||
|
||||
191
packages/client-ts/src/models/ObjectAttribute.ts
generated
Normal file
@@ -0,0 +1,191 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* authentik
|
||||
* Making authentication simple.
|
||||
*
|
||||
* The version of the OpenAPI document: 2026.5.0-rc1
|
||||
* Contact: hello@goauthentik.io
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import type { ContentType } from "./ContentType";
|
||||
import { ContentTypeFromJSON } from "./ContentType";
|
||||
import type { ObjectAttributeTypeEnum } from "./ObjectAttributeTypeEnum";
|
||||
import {
|
||||
ObjectAttributeTypeEnumFromJSON,
|
||||
ObjectAttributeTypeEnumToJSON,
|
||||
} from "./ObjectAttributeTypeEnum";
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ObjectAttribute
|
||||
*/
|
||||
export interface ObjectAttribute {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
readonly pk: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
objectType: string;
|
||||
/**
|
||||
*
|
||||
* @type {ContentType}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
readonly objectTypeObj: ContentType;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
readonly created: Date;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
readonly lastUpdated: Date;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
regex?: string;
|
||||
/**
|
||||
*
|
||||
* @type {ObjectAttributeTypeEnum}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
type: ObjectAttributeTypeEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
group?: string;
|
||||
/**
|
||||
* Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.
|
||||
* @type {string}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
managed?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
isUnique?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
isRequired?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttribute
|
||||
*/
|
||||
isArray?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the ObjectAttribute interface.
|
||||
*/
|
||||
export function instanceOfObjectAttribute(value: object): value is ObjectAttribute {
|
||||
if (!("pk" in value) || value["pk"] === undefined) return false;
|
||||
if (!("objectType" in value) || value["objectType"] === undefined) return false;
|
||||
if (!("objectTypeObj" in value) || value["objectTypeObj"] === undefined) return false;
|
||||
if (!("created" in value) || value["created"] === undefined) return false;
|
||||
if (!("key" in value) || value["key"] === undefined) return false;
|
||||
if (!("label" in value) || value["label"] === undefined) return false;
|
||||
if (!("lastUpdated" in value) || value["lastUpdated"] === undefined) return false;
|
||||
if (!("type" in value) || value["type"] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ObjectAttributeFromJSON(json: any): ObjectAttribute {
|
||||
return ObjectAttributeFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function ObjectAttributeFromJSONTyped(
|
||||
json: any,
|
||||
ignoreDiscriminator: boolean,
|
||||
): ObjectAttribute {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
pk: json["pk"],
|
||||
objectType: json["object_type"],
|
||||
objectTypeObj: ContentTypeFromJSON(json["object_type_obj"]),
|
||||
enabled: json["enabled"] == null ? undefined : json["enabled"],
|
||||
created: new Date(json["created"]),
|
||||
key: json["key"],
|
||||
label: json["label"],
|
||||
lastUpdated: new Date(json["last_updated"]),
|
||||
regex: json["regex"] == null ? undefined : json["regex"],
|
||||
type: ObjectAttributeTypeEnumFromJSON(json["type"]),
|
||||
group: json["group"] == null ? undefined : json["group"],
|
||||
managed: json["managed"] == null ? undefined : json["managed"],
|
||||
isUnique: json["is_unique"] == null ? undefined : json["is_unique"],
|
||||
isRequired: json["is_required"] == null ? undefined : json["is_required"],
|
||||
isArray: json["is_array"] == null ? undefined : json["is_array"],
|
||||
};
|
||||
}
|
||||
|
||||
export function ObjectAttributeToJSON(json: any): ObjectAttribute {
|
||||
return ObjectAttributeToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function ObjectAttributeToJSONTyped(
|
||||
value?: Omit<ObjectAttribute, "pk" | "object_type_obj" | "created" | "last_updated"> | null,
|
||||
ignoreDiscriminator: boolean = false,
|
||||
): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
object_type: value["objectType"],
|
||||
enabled: value["enabled"],
|
||||
key: value["key"],
|
||||
label: value["label"],
|
||||
regex: value["regex"],
|
||||
type: ObjectAttributeTypeEnumToJSON(value["type"]),
|
||||
group: value["group"],
|
||||
managed: value["managed"],
|
||||
is_unique: value["isUnique"],
|
||||
is_required: value["isRequired"],
|
||||
is_array: value["isArray"],
|
||||
};
|
||||
}
|
||||
157
packages/client-ts/src/models/ObjectAttributeRequest.ts
generated
Normal file
@@ -0,0 +1,157 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* authentik
|
||||
* Making authentication simple.
|
||||
*
|
||||
* The version of the OpenAPI document: 2026.5.0-rc1
|
||||
* Contact: hello@goauthentik.io
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import type { ObjectAttributeTypeEnum } from "./ObjectAttributeTypeEnum";
|
||||
import {
|
||||
ObjectAttributeTypeEnumFromJSON,
|
||||
ObjectAttributeTypeEnumToJSON,
|
||||
} from "./ObjectAttributeTypeEnum";
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ObjectAttributeRequest
|
||||
*/
|
||||
export interface ObjectAttributeRequest {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
objectType: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
regex?: string;
|
||||
/**
|
||||
*
|
||||
* @type {ObjectAttributeTypeEnum}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
type: ObjectAttributeTypeEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
group?: string;
|
||||
/**
|
||||
* Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.
|
||||
* @type {string}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
managed?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
isUnique?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
isRequired?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ObjectAttributeRequest
|
||||
*/
|
||||
isArray?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the ObjectAttributeRequest interface.
|
||||
*/
|
||||
export function instanceOfObjectAttributeRequest(value: object): value is ObjectAttributeRequest {
|
||||
if (!("objectType" in value) || value["objectType"] === undefined) return false;
|
||||
if (!("key" in value) || value["key"] === undefined) return false;
|
||||
if (!("label" in value) || value["label"] === undefined) return false;
|
||||
if (!("type" in value) || value["type"] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ObjectAttributeRequestFromJSON(json: any): ObjectAttributeRequest {
|
||||
return ObjectAttributeRequestFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function ObjectAttributeRequestFromJSONTyped(
|
||||
json: any,
|
||||
ignoreDiscriminator: boolean,
|
||||
): ObjectAttributeRequest {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
objectType: json["object_type"],
|
||||
enabled: json["enabled"] == null ? undefined : json["enabled"],
|
||||
key: json["key"],
|
||||
label: json["label"],
|
||||
regex: json["regex"] == null ? undefined : json["regex"],
|
||||
type: ObjectAttributeTypeEnumFromJSON(json["type"]),
|
||||
group: json["group"] == null ? undefined : json["group"],
|
||||
managed: json["managed"] == null ? undefined : json["managed"],
|
||||
isUnique: json["is_unique"] == null ? undefined : json["is_unique"],
|
||||
isRequired: json["is_required"] == null ? undefined : json["is_required"],
|
||||
isArray: json["is_array"] == null ? undefined : json["is_array"],
|
||||
};
|
||||
}
|
||||
|
||||
export function ObjectAttributeRequestToJSON(json: any): ObjectAttributeRequest {
|
||||
return ObjectAttributeRequestToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function ObjectAttributeRequestToJSONTyped(
|
||||
value?: ObjectAttributeRequest | null,
|
||||
ignoreDiscriminator: boolean = false,
|
||||
): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
object_type: value["objectType"],
|
||||
enabled: value["enabled"],
|
||||
key: value["key"],
|
||||
label: value["label"],
|
||||
regex: value["regex"],
|
||||
type: ObjectAttributeTypeEnumToJSON(value["type"]),
|
||||
group: value["group"],
|
||||
managed: value["managed"],
|
||||
is_unique: value["isUnique"],
|
||||
is_required: value["isRequired"],
|
||||
is_array: value["isArray"],
|
||||
};
|
||||
}
|
||||
59
packages/client-ts/src/models/ObjectAttributeTypeEnum.ts
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* authentik
|
||||
* Making authentication simple.
|
||||
*
|
||||
* The version of the OpenAPI document: 2026.5.0-rc1
|
||||
* Contact: hello@goauthentik.io
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const ObjectAttributeTypeEnum = {
|
||||
Text: "text",
|
||||
Number: "number",
|
||||
Boolean: "boolean",
|
||||
UnknownDefaultOpenApi: "11184809",
|
||||
} as const;
|
||||
export type ObjectAttributeTypeEnum =
|
||||
(typeof ObjectAttributeTypeEnum)[keyof typeof ObjectAttributeTypeEnum];
|
||||
|
||||
export function instanceOfObjectAttributeTypeEnum(value: any): boolean {
|
||||
for (const key in ObjectAttributeTypeEnum) {
|
||||
if (Object.prototype.hasOwnProperty.call(ObjectAttributeTypeEnum, key)) {
|
||||
if (ObjectAttributeTypeEnum[key as keyof typeof ObjectAttributeTypeEnum] === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function ObjectAttributeTypeEnumFromJSON(json: any): ObjectAttributeTypeEnum {
|
||||
return ObjectAttributeTypeEnumFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function ObjectAttributeTypeEnumFromJSONTyped(
|
||||
json: any,
|
||||
ignoreDiscriminator: boolean,
|
||||
): ObjectAttributeTypeEnum {
|
||||
return json as ObjectAttributeTypeEnum;
|
||||
}
|
||||
|
||||
export function ObjectAttributeTypeEnumToJSON(value?: ObjectAttributeTypeEnum | null): any {
|
||||
return value as any;
|
||||
}
|
||||
|
||||
export function ObjectAttributeTypeEnumToJSONTyped(
|
||||
value: any,
|
||||
ignoreDiscriminator: boolean,
|
||||
): ObjectAttributeTypeEnum {
|
||||
return value as ObjectAttributeTypeEnum;
|
||||
}
|
||||
93
packages/client-ts/src/models/PaginatedObjectAttributeList.ts
generated
Normal file
@@ -0,0 +1,93 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* authentik
|
||||
* Making authentication simple.
|
||||
*
|
||||
* The version of the OpenAPI document: 2026.5.0-rc1
|
||||
* Contact: hello@goauthentik.io
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import type { ObjectAttribute } from "./ObjectAttribute";
|
||||
import { ObjectAttributeFromJSON, ObjectAttributeToJSON } from "./ObjectAttribute";
|
||||
import type { Pagination } from "./Pagination";
|
||||
import { PaginationFromJSON, PaginationToJSON } from "./Pagination";
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PaginatedObjectAttributeList
|
||||
*/
|
||||
export interface PaginatedObjectAttributeList {
|
||||
/**
|
||||
*
|
||||
* @type {Pagination}
|
||||
* @memberof PaginatedObjectAttributeList
|
||||
*/
|
||||
pagination: Pagination;
|
||||
/**
|
||||
*
|
||||
* @type {Array<ObjectAttribute>}
|
||||
* @memberof PaginatedObjectAttributeList
|
||||
*/
|
||||
results: Array<ObjectAttribute>;
|
||||
/**
|
||||
*
|
||||
* @type {{ [key: string]: any; }}
|
||||
* @memberof PaginatedObjectAttributeList
|
||||
*/
|
||||
autocomplete: { [key: string]: any };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the PaginatedObjectAttributeList interface.
|
||||
*/
|
||||
export function instanceOfPaginatedObjectAttributeList(
|
||||
value: object,
|
||||
): value is PaginatedObjectAttributeList {
|
||||
if (!("pagination" in value) || value["pagination"] === undefined) return false;
|
||||
if (!("results" in value) || value["results"] === undefined) return false;
|
||||
if (!("autocomplete" in value) || value["autocomplete"] === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function PaginatedObjectAttributeListFromJSON(json: any): PaginatedObjectAttributeList {
|
||||
return PaginatedObjectAttributeListFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PaginatedObjectAttributeListFromJSONTyped(
|
||||
json: any,
|
||||
ignoreDiscriminator: boolean,
|
||||
): PaginatedObjectAttributeList {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
pagination: PaginationFromJSON(json["pagination"]),
|
||||
results: (json["results"] as Array<any>).map(ObjectAttributeFromJSON),
|
||||
autocomplete: json["autocomplete"],
|
||||
};
|
||||
}
|
||||
|
||||
export function PaginatedObjectAttributeListToJSON(json: any): PaginatedObjectAttributeList {
|
||||
return PaginatedObjectAttributeListToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PaginatedObjectAttributeListToJSONTyped(
|
||||
value?: PaginatedObjectAttributeList | null,
|
||||
ignoreDiscriminator: boolean = false,
|
||||
): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
pagination: PaginationToJSON(value["pagination"]),
|
||||
results: (value["results"] as Array<any>).map(ObjectAttributeToJSON),
|
||||
autocomplete: value["autocomplete"],
|
||||
};
|
||||
}
|
||||
155
packages/client-ts/src/models/PatchedObjectAttributeRequest.ts
generated
Normal file
@@ -0,0 +1,155 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* authentik
|
||||
* Making authentication simple.
|
||||
*
|
||||
* The version of the OpenAPI document: 2026.5.0-rc1
|
||||
* Contact: hello@goauthentik.io
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import type { ObjectAttributeTypeEnum } from "./ObjectAttributeTypeEnum";
|
||||
import {
|
||||
ObjectAttributeTypeEnumFromJSON,
|
||||
ObjectAttributeTypeEnumToJSON,
|
||||
} from "./ObjectAttributeTypeEnum";
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PatchedObjectAttributeRequest
|
||||
*/
|
||||
export interface PatchedObjectAttributeRequest {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
objectType?: string;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
key?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
regex?: string;
|
||||
/**
|
||||
*
|
||||
* @type {ObjectAttributeTypeEnum}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
type?: ObjectAttributeTypeEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
group?: string;
|
||||
/**
|
||||
* Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.
|
||||
* @type {string}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
managed?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
isUnique?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
isRequired?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof PatchedObjectAttributeRequest
|
||||
*/
|
||||
isArray?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the PatchedObjectAttributeRequest interface.
|
||||
*/
|
||||
export function instanceOfPatchedObjectAttributeRequest(
|
||||
value: object,
|
||||
): value is PatchedObjectAttributeRequest {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function PatchedObjectAttributeRequestFromJSON(json: any): PatchedObjectAttributeRequest {
|
||||
return PatchedObjectAttributeRequestFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PatchedObjectAttributeRequestFromJSONTyped(
|
||||
json: any,
|
||||
ignoreDiscriminator: boolean,
|
||||
): PatchedObjectAttributeRequest {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
objectType: json["object_type"] == null ? undefined : json["object_type"],
|
||||
enabled: json["enabled"] == null ? undefined : json["enabled"],
|
||||
key: json["key"] == null ? undefined : json["key"],
|
||||
label: json["label"] == null ? undefined : json["label"],
|
||||
regex: json["regex"] == null ? undefined : json["regex"],
|
||||
type: json["type"] == null ? undefined : ObjectAttributeTypeEnumFromJSON(json["type"]),
|
||||
group: json["group"] == null ? undefined : json["group"],
|
||||
managed: json["managed"] == null ? undefined : json["managed"],
|
||||
isUnique: json["is_unique"] == null ? undefined : json["is_unique"],
|
||||
isRequired: json["is_required"] == null ? undefined : json["is_required"],
|
||||
isArray: json["is_array"] == null ? undefined : json["is_array"],
|
||||
};
|
||||
}
|
||||
|
||||
export function PatchedObjectAttributeRequestToJSON(json: any): PatchedObjectAttributeRequest {
|
||||
return PatchedObjectAttributeRequestToJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function PatchedObjectAttributeRequestToJSONTyped(
|
||||
value?: PatchedObjectAttributeRequest | null,
|
||||
ignoreDiscriminator: boolean = false,
|
||||
): any {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
object_type: value["objectType"],
|
||||
enabled: value["enabled"],
|
||||
key: value["key"],
|
||||
label: value["label"],
|
||||
regex: value["regex"],
|
||||
type: ObjectAttributeTypeEnumToJSON(value["type"]),
|
||||
group: value["group"],
|
||||
managed: value["managed"],
|
||||
is_unique: value["isUnique"],
|
||||
is_required: value["isRequired"],
|
||||
is_array: value["isArray"],
|
||||
};
|
||||
}
|
||||
12
packages/client-ts/src/models/TokenModel.ts
generated
@@ -12,8 +12,8 @@
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import type { OAuth2Provider } from "./OAuth2Provider";
|
||||
import { OAuth2ProviderFromJSON, OAuth2ProviderToJSON } from "./OAuth2Provider";
|
||||
import type { Provider } from "./Provider";
|
||||
import { ProviderFromJSON, ProviderToJSON } from "./Provider";
|
||||
import type { User } from "./User";
|
||||
import { UserFromJSON, UserToJSON } from "./User";
|
||||
|
||||
@@ -31,10 +31,10 @@ export interface TokenModel {
|
||||
readonly pk: number;
|
||||
/**
|
||||
*
|
||||
* @type {OAuth2Provider}
|
||||
* @type {Provider}
|
||||
* @memberof TokenModel
|
||||
*/
|
||||
provider: OAuth2Provider;
|
||||
provider: Provider;
|
||||
/**
|
||||
*
|
||||
* @type {User}
|
||||
@@ -96,7 +96,7 @@ export function TokenModelFromJSONTyped(json: any, ignoreDiscriminator: boolean)
|
||||
}
|
||||
return {
|
||||
pk: json["pk"],
|
||||
provider: OAuth2ProviderFromJSON(json["provider"]),
|
||||
provider: ProviderFromJSON(json["provider"]),
|
||||
user: UserFromJSON(json["user"]),
|
||||
isExpired: json["is_expired"],
|
||||
expires: json["expires"] == null ? undefined : new Date(json["expires"]),
|
||||
@@ -119,7 +119,7 @@ export function TokenModelToJSONTyped(
|
||||
}
|
||||
|
||||
return {
|
||||
provider: OAuth2ProviderToJSON(value["provider"]),
|
||||
provider: ProviderToJSON(value["provider"]),
|
||||
user: UserToJSON(value["user"]),
|
||||
expires: value["expires"] == null ? value["expires"] : value["expires"].toISOString(),
|
||||
scope: value["scope"],
|
||||
|
||||
5
packages/client-ts/src/models/index.ts
generated
@@ -344,6 +344,9 @@ export * from "./OAuthSource";
|
||||
export * from "./OAuthSourcePropertyMapping";
|
||||
export * from "./OAuthSourcePropertyMappingRequest";
|
||||
export * from "./OAuthSourceRequest";
|
||||
export * from "./ObjectAttribute";
|
||||
export * from "./ObjectAttributeRequest";
|
||||
export * from "./ObjectAttributeTypeEnum";
|
||||
export * from "./OpenIDConnectConfiguration";
|
||||
export * from "./OperatingSystem";
|
||||
export * from "./OperatingSystemRequest";
|
||||
@@ -439,6 +442,7 @@ export * from "./PaginatedNotificationWebhookMappingList";
|
||||
export * from "./PaginatedOAuth2ProviderList";
|
||||
export * from "./PaginatedOAuthSourceList";
|
||||
export * from "./PaginatedOAuthSourcePropertyMappingList";
|
||||
export * from "./PaginatedObjectAttributeList";
|
||||
export * from "./PaginatedOutpostList";
|
||||
export * from "./PaginatedPasswordExpiryPolicyList";
|
||||
export * from "./PaginatedPasswordPolicyList";
|
||||
@@ -595,6 +599,7 @@ export * from "./PatchedNotificationWebhookMappingRequest";
|
||||
export * from "./PatchedOAuth2ProviderRequest";
|
||||
export * from "./PatchedOAuthSourcePropertyMappingRequest";
|
||||
export * from "./PatchedOAuthSourceRequest";
|
||||
export * from "./PatchedObjectAttributeRequest";
|
||||
export * from "./PatchedOutpostRequest";
|
||||
export * from "./PatchedPasswordExpiryPolicyRequest";
|
||||
export * from "./PatchedPasswordPolicyRequest";
|
||||
|
||||
@@ -60,7 +60,7 @@ export const LogLevels = /** @type {Level[]} */ (Object.keys(LogLevelLabel));
|
||||
/**
|
||||
* @callback LoggerFactory
|
||||
* @param {string | null} [prefix]
|
||||
* @param {...string} args
|
||||
* @param {...string[]} args
|
||||
* @returns {Logger}
|
||||
*/
|
||||
|
||||
@@ -207,7 +207,7 @@ export function pinoLight(options) {
|
||||
* Creates a logger with the given prefix.
|
||||
*
|
||||
* @param {string} [prefix]
|
||||
* @param {...string} args
|
||||
* @param {...string[]} args
|
||||
* @returns {Logger}
|
||||
*
|
||||
*/
|
||||
|
||||
6
packages/logger-js/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/logger-js",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/logger-js",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.3",
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"../tsconfig": {
|
||||
"name": "@goauthentik/tsconfig",
|
||||
"version": "1.0.8",
|
||||
"version": "1.0.9",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/logger-js",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"description": "Pino-based logger for authentik",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -57,7 +57,7 @@ dependencies = [
|
||||
"pyyaml==6.0.3",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"scim2-filter-parser==0.7.0",
|
||||
"sentry-sdk==2.58.0",
|
||||
"sentry-sdk==2.59.0",
|
||||
"service-identity==24.2.0",
|
||||
"setproctitle==1.3.7",
|
||||
"structlog==25.5.0",
|
||||
@@ -85,7 +85,7 @@ dev = [
|
||||
"coverage[toml]==7.13.5",
|
||||
"daphne==4.2.1",
|
||||
"debugpy==1.8.20",
|
||||
"django-stubs[compatible-mypy]==6.0.3",
|
||||
"django-stubs[compatible-mypy]==6.0.4",
|
||||
"djangorestframework-stubs[compatible-mypy]==3.16.9",
|
||||
"drf-jsonschema-serializer==3.0.0",
|
||||
"freezegun==1.5.5",
|
||||
|
||||
345
schema.yml
@@ -134,6 +134,12 @@ paths:
|
||||
get:
|
||||
operationId: admin_models_list
|
||||
description: Read-only view list all installed models
|
||||
parameters:
|
||||
- in: query
|
||||
name: filter_has_attributes
|
||||
schema:
|
||||
type: boolean
|
||||
nullable: true
|
||||
tags:
|
||||
- admin
|
||||
security:
|
||||
@@ -3720,6 +3726,172 @@ paths:
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
/core/object_attributes/:
|
||||
get:
|
||||
operationId: core_object_attributes_list
|
||||
parameters:
|
||||
- in: query
|
||||
name: enabled
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: object_type__app_label
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: object_type__model
|
||||
schema:
|
||||
type: string
|
||||
- $ref: '#/components/parameters/QueryPaginationOrdering'
|
||||
- $ref: '#/components/parameters/QueryPaginationPage'
|
||||
- $ref: '#/components/parameters/QueryPaginationPageSize'
|
||||
- $ref: '#/components/parameters/QuerySearch'
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedObjectAttributeList'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
post:
|
||||
operationId: core_object_attributes_create
|
||||
tags:
|
||||
- core
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ObjectAttributeRequest'
|
||||
required: true
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ObjectAttribute'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
/core/object_attributes/{attribute_id}/:
|
||||
get:
|
||||
operationId: core_object_attributes_retrieve
|
||||
parameters:
|
||||
- in: path
|
||||
name: attribute_id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Object Attribute.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ObjectAttribute'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
put:
|
||||
operationId: core_object_attributes_update
|
||||
parameters:
|
||||
- in: path
|
||||
name: attribute_id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Object Attribute.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ObjectAttributeRequest'
|
||||
required: true
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ObjectAttribute'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
patch:
|
||||
operationId: core_object_attributes_partial_update
|
||||
parameters:
|
||||
- in: path
|
||||
name: attribute_id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Object Attribute.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedObjectAttributeRequest'
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ObjectAttribute'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
delete:
|
||||
operationId: core_object_attributes_destroy
|
||||
parameters:
|
||||
- in: path
|
||||
name: attribute_id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Object Attribute.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'204':
|
||||
description: No response body
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
/core/tokens/:
|
||||
get:
|
||||
operationId: core_tokens_list
|
||||
@@ -5386,6 +5558,8 @@ paths:
|
||||
using this object
|
||||
tags:
|
||||
- endpoints
|
||||
security:
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
@@ -5430,6 +5604,8 @@ paths:
|
||||
using this object
|
||||
tags:
|
||||
- endpoints
|
||||
security:
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
@@ -5453,6 +5629,8 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeviceFactsRequest'
|
||||
security:
|
||||
- {}
|
||||
responses:
|
||||
'204':
|
||||
description: Successfully checked in
|
||||
@@ -5473,6 +5651,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/EnrollRequest'
|
||||
required: true
|
||||
security:
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
@@ -36850,8 +37030,12 @@ components:
|
||||
verbose_name_plural:
|
||||
type: string
|
||||
readOnly: true
|
||||
fully_qualified_model:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- app_label
|
||||
- fully_qualified_model
|
||||
- id
|
||||
- model
|
||||
- verbose_name_plural
|
||||
@@ -39078,7 +39262,7 @@ components:
|
||||
readOnly: true
|
||||
title: ID
|
||||
provider:
|
||||
$ref: '#/components/schemas/OAuth2Provider'
|
||||
$ref: '#/components/schemas/Provider'
|
||||
user:
|
||||
$ref: '#/components/schemas/User'
|
||||
is_expired:
|
||||
@@ -43413,6 +43597,7 @@ components:
|
||||
- authentik_core.application
|
||||
- authentik_core.applicationentitlement
|
||||
- authentik_core.token
|
||||
- authentik_core.objectattribute
|
||||
- authentik_crypto.certificatekeypair
|
||||
- authentik_endpoints.deviceuserbinding
|
||||
- authentik_endpoints.deviceaccessgroup
|
||||
@@ -44694,6 +44879,111 @@ components:
|
||||
- name
|
||||
- provider_type
|
||||
- slug
|
||||
ObjectAttribute:
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
title: Attribute id
|
||||
object_type:
|
||||
type: string
|
||||
object_type_obj:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ContentType'
|
||||
readOnly: true
|
||||
enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
key:
|
||||
type: string
|
||||
label:
|
||||
type: string
|
||||
last_updated:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
regex:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/ObjectAttributeTypeEnum'
|
||||
group:
|
||||
type: string
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects that are managed by authentik. These objects are created
|
||||
and updated automatically. This flag only indicates that an object can
|
||||
be overwritten by migrations. You can still modify the objects via the
|
||||
API, but expect changes to be overwritten in a later update.
|
||||
is_unique:
|
||||
type: boolean
|
||||
is_required:
|
||||
type: boolean
|
||||
is_array:
|
||||
type: boolean
|
||||
required:
|
||||
- created
|
||||
- key
|
||||
- label
|
||||
- last_updated
|
||||
- object_type
|
||||
- object_type_obj
|
||||
- pk
|
||||
- type
|
||||
ObjectAttributeRequest:
|
||||
type: object
|
||||
properties:
|
||||
object_type:
|
||||
type: string
|
||||
minLength: 1
|
||||
enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
key:
|
||||
type: string
|
||||
minLength: 1
|
||||
label:
|
||||
type: string
|
||||
minLength: 1
|
||||
regex:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/ObjectAttributeTypeEnum'
|
||||
group:
|
||||
type: string
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
minLength: 1
|
||||
title: Managed by authentik
|
||||
description: Objects that are managed by authentik. These objects are created
|
||||
and updated automatically. This flag only indicates that an object can
|
||||
be overwritten by migrations. You can still modify the objects via the
|
||||
API, but expect changes to be overwritten in a later update.
|
||||
is_unique:
|
||||
type: boolean
|
||||
is_required:
|
||||
type: boolean
|
||||
is_array:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
- label
|
||||
- object_type
|
||||
- type
|
||||
ObjectAttributeTypeEnum:
|
||||
enum:
|
||||
- text
|
||||
- number
|
||||
- boolean
|
||||
type: string
|
||||
OpenIDConnectConfiguration:
|
||||
type: object
|
||||
description: rest_framework Serializer for OIDC Configuration
|
||||
@@ -46244,6 +46534,21 @@ components:
|
||||
- autocomplete
|
||||
- pagination
|
||||
- results
|
||||
PaginatedObjectAttributeList:
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
$ref: '#/components/schemas/Pagination'
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ObjectAttribute'
|
||||
autocomplete:
|
||||
$ref: '#/components/schemas/Autocomplete'
|
||||
required:
|
||||
- autocomplete
|
||||
- pagination
|
||||
- results
|
||||
PaginatedOutpostList:
|
||||
type: object
|
||||
properties:
|
||||
@@ -50026,6 +50331,42 @@ components:
|
||||
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
|
||||
description: How to perform authentication during an authorization_code
|
||||
token request flow
|
||||
PatchedObjectAttributeRequest:
|
||||
type: object
|
||||
properties:
|
||||
object_type:
|
||||
type: string
|
||||
minLength: 1
|
||||
enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
key:
|
||||
type: string
|
||||
minLength: 1
|
||||
label:
|
||||
type: string
|
||||
minLength: 1
|
||||
regex:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/ObjectAttributeTypeEnum'
|
||||
group:
|
||||
type: string
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
minLength: 1
|
||||
title: Managed by authentik
|
||||
description: Objects that are managed by authentik. These objects are created
|
||||
and updated automatically. This flag only indicates that an object can
|
||||
be overwritten by migrations. You can still modify the objects via the
|
||||
API, but expect changes to be overwritten in a later update.
|
||||
is_unique:
|
||||
type: boolean
|
||||
is_required:
|
||||
type: boolean
|
||||
is_array:
|
||||
type: boolean
|
||||
PatchedOutpostRequest:
|
||||
type: object
|
||||
description: Outpost Serializer
|
||||
@@ -57253,7 +57594,7 @@ components:
|
||||
readOnly: true
|
||||
title: ID
|
||||
provider:
|
||||
$ref: '#/components/schemas/OAuth2Provider'
|
||||
$ref: '#/components/schemas/Provider'
|
||||
user:
|
||||
$ref: '#/components/schemas/User'
|
||||
is_expired:
|
||||
|
||||
16
uv.lock
generated
@@ -366,7 +366,7 @@ requires-dist = [
|
||||
{ name = "pyyaml", specifier = "==6.0.3" },
|
||||
{ name = "requests-oauthlib", specifier = "==2.0.0" },
|
||||
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.58.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.59.0" },
|
||||
{ name = "service-identity", specifier = "==24.2.0" },
|
||||
{ name = "setproctitle", specifier = "==1.3.7" },
|
||||
{ name = "structlog", specifier = "==25.5.0" },
|
||||
@@ -394,7 +394,7 @@ dev = [
|
||||
{ name = "coverage", extras = ["toml"], specifier = "==7.13.5" },
|
||||
{ name = "daphne", specifier = "==4.2.1" },
|
||||
{ name = "debugpy", specifier = "==1.8.20" },
|
||||
{ name = "django-stubs", extras = ["compatible-mypy"], specifier = "==6.0.3" },
|
||||
{ name = "django-stubs", extras = ["compatible-mypy"], specifier = "==6.0.4" },
|
||||
{ name = "djangorestframework-stubs", extras = ["compatible-mypy"], specifier = "==3.16.9" },
|
||||
{ name = "drf-jsonschema-serializer", specifier = "==3.0.0" },
|
||||
{ name = "freezegun", specifier = "==1.5.5" },
|
||||
@@ -1269,7 +1269,7 @@ s3 = [
|
||||
|
||||
[[package]]
|
||||
name = "django-stubs"
|
||||
version = "6.0.3"
|
||||
version = "6.0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
@@ -1277,9 +1277,9 @@ dependencies = [
|
||||
{ name = "types-pyyaml" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/0c/8d0d875af79bf774c1c3997c84aa118dba3a77be12086b9c14e130e8ec72/django_stubs-6.0.3.tar.gz", hash = "sha256:ee895f403c373608eeb50822f0733f9d9ec5ab12731d4ab58956053bb95fdd9e", size = 278214, upload-time = "2026-04-18T15:11:22.327Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/82/ccf2a2dc9cdb4bd9cbe91f11e887589bf2da7609506db00ccbc73bd8a6da/django_stubs-6.0.4.tar.gz", hash = "sha256:7aee77e8de9c14c0d9cf84988befe826d93cbc15a87e0ade2943f14d553451cf", size = 280019, upload-time = "2026-05-09T21:24:30.436Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/80/a3/6751b7684d20fc4f228bdd3dd8341d382ab3faaf65d3d050c0d59ab0a1b0/django_stubs-6.0.3-py3-none-any.whl", hash = "sha256:5fee22bcbbad59a78c727a820b6f4e68ff442ca76a922b7002e57c25dd7cb390", size = 541570, upload-time = "2026-04-18T15:11:20.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/e7/5128914ada94dd6277626ef5a4a5680a4def7d2f9366214d26c1cd86723b/django_stubs-6.0.4-py3-none-any.whl", hash = "sha256:e991c68f77239663577a5f4fc75e99c84f867f378cafc97cbf4acc5aff378279", size = 543791, upload-time = "2026-05-09T21:24:28.218Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -3332,15 +3332,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.58.0"
|
||||
version = "2.59.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/b3/fb8291170d0e844173164709fc0fa0c221ed75a5da740c8746f2a83b4eb1/sentry_sdk-2.58.0.tar.gz", hash = "sha256:c1144d947352d54e5b7daa63596d9f848adf684989c06c4f5a659f0c85a18f6f", size = 438764, upload-time = "2026-04-13T17:23:26.265Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/e0/9bf5e5fc7442b10880f3ec0eff0ef4208b84a099606f343ec4f5445227fb/sentry_sdk-2.59.0.tar.gz", hash = "sha256:cd265808ef8bf3f3edf69b527c0a0b2b6b1322762679e55b8987db2e9584aec1", size = 447331, upload-time = "2026-05-04T12:19:06.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/eb/d875669993b762556ae8b2efd86219943b4c0864d22204d622a9aee3052b/sentry_sdk-2.58.0-py2.py3-none-any.whl", hash = "sha256:688d1c704ddecf382ea3326f21a67453d4caa95592d722b7c780a36a9d23109e", size = 460919, upload-time = "2026-04-13T17:23:24.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/00/b8cc413748fb6383d1582e7cda51314f99743351c462a92dc690d5b5853b/sentry_sdk-2.59.0-py2.py3-none-any.whl", hash = "sha256:abcf65ee9a9d9cdebf9ad369782408ecca9c1c792686ef06ba34f5ab233527fe", size = 468432, upload-time = "2026-05-04T12:19:04.741Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 12 KiB |
@@ -1 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 144.29"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><path class="cls-1" d="M106,41.08h25.39v101.2H106v-10.7a50,50,0,0,1-14.92,10.19,41.84,41.84,0,0,1-16.21,3.11q-19.61,0-33.91-15.21T26.64,91.86q0-23.43,13.85-38.41t33.63-15a42.78,42.78,0,0,1,17.09,3.44A46.82,46.82,0,0,1,106,52.24ZM79.29,61.91a25.65,25.65,0,0,0-19.56,8.33q-7.78,8.33-7.79,21.34t7.93,21.58a25.66,25.66,0,0,0,19.51,8.47,26.15,26.15,0,0,0,19.84-8.33q7.88-8.33,7.88-21.81,0-13.2-7.88-21.39T79.29,61.91Z"/><path class="cls-1" d="M168.39,41.08h25.67V89.82q0,14.22,2,19.76a17.24,17.24,0,0,0,6.29,8.61A18.06,18.06,0,0,0,213,121.26a18.6,18.6,0,0,0,10.77-3,17.7,17.7,0,0,0,6.57-8.88q1.59-4.36,1.59-18.7V41.08h25.39V84q0,26.51-4.18,36.27a39.6,39.6,0,0,1-15.07,18.28q-10,6.38-25.3,6.37-16.65,0-26.93-7.44T171.36,116.7q-3-9.21-3-33.49Z"/><path class="cls-1" d="M297.3,3.78h25.39v37.3h15.07V62.93H322.69v79.35H297.3V62.93h-13V41.08h13Z"/><path class="cls-1" d="M362.86,2h25.21v49.3a57.74,57.74,0,0,1,15-9.63,38.56,38.56,0,0,1,15.25-3.21,34.36,34.36,0,0,1,25.39,10.42q8.83,9,8.84,26.51v66.88h-25V97.91q0-17.58-1.68-23.81t-5.71-9.3a16.07,16.07,0,0,0-10-3.07,18.85,18.85,0,0,0-13.26,5.11q-5.53,5.11-7.67,14-1.12,4.56-1.12,20.84v40.65H362.86Z"/><path class="cls-1" d="M589.91,99H508.33q1.77,10.78,9.44,17.16t19.58,6.37a33.86,33.86,0,0,0,24.46-10l21.4,10a50.54,50.54,0,0,1-19.16,16.79q-11.16,5.44-26.51,5.44-23.82,0-38.79-15t-15-37.63q0-23.16,14.93-38.46t37.44-15.3q23.91,0,38.88,15.3t15,40.42Zm-25.4-20a25.48,25.48,0,0,0-9.92-13.77A28.81,28.81,0,0,0,537.4,60a30.42,30.42,0,0,0-18.64,5.95q-5,3.72-9.31,13.12Z"/><path class="cls-1" d="M621.89,41.08h25.39V51.45q8.64-7.29,15.65-10.13a37.82,37.82,0,0,1,14.35-2.85A34.77,34.77,0,0,1,702.83,49q8.82,8.94,8.82,26.42v66.88H686.54V98q0-18.12-1.63-24.06a16.44,16.44,0,0,0-5.66-9.06,15.8,15.8,0,0,0-10-3.11,18.73,18.73,0,0,0-13.23,5.15Q650.53,72,648.4,81.14q-1.12,4.74-1.12,20.54v40.6H621.89Z"/><path class="cls-1" d="M750.71,3.78H776.1v37.3h15.07V62.93H776.1v79.35H750.71V62.93h-13V41.08h13Z"/><path class="cls-1" d="M826.09-.6a15.55,15.55,0,0,1,11.45,4.84A16.08,16.08,0,0,1,842.31,16a15.87,15.87,0,0,1-4.72,11.58,15.34,15.34,0,0,1-11.32,4.79,15.6,15.6,0,0,1-11.55-4.88A16.35,16.35,0,0,1,810,15.59a15.57,15.57,0,0,1,4.73-11.44A15.53,15.53,0,0,1,826.09-.6Z"/><rect class="cls-1" x="813.39" y="41.08" width="25.39" height="101.2"/><path class="cls-1" d="M873.47,2h25.39V82.8l37.39-41.72h31.89l-43.59,48.5,48.81,52.7H941.83l-43-46.64v46.64H873.47Z"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3064.87 487.37"><defs><symbol id="a" viewBox="0 0 2865.3 437.72"><g style="isolation:isolate;"><path d="M238.73,125.38h76.4v304.5h-76.4v-32.18c-14.91,14.18-29.87,24.4-44.87,30.65-15,6.25-31.26,9.37-48.78,9.37-39.32,0-73.33-15.25-102.04-45.76C14.35,361.45,0,323.53,0,278.19s13.89-85.54,41.65-115.58c27.77-30.04,61.5-45.06,101.19-45.06,18.26,0,35.4,3.45,51.43,10.35,16.03,6.91,30.84,17.26,44.45,31.07v-33.58ZM158.41,188.07c-23.62,0-43.24,8.35-58.86,25.05-15.62,16.7-23.43,38.11-23.43,64.23s7.95,47.96,23.84,64.93c15.9,16.98,35.47,25.47,58.72,25.47s43.89-8.35,59.69-25.05c15.8-16.7,23.71-38.57,23.71-65.63s-7.9-47.95-23.71-64.37c-15.81-16.42-35.8-24.63-59.97-24.63Z" style="fill:#fd4b2d;"/><path d="M403.16,125.38h77.24v146.65c0,28.55,1.96,48.37,5.89,59.47,3.93,11.1,10.24,19.73,18.94,25.89,8.69,6.16,19.4,9.24,32.12,9.24s23.52-3.03,32.4-9.1c8.88-6.06,15.47-14.97,19.78-26.73,3.18-8.77,4.77-27.52,4.77-56.25V125.38h76.41v129.02c0,53.18-4.2,89.56-12.59,109.15-10.26,23.88-25.38,42.22-45.34,54.99-19.97,12.78-45.34,19.17-76.13,19.17-33.4,0-60.41-7.46-81.02-22.39-20.62-14.92-35.13-35.73-43.52-62.41-5.97-18.47-8.96-52.06-8.96-100.75v-126.78Z" style="fill:#fd4b2d;"/><path d="M796.76,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M999.76,7.84h75.85v148.33c14.93-12.88,29.95-22.53,45.06-28.97,15.11-6.44,30.41-9.65,45.9-9.65,30.23,0,55.7,10.45,76.41,31.34,17.73,18.1,26.59,44.69,26.59,79.76v201.23h-75.29v-133.5c0-35.27-1.68-59.15-5.04-71.65-3.36-12.5-9.09-21.83-17.21-27.99-8.12-6.15-18.15-9.23-30.09-9.23-15.49,0-28.78,5.13-39.88,15.39-11.11,10.26-18.8,24.26-23.09,41.98-2.24,9.14-3.36,30.04-3.36,62.69v122.3h-75.85V7.84Z" style="fill:#fd4b2d;"/><path d="M1688.63,299.74h-245.45c3.54,21.65,13.01,38.86,28.41,51.64,15.39,12.78,35.03,19.17,58.91,19.17,28.55,0,53.08-9.98,73.6-29.95l64.37,30.23c-16.05,22.77-35.26,39.6-57.65,50.52-22.39,10.91-48.98,16.37-79.76,16.37-47.77,0-86.67-15.06-116.71-45.2-30.04-30.13-45.06-67.87-45.06-113.21s14.97-85.03,44.92-115.73c29.95-30.69,67.49-46.04,112.65-46.04,47.95,0,86.95,15.35,116.99,46.04,30.04,30.69,45.06,71.23,45.06,121.61l-.28,14.55ZM1612.22,239.57c-5.05-16.98-15-30.79-29.86-41.42-14.86-10.63-32.1-15.95-51.72-15.95-21.3,0-40,5.98-56.07,17.91-10.09,7.47-19.44,20.62-28.03,39.46h165.68Z" style="fill:#fd4b2d;"/><path d="M1790.6,125.38h76.41v31.21c17.33-14.61,33.02-24.77,47.09-30.48,14.06-5.71,28.46-8.57,43.18-8.57,30.18,0,55.8,10.54,76.85,31.62,17.7,17.91,26.55,44.41,26.55,79.48v201.23h-75.57v-133.35c0-36.34-1.63-60.47-4.89-72.4-3.26-11.93-8.93-21.01-17.03-27.26-8.1-6.24-18.1-9.36-30.01-9.36-15.45,0-28.71,5.17-39.78,15.51-11.08,10.35-18.76,24.65-23.04,42.91-2.24,9.5-3.35,30.1-3.35,61.78v122.16h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2183.92,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M2416.46,0c13.39,0,24.88,4.85,34.46,14.55,9.58,9.7,14.38,21.46,14.38,35.27s-4.75,25.24-14.24,34.84c-9.49,9.61-20.84,14.41-34.04,14.41s-25.16-4.9-34.75-14.69c-9.58-9.79-14.37-21.69-14.37-35.68s4.74-24.91,14.23-34.43c9.49-9.51,20.93-14.27,34.33-14.27ZM2378.26,125.38h76.41v304.5h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2564.75,7.84h76.41v243.09l112.51-125.54h95.96l-131.17,145.94,146.86,158.56h-94.85l-129.3-140.34v140.34h-76.41V7.84Z" style="fill:#fd4b2d;"/></g></symbol></defs><use width="2865.3" height="437.72" transform="translate(99.78 24.83)" xlink:href="#a"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.0 KiB |
@@ -1 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><rect class="cls-1" x="546.66" y="275.34" width="34.99" height="99.97"/><rect class="cls-1" x="637.66" y="271.13" width="34.99" height="78.19"/><path class="cls-1" d="M127.64,385.31a127.57,127.57,0,0,0-112.13,66.9H74.82c26.27-22.67,64.42-29.28,92,0h62.8C205.11,419.06,168.36,385.31,127.64,385.31Z"/><path class="cls-1" d="M212.39,512.53C130.55,683.65-12.89,537.81,74.82,452.21H15.51C-31,533.33,33.3,642.73,127.64,640.24c73,0,133.2-108.3,133.2-127.46,0-8.47-11.78-34.33-31.2-60.57h-62.8C187.65,471.08,205.81,498.56,212.39,512.53Zm2.17-5h0Z"/><path class="cls-1" d="M999.94,274.11V725.89c0,86.58-70.42,157.06-157.05,157.06H776.22V729.12H457.88V883H391.22c-86.64,0-157.06-70.48-157.06-157.06V583.81H738.87V312.11H495.24V464.76H234.16V274.11a151.29,151.29,0,0,1,1.06-18,154.4,154.4,0,0,1,3.88-21.15c.58-2.23,1.23-4.46,1.88-6.64a13.66,13.66,0,0,1,.52-1.64c.36-1.12.71-2.17,1.06-3.23s.76-2.17,1.18-3.23c.47-1.23.88-2.41,1.35-3.58s1-2.35,1.47-3.53a159,159,0,0,1,14.27-26.49c.06-.06.12-.17.17-.23,1.41-2.06,2.88-4.11,4.41-6.17,1.29-1.7,2.58-3.35,3.88-5,1.52-1.82,3.11-3.7,4.69-5.46s3.12-3.47,4.76-5.11l.18-.18a36.53,36.53,0,0,1,2.64-2.64,159.75,159.75,0,0,1,18.68-15.63c1.76-1.29,3.64-2.52,5.52-3.76,2.11-1.35,4.23-2.64,6.4-3.93,4.11-2.41,8.28-4.64,12.63-6.64,1.35-.64,2.76-1.29,4.11-1.88a152.81,152.81,0,0,1,18.38-6.63c2.41-.71,4.82-1.35,7.29-1.94,1.17-.3,2.35-.59,3.58-.82a158.5,158.5,0,0,1,21.26-3.12l3.12-.17c.52,0,1-.06,1.52-.06,2.35-.12,4.76-.18,7.17-.18H842.89c2.4,0,4.81.06,7.16.18.53,0,1,.06,1.53.06l3.11.17A158.26,158.26,0,0,1,876,120.58c1.24.23,2.41.52,3.59.82,2.46.59,4.87,1.23,7.28,1.94A152.81,152.81,0,0,1,905.2,130c1.35.59,2.76,1.24,4.11,1.88,4.35,2,8.52,4.23,12.63,6.64,2.18,1.29,4.29,2.58,6.4,3.93,1.88,1.24,3.76,2.47,5.52,3.76a157.53,157.53,0,0,1,21.5,18.45c1.65,1.64,3.23,3.34,4.76,5.11s3.17,3.64,4.7,5.46c1.29,1.64,2.58,3.29,3.87,5,1.53,2.06,3,4.11,4.41,6.17.06.06.12.17.18.23a159.71,159.71,0,0,1,14.27,26.49c.47,1.18,1,2.35,1.47,3.53s.88,2.35,1.35,3.58c.41,1.06.82,2.11,1.17,3.23s.71,2.11,1.06,3.23a15.74,15.74,0,0,1,.53,1.64c.64,2.18,1.29,4.41,1.88,6.64a155.92,155.92,0,0,1,3.87,21.15A151.29,151.29,0,0,1,999.94,274.11Z"/><path class="cls-1" d="M973.27,186.59H260.84A157.05,157.05,0,0,1,391.2,117.07H842.9A157.08,157.08,0,0,1,973.27,186.59Z"/><path class="cls-1" d="M998.94,256.1H235.16a155.35,155.35,0,0,1,25.68-69.51H973.27A155.34,155.34,0,0,1,998.94,256.1Z"/><path class="cls-1" d="M1000,274.11v51.51H738.87V312.11H495.24v13.51H234.1V274.11a153.41,153.41,0,0,1,1.06-18H998.94A151.29,151.29,0,0,1,1000,274.11Z"/><rect class="cls-1" x="234.1" y="325.62" width="261.13" height="69.54"/><rect class="cls-1" x="738.87" y="325.62" width="261.13" height="69.54"/><rect class="cls-1" x="234.1" y="395.16" width="261.13" height="69.48"/><rect class="cls-1" x="738.87" y="395.16" width="261.13" height="69.48"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="c" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 1000"><defs><symbol id="a" viewBox="0 0 998.94 763.82"><path d="M829.67,0h-425.28c-93.1,0-169.27,76.17-169.27,169.27v425.28c0,93.1,76.17,169.27,169.27,169.27h50.18v-165.68h324.96v165.68h50.14c93.1,0,169.27-76.17,169.27-169.27V169.27C998.94,76.17,922.77,0,829.67,0ZM755.98,463.53H235.4v-114.49h268.96v-158.97h43.68v94.7h25.61v-94.7h30.88v69.64h25.61v-69.64h30.88v116.35h25.61v-116.35h43.68v158.97h25.69v114.49Z" style="fill:#fd4b2d;"/><g id="b"><path d="M237.36,342.19h-.02c-25.34-34.27-63.32-69.15-105.42-69.15-48.4.03-92.89,26.58-115.91,69.15-48.08,83.85,18.39,196.94,115.91,194.36,75.46,0,137.69-111.95,137.69-131.75,0-8.76-12.18-35.49-32.25-62.61ZM77.32,342.19c27.16-23.43,66.59-30.27,95.1,0h.02c21.51,19.51,40.28,47.91,47.08,62.35-84.6,176.88-232.87,26.13-142.2-62.35Z" style="fill:#fd4b2d;"/></g></symbol></defs><use width="998.94" height="763.82" transform="translate(1 117.03)" xlink:href="#a"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 17 KiB |
@@ -1 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 994.71 151.65"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><path class="cls-1" d="M284.72,50.4H305.5v82.84H284.72v-8.76a40.79,40.79,0,0,1-12.21,8.34,34.14,34.14,0,0,1-13.27,2.55q-16.05,0-27.76-12.45T219.77,92q0-19.18,11.33-31.45t27.53-12.26a34.94,34.94,0,0,1,14,2.82,38.32,38.32,0,0,1,12.1,8.45ZM262.87,67.45a21,21,0,0,0-16,6.82q-6.37,6.81-6.38,17.47T247,109.4a21,21,0,0,0,16,6.93,21.42,21.42,0,0,0,16.24-6.81q6.45-6.81,6.45-17.86,0-10.8-6.45-17.51A21.71,21.71,0,0,0,262.87,67.45Z"/><path class="cls-1" d="M335.8,50.4h21V90.29q0,11.65,1.6,16.18a14.16,14.16,0,0,0,5.16,7,14.76,14.76,0,0,0,8.74,2.51,15.25,15.25,0,0,0,8.81-2.48,14.49,14.49,0,0,0,5.38-7.27q1.31-3.57,1.3-15.3V50.4h20.79V85.5q0,21.69-3.43,29.69a32.32,32.32,0,0,1-12.33,15q-8.16,5.22-20.71,5.22-13.64,0-22.05-6.09a32.2,32.2,0,0,1-11.84-17q-2.43-7.55-2.43-27.41Z"/><path class="cls-1" d="M441.32,19.86H462.1V50.4h12.34V68.29H462.1v65H441.32V68.29H430.66V50.4h10.66Z"/><path class="cls-1" d="M495,18.42h20.63V58.77a47.41,47.41,0,0,1,12.26-7.88,31.62,31.62,0,0,1,12.49-2.63,28.13,28.13,0,0,1,20.78,8.53q7.23,7.4,7.24,21.7v54.75H547.9V96.92q0-14.4-1.37-19.49a13.6,13.6,0,0,0-4.68-7.62,13.19,13.19,0,0,0-8.18-2.51,15.43,15.43,0,0,0-10.85,4.19,22.14,22.14,0,0,0-6.28,11.42q-.91,3.72-.92,17v33.28H495Z"/><path class="cls-1" d="M680.84,97.83H614.06a22.25,22.25,0,0,0,7.73,14q6.29,5.22,16,5.21a27.7,27.7,0,0,0,20-8.14l17.51,8.22a41.31,41.31,0,0,1-15.68,13.74q-9.13,4.46-21.7,4.46-19.5,0-31.75-12.3T594,92.27q0-19,12.22-31.48t30.65-12.53q19.56,0,31.82,12.53t12.26,33.08ZM660.05,81.46a20.87,20.87,0,0,0-8.12-11.27,23.61,23.61,0,0,0-14.08-4.34,24.88,24.88,0,0,0-15.25,4.88q-4.11,3-7.62,10.73Z"/><path class="cls-1" d="M707,50.4H727.8v8.49a50.15,50.15,0,0,1,12.81-8.3,31.08,31.08,0,0,1,11.75-2.33,28.44,28.44,0,0,1,20.91,8.61q7.22,7.31,7.22,21.62v54.75H759.93V97q0-14.83-1.33-19.7A13.48,13.48,0,0,0,754,69.85a13,13,0,0,0-8.16-2.55A15.32,15.32,0,0,0,735,71.52a22.6,22.6,0,0,0-6.27,11.67q-.9,3.89-.91,16.81v33.24H707Z"/><path class="cls-1" d="M812.46,19.86h20.79V50.4h12.33V68.29H833.25v65H812.46V68.29H801.8V50.4h10.66Z"/><path class="cls-1" d="M874.16,16.29a12.74,12.74,0,0,1,9.38,3.95,13.18,13.18,0,0,1,3.91,9.6,13,13,0,0,1-3.87,9.48,12.6,12.6,0,0,1-9.27,3.92,12.73,12.73,0,0,1-9.45-4A13.39,13.39,0,0,1,861,29.53a12.78,12.78,0,0,1,3.87-9.36A12.71,12.71,0,0,1,874.16,16.29Z"/><rect class="cls-1" x="863.77" y="50.4" width="20.79" height="82.84"/><path class="cls-1" d="M913,18.42h20.78V84.55L964.34,50.4h26.11L954.76,90.1l40,43.14h-25.8L933.73,95.06v38.18H913Z"/><rect class="cls-1" x="107.1" y="34.93" width="6.37" height="18.2"/><rect class="cls-1" x="123.67" y="34.16" width="6.37" height="14.23"/><path class="cls-1" d="M30.83,55A23.23,23.23,0,0,0,10.41,67.13h10.8C26,63,32.94,61.8,38,67.13H49.39C44.93,61.09,38.24,55,30.83,55Z"/><path class="cls-1" d="M46.25,78.11c-14.89,31.15-41,4.6-25-11H10.41c-8.47,14.76,3.24,34.68,20.42,34.23,13.28,0,24.24-19.72,24.24-23.21,0-1.54-2.14-6.25-5.68-11H38A40.52,40.52,0,0,1,46.25,78.11Zm.4-.91Z"/><path class="cls-1" d="M189.62,34.71V117A28.62,28.62,0,0,1,161,145.54H148.89v-28H90.94v28H78.81A28.62,28.62,0,0,1,50.22,117V91.08h91.87V41.62H97.74V69.41H50.22V34.71a27.43,27.43,0,0,1,.19-3.29,27.09,27.09,0,0,1,.71-3.84c.1-.41.22-.82.34-1.21a2.13,2.13,0,0,1,.09-.3c.07-.21.13-.4.2-.59s.14-.4.21-.59.16-.44.25-.65.18-.43.26-.64a29.35,29.35,0,0,1,2.6-4.82l0-.05c.26-.37.53-.75.81-1.12s.47-.61.7-.91.57-.67.86-1,.56-.63.86-.93l0,0a4.53,4.53,0,0,1,.49-.49,29.23,29.23,0,0,1,3.4-2.84c.32-.24.66-.46,1-.68s.77-.49,1.17-.72a23.78,23.78,0,0,1,2.29-1.21l.75-.34a27.84,27.84,0,0,1,3.35-1.21c.44-.13.88-.24,1.33-.35a6.19,6.19,0,0,1,.65-.15,28.86,28.86,0,0,1,3.87-.57l.56,0h.28c.43,0,.87,0,1.31,0H161c.43,0,.87,0,1.3,0h.28l.56,0a29.25,29.25,0,0,1,3.88.57c.22,0,.43.09.65.15.45.11.88.22,1.32.35a27.23,27.23,0,0,1,3.35,1.21l.75.34a25.19,25.19,0,0,1,2.3,1.21c.39.23.78.47,1.16.72s.69.44,1,.68a29.23,29.23,0,0,1,3.91,3.36q.45.45.87.93c.29.32.57.66.85,1l.71.91c.28.37.54.75.8,1.12l0,.05a28.61,28.61,0,0,1,2.6,4.82l.27.64.24.65c.08.19.15.39.22.59l.19.59c0,.09.06.19.1.3.11.39.23.8.34,1.21a28.56,28.56,0,0,1,.7,3.84A27.42,27.42,0,0,1,189.62,34.71Z"/><path class="cls-1" d="M184.76,18.78H55.07A28.59,28.59,0,0,1,78.8,6.12H161A28.59,28.59,0,0,1,184.76,18.78Z"/><path class="cls-1" d="M189.43,31.43H50.4a28.29,28.29,0,0,1,4.67-12.65H184.76A28.17,28.17,0,0,1,189.43,31.43Z"/><path class="cls-1" d="M189.63,34.71v9.37H142.09V41.62H97.74v2.46H50.21V34.71a27.43,27.43,0,0,1,.19-3.29h139A27.42,27.42,0,0,1,189.63,34.71Z"/><rect class="cls-1" x="50.21" y="44.08" width="47.54" height="12.66"/><rect class="cls-1" x="142.09" y="44.08" width="47.54" height="12.66"/><rect class="cls-1" x="50.21" y="56.74" width="47.54" height="12.65"/><rect class="cls-1" x="142.09" y="56.74" width="47.54" height="12.65"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="i" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3767.3 592.89"><defs><symbol id="a" viewBox="0 0 998.94 763.82"><path d="M829.67,0h-425.28c-93.1,0-169.27,76.17-169.27,169.27v425.28c0,93.1,76.17,169.27,169.27,169.27h50.18v-165.68h324.96v165.68h50.14c93.1,0,169.27-76.17,169.27-169.27V169.27C998.94,76.17,922.77,0,829.67,0ZM755.98,463.53H235.4v-114.49h268.96v-158.97h43.68v94.7h25.61v-94.7h30.88v69.64h25.61v-69.64h30.88v116.35h25.61v-116.35h43.68v158.97h25.69v114.49Z" style="fill:#fd4b2d;"/><g id="b"><path d="M237.36,342.19h-.02c-25.34-34.27-63.32-69.15-105.42-69.15-48.4.03-92.89,26.58-115.91,69.15-48.08,83.85,18.39,196.94,115.91,194.36,75.46,0,137.69-111.95,137.69-131.75,0-8.76-12.18-35.49-32.25-62.61ZM77.32,342.19c27.16-23.43,66.59-30.27,95.1,0h.02c21.51,19.51,40.28,47.91,47.08,62.35-84.6,176.88-232.87,26.13-142.2-62.35Z" style="fill:#fd4b2d;"/></g></symbol><symbol id="c" viewBox="0 0 2865.3 437.72"><g style="isolation:isolate;"><path d="M238.73,125.38h76.4v304.5h-76.4v-32.18c-14.91,14.18-29.87,24.4-44.87,30.65-15,6.25-31.26,9.37-48.78,9.37-39.32,0-73.33-15.25-102.04-45.76C14.35,361.45,0,323.53,0,278.19s13.89-85.54,41.65-115.58c27.77-30.04,61.5-45.06,101.19-45.06,18.26,0,35.4,3.45,51.43,10.35,16.03,6.91,30.84,17.26,44.45,31.07v-33.58ZM158.41,188.07c-23.62,0-43.24,8.35-58.86,25.05-15.62,16.7-23.43,38.11-23.43,64.23s7.95,47.96,23.84,64.93c15.9,16.98,35.47,25.47,58.72,25.47s43.89-8.35,59.69-25.05c15.8-16.7,23.71-38.57,23.71-65.63s-7.9-47.95-23.71-64.37c-15.81-16.42-35.8-24.63-59.97-24.63Z" style="fill:#fd4b2d;"/><path d="M403.16,125.38h77.24v146.65c0,28.55,1.96,48.37,5.89,59.47,3.93,11.1,10.24,19.73,18.94,25.89,8.69,6.16,19.4,9.24,32.12,9.24s23.52-3.03,32.4-9.1c8.88-6.06,15.47-14.97,19.78-26.73,3.18-8.77,4.77-27.52,4.77-56.25V125.38h76.41v129.02c0,53.18-4.2,89.56-12.59,109.15-10.26,23.88-25.38,42.22-45.34,54.99-19.97,12.78-45.34,19.17-76.13,19.17-33.4,0-60.41-7.46-81.02-22.39-20.62-14.92-35.13-35.73-43.52-62.41-5.97-18.47-8.96-52.06-8.96-100.75v-126.78Z" style="fill:#fd4b2d;"/><path d="M796.76,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M999.76,7.84h75.85v148.33c14.93-12.88,29.95-22.53,45.06-28.97,15.11-6.44,30.41-9.65,45.9-9.65,30.23,0,55.7,10.45,76.41,31.34,17.73,18.1,26.59,44.69,26.59,79.76v201.23h-75.29v-133.5c0-35.27-1.68-59.15-5.04-71.65-3.36-12.5-9.09-21.83-17.21-27.99-8.12-6.15-18.15-9.23-30.09-9.23-15.49,0-28.78,5.13-39.88,15.39-11.11,10.26-18.8,24.26-23.09,41.98-2.24,9.14-3.36,30.04-3.36,62.69v122.3h-75.85V7.84Z" style="fill:#fd4b2d;"/><path d="M1688.63,299.74h-245.45c3.54,21.65,13.01,38.86,28.41,51.64,15.39,12.78,35.03,19.17,58.91,19.17,28.55,0,53.08-9.98,73.6-29.95l64.37,30.23c-16.05,22.77-35.26,39.6-57.65,50.52-22.39,10.91-48.98,16.37-79.76,16.37-47.77,0-86.67-15.06-116.71-45.2-30.04-30.13-45.06-67.87-45.06-113.21s14.97-85.03,44.92-115.73c29.95-30.69,67.49-46.04,112.65-46.04,47.95,0,86.95,15.35,116.99,46.04,30.04,30.69,45.06,71.23,45.06,121.61l-.28,14.55ZM1612.22,239.57c-5.05-16.98-15-30.79-29.86-41.42-14.86-10.63-32.1-15.95-51.72-15.95-21.3,0-40,5.98-56.07,17.91-10.09,7.47-19.44,20.62-28.03,39.46h165.68Z" style="fill:#fd4b2d;"/><path d="M1790.6,125.38h76.41v31.21c17.33-14.61,33.02-24.77,47.09-30.48,14.06-5.71,28.46-8.57,43.18-8.57,30.18,0,55.8,10.54,76.85,31.62,17.7,17.91,26.55,44.41,26.55,79.48v201.23h-75.57v-133.35c0-36.34-1.63-60.47-4.89-72.4-3.26-11.93-8.93-21.01-17.03-27.26-8.1-6.24-18.1-9.36-30.01-9.36-15.45,0-28.71,5.17-39.78,15.51-11.08,10.35-18.76,24.65-23.04,42.91-2.24,9.5-3.35,30.1-3.35,61.78v122.16h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2183.92,13.15h76.41v112.23h45.34v65.77h-45.34v238.73h-76.41v-238.73h-39.18v-65.77h39.18V13.15Z" style="fill:#fd4b2d;"/><path d="M2416.46,0c13.39,0,24.88,4.85,34.46,14.55,9.58,9.7,14.38,21.46,14.38,35.27s-4.75,25.24-14.24,34.84c-9.49,9.61-20.84,14.41-34.04,14.41s-25.16-4.9-34.75-14.69c-9.58-9.79-14.37-21.69-14.37-35.68s4.74-24.91,14.23-34.43c9.49-9.51,20.93-14.27,34.33-14.27ZM2378.26,125.38h76.41v304.5h-76.41V125.38Z" style="fill:#fd4b2d;"/><path d="M2564.75,7.84h76.41v243.09l112.51-125.54h95.96l-131.17,145.94,146.86,158.56h-94.85l-129.3-140.34v140.34h-76.41V7.84Z" style="fill:#fd4b2d;"/></g></symbol></defs><use width="998.94" height="763.82" transform="translate(28.54 36.14) scale(.68)" xlink:href="#a"/><use width="2865.3" height="437.72" transform="translate(802.22 67.81)" xlink:href="#c"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -63,7 +63,7 @@ const LogLevelColors = /** @type {const} */ ({
|
||||
* Creates a logger with the given prefix.
|
||||
*
|
||||
* @param {string} [prefix]
|
||||
* @param {...string} args
|
||||
* @param {...string[]} args
|
||||
* @returns {Logger}
|
||||
*
|
||||
*/
|
||||
|
||||
1848
web/package-lock.json
generated
@@ -127,14 +127,15 @@
|
||||
"@types/codemirror": "^5.60.17",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/guacamole-common-js": "^1.5.5",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/node": "^25.6.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"@typescript-eslint/utils": "^8.57.2",
|
||||
"@vitest/browser": "^4.1.5",
|
||||
"@vitest/browser-playwright": "^4.0.15",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260421.2",
|
||||
"@vitest/browser": "^4.1.6",
|
||||
"@vitest/browser-playwright": "^4.1.6",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"change-case": "^5.4.4",
|
||||
@@ -166,7 +167,7 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.2",
|
||||
"playwright": "^1.58.2",
|
||||
"playwright": "^1.60.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"pseudolocale": "^2.2.0",
|
||||
@@ -183,6 +184,7 @@
|
||||
"remark-mdx-frontmatter": "^5.2.0",
|
||||
"storybook": "^10.2.1",
|
||||
"style-mod": "^4.1.3",
|
||||
"stylelint": "^17.11.0",
|
||||
"trusted-types": "^2.0.0",
|
||||
"ts-pattern": "^5.9.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
@@ -190,8 +192,8 @@
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"vite": "^8.0.10",
|
||||
"vitest": "^4.1.1",
|
||||
"vite": "^8.0.12",
|
||||
"vitest": "^4.1.6",
|
||||
"webcomponent-qr-code": "^1.3.0",
|
||||
"wireit": "^0.14.12",
|
||||
"yaml": "^2.8.4"
|
||||
@@ -202,8 +204,7 @@
|
||||
"@esbuild/linux-x64": "^0.28.0",
|
||||
"@rollup/rollup-darwin-arm64": "^4.57.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "^4.57.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.57.1",
|
||||
"chromedriver": "^147.0.4"
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.57.1"
|
||||
},
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
@@ -260,10 +261,7 @@
|
||||
"command": "lit-analyzer src"
|
||||
},
|
||||
"lint:types": {
|
||||
"command": "tsc -p .",
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--max_old_space_size=8192"
|
||||
},
|
||||
"command": "tsgo -p .",
|
||||
"dependencies": [
|
||||
"build-locales"
|
||||
]
|
||||
@@ -292,10 +290,7 @@
|
||||
}
|
||||
},
|
||||
"tsc": {
|
||||
"command": "tsc -p .",
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--max_old_space_size=8192"
|
||||
},
|
||||
"command": "tsgo -p .",
|
||||
"dependencies": [
|
||||
"build-locales"
|
||||
]
|
||||
@@ -332,7 +327,8 @@
|
||||
"typescript": "$typescript"
|
||||
},
|
||||
"@mrmarble/djangoql-completion": {
|
||||
"lex": "$lex"
|
||||
"lex": "$lex",
|
||||
"lodash": "^4.18.1"
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"typescript": "$typescript"
|
||||
@@ -340,6 +336,9 @@
|
||||
"@typescript-eslint/parser": {
|
||||
"typescript": "$typescript"
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"typescript": "$typescript"
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"typescript": "$typescript"
|
||||
},
|
||||
@@ -354,6 +353,9 @@
|
||||
},
|
||||
"typescript-eslint": {
|
||||
"typescript": "$typescript"
|
||||
},
|
||||
"wireit": {
|
||||
"brace-expansion": "^1.1.14"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@goauthentik/tsconfig": "^1.0.9",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/node": "^25.6.2",
|
||||
"@types/semver": "^7.7.1",
|
||||
"semver": "^7.7.4",
|
||||
"typescript": "^6.0.3"
|
||||
|
||||
2
web/packages/lex/index.js
vendored
@@ -25,7 +25,7 @@
|
||||
*
|
||||
* @callback LexerAction
|
||||
* @this {Lexer}
|
||||
* @param {...string} match
|
||||
* @param {...string[]} match
|
||||
* @returns {Token | Token[] | null | void}
|
||||
*/
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ export const ROUTES: Route[] = [
|
||||
await import("#admin/policies/reputation/ReputationListPage");
|
||||
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
|
||||
}),
|
||||
new Route(new RegExp("^/identity/object-attributes$"), async () => {
|
||||
await import("#admin/object-attributes/ObjectAttributeListPage");
|
||||
return html`<ak-object-attribute-list></ak-object-attribute-list>`;
|
||||
}),
|
||||
new Route(new RegExp("^/identity/groups$"), async () => {
|
||||
await import("#admin/groups/GroupListPage");
|
||||
return html`<ak-group-list></ak-group-list>`;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import "#elements/CodeMirror";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/Radio";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { ObjectAttributeModelForm } from "#admin/object-attributes/renderAttributes";
|
||||
|
||||
import { ApplicationEntitlement, CoreApi } from "@goauthentik/api";
|
||||
|
||||
import YAML from "yaml";
|
||||
import { ApplicationEntitlement, CoreApi, ModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, html, TemplateResult } from "lit";
|
||||
@@ -18,7 +15,12 @@ import { customElement, property } from "lit/decorators.js";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
|
||||
@customElement("ak-application-entitlement-form")
|
||||
export class ApplicationEntitlementForm extends ModelForm<ApplicationEntitlement, string> {
|
||||
export class ApplicationEntitlementForm extends ObjectAttributeModelForm<
|
||||
ApplicationEntitlement,
|
||||
string
|
||||
> {
|
||||
public model = ModelEnum.AuthentikCoreApplicationentitlement;
|
||||
|
||||
async loadInstance(pk: string): Promise<ApplicationEntitlement> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsRetrieve({
|
||||
pbmUuid: pk,
|
||||
@@ -61,16 +63,7 @@ export class ApplicationEntitlementForm extends ModelForm<ApplicationEntitlement
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Attributes")} name="attributes">
|
||||
<ak-codemirror
|
||||
mode="yaml"
|
||||
value="${YAML.stringify(this.instance?.attributes ?? {})}"
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Set custom attributes using YAML or JSON.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
${this.renderObjectAttributes(this.objAttributes, this.instance)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,16 @@ import "#elements/forms/HorizontalFormElement";
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
|
||||
import { DeviceAccessGroup, DeviceAccessGroupRequest, EndpointsApi } from "@goauthentik/api";
|
||||
import { ObjectAttributeModelForm } from "#admin/object-attributes/renderAttributes";
|
||||
|
||||
import {
|
||||
DeviceAccessGroup,
|
||||
DeviceAccessGroupRequest,
|
||||
EndpointsApi,
|
||||
ModelEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
@@ -20,7 +26,11 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
* @prop {string} instancePk - The primary key of the instance to load.
|
||||
*/
|
||||
@customElement("ak-endpoints-device-access-groups-form")
|
||||
export class DeviceAccessGroupForm extends WithBrandConfig(ModelForm<DeviceAccessGroup, string>) {
|
||||
export class DeviceAccessGroupForm extends WithBrandConfig(
|
||||
ObjectAttributeModelForm<DeviceAccessGroup, string>,
|
||||
) {
|
||||
public model = ModelEnum.AuthentikEndpointsDeviceaccessgroup;
|
||||
|
||||
public static override verboseName = msg("Device Access Group");
|
||||
public static override verboseNamePlural = msg("Device Access Groups");
|
||||
|
||||
@@ -53,13 +63,14 @@ export class DeviceAccessGroupForm extends WithBrandConfig(ModelForm<DeviceAcces
|
||||
|
||||
protected override renderForm() {
|
||||
return html`<ak-text-input
|
||||
name="name"
|
||||
autocomplete="off"
|
||||
placeholder=${msg("Type a group name...")}
|
||||
label=${msg("Group Name")}
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
required
|
||||
></ak-text-input>`;
|
||||
name="name"
|
||||
autocomplete="off"
|
||||
placeholder=${msg("Type a group name...")}
|
||||
label=${msg("Group Name")}
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
required
|
||||
></ak-text-input>
|
||||
${this.renderObjectAttributes(this.objAttributes, this.instance)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import "#admin/groups/ak-group-member-table";
|
||||
import "#components/ak-switch-input";
|
||||
import "#components/ak-text-input";
|
||||
import "#elements/CodeMirror";
|
||||
import "#elements/ak-dual-select/ak-dual-select-provider";
|
||||
import "#elements/chips/Chip";
|
||||
import "#elements/chips/ChipGroup";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
import "#components/ak-text-input";
|
||||
import "#components/ak-switch-input";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { DataProvision, DualSelectPair } from "#elements/ak-dual-select/types";
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
|
||||
import { CoreApi, Group, RbacApi, RelatedGroup, Role } from "@goauthentik/api";
|
||||
import { ObjectAttributeModelForm } from "#admin/object-attributes/renderAttributes";
|
||||
|
||||
import YAML from "yaml";
|
||||
import { CoreApi, Group, ModelEnum, RbacApi, RelatedGroup, Role } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, TemplateResult } from "lit";
|
||||
@@ -30,7 +29,8 @@ export function rbacRolePair(item: Role): DualSelectPair {
|
||||
}
|
||||
|
||||
@customElement("ak-group-form")
|
||||
export class GroupForm extends ModelForm<Group, string> {
|
||||
export class GroupForm extends ObjectAttributeModelForm<Group, string> {
|
||||
public model = ModelEnum.AuthentikCoreGroup;
|
||||
static styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
css`
|
||||
@@ -144,16 +144,7 @@ export class GroupForm extends ModelForm<Group, string> {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Attributes")} name="attributes">
|
||||
<ak-codemirror
|
||||
mode="yaml"
|
||||
value="${YAML.stringify(this.instance?.attributes ?? {})}"
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Set custom attributes using YAML or JSON.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
${this.renderObjectAttributes(this.objAttributes, this.instance)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ export const createAdminSidebarEntries = (): readonly SidebarEntry[] => [
|
||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/object-attributes", msg("Object attributes")],
|
||||
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
|
||||
163
web/src/admin/object-attributes/ObjectAttributeForm.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import "#elements/forms/FormGroup";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/Radio";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
import "#components/ak-text-input";
|
||||
import "#components/ak-switch-input";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
|
||||
import {
|
||||
AdminApi,
|
||||
AdminModelsListRequest,
|
||||
App,
|
||||
CoreApi,
|
||||
ObjectAttribute,
|
||||
ObjectAttributeTypeEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-object-attribute-form")
|
||||
export class ObjectAttributeForm extends ModelForm<ObjectAttribute, string> {
|
||||
async loadInstance(pk: string): Promise<ObjectAttribute> {
|
||||
return await new CoreApi(DEFAULT_CONFIG).coreObjectAttributesRetrieve({
|
||||
attributeId: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated attribute.")
|
||||
: msg("Successfully created attribute.");
|
||||
}
|
||||
|
||||
async send(data: ObjectAttribute): Promise<ObjectAttribute> {
|
||||
data.regex = data.regex !== "" ? data.regex : undefined;
|
||||
if (this.instance?.pk) {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreObjectAttributesUpdate({
|
||||
attributeId: this.instance.pk,
|
||||
objectAttributeRequest: data,
|
||||
});
|
||||
}
|
||||
return new CoreApi(DEFAULT_CONFIG).coreObjectAttributesCreate({
|
||||
objectAttributeRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
//#region Renders
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html`<ak-text-input
|
||||
name="label"
|
||||
value="${this.instance?.label ?? ""}"
|
||||
label=${msg("Label")}
|
||||
placeholder=${msg("Type a human-readable name...")}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="key"
|
||||
value="${this.instance?.key ?? ""}"
|
||||
label=${msg("Key")}
|
||||
placeholder=${msg("Type a unique identifier...")}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="group"
|
||||
value="${this.instance?.group ?? ""}"
|
||||
label=${msg("Group")}
|
||||
placeholder=${msg("Type an optional group identifier...")}
|
||||
></ak-text-input>
|
||||
<ak-switch-input
|
||||
name="enabled"
|
||||
label=${msg("Enabled")}
|
||||
?checked=${this.instance?.enabled ?? true}
|
||||
help=${msg("Value of the attribute cannot be empty.")}
|
||||
></ak-switch-input>
|
||||
<ak-form-element-horizontal label=${msg("Type")} required name="type">
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("Text"),
|
||||
value: ObjectAttributeTypeEnum.Text,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg("Number"),
|
||||
value: ObjectAttributeTypeEnum.Number,
|
||||
},
|
||||
{
|
||||
label: msg("Boolean"),
|
||||
value: ObjectAttributeTypeEnum.Boolean,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.type}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label="Object type" name="objectType" required>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (): Promise<App[]> => {
|
||||
const args: AdminModelsListRequest = {
|
||||
filterHasAttributes: true,
|
||||
};
|
||||
return await new AdminApi(DEFAULT_CONFIG).adminModelsList(args);
|
||||
}}
|
||||
.renderElement=${(app: App): string => {
|
||||
return app.label;
|
||||
}}
|
||||
.value=${(app: App | undefined): string | undefined => {
|
||||
return app?.name;
|
||||
}}
|
||||
.selected=${(app: App): boolean => {
|
||||
return app.name === this.instance?.objectTypeObj.fullyQualifiedModel;
|
||||
}}
|
||||
>
|
||||
</ak-search-select>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group label=${msg("Validation")} open>
|
||||
<div class="pf-c-form">
|
||||
<ak-switch-input
|
||||
name="isRequired"
|
||||
label=${msg("Attribute is required")}
|
||||
?checked=${this.instance?.isRequired}
|
||||
help=${msg("Value of the attribute cannot be empty.")}
|
||||
></ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="isUnique"
|
||||
label=${msg("Attribute is unique")}
|
||||
?checked=${this.instance?.isUnique}
|
||||
help=${msg(
|
||||
"Value of the attribute must be unique across all instances of the selected object type.",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="isArray"
|
||||
label=${msg("Attribute is an array")}
|
||||
?checked=${this.instance?.isArray}
|
||||
help=${msg("Value can have multiple entries.")}
|
||||
></ak-switch-input>
|
||||
<ak-text-input
|
||||
name="regex"
|
||||
value="${this.instance?.regex ?? ""}"
|
||||
label=${msg("RegEx")}
|
||||
input-hint="code"
|
||||
placeholder=${msg("Enter a regex for validation...")}
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-object-attribute-form": ObjectAttributeForm;
|
||||
}
|
||||
}
|
||||
129
web/src/admin/object-attributes/ObjectAttributeListPage.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import "#admin/rbac/ObjectPermissionModal";
|
||||
import "#admin/object-attributes/ObjectAttributeForm";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
import "#components/ak-status-label";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { CoreApi, ObjectAttribute, ObjectAttributeTypeEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
export function objectAttributeTypeToLabel(type?: ObjectAttributeTypeEnum): string {
|
||||
if (!type) return "";
|
||||
switch (type) {
|
||||
case ObjectAttributeTypeEnum.Text:
|
||||
return msg("Text");
|
||||
case ObjectAttributeTypeEnum.Number:
|
||||
return msg("Number");
|
||||
case ObjectAttributeTypeEnum.Boolean:
|
||||
return msg("Boolean");
|
||||
}
|
||||
return msg("Unknown type");
|
||||
}
|
||||
|
||||
@customElement("ak-object-attribute-list")
|
||||
export class ObjectAttributeListPage extends TablePage<ObjectAttribute> {
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Object attributes");
|
||||
public pageDescription = "Configure attributes on objects such as users and groups.";
|
||||
public pageIcon = "pf-icon pf-icon-flavor";
|
||||
|
||||
protected override rowLabel(item: ObjectAttribute): string | null {
|
||||
return item.pk ?? null;
|
||||
}
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
@property()
|
||||
order = "key";
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<ObjectAttribute>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreObjectAttributesList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
[msg("Label"), "label"],
|
||||
[msg("Type"), "type"],
|
||||
[msg("Status"), "enabled"],
|
||||
[msg("Object type"), "object_type"],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Object Attribute(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
.metadata=${(item: ObjectAttribute) => {
|
||||
return [
|
||||
{ key: msg("Object type"), value: item.objectTypeObj.verboseNamePlural },
|
||||
{ key: msg("Label"), value: item.label },
|
||||
{ key: msg("Key"), value: item.key },
|
||||
];
|
||||
}}
|
||||
.delete=${(item: ObjectAttribute) => {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreObjectAttributesDestroy({
|
||||
attributeId: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${msg("Delete")}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("New Attribute")}</span>
|
||||
<ak-object-attribute-form slot="form"> </ak-object-attribute-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
}
|
||||
|
||||
row(item: ObjectAttribute): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<div>
|
||||
<div>${item.group}: ${item.label}</div>
|
||||
<code>${item.key}</code>
|
||||
</div>`,
|
||||
html`${objectAttributeTypeToLabel(item.type)}`,
|
||||
html`<ak-status-label ?good=${item.enabled} type="info"></ak-status-label>`,
|
||||
html`${item.objectTypeObj.verboseNamePlural}`,
|
||||
html`<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Attribute")}</span>
|
||||
<ak-object-attribute-form
|
||||
slot="form"
|
||||
.instancePk=${item.pk}
|
||||
></ak-object-attribute-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal> `,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-object-attribute-list": ObjectAttributeListPage;
|
||||
}
|
||||
}
|
||||
104
web/src/admin/object-attributes/renderAttributes.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import "#components/ak-text-input";
|
||||
import "#components/ak-switch-input";
|
||||
import "#components/ak-number-input";
|
||||
import "#elements/forms/FormGroup";
|
||||
import "#elements/CodeMirror/ak-codemirror";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { groupBy } from "#common/utils";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
|
||||
import { CoreApi, ModelEnum, ObjectAttribute, ObjectAttributeTypeEnum } from "@goauthentik/api";
|
||||
|
||||
import YAML from "yaml";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing } from "lit-html";
|
||||
import { state } from "lit/decorators.js";
|
||||
|
||||
export interface ObjectAttributeOptions {
|
||||
disableRawAttributes: boolean;
|
||||
}
|
||||
|
||||
export type AttributesMixin = {
|
||||
attributes?: { [key: string]: unknown };
|
||||
};
|
||||
|
||||
export abstract class ObjectAttributeModelForm<
|
||||
T extends object = object,
|
||||
PKT extends string | number = string | number,
|
||||
D = T,
|
||||
> extends ModelForm<T, PKT, D> {
|
||||
@state()
|
||||
objAttributes: ObjectAttribute[] = [];
|
||||
|
||||
public abstract model: ModelEnum;
|
||||
|
||||
async load() {
|
||||
const [app, model] = this.model.split(".");
|
||||
this.objAttributes = (
|
||||
await new CoreApi(DEFAULT_CONFIG).coreObjectAttributesList({
|
||||
objectTypeAppLabel: app,
|
||||
objectTypeModel: model,
|
||||
enabled: true,
|
||||
})
|
||||
).results;
|
||||
}
|
||||
|
||||
renderObjectAttributes(
|
||||
defs: ObjectAttribute[],
|
||||
obj: AttributesMixin | null,
|
||||
options?: ObjectAttributeOptions,
|
||||
) {
|
||||
const attrs = obj?.attributes || {};
|
||||
const renderSingleAttribute = (attr: ObjectAttribute) => {
|
||||
switch (attr.type) {
|
||||
case ObjectAttributeTypeEnum.Text:
|
||||
return html`<ak-text-input
|
||||
name="attributes.${attr.key}"
|
||||
label=${attr.label}
|
||||
autocomplete="off"
|
||||
.value="${attrs[attr.key]}"
|
||||
?required=${attr.isRequired}
|
||||
></ak-text-input>`;
|
||||
case ObjectAttributeTypeEnum.Number:
|
||||
return html`<ak-number-input
|
||||
name="attributes.${attr.key}"
|
||||
label=${attr.label}
|
||||
.value="${attrs[attr.key]}"
|
||||
?required=${attr.isRequired}
|
||||
></ak-number-input>`;
|
||||
case ObjectAttributeTypeEnum.Boolean:
|
||||
return html`<ak-switch-input
|
||||
name="attributes.${attr.key}"
|
||||
label=${attr.label}
|
||||
?checked=${attrs[attr.key]}
|
||||
?required=${attr.isRequired}
|
||||
>
|
||||
</ak-switch-input>`;
|
||||
}
|
||||
};
|
||||
return html`${groupBy(defs, (def) => def.group || "").map(([group, attrs]) => {
|
||||
if (group === "") {
|
||||
return html`${attrs.map((attr) => renderSingleAttribute(attr))}`;
|
||||
}
|
||||
return html`<ak-form-group label=${group}>
|
||||
<div class="pf-c-form">${attrs.map((attr) => renderSingleAttribute(attr))}</div>
|
||||
</ak-form-group>`;
|
||||
})}
|
||||
${options?.disableRawAttributes
|
||||
? nothing
|
||||
: html`<ak-form-group label=${msg("Advanced settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Attributes")} name="attributes">
|
||||
<ak-codemirror mode="yaml" value="${YAML.stringify(attrs)}">
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Set custom attributes using YAML or JSON.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>`}`;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import "#elements/ak-checkbox-group/ak-checkbox-group";
|
||||
import "#elements/Alert";
|
||||
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
|
||||
import "#elements/ak-dual-select/ak-dual-select-provider";
|
||||
import "#elements/forms/FormGroup";
|
||||
@@ -361,6 +362,14 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
||||
"Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.",
|
||||
)}
|
||||
</p>
|
||||
<ak-alert inline>
|
||||
${
|
||||
/* TODO: Remove this after 2024.6..or maybe later? */
|
||||
msg(
|
||||
"This restriction only applies to devices created in authentik 2024.4 or later.",
|
||||
)
|
||||
}
|
||||
</ak-alert>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
@@ -8,14 +8,14 @@ import "#components/ak-switch-input";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { RadioOption } from "#elements/forms/Radio";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { CoreApi, Group, RbacApi, Role, User, UserTypeEnum } from "@goauthentik/api";
|
||||
import { ObjectAttributeModelForm } from "#admin/object-attributes/renderAttributes";
|
||||
|
||||
import { CoreApi, Group, ModelEnum, RbacApi, Role, User, UserTypeEnum } from "@goauthentik/api";
|
||||
|
||||
import { match } from "ts-pattern";
|
||||
import YAML from "yaml";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { css, CSSResult, html } from "lit";
|
||||
@@ -44,8 +44,11 @@ const UserTypeOptions: readonly RadioOption<UserTypeEnum>[] = [
|
||||
description: html`${msg("Machine-to-machine authentication or other automations.")}`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("ak-user-form")
|
||||
export class UserForm extends ModelForm<User, number> {
|
||||
export class UserForm extends ObjectAttributeModelForm<User, number> {
|
||||
public model = ModelEnum.AuthentikCoreUser;
|
||||
|
||||
#coreAPI = new CoreApi(DEFAULT_CONFIG);
|
||||
#rbacAPI = new RbacApi(DEFAULT_CONFIG);
|
||||
|
||||
@@ -242,7 +245,6 @@ export class UserForm extends ModelForm<User, number> {
|
||||
)}
|
||||
>
|
||||
</ak-switch-input>
|
||||
|
||||
<ak-text-input
|
||||
name="path"
|
||||
label=${msg("Path")}
|
||||
@@ -263,18 +265,7 @@ export class UserForm extends ModelForm<User, number> {
|
||||
</p>`}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Attributes")} name="attributes">
|
||||
<ak-codemirror
|
||||
mode="yaml"
|
||||
value="${YAML.stringify(
|
||||
this.instance?.attributes ?? UserForm.defaultUserAttributes,
|
||||
)}"
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Set custom attributes using YAML or JSON.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
${this.renderObjectAttributes(this.objAttributes, this.instance)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,73 @@
|
||||
import Style from "./ak-drawer.css";
|
||||
import AKDrawer from "./ak-drawer.styles";
|
||||
import { DrawerResizeController } from "./drawerResizeController";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { classList } from "#elements/directives/class-list";
|
||||
|
||||
import { html } from "lit";
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||
export class DrawerExpandRequest extends Event {
|
||||
static readonly eventName = "ak-drawer-expand-request";
|
||||
expanded: boolean | null = null;
|
||||
|
||||
export class Drawer extends AKElement {
|
||||
static readonly styles = [PFDrawer, Style];
|
||||
constructor(expanded: boolean | null = null) {
|
||||
super(DrawerExpandRequest.eventName, { bubbles: true, composed: true });
|
||||
this.expanded = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
export class AkDrawer extends LitElement {
|
||||
static readonly styles = [AKDrawer];
|
||||
|
||||
@property({ type: Boolean })
|
||||
public resizable = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public open = false;
|
||||
public expanded = false;
|
||||
|
||||
render() {
|
||||
const open = [(this.open && "pf-m-expanded") || "pf-m-collapsed"];
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public resizing = false;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
public width = "33";
|
||||
|
||||
private resize = new DrawerResizeController(this);
|
||||
|
||||
onDrawerRequest = (ev: DrawerExpandRequest) => {
|
||||
ev.stopPropagation();
|
||||
this.expanded = ev.expanded === null ? !this.expanded : ev.expanded;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(DrawerExpandRequest.eventName, this.onDrawerRequest);
|
||||
}
|
||||
|
||||
public override render() {
|
||||
return html`
|
||||
<div class="pf-c-page__drawer">
|
||||
<div class="pf-c-drawer ${classList(open)}" id="flow-drawer">
|
||||
<div class="pf-c-drawer__main">
|
||||
<div class="pf-c-drawer__content">
|
||||
<div class="pf-c-drawer__body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="ak-v2-c-drawer" part="drawer">
|
||||
<div class="ak-v2-c-drawer__main" part="drawer-main">
|
||||
<div class="ak-v2-c-drawer__content" part="drawer-content">
|
||||
<div class="ak-v2-c-drawer__body" part="drawer-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="pf-c-drawer__panel pf-m-width-33">
|
||||
</div>
|
||||
<div class="ak-v2-c-drawer__panel" part="drawer-panel">
|
||||
${this.resizable
|
||||
? html` <div
|
||||
class="ak-v2-c-drawer__splitter"
|
||||
part="drawer-splitter"
|
||||
@mousedown=${this.resize.handleMouseDown}
|
||||
@keydown=${this.resize.handleKeyDown}
|
||||
@touchstart=${this.resize.handleTouchStart}
|
||||
role="separator"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ak-v2-c-drawer__splitter-handle"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</div>`
|
||||
: nothing}
|
||||
<div class="ak-v2-c-drawer__panel-main" part="drawer-panel-main">
|
||||
<slot name="panel"></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,4 +75,26 @@ export class Drawer extends AKElement {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public override updated(changed: PropertyValues<this>) {
|
||||
super.updated(changed);
|
||||
|
||||
// Simulate the behavior of summary/details, another disclosure pattern.
|
||||
const expanded = changed.get("expanded");
|
||||
if (expanded !== undefined) {
|
||||
const expandedMsg = (i: boolean) => (i ? "open" : "closed");
|
||||
this.dispatchEvent(
|
||||
new ToggleEvent("toggle", {
|
||||
newState: expandedMsg(this.expanded),
|
||||
oldState: expandedMsg(expanded),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface GlobalEventHandlersEventMap {
|
||||
[DrawerExpandRequest.eventName]: DrawerExpandRequest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
slot {
|
||||
display: content;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--pf-c-drawer__panel--BackgroundColor: var(--ak-dark-background);
|
||||
}
|
||||
|
||||
.pf-c-drawer {
|
||||
/* TODO: Revisit this after native <dialog> modals are implemented. */
|
||||
--pf-c-drawer__content--ZIndex: auto;
|
||||
}
|
||||
|
||||
.pf-c-drawer__body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.pf-c-drawer__content {
|
||||
--pf-c-drawer__content--BackgroundColor: transparent;
|
||||
}
|
||||
|
||||
.pf-c-drawer {
|
||||
.pf-c-drawer__panel {
|
||||
background-color: var(--pf-c-drawer__panel--BackgroundColor);
|
||||
|
||||
transition-behavior: allow-discrete;
|
||||
|
||||
gap: var(--pf-global--spacer--sm);
|
||||
|
||||
@media (width > 768px) {
|
||||
flex-flow: row;
|
||||
|
||||
.pf-c-drawer__panel_content {
|
||||
flex: 1 1 auto;
|
||||
max-width: 33dvw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
web/src/elements/ak-drawer/ak-drawer.root.css
Normal file
@@ -0,0 +1,141 @@
|
||||
/* ----------- CSS Custom Properties for DRAWER --------------------------- */
|
||||
|
||||
:root {
|
||||
--ak-v2-c-drawer__content--FlexBasis: 100%;
|
||||
--ak-v2-c-drawer__content--BackgroundColor: var(--ak-v2-global--ContentSurface);
|
||||
--ak-v2-c-drawer__content--ZIndex: var(--ak-v2-global--ZIndex--xs, auto);
|
||||
--ak-v2-c-drawer__panel--MinWidth: 50%;
|
||||
--ak-v2-c-drawer__panel--MaxHeight: auto;
|
||||
--ak-v2-c-drawer__panel--ZIndex: var(--ak-v2-global--ZIndex--sm);
|
||||
--ak-v2-c-drawer__panel--BackgroundColor: var(--ak-v2-global--ContentSurface);
|
||||
--ak-v2-c-drawer__panel--TransitionDuration: var(--ak-v2-global--TransitionDuration);
|
||||
--ak-v2-c-drawer__panel--TransitionProperty: margin, transform, box-shadow, flex-basis;
|
||||
--ak-v2-c-drawer__panel--FlexBasis: 100%;
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis--min: 1.5rem;
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis--max: 100%;
|
||||
--ak-v2-c-drawer__panel--xl--MinWidth: 28.125rem;
|
||||
--ak-v2-c-drawer__panel--xl--FlexBasis: 28.125rem;
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--md--MinHeight: 50%;
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--xl--MinHeight: 18.75rem;
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--xl--FlexBasis: 18.75rem;
|
||||
--ak-v2-c-drawer__panel--m-resizable--FlexDirection: row;
|
||||
--ak-v2-c-drawer__panel--m-resizable--md--FlexBasis--min: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical--Width
|
||||
);
|
||||
--ak-v2-c-drawer__panel--m-resizable--MinWidth: 1.5rem;
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--FlexDirection: column;
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--md--FlexBasis--min: 1.5rem;
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--MinHeight: 1.5rem;
|
||||
--ak-v2-c-drawer__splitter--Height: 0.5625rem;
|
||||
--ak-v2-c-drawer__splitter--Width: 100%;
|
||||
--ak-v2-c-drawer__splitter--BackgroundColor: var(--ak-v2-global--ContentSurface);
|
||||
--ak-v2-c-drawer__splitter--Cursor: row-resize;
|
||||
--ak-v2-c-drawer__splitter--m-vertical--Height: 100%;
|
||||
--ak-v2-c-drawer__splitter--m-vertical--Width: 0.5625rem;
|
||||
--ak-v2-c-drawer__splitter--m-vertical--Cursor: col-resize;
|
||||
--ak-v2-c-drawer--m-inline__splitter--focus--OutlineOffset: -0.0625rem;
|
||||
--ak-v2-c-drawer__splitter--after--BorderColor: var(--ak-v2-global--BorderColor--100);
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base: var(--ak-v2-global--BorderWidth--sm);
|
||||
--ak-v2-c-drawer__splitter--after--BorderTopWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--after--BorderRightWidth: var(
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderBottomWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: 0;
|
||||
--ak-v2-c-drawer--m-panel-left__splitter--after--BorderLeftWidth: var(
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base
|
||||
);
|
||||
--ak-v2-c-drawer--m-panel-bottom__splitter--after--BorderBottomWidth: var(
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base
|
||||
);
|
||||
--ak-v2-c-drawer--m-inline__splitter--m-vertical--Width: 0.625rem;
|
||||
--ak-v2-c-drawer--m-inline__splitter-handle--Left: 50%;
|
||||
--ak-v2-c-drawer--m-inline__splitter--after--BorderRightWidth: var(
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base
|
||||
);
|
||||
--ak-v2-c-drawer--m-inline__splitter--after--BorderLeftWidth: var(
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base
|
||||
);
|
||||
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--Height: 0.625rem;
|
||||
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter-handle--Top: 50%;
|
||||
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--after--BorderTopWidth: var(
|
||||
--ak-v2-c-drawer__splitter--after--border-width--base
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--Top: 50%;
|
||||
--ak-v2-c-drawer__splitter-handle--Left: calc(
|
||||
50% - var(--ak-v2-c-drawer__splitter--after--border-width--base)
|
||||
);
|
||||
--ak-v2-c-drawer--m-panel-left__splitter-handle--Left: 50%;
|
||||
--ak-v2-c-drawer--m-panel-bottom__splitter-handle--Top: calc(
|
||||
50% - var(--ak-v2-c-drawer__splitter--after--border-width--base)
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderColor: var(--ak-v2-global--Color--200);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderTopWidth: var(--ak-v2-global--BorderWidth--sm);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderRightWidth: 0;
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderBottomWidth: var(
|
||||
--ak-v2-global--BorderWidth--sm
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderLeftWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--hover__splitter-handle--after--BorderColor: var(
|
||||
--ak-v2-global--Color--100
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--focus__splitter-handle--after--BorderColor: var(
|
||||
--ak-v2-global--Color--100
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderTopWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderRightWidth: var(
|
||||
--ak-v2-global--BorderWidth--sm
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderBottomWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderLeftWidth: var(
|
||||
--ak-v2-global--BorderWidth--sm
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--Width: 0.75rem;
|
||||
--ak-v2-c-drawer__splitter-handle--after--Height: 0.25rem;
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Width: 0.25rem;
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Height: 0.75rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
:root {
|
||||
--ak-v2-c-drawer__panel--MinWidth: var(--ak-v2-c-drawer__panel--xl--MinWidth);
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--ak-v2-c-drawer__panel--BoxShadow: none;
|
||||
--ak-v2-c-drawer--m-expanded--m-panel-bottom__panel--BoxShadow: var(
|
||||
--ak-v2-global--BoxShadow--lg-top
|
||||
);
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: var(--ak-v2-global--BoxShadow--lg-left);
|
||||
}
|
||||
|
||||
:root {
|
||||
--ak-v2-c-drawer--m-expanded--m-panel-left__panel--BoxShadow: var(
|
||||
--ak-v2-global--BoxShadow--lg-right
|
||||
);
|
||||
}
|
||||
|
||||
:root {
|
||||
--ak-v2-c-drawer__panel--after--Width: var(--ak-v2-global--BorderWidth--sm);
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--after--Height: var(--ak-v2-global--BorderWidth--sm);
|
||||
--ak-v2-c-drawer__panel--after--BackgroundColor: transparent;
|
||||
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor: var(
|
||||
--ak-v2-global--BorderColor--100
|
||||
);
|
||||
--ak-v2-c-drawer--m-inline__panel--PaddingLeft: var(--ak-v2-c-drawer__panel--after--Width);
|
||||
--ak-v2-c-drawer--m-panel-left--m-inline__panel--PaddingRight: var(
|
||||
--ak-v2-c-drawer__panel--after--Width
|
||||
);
|
||||
--ak-v2-c-drawer--m-panel-bottom--m-inline__panel--PaddingTop: var(
|
||||
--ak-v2-c-drawer__panel--after--Width
|
||||
);
|
||||
}
|
||||
|
||||
html[data-theme="dark"],
|
||||
.ak-t-dark,
|
||||
.pf-t-dark {
|
||||
--ak-v2-c-drawer__panel--BackgroundColor: var(--ak-v2-global--ContentSurface);
|
||||
--ak-v2-c-drawer__splitter--BackgroundColor: transparent;
|
||||
}
|
||||
151
web/src/elements/ak-drawer/ak-drawer.stories.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import "./ak-drawer";
|
||||
|
||||
import { DrawerExpandRequest } from "./ak-drawer.component";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/web-components-vite";
|
||||
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
const toggle = (e: Event) => {
|
||||
const button = e.target as HTMLButtonElement;
|
||||
button.dispatchEvent(new DrawerExpandRequest());
|
||||
};
|
||||
|
||||
const contentBlock = html`
|
||||
<div style="padding: 1rem;">
|
||||
<h2>Main Content</h2>
|
||||
<p><button @click=${toggle}>Toggle Drawer</button></p>
|
||||
<p>
|
||||
This is the drawer's main: fill it by inserting slotted content without a slot name.
|
||||
This is the part that stays visible most of the time.
|
||||
</p>
|
||||
<p>
|
||||
Macaroon lollipop croissant sweet biscuit croissant chocolate cake. Cake cake pastry
|
||||
soufflé pudding. Tiramisu lollipop chocolate cake toffee oat cake muffin topping tootsie
|
||||
roll. Carrot cake bonbon chupa chups sugar plum fruitcake. Brownie sweet halvah oat cake
|
||||
cheesecake topping chocolate. Wafer macaroon topping lollipop powder cupcake sugar plum
|
||||
donut. Muffin wafer icing danish jelly-o bonbon. Powder shortbread brownie caramels
|
||||
tootsie roll dragée liquorice. Cake lemon drops powder danish toffee.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const panelBlock = html`
|
||||
<style>
|
||||
[slot="panel"] {
|
||||
padding: 1rem;
|
||||
background-color: var(--pf-v5-global--BackgroundColor--200, #f0f0f0);
|
||||
}
|
||||
</style>
|
||||
<div slot="panel">
|
||||
<h3>Panel Content</h3>
|
||||
<p>This is the side panel. This is where you put the secondary information.</p>
|
||||
<ul>
|
||||
<li>
|
||||
Seasonal, steamed, con panna and rich ut aged cup decaffeinated single origin con
|
||||
panna bar
|
||||
</li>
|
||||
<li>Skinny mazagran whipped, black iced beans carajillo eu cream</li>
|
||||
<li>Americano pumpkin spice milk ristretto caffeine single shot</li>
|
||||
</ul>
|
||||
<p><button @click=${toggle}>Toggle Drawer</button></p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
interface DrawerProps {
|
||||
expanded?: boolean;
|
||||
inline?: boolean;
|
||||
static?: boolean;
|
||||
resizable?: boolean;
|
||||
width?: string;
|
||||
position?: string;
|
||||
content?: TemplateResult;
|
||||
panel?: TemplateResult;
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "Components/Drawer",
|
||||
component: "ak-drawer",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(story) =>
|
||||
html`<div style="min-height: 400px; border: 1px solid #d2d2d2; overflow: hidden;">
|
||||
${story()}
|
||||
</div>`,
|
||||
],
|
||||
argTypes: {
|
||||
expanded: { control: "boolean" },
|
||||
position: {
|
||||
control: { type: "select" },
|
||||
options: ["right", "left", "bottom"],
|
||||
},
|
||||
inline: { control: "boolean" },
|
||||
static: { control: "boolean" },
|
||||
resizable: { control: "boolean" },
|
||||
width: {
|
||||
control: { type: "select" },
|
||||
options: ["25", "33", "50", "66", "75", "100"],
|
||||
},
|
||||
},
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj;
|
||||
|
||||
const Template: Story = {
|
||||
args: {
|
||||
expanded: false,
|
||||
inline: false,
|
||||
static: false,
|
||||
resizable: false,
|
||||
width: undefined,
|
||||
position: undefined,
|
||||
content: contentBlock,
|
||||
panel: panelBlock,
|
||||
},
|
||||
render: (args) => {
|
||||
return html` <ak-drawer
|
||||
?expanded=${args.expanded}
|
||||
?inline=${args.inline}
|
||||
?resizable=${args.resizable}
|
||||
position=${ifDefined(args.position)}
|
||||
width=${ifDefined(args.width)}
|
||||
>
|
||||
${args.content} ${args.panel}
|
||||
</ak-drawer>`;
|
||||
},
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => html` <ak-drawer> ${contentBlock} ${panelBlock} </ak-drawer> `,
|
||||
};
|
||||
|
||||
export const story = (args: DrawerProps = {}, name?: string): Story => ({
|
||||
...Template,
|
||||
...(name ? { name } : {}),
|
||||
args: {
|
||||
...Template.args,
|
||||
...args,
|
||||
},
|
||||
});
|
||||
|
||||
export const Expanded: Story = story({ expanded: true });
|
||||
|
||||
export const PanelLeft: Story = story({ expanded: true, position: "left" });
|
||||
|
||||
export const PanelBottom = story({ expanded: true, position: "bottom" });
|
||||
|
||||
export const Inline = story({ expanded: true, inline: true });
|
||||
|
||||
export const Static = story({ expanded: true, static: true });
|
||||
|
||||
export const Resizable = story({ expanded: true, resizable: true });
|
||||
|
||||
export const ResizableLeft = story({ expanded: true, resizable: true, position: "left" });
|
||||
|
||||
export const ResizableBottom = story({ expanded: true, resizable: true, position: "bottom" });
|
||||
|
||||
export const CustomWidth = story({ expanded: true, width: "33" });
|
||||
|
||||
export const ResponsiveWidth = story({ expanded: true, width: "75-on-xl" });
|
||||
914
web/src/elements/ak-drawer/ak-drawer.styles.ts
Normal file
@@ -0,0 +1,914 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
:host([position="bottom"]) .ak-v2-c-drawer {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
slot {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
:host([inline]:not([no-border])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([inline]:not([resizable])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([static]:not([no-border])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([static]:not([resizable])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
padding-inline-start: var(--ak-v2-c-drawer--m-inline__panel--PaddingLeft);
|
||||
}
|
||||
|
||||
:host([position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
order: 0;
|
||||
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([position="left"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
:host([position="bottom"]) .ak-v2-c-drawer__main {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:host(:not([inline], [static])) .ak-v2-c-drawer__main {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host(:not([inline], [static])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 0;
|
||||
max-width: var(--ak-v2-c-drawer__panel--FlexBasis);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host(:not([inline], [static]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([expanded]:not([inline], [static])) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([position="left"]:not([inline], [static]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
inset-inline-end: auto;
|
||||
inset-inline-start: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([position="left"]:not([inline], [static]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([expanded][position="left"]:not([inline], [static]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([position="bottom"]:not([inline], [static]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
inset-inline-end: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-block-start: auto;
|
||||
inset-block-end: 0;
|
||||
max-width: none;
|
||||
max-height: var(--ak-v2-c-drawer__panel--FlexBasis);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
:host([position="bottom"][expanded]:not([inline], [static]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
:host([class*="pf-m-resizing"]) {
|
||||
--ak-v2-c-drawer__panel--TransitionProperty: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host([class*="pf-m-resizing"]) .ak-v2-c-drawer__splitter {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__content,
|
||||
.ak-v2-c-drawer__panel,
|
||||
.ak-v2-c-drawer__panel-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
overflow: auto;
|
||||
--ak-v2-c-drawer__content--BackgroundColor: transparent;
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__content {
|
||||
z-index: var(--ak-v2-c-drawer__content--ZIndex);
|
||||
flex-basis: var(--ak-v2-c-drawer__content--FlexBasis);
|
||||
order: 0;
|
||||
background-color: var(--ak-v2-c-drawer__content--BackgroundColor);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__panel {
|
||||
position: relative;
|
||||
z-index: var(--ak-v2-c-drawer__panel--ZIndex);
|
||||
flex-basis: var(--ak-v2-c-drawer__panel--FlexBasis);
|
||||
order: 1;
|
||||
max-height: var(--ak-v2-c-drawer__panel--MaxHeight);
|
||||
gap: var(--ak-v2-global--spacer--sm);
|
||||
overflow: auto;
|
||||
background-color: var(--ak-v2-c-drawer__panel--BackgroundColor);
|
||||
box-shadow: var(--ak-v2-c-drawer__panel--BoxShadow);
|
||||
transition-duration: var(--ak-v2-c-drawer__panel--TransitionDuration);
|
||||
transition-property: var(--ak-v2-c-drawer__panel--TransitionProperty);
|
||||
transition-behavior: allow-discrete;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__panel::after {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: 0;
|
||||
width: var(--ak-v2-c-drawer__panel--after--Width);
|
||||
height: 100%;
|
||||
content: "";
|
||||
background-color: var(--ak-v2-c-drawer__panel--after--BackgroundColor);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer__panel--FlexBasis: max(
|
||||
var(--ak-v2-c-drawer__panel--md--FlexBasis--min),
|
||||
min(
|
||||
var(--ak-v2-c-drawer__panel--md--FlexBasis),
|
||||
var(--ak-v2-c-drawer__panel--md--FlexBasis--max)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
:host(:not([width])) .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: var(--ak-v2-c-drawer__panel--xl--FlexBasis);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
:host([position="bottom"]) .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--xl--FlexBasis
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
:where(
|
||||
:host(:not([position])),
|
||||
:host([position="left"]),
|
||||
:host([position="right"]),
|
||||
:host([position="start"]),
|
||||
:host([position="end"])
|
||||
)
|
||||
.ak-v2-c-drawer__splitter {
|
||||
--ak-v2-c-drawer__splitter--Height: var(--ak-v2-c-drawer__splitter--m-vertical--Height);
|
||||
--ak-v2-c-drawer__splitter--Width: var(--ak-v2-c-drawer__splitter--m-vertical--Width);
|
||||
--ak-v2-c-drawer__splitter--Cursor: var(--ak-v2-c-drawer__splitter--m-vertical--Cursor);
|
||||
--ak-v2-c-drawer__splitter-handle--after--Width: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Width
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--Height: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--Height
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderTopWidth: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderTopWidth
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderRightWidth: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderRightWidth
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderBottomWidth: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderBottomWidth
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderLeftWidth: var(
|
||||
--ak-v2-c-drawer__splitter--m-vertical__splitter-handle--after--BorderLeftWidth
|
||||
);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter {
|
||||
position: relative;
|
||||
display: none;
|
||||
width: var(--ak-v2-c-drawer__splitter--Width);
|
||||
height: var(--ak-v2-c-drawer__splitter--Height);
|
||||
cursor: var(--ak-v2-c-drawer__splitter--Cursor);
|
||||
background-color: var(--ak-v2-c-drawer__splitter--BackgroundColor);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter:hover {
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderColor: var(
|
||||
--ak-v2-c-drawer__splitter--hover__splitter-handle--after--BorderColor
|
||||
);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter:focus {
|
||||
--ak-v2-c-drawer__splitter-handle--after--BorderColor: var(
|
||||
--ak-v2-c-drawer__splitter--focus__splitter-handle--after--BorderColor
|
||||
);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter::after {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-block-end: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
content: "";
|
||||
border: solid var(--ak-v2-c-drawer__splitter--after--BorderColor);
|
||||
border-block-start-width: var(--ak-v2-c-drawer__splitter--after--BorderTopWidth);
|
||||
border-block-end-width: var(--ak-v2-c-drawer__splitter--after--BorderBottomWidth);
|
||||
border-inline-start-width: var(--ak-v2-c-drawer__splitter--after--BorderLeftWidth);
|
||||
border-inline-end-width: var(--ak-v2-c-drawer__splitter--after--BorderRightWidth);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter-handle {
|
||||
position: absolute;
|
||||
inset-block-start: var(--ak-v2-c-drawer__splitter-handle--Top);
|
||||
inset-inline-start: var(--ak-v2-c-drawer__splitter-handle--Left);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"]) .ak-v2-c-drawer__splitter-handle {
|
||||
transform: translate(calc(-50% * var(--ak-v2-global--inverse--multiplier)), -50%);
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter-handle::after {
|
||||
display: block;
|
||||
width: var(--ak-v2-c-drawer__splitter-handle--after--Width);
|
||||
height: var(--ak-v2-c-drawer__splitter-handle--after--Height);
|
||||
content: "";
|
||||
border-color: var(--ak-v2-c-drawer__splitter-handle--after--BorderColor);
|
||||
border-style: solid;
|
||||
border-block-start-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderTopWidth);
|
||||
border-block-end-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderBottomWidth);
|
||||
border-inline-start-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderLeftWidth);
|
||||
border-inline-end-width: var(--ak-v2-c-drawer__splitter-handle--after--BorderRightWidth);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
:host {
|
||||
min-width: var(--ak-v2-c-drawer__panel--MinWidth);
|
||||
}
|
||||
|
||||
:host([expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
box-shadow: var(--ak-v2-c-drawer--m-expanded__panel--BoxShadow);
|
||||
}
|
||||
|
||||
:host([expanded][resizable]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis--min: var(
|
||||
--ak-v2-c-drawer__panel--m-resizable--md--FlexBasis--min
|
||||
);
|
||||
flex-direction: var(--ak-v2-c-drawer__panel--m-resizable--FlexDirection);
|
||||
min-width: var(--ak-v2-c-drawer__panel--m-resizable--MinWidth);
|
||||
}
|
||||
|
||||
:host([expanded][resizable]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel::after {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
:host([expanded][resizable])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel
|
||||
> .ak-v2-c-drawer__splitter {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:host([expanded][resizable])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel
|
||||
> .ak-v2-c-drawer__panel-main {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
:host([position="left"]) {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: var(
|
||||
--ak-v2-c-drawer--m-expanded--m-panel-left__panel--BoxShadow
|
||||
);
|
||||
}
|
||||
|
||||
:host([position="left"][inline])
|
||||
> .ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable),
|
||||
:host([position="left"][static])
|
||||
> .ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable) {
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: var(--ak-v2-c-drawer--m-panel-left--m-inline__panel--PaddingRight);
|
||||
}
|
||||
|
||||
:host([position="left"][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([position="left"][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel::after {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
:host([position="left"][expanded][resizable])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel
|
||||
> .ak-v2-c-drawer__splitter {
|
||||
--ak-v2-c-drawer__splitter-handle--Left: var(
|
||||
--ak-v2-c-drawer--m-panel-left__splitter-handle--Left
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderRightWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: var(
|
||||
--ak-v2-c-drawer--m-panel-left__splitter--after--BorderLeftWidth
|
||||
);
|
||||
order: 1;
|
||||
}
|
||||
|
||||
:host([position="bottom"]) {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: var(
|
||||
--ak-v2-c-drawer--m-expanded--m-panel-bottom__panel--BoxShadow
|
||||
);
|
||||
--ak-v2-c-drawer__panel--MaxHeight: 100%;
|
||||
--ak-v2-c-drawer__panel--FlexBasis--min: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--FlexBasis--min
|
||||
);
|
||||
min-width: auto;
|
||||
min-height: var(--ak-v2-c-drawer--m-panel-bottom__panel--md--MinHeight);
|
||||
}
|
||||
|
||||
:host([position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel::after {
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: auto;
|
||||
width: 100%;
|
||||
height: var(--ak-v2-c-drawer--m-panel-bottom__panel--after--Height);
|
||||
}
|
||||
|
||||
:host([position="bottom"][resizable]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis--min: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--md--FlexBasis--min
|
||||
);
|
||||
--ak-v2-c-drawer__panel--m-resizable--FlexDirection: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--FlexDirection
|
||||
);
|
||||
--ak-v2-c-drawer__panel--m-resizable--MinWidth: 0;
|
||||
min-height: var(--ak-v2-c-drawer--m-panel-bottom__panel--m-resizable--MinHeight);
|
||||
}
|
||||
|
||||
:host([position="bottom"][resizable])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel
|
||||
> .ak-v2-c-drawer__splitter {
|
||||
--ak-v2-c-drawer__splitter-handle--Top: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__splitter-handle--Top
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderRightWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--after--BorderBottomWidth: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__splitter--after--BorderBottomWidth
|
||||
);
|
||||
}
|
||||
|
||||
:host([position="left"][inline]:not([no-border], [resizable]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable),
|
||||
:host([position="left"][static]:not([no-border], [resizable]))
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel:not(.pf-m-no-border, .pf-m-resizable) {
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: var(--ak-v2-c-drawer--m-panel-left--m-inline__panel--PaddingRight);
|
||||
}
|
||||
|
||||
:host([inline][resizable])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel
|
||||
> .ak-v2-c-drawer__splitter {
|
||||
--ak-v2-c-drawer__splitter--m-vertical--Width: var(
|
||||
--ak-v2-c-drawer--m-inline__splitter--m-vertical--Width
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--Left: var(
|
||||
--ak-v2-c-drawer--m-inline__splitter-handle--Left
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderRightWidth: var(
|
||||
--ak-v2-c-drawer--m-inline__splitter--after--BorderRightWidth
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: var(
|
||||
--ak-v2-c-drawer--m-inline__splitter--after--BorderLeftWidth
|
||||
);
|
||||
outline-offset: var(--ak-v2-c-drawer--m-inline__splitter--focus--OutlineOffset);
|
||||
}
|
||||
|
||||
:host([position="bottom"][inline][resizable])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel
|
||||
> .ak-v2-c-drawer__splitter {
|
||||
--ak-v2-c-drawer__splitter--Height: var(
|
||||
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--Height
|
||||
);
|
||||
--ak-v2-c-drawer__splitter-handle--Top: var(
|
||||
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter-handle--Top
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderTopWidth: var(
|
||||
--ak-v2-c-drawer--m-inline--m-panel-bottom__splitter--after--BorderTopWidth
|
||||
);
|
||||
--ak-v2-c-drawer__splitter--after--BorderRightWidth: 0;
|
||||
--ak-v2-c-drawer__splitter--after--BorderLeftWidth: 0;
|
||||
}
|
||||
|
||||
:host([no-panel-border]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
|
||||
}
|
||||
|
||||
.ak-v2-c-drawer__splitter {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
:host([width="25"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
|
||||
}
|
||||
|
||||
:host([width="33"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
|
||||
}
|
||||
|
||||
:host([width="50"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
|
||||
}
|
||||
|
||||
:host([width="66"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
|
||||
}
|
||||
|
||||
:host([width="75"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
|
||||
}
|
||||
|
||||
:host([width="100"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
:host([width="25-on-lg"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
|
||||
}
|
||||
|
||||
:host([width="33-on-lg"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
|
||||
}
|
||||
|
||||
:host([width="50-on-lg"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
|
||||
}
|
||||
|
||||
:host([width="66-on-lg"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
|
||||
}
|
||||
|
||||
:host([width="75-on-lg"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
|
||||
}
|
||||
|
||||
:host([width="100-on-lg"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
:host([width="25-on-xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
|
||||
}
|
||||
|
||||
:host([width="33-on-xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
|
||||
}
|
||||
|
||||
:host([width="50-on-xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
|
||||
}
|
||||
|
||||
:host([width="66-on-xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
|
||||
}
|
||||
|
||||
:host([width="75-on-xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
|
||||
}
|
||||
|
||||
:host([width="100-on-xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1450px) {
|
||||
:host([width="25-on-2xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 25%;
|
||||
}
|
||||
|
||||
:host([width="33-on-2xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 33%;
|
||||
}
|
||||
|
||||
:host([width="50-on-2xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 50%;
|
||||
}
|
||||
|
||||
:host([width="66-on-2xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 66%;
|
||||
}
|
||||
|
||||
:host([width="75-on-2xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 75%;
|
||||
}
|
||||
|
||||
:host([width="100-on-2xl"]) {
|
||||
--ak-v2-c-drawer__panel--md--FlexBasis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
|
||||
:host([static]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([static]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
|
||||
}
|
||||
|
||||
:host([inline]:not([no-border])),
|
||||
:host([static]:not([no-border])) {
|
||||
background-color: var(
|
||||
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:host([inline]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline][position="left"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline][position="left"][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
:host([inline][expanded][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-block-end: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
:host([static]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
|
||||
:host([static-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([static-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
|
||||
}
|
||||
|
||||
:host([inline-on-lg]:not([no-border])),
|
||||
:host([static-on-lg]:not([no-border])) {
|
||||
background-color: var(
|
||||
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:host([inline-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline-on-lg])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline-on-lg][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline-on-lg][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline-on-lg][position="left"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline-on-lg][position="left"][expanded])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline-on-lg][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
:host([inline-on-lg][expanded][position="bottom"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
margin-block-end: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
:host([static-on-lg]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static-on-lg][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static-on-lg][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
|
||||
:host([static-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([static-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
|
||||
}
|
||||
|
||||
:host([inline-on-xl]:not([no-border])),
|
||||
:host([static-on-xl]:not([no-border])) {
|
||||
background-color: var(
|
||||
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:host([inline-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline-on-xl])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline-on-xl][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline-on-xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline-on-xl][position="left"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline-on-xl][position="left"][expanded])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline-on-xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
:host([inline-on-xl][expanded][position="bottom"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
margin-block-end: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
:host([static-on-xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static-on-xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static-on-xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
@media (min-width: 1450px) {
|
||||
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content,
|
||||
:host([static-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel,
|
||||
:host([static-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
--ak-v2-c-drawer--m-expanded__panel--BoxShadow: none;
|
||||
}
|
||||
|
||||
:host([inline-on-2xl]:not([no-border])),
|
||||
:host([static-on-2xl]:not([no-border])) {
|
||||
background-color: var(
|
||||
--ak-v2-c-drawer--m-inline--m-expanded__panel--after--BackgroundColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:host([inline-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline-on-2xl])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline-on-2xl][expanded]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline-on-2xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:where(.ak-v2-m-dir-rtl, [dir="rtl"])
|
||||
:host([inline-on-2xl][position="left"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
transform: translateX(calc(-100% * var(--ak-v2-global--inverse--multiplier)));
|
||||
}
|
||||
|
||||
:host([inline-on-2xl][position="left"][expanded])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([inline-on-2xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-block-end: calc(var(--ak-v2-c-drawer__panel--FlexBasis) * -1);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
:host([inline-on-2xl][expanded][position="bottom"])
|
||||
.ak-v2-c-drawer__main
|
||||
> .ak-v2-c-drawer__panel {
|
||||
margin-block-end: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
:host([static-on-2xl]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static-on-2xl][position="left"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
margin-inline-end: 0;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:host([static-on-2xl][position="bottom"]) .ak-v2-c-drawer__main > .ak-v2-c-drawer__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
:host([position="bottom"]) {
|
||||
--ak-v2-c-drawer__panel--MinWidth: auto;
|
||||
--ak-v2-c-drawer__panel--MinHeight: var(
|
||||
--ak-v2-c-drawer--m-panel-bottom__panel--xl--MinHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
//
|
||||
export default styles;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Drawer } from "./ak-drawer.component.js";
|
||||
import { AkDrawer } from "./ak-drawer.component.js";
|
||||
|
||||
export { Drawer };
|
||||
export { AkDrawer };
|
||||
|
||||
window.customElements.define("ak-drawer", Drawer);
|
||||
window.customElements.define("ak-drawer", AkDrawer);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-drawer": Drawer;
|
||||
"ak-drawer": AkDrawer;
|
||||
}
|
||||
}
|
||||
|
||||