Compare commits
6 Commits
admin/add-
...
core/soft-
Author | SHA1 | Date | |
---|---|---|---|
a5379c35aa | |||
e4c11a5284 | |||
a4853a1e09 | |||
b65b72d910 | |||
cd7be6a1a4 | |||
e5cb8ef541 |
@ -4,7 +4,6 @@ from collections.abc import Iterable
|
||||
from uuid import UUID
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Model, Q, QuerySet
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
@ -47,8 +46,6 @@ class Exporter:
|
||||
def get_model_instances(self, model: type[Model]) -> QuerySet:
|
||||
"""Return a queryset for `model`. Can be used to filter some
|
||||
objects on some models"""
|
||||
if model == get_user_model():
|
||||
return model.objects.exclude_anonymous()
|
||||
return model.objects.all()
|
||||
|
||||
def _pre_export(self, blueprint: Blueprint):
|
||||
|
@ -408,7 +408,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
filterset_class = UsersFilter
|
||||
|
||||
def get_queryset(self):
|
||||
base_qs = User.objects.all().exclude_anonymous()
|
||||
base_qs = User.objects.all()
|
||||
if self.serializer_class(context={"request": self.request})._should_include_groups:
|
||||
base_qs = base_qs.prefetch_related("ak_groups")
|
||||
return base_qs
|
||||
|
@ -10,7 +10,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.models import Count
|
||||
|
||||
import authentik.core.models
|
||||
import authentik.lib.models
|
||||
import authentik.lib.validators
|
||||
|
||||
|
||||
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
@ -160,7 +160,7 @@ class Migration(migrations.Migration):
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
validators=[authentik.lib.models.DomainlessFormattedURLValidator()],
|
||||
validators=[authentik.lib.validators.DomainlessFormattedURLValidator()],
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
|
23
authentik/core/migrations/0036_user_group_soft_delete.py
Normal file
23
authentik/core/migrations/0036_user_group_soft_delete.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-23 16:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0035_alter_group_options_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="group",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
@ -28,10 +28,12 @@ from authentik.lib.avatars import get_avatar
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.models import (
|
||||
CreatedUpdatedModel,
|
||||
DomainlessFormattedURLValidator,
|
||||
SerializerModel,
|
||||
SoftDeleteModel,
|
||||
SoftDeleteQuerySet,
|
||||
)
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.lib.validators import DomainlessFormattedURLValidator
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGTH
|
||||
from authentik.tenants.utils import get_current_tenant, get_unique_identifier
|
||||
@ -96,7 +98,7 @@ class UserTypes(models.TextChoices):
|
||||
INTERNAL_SERVICE_ACCOUNT = "internal_service_account"
|
||||
|
||||
|
||||
class Group(SerializerModel):
|
||||
class Group(SoftDeleteModel, SerializerModel):
|
||||
"""Group model which supports a basic hierarchy and has attributes"""
|
||||
|
||||
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||
@ -186,31 +188,21 @@ class Group(SerializerModel):
|
||||
]
|
||||
|
||||
|
||||
class UserQuerySet(models.QuerySet):
|
||||
"""User queryset"""
|
||||
|
||||
def exclude_anonymous(self):
|
||||
"""Exclude anonymous user"""
|
||||
return self.exclude(**{User.USERNAME_FIELD: settings.ANONYMOUS_USER_NAME})
|
||||
|
||||
|
||||
class UserManager(DjangoUserManager):
|
||||
"""User manager that doesn't assign is_superuser and is_staff"""
|
||||
|
||||
def get_queryset(self):
|
||||
"""Create special user queryset"""
|
||||
return UserQuerySet(self.model, using=self._db)
|
||||
return SoftDeleteQuerySet(self.model, using=self._db).exclude(
|
||||
**{User.USERNAME_FIELD: settings.ANONYMOUS_USER_NAME}
|
||||
)
|
||||
|
||||
def create_user(self, username, email=None, password=None, **extra_fields):
|
||||
"""User manager that doesn't assign is_superuser and is_staff"""
|
||||
return self._create_user(username, email, password, **extra_fields)
|
||||
|
||||
def exclude_anonymous(self) -> QuerySet:
|
||||
"""Exclude anonymous user"""
|
||||
return self.get_queryset().exclude_anonymous()
|
||||
|
||||
|
||||
class User(SerializerModel, GuardianUserMixin, AbstractUser):
|
||||
class User(SoftDeleteModel, SerializerModel, GuardianUserMixin, AbstractUser):
|
||||
"""authentik User model, based on django's contrib auth user model."""
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, unique=True)
|
||||
|
@ -132,7 +132,7 @@ class LicenseKey:
|
||||
@staticmethod
|
||||
def base_user_qs() -> QuerySet:
|
||||
"""Base query set for all users"""
|
||||
return User.objects.all().exclude_anonymous().exclude(is_active=False)
|
||||
return User.objects.all().exclude(is_active=False)
|
||||
|
||||
@staticmethod
|
||||
def get_default_user_count():
|
||||
|
@ -10,7 +10,7 @@ from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.events.models
|
||||
import authentik.lib.models
|
||||
import authentik.lib.validators
|
||||
from authentik.lib.migrations import progress_bar
|
||||
|
||||
|
||||
@ -377,7 +377,7 @@ class Migration(migrations.Migration):
|
||||
model_name="notificationtransport",
|
||||
name="webhook_url",
|
||||
field=models.TextField(
|
||||
blank=True, validators=[authentik.lib.models.DomainlessURLValidator()]
|
||||
blank=True, validators=[authentik.lib.validators.DomainlessURLValidator()]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -41,10 +41,11 @@ from authentik.events.utils import (
|
||||
sanitize_dict,
|
||||
sanitize_item,
|
||||
)
|
||||
from authentik.lib.models import DomainlessURLValidator, SerializerModel
|
||||
from authentik.lib.models import SerializerModel
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.lib.validators import DomainlessURLValidator
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
from authentik.stages.email.utils import TemplateEmailMessage
|
||||
|
@ -1,13 +1,16 @@
|
||||
"""Generic models"""
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from django.core.validators import URLValidator
|
||||
from django.db import models
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
from django.dispatch import Signal
|
||||
from django.utils import timezone
|
||||
from model_utils.managers import InheritanceManager
|
||||
from rest_framework.serializers import BaseSerializer
|
||||
|
||||
pre_soft_delete = Signal()
|
||||
post_soft_delete = Signal()
|
||||
|
||||
|
||||
class SerializerModel(models.Model):
|
||||
"""Base Abstract Model which has a serializer"""
|
||||
@ -51,46 +54,57 @@ class InheritanceForeignKey(models.ForeignKey):
|
||||
forward_related_accessor_class = InheritanceForwardManyToOneDescriptor
|
||||
|
||||
|
||||
class DomainlessURLValidator(URLValidator):
|
||||
"""Subclass of URLValidator which doesn't check the domain
|
||||
(to allow hostnames without domain)"""
|
||||
class SoftDeleteQuerySet(models.QuerySet):
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.host_re = "(" + self.hostname_re + self.domain_re + "|localhost)"
|
||||
self.regex = _lazy_re_compile(
|
||||
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
||||
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
||||
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
||||
r"(?::\d{2,5})?" # port
|
||||
r"(?:[/?#][^\s]*)?" # resource path
|
||||
r"\Z",
|
||||
re.IGNORECASE,
|
||||
def delete(self):
|
||||
for obj in self.all():
|
||||
obj.delete()
|
||||
|
||||
def hard_delete(self):
|
||||
return super().delete()
|
||||
|
||||
|
||||
class SoftDeleteManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
return SoftDeleteQuerySet(self.model, using=self._db).filter(deleted_at__isnull=True)
|
||||
|
||||
|
||||
class DeletedSoftDeleteManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().exclude(deleted_at__isnull=True)
|
||||
|
||||
|
||||
class SoftDeleteModel(models.Model):
|
||||
"""Model which doesn't fully delete itself, but rather saved the delete status
|
||||
so cleanup events can run."""
|
||||
|
||||
deleted_at = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
objects = SoftDeleteManager()
|
||||
deleted = DeletedSoftDeleteManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def is_deleted(self):
|
||||
return self.deleted_at is not None
|
||||
|
||||
def delete(self, using: Any = ..., keep_parents: bool = ...) -> tuple[int, dict[str, int]]:
|
||||
pre_soft_delete.send(sender=self.__class__, instance=self)
|
||||
now = timezone.now()
|
||||
self.deleted_at = now
|
||||
self.save(
|
||||
update_fields=[
|
||||
"deleted_at",
|
||||
]
|
||||
)
|
||||
self.schemes = ["http", "https", "blank"] + list(self.schemes)
|
||||
post_soft_delete.send(sender=self.__class__, instance=self)
|
||||
return tuple()
|
||||
|
||||
def __call__(self, value: str):
|
||||
# Check if the scheme is valid.
|
||||
scheme = value.split("://")[0].lower()
|
||||
if scheme not in self.schemes:
|
||||
value = "default" + value
|
||||
super().__call__(value)
|
||||
|
||||
|
||||
class DomainlessFormattedURLValidator(DomainlessURLValidator):
|
||||
"""URL validator which allows for python format strings"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.formatter_re = r"([%\(\)a-zA-Z])*"
|
||||
self.host_re = "(" + self.formatter_re + self.hostname_re + self.domain_re + "|localhost)"
|
||||
self.regex = _lazy_re_compile(
|
||||
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
||||
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
||||
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
||||
r"(?::\d{2,5})?" # port
|
||||
r"(?:[/?#][^\s]*)?" # resource path
|
||||
r"\Z",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
self.schemes = ["http", "https", "blank"] + list(self.schemes)
|
||||
def force_delete(self, using: Any = ...):
|
||||
if not self.deleted_at:
|
||||
raise models.ProtectedError("Refusing to force delete non-deleted model", {self})
|
||||
return super().delete(using=using)
|
||||
|
@ -1,5 +1,9 @@
|
||||
"""Serializer validators"""
|
||||
|
||||
import re
|
||||
|
||||
from django.core.validators import URLValidator
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.serializers import Serializer
|
||||
@ -29,3 +33,48 @@ class RequiredTogetherValidator:
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(fields={smart_repr(self.fields)})>"
|
||||
|
||||
|
||||
class DomainlessURLValidator(URLValidator):
|
||||
"""Subclass of URLValidator which doesn't check the domain
|
||||
(to allow hostnames without domain)"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.host_re = "(" + self.hostname_re + self.domain_re + "|localhost)"
|
||||
self.regex = _lazy_re_compile(
|
||||
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
||||
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
||||
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
||||
r"(?::\d{2,5})?" # port
|
||||
r"(?:[/?#][^\s]*)?" # resource path
|
||||
r"\Z",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
self.schemes = ["http", "https", "blank"] + list(self.schemes)
|
||||
|
||||
def __call__(self, value: str):
|
||||
# Check if the scheme is valid.
|
||||
scheme = value.split("://")[0].lower()
|
||||
if scheme not in self.schemes:
|
||||
value = "default" + value
|
||||
super().__call__(value)
|
||||
|
||||
|
||||
class DomainlessFormattedURLValidator(DomainlessURLValidator):
|
||||
"""URL validator which allows for python format strings"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.formatter_re = r"([%\(\)a-zA-Z])*"
|
||||
self.host_re = "(" + self.formatter_re + self.hostname_re + self.domain_re + "|localhost)"
|
||||
self.regex = _lazy_re_compile(
|
||||
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
||||
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
||||
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
||||
r"(?::\d{2,5})?" # port
|
||||
r"(?:[/?#][^\s]*)?" # resource path
|
||||
r"\Z",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
self.schemes = ["http", "https", "blank"] + list(self.schemes)
|
||||
|
18
authentik/outposts/migrations/0022_outpost_deleted_at.py
Normal file
18
authentik/outposts/migrations/0022_outpost_deleted_at.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-23 21:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_outposts", "0021_alter_outpost_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="outpost",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
@ -33,7 +33,7 @@ from authentik.core.models import (
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import InheritanceForeignKey, SerializerModel
|
||||
from authentik.lib.models import InheritanceForeignKey, SerializerModel, SoftDeleteModel
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.outposts.controllers.k8s.utils import get_namespace
|
||||
@ -131,7 +131,7 @@ class OutpostServiceConnection(models.Model):
|
||||
verbose_name = _("Outpost Service-Connection")
|
||||
verbose_name_plural = _("Outpost Service-Connections")
|
||||
|
||||
def __str__(self) -> __version__:
|
||||
def __str__(self):
|
||||
return f"Outpost service connection {self.name}"
|
||||
|
||||
@property
|
||||
@ -241,7 +241,7 @@ class KubernetesServiceConnection(SerializerModel, OutpostServiceConnection):
|
||||
return "ak-service-connection-kubernetes-form"
|
||||
|
||||
|
||||
class Outpost(SerializerModel, ManagedModel):
|
||||
class Outpost(SoftDeleteModel, SerializerModel, ManagedModel):
|
||||
"""Outpost instance which manages a service user and token"""
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
||||
|
@ -2,13 +2,14 @@
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_delete, pre_save
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.models import Provider
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.models import post_soft_delete
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.outposts.models import Outpost, OutpostServiceConnection
|
||||
from authentik.outposts.tasks import CACHE_KEY_OUTPOST_DOWN, outpost_controller, outpost_post_save
|
||||
@ -67,9 +68,7 @@ def post_save_update(sender, instance: Model, created: bool, **_):
|
||||
outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Outpost)
|
||||
def pre_delete_cleanup(sender, instance: Outpost, **_):
|
||||
@receiver(post_soft_delete, sender=Outpost)
|
||||
def outpost_cleanup(sender, instance: Outpost, **_):
|
||||
"""Ensure that Outpost's user is deleted (which will delete the token through cascade)"""
|
||||
instance.user.delete()
|
||||
cache.set(CACHE_KEY_OUTPOST_DOWN % instance.pk.hex, instance)
|
||||
outpost_controller.delay(instance.pk.hex, action="down", from_cache=True)
|
||||
outpost_controller.delay(instance.pk.hex, action="down")
|
||||
|
@ -129,17 +129,14 @@ def outpost_controller_all():
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=SystemTask)
|
||||
def outpost_controller(
|
||||
self: SystemTask, outpost_pk: str, action: str = "up", from_cache: bool = False
|
||||
):
|
||||
def outpost_controller(self: SystemTask, outpost_pk: str, action: str = "up"):
|
||||
"""Create/update/monitor/delete the deployment of an Outpost"""
|
||||
logs = []
|
||||
if from_cache:
|
||||
outpost: Outpost = cache.get(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
|
||||
LOGGER.debug("Getting outpost from cache to delete")
|
||||
else:
|
||||
outpost: Outpost = Outpost.objects.filter(pk=outpost_pk).first()
|
||||
LOGGER.debug("Getting outpost from DB")
|
||||
outpost: Outpost = None
|
||||
if action == "up":
|
||||
outpost = Outpost.objects.filter(pk=outpost_pk).first()
|
||||
elif action == "down":
|
||||
outpost = Outpost.deleted.filter(pk=outpost_pk).first()
|
||||
if not outpost:
|
||||
LOGGER.warning("No outpost")
|
||||
return
|
||||
@ -155,9 +152,10 @@ def outpost_controller(
|
||||
except (ControllerException, ServiceConnectionInvalid) as exc:
|
||||
self.set_error(exc)
|
||||
else:
|
||||
if from_cache:
|
||||
cache.delete(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
|
||||
self.set_status(TaskStatus.SUCCESSFUL, *logs)
|
||||
finally:
|
||||
if outpost.deleted_at:
|
||||
outpost.force_delete()
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=SystemTask)
|
||||
|
@ -6,7 +6,7 @@ from django.core.exceptions import FieldError
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
import authentik.lib.models
|
||||
import authentik.lib.validators
|
||||
import authentik.providers.proxy.models
|
||||
|
||||
|
||||
@ -80,7 +80,9 @@ class Migration(migrations.Migration):
|
||||
models.TextField(
|
||||
blank=True,
|
||||
validators=[
|
||||
authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))
|
||||
authentik.lib.validators.DomainlessURLValidator(
|
||||
schemes=("http", "https")
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -88,7 +90,9 @@ class Migration(migrations.Migration):
|
||||
"external_host",
|
||||
models.TextField(
|
||||
validators=[
|
||||
authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))
|
||||
authentik.lib.validators.DomainlessURLValidator(
|
||||
schemes=("http", "https")
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
|
@ -10,7 +10,7 @@ from django.utils.translation import gettext as _
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.models import DomainlessURLValidator
|
||||
from authentik.lib.validators import DomainlessURLValidator
|
||||
from authentik.outposts.models import OutpostModel
|
||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
||||
|
||||
|
@ -49,7 +49,7 @@ class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
|
||||
if type == User:
|
||||
# Get queryset of all users with consistent ordering
|
||||
# according to the provider's settings
|
||||
base = User.objects.all().exclude_anonymous()
|
||||
base = User.objects.all()
|
||||
if self.exclude_users_service_account:
|
||||
base = base.exclude(type=UserTypes.SERVICE_ACCOUNT).exclude(
|
||||
type=UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||
|
@ -19,7 +19,7 @@ class SCIMGroupTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
# Delete all users and groups as the mocked HTTP responses only return one ID
|
||||
# which will cause errors with multiple users
|
||||
User.objects.all().exclude_anonymous().delete()
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
self.provider: SCIMProvider = SCIMProvider.objects.create(
|
||||
name=generate_id(),
|
||||
|
@ -21,7 +21,7 @@ class SCIMMembershipTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
# Delete all users and groups as the mocked HTTP responses only return one ID
|
||||
# which will cause errors with multiple users
|
||||
User.objects.all().exclude_anonymous().delete()
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
Tenant.objects.update(avatars="none")
|
||||
|
||||
|
@ -22,7 +22,7 @@ class SCIMUserTests(TestCase):
|
||||
# Delete all users and groups as the mocked HTTP responses only return one ID
|
||||
# which will cause errors with multiple users
|
||||
Tenant.objects.update(avatars="none")
|
||||
User.objects.all().exclude_anonymous().delete()
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
self.provider: SCIMProvider = SCIMProvider.objects.create(
|
||||
name=generate_id(),
|
||||
|
@ -4,7 +4,7 @@ import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.lib.models
|
||||
import authentik.lib.validators
|
||||
|
||||
|
||||
def set_managed_flag(apps: Apps, schema_editor):
|
||||
@ -105,7 +105,9 @@ class Migration(migrations.Migration):
|
||||
"server_uri",
|
||||
models.TextField(
|
||||
validators=[
|
||||
authentik.lib.models.DomainlessURLValidator(schemes=["ldap", "ldaps"])
|
||||
authentik.lib.validators.DomainlessURLValidator(
|
||||
schemes=["ldap", "ldaps"]
|
||||
)
|
||||
],
|
||||
verbose_name="Server URI",
|
||||
),
|
||||
|
@ -17,7 +17,7 @@ from rest_framework.serializers import Serializer
|
||||
from authentik.core.models import Group, PropertyMapping, Source
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.models import DomainlessURLValidator
|
||||
from authentik.lib.validators import DomainlessURLValidator
|
||||
|
||||
LDAP_TIMEOUT = 15
|
||||
|
||||
|
@ -161,7 +161,6 @@ class TestSourceSAML(SeleniumTestCase):
|
||||
self.assert_user(
|
||||
User.objects.exclude(username="akadmin")
|
||||
.exclude(username__startswith="ak-outpost")
|
||||
.exclude_anonymous()
|
||||
.exclude(pk=self.user.pk)
|
||||
.first()
|
||||
)
|
||||
@ -244,7 +243,6 @@ class TestSourceSAML(SeleniumTestCase):
|
||||
self.assert_user(
|
||||
User.objects.exclude(username="akadmin")
|
||||
.exclude(username__startswith="ak-outpost")
|
||||
.exclude_anonymous()
|
||||
.exclude(pk=self.user.pk)
|
||||
.first()
|
||||
)
|
||||
@ -314,7 +312,6 @@ class TestSourceSAML(SeleniumTestCase):
|
||||
self.assert_user(
|
||||
User.objects.exclude(username="akadmin")
|
||||
.exclude(username__startswith="ak-outpost")
|
||||
.exclude_anonymous()
|
||||
.exclude(pk=self.user.pk)
|
||||
.first()
|
||||
)
|
||||
|
Reference in New Issue
Block a user