API: add endpoint to show by what objects an object is used (#995)

* core: add used_by API to show what objects are affected before deletion

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/elements: add support for used_by API

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: add authentik_used_by_shadows to shadow other models

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: implement used_by API

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* *: fix duplicate imports

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: add action field to used_by api

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: add UI for used_by action

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: add notice to tenant form

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: fix naming in used_by

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: check length for used_by

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: fix used_by for non-pk models

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* *: improve __str__ on models

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: add support for many to many in used_by

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2021-06-10 11:58:12 +02:00 committed by GitHub
parent bf683514ee
commit 34ae9e6dab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 2713 additions and 130 deletions

View File

@ -29,6 +29,7 @@ from structlog.stdlib import get_logger
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Application, User from authentik.core.models import Application, User
from authentik.events.models import EventAction from authentik.events.models import EventAction
from authentik.policies.api.exec import PolicyTestResultSerializer from authentik.policies.api.exec import PolicyTestResultSerializer
@ -73,7 +74,7 @@ class ApplicationSerializer(ModelSerializer):
} }
class ApplicationViewSet(ModelViewSet): class ApplicationViewSet(UsedByMixin, ModelViewSet):
"""Application Viewset""" """Application Viewset"""
queryset = Application.objects.all() queryset = Application.objects.all()

View File

@ -11,6 +11,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from ua_parser import user_agent_parser from ua_parser import user_agent_parser
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import AuthenticatedSession from authentik.core.models import AuthenticatedSession
from authentik.events.geo import GEOIP_READER, GeoIPDict from authentik.events.geo import GEOIP_READER, GeoIPDict
@ -92,6 +93,7 @@ class AuthenticatedSessionSerializer(ModelSerializer):
class AuthenticatedSessionViewSet( class AuthenticatedSessionViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -5,6 +5,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict from authentik.core.api.utils import is_dict
from authentik.core.models import Group from authentik.core.models import Group
@ -20,7 +21,7 @@ class GroupSerializer(ModelSerializer):
fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"] fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"]
class GroupViewSet(ModelViewSet): class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset""" """Group Viewset"""
queryset = Group.objects.all() queryset = Group.objects.all()

View File

@ -14,6 +14,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ( from authentik.core.api.utils import (
MetaNameSerializer, MetaNameSerializer,
PassiveSerializer, PassiveSerializer,
@ -65,6 +66,7 @@ class PropertyMappingSerializer(ManagedSerializer, ModelSerializer, MetaNameSeri
class PropertyMappingViewSet( class PropertyMappingViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -9,6 +9,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.reflection import all_subclasses
@ -48,6 +49,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
class ProviderViewSet( class ProviderViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -10,6 +10,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import Source from authentik.core.models import Source
from authentik.core.types import UserSettingSerializer from authentik.core.types import UserSettingSerializer
@ -52,6 +53,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
class SourceViewSet( class SourceViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -9,6 +9,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Token, TokenIntents from authentik.core.models import Token, TokenIntents
@ -43,7 +44,7 @@ class TokenViewSerializer(PassiveSerializer):
key = CharField(read_only=True) key = CharField(read_only=True)
class TokenViewSet(ModelViewSet): class TokenViewSet(UsedByMixin, ModelViewSet):
"""Token Viewset""" """Token Viewset"""
lookup_field = "identifier" lookup_field = "identifier"

View File

@ -0,0 +1,102 @@
"""used_by mixin"""
from enum import Enum
from inspect import getmembers
from django.db.models.base import Model
from django.db.models.deletion import SET_DEFAULT, SET_NULL
from django.db.models.manager import Manager
from drf_spectacular.utils import extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, ChoiceField
from rest_framework.request import Request
from rest_framework.response import Response
from authentik.core.api.utils import PassiveSerializer
class DeleteAction(Enum):
"""Which action a delete will have on a used object"""
CASCADE = "cascade"
CASCADE_MANY = "cascade_many"
SET_NULL = "set_null"
SET_DEFAULT = "set_default"
class UsedBySerializer(PassiveSerializer):
"""A list of all objects referencing the queried object"""
app = CharField()
model_name = CharField()
pk = CharField()
name = CharField()
action = ChoiceField(choices=[(x.name, x.name) for x in DeleteAction])
def get_delete_action(manager: Manager) -> str:
"""Get the delete action from the Foreign key, falls back to cascade"""
if hasattr(manager, "field"):
if manager.field.remote_field.on_delete.__name__ == SET_NULL.__name__:
return DeleteAction.SET_NULL.name
if manager.field.remote_field.on_delete.__name__ == SET_DEFAULT.__name__:
return DeleteAction.SET_DEFAULT.name
if hasattr(manager, "source_field"):
return DeleteAction.CASCADE_MANY.name
return DeleteAction.CASCADE.name
class UsedByMixin:
"""Mixin to add a used_by endpoint to return a list of all objects using this object"""
@extend_schema(
responses={200: UsedBySerializer(many=True)},
)
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name, unused-argument, too-many-locals
def used_by(self, request: Request, *args, **kwargs) -> Response:
"""Get a list of all objects that use this object"""
# pyright: reportGeneralTypeIssues=false
model: Model = self.get_object()
used_by = []
shadows = []
for attr_name, manager in getmembers(model, lambda x: isinstance(x, Manager)):
if attr_name == "objects": # pragma: no cover
continue
manager: Manager
if manager.model._meta.abstract:
continue
app = manager.model._meta.app_label
model_name = manager.model._meta.model_name
delete_action = get_delete_action(manager)
# To make sure we only apply shadows when there are any objects,
# but so we only apply them once, have a simple flag for the first object
first_object = True
for obj in get_objects_for_user(
request.user, f"{app}.view_{model_name}", manager
).all():
# Only merge shadows on first object
if first_object:
shadows += getattr(
manager.model._meta, "authentik_used_by_shadows", []
)
first_object = False
serializer = UsedBySerializer(
data={
"app": app,
"model_name": model_name,
"pk": str(obj.pk),
"name": str(obj),
"action": delete_action,
}
)
serializer.is_valid()
used_by.append(serializer.data)
# Check the shadows map and remove anything that should be shadowed
for idx, user in enumerate(used_by):
full_model_name = f"{user['app']}.{user['model_name']}"
if full_model_name in shadows:
del used_by[idx]
return Response(used_by)

View File

@ -25,6 +25,7 @@ from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.groups import GroupSerializer from authentik.core.api.groups import GroupSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
from authentik.core.middleware import ( from authentik.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_ORIGINAL_USER,
@ -131,7 +132,7 @@ class UsersFilter(FilterSet):
fields = ["username", "name", "is_active", "is_superuser", "attributes"] fields = ["username", "name", "is_active", "is_superuser", "attributes"]
class UserViewSet(ModelViewSet): class UserViewSet(UsedByMixin, ModelViewSet):
"""User Viewset""" """User Viewset"""
queryset = User.objects.none() queryset = User.objects.none()

View File

@ -5,6 +5,7 @@ from typing import Any, Optional, Type
from urllib.parse import urlencode from urllib.parse import urlencode
from uuid import uuid4 from uuid import uuid4
import django.db.models.options as options
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager from django.contrib.auth.models import UserManager as DjangoUserManager
@ -41,6 +42,9 @@ GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("dist/assets/images/user_default.png") DEFAULT_AVATAR = static("dist/assets/images/user_default.png")
options.DEFAULT_NAMES = options.DEFAULT_NAMES + ("authentik_used_by_shadows",)
def default_token_duration(): def default_token_duration():
"""Default duration a Token is valid""" """Default duration a Token is valid"""
return now() + timedelta(minutes=30) return now() + timedelta(minutes=30)

View File

@ -1,10 +1,11 @@
"""Crypto API Views""" """Crypto API Views"""
import django_filters
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate from cryptography.x509 import load_pem_x509_certificate
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_filters import FilterSet
from django_filters.filters import BooleanFilter
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
@ -20,6 +21,7 @@ from rest_framework.serializers import ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.crypto.builder import CertificateBuilder from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
@ -100,10 +102,10 @@ class CertificateGenerationSerializer(PassiveSerializer):
validity_days = IntegerField(initial=365) validity_days = IntegerField(initial=365)
class CertificateKeyPairFilter(django_filters.FilterSet): class CertificateKeyPairFilter(FilterSet):
"""Filter for certificates""" """Filter for certificates"""
has_key = django_filters.BooleanFilter( has_key = BooleanFilter(
label="Only return certificate-key pairs with keys", method="filter_has_key" label="Only return certificate-key pairs with keys", method="filter_has_key"
) )
@ -117,7 +119,7 @@ class CertificateKeyPairFilter(django_filters.FilterSet):
fields = ["name"] fields = ["name"]
class CertificateKeyPairViewSet(ModelViewSet): class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
"""CertificateKeyPair Viewset""" """CertificateKeyPair Viewset"""
queryset = CertificateKeyPair.objects.all() queryset = CertificateKeyPair.objects.all()

View File

@ -4,10 +4,14 @@ import datetime
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from authentik.core.api.used_by import DeleteAction
from authentik.core.models import User from authentik.core.models import User
from authentik.crypto.api import CertificateKeyPairSerializer from authentik.crypto.api import CertificateKeyPairSerializer
from authentik.crypto.builder import CertificateBuilder from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.providers.oauth2.models import OAuth2Provider
class TestCrypto(TestCase): class TestCrypto(TestCase):
@ -91,3 +95,35 @@ class TestCrypto(TestCase):
) )
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.assertIn("Content-Disposition", response) self.assertIn("Content-Disposition", response)
def test_used_by(self):
"""Test used_by endpoint"""
self.client.force_login(User.objects.get(username="akadmin"))
keypair = CertificateKeyPair.objects.first()
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://localhost",
rsa_key=CertificateKeyPair.objects.first(),
)
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-used-by",
kwargs={"pk": keypair.pk},
)
)
self.assertEqual(200, response.status_code)
self.assertJSONEqual(
response.content.decode(),
[
{
"app": "authentik_providers_oauth2",
"model_name": "oauth2provider",
"pk": str(provider.pk),
"name": str(provider),
"action": DeleteAction.SET_NULL.name,
}
],
)

View File

@ -7,6 +7,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.events.api.event import EventSerializer from authentik.events.api.event import EventSerializer
from authentik.events.models import Notification from authentik.events.models import Notification
@ -35,6 +36,7 @@ class NotificationViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -3,6 +3,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.groups import GroupSerializer from authentik.core.api.groups import GroupSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.events.models import NotificationRule from authentik.events.models import NotificationRule
@ -24,7 +25,7 @@ class NotificationRuleSerializer(ModelSerializer):
] ]
class NotificationRuleViewSet(ModelViewSet): class NotificationRuleViewSet(UsedByMixin, ModelViewSet):
"""NotificationRule Viewset""" """NotificationRule Viewset"""
queryset = NotificationRule.objects.all() queryset = NotificationRule.objects.all()

View File

@ -9,6 +9,7 @@ from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.events.models import ( from authentik.events.models import (
Notification, Notification,
NotificationSeverity, NotificationSeverity,
@ -52,7 +53,7 @@ class NotificationTransportTestSerializer(Serializer):
raise NotImplementedError raise NotImplementedError
class NotificationTransportViewSet(ModelViewSet): class NotificationTransportViewSet(UsedByMixin, ModelViewSet):
"""NotificationTransport Viewset""" """NotificationTransport Viewset"""
queryset = NotificationTransport.objects.all() queryset = NotificationTransport.objects.all()

View File

@ -2,6 +2,7 @@
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.flows.models import FlowStageBinding from authentik.flows.models import FlowStageBinding
@ -27,7 +28,7 @@ class FlowStageBindingSerializer(ModelSerializer):
] ]
class FlowStageBindingViewSet(ModelViewSet): class FlowStageBindingViewSet(UsedByMixin, ModelViewSet):
"""FlowStageBinding Viewset""" """FlowStageBinding Viewset"""
queryset = FlowStageBinding.objects.all() queryset = FlowStageBinding.objects.all()

View File

@ -24,6 +24,7 @@ from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import CacheSerializer, LinkSerializer from authentik.core.api.utils import CacheSerializer, LinkSerializer
from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow from authentik.flows.models import Flow
@ -94,7 +95,7 @@ class DiagramElement:
return f"{self.identifier}=>{self.type}: {self.rest}" return f"{self.identifier}=>{self.type}: {self.rest}"
class FlowViewSet(ModelViewSet): class FlowViewSet(UsedByMixin, ModelViewSet):
"""Flow Viewset""" """Flow Viewset"""
queryset = Flow.objects.all() queryset = Flow.objects.all()

View File

@ -11,6 +11,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.types import UserSettingSerializer from authentik.core.types import UserSettingSerializer
from authentik.flows.api.flows import FlowSerializer from authentik.flows.api.flows import FlowSerializer
@ -49,6 +50,7 @@ class StageSerializer(ModelSerializer, MetaNameSerializer):
class StageViewSet( class StageViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -72,7 +72,7 @@ class Stage(SerializerModel):
def __str__(self): def __str__(self):
if hasattr(self, "__in_memory_type"): if hasattr(self, "__in_memory_type"):
return f"In-memory Stage {getattr(self, '__in_memory_type')}" return f"In-memory Stage {getattr(self, '__in_memory_type')}"
return self.name return f"Stage {self.name}"
def in_memory_stage(view: Type["StageView"]) -> Stage: def in_memory_stage(view: Type["StageView"]) -> Stage:
@ -212,7 +212,7 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
return FlowStageBindingSerializer return FlowStageBindingSerializer
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.target} #{self.order}" return f"Flow-stage binding #{self.order} to {self.target}"
class Meta: class Meta:

View File

@ -11,6 +11,7 @@ from rest_framework.serializers import JSONField, ModelSerializer, ValidationErr
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.outposts.api.service_connections import ServiceConnectionSerializer from authentik.outposts.api.service_connections import ServiceConnectionSerializer
@ -95,7 +96,7 @@ class OutpostHealthSerializer(PassiveSerializer):
version_outdated = BooleanField(read_only=True) version_outdated = BooleanField(read_only=True)
class OutpostViewSet(ModelViewSet): class OutpostViewSet(UsedByMixin, ModelViewSet):
"""Outpost Viewset""" """Outpost Viewset"""
queryset = Outpost.objects.all() queryset = Outpost.objects.all()

View File

@ -14,6 +14,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ( from authentik.core.api.utils import (
MetaNameSerializer, MetaNameSerializer,
PassiveSerializer, PassiveSerializer,
@ -55,6 +56,7 @@ class ServiceConnectionStateSerializer(PassiveSerializer):
class ServiceConnectionViewSet( class ServiceConnectionViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):
@ -105,7 +107,7 @@ class DockerServiceConnectionSerializer(ServiceConnectionSerializer):
] ]
class DockerServiceConnectionViewSet(ModelViewSet): class DockerServiceConnectionViewSet(UsedByMixin, ModelViewSet):
"""DockerServiceConnection Viewset""" """DockerServiceConnection Viewset"""
queryset = DockerServiceConnection.objects.all() queryset = DockerServiceConnection.objects.all()
@ -139,7 +141,7 @@ class KubernetesServiceConnectionSerializer(ServiceConnectionSerializer):
fields = ServiceConnectionSerializer.Meta.fields + ["kubeconfig"] fields = ServiceConnectionSerializer.Meta.fields + ["kubeconfig"]
class KubernetesServiceConnectionViewSet(ModelViewSet): class KubernetesServiceConnectionViewSet(UsedByMixin, ModelViewSet):
"""KubernetesServiceConnection Viewset""" """KubernetesServiceConnection Viewset"""
queryset = KubernetesServiceConnection.objects.all() queryset = KubernetesServiceConnection.objects.all()

View File

@ -11,6 +11,7 @@ from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.api.groups import GroupSerializer from authentik.core.api.groups import GroupSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer from authentik.core.api.users import UserSerializer
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.models import PolicyBinding, PolicyBindingModel from authentik.policies.models import PolicyBinding, PolicyBindingModel
@ -99,7 +100,7 @@ class PolicyBindingSerializer(ModelSerializer):
return data return data
class PolicyBindingViewSet(ModelViewSet): class PolicyBindingViewSet(UsedByMixin, ModelViewSet):
"""PolicyBinding Viewset""" """PolicyBinding Viewset"""
queryset = ( queryset = (

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.applications import user_app_cache_key from authentik.core.api.applications import user_app_cache_key
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ( from authentik.core.api.utils import (
CacheSerializer, CacheSerializer,
MetaNameSerializer, MetaNameSerializer,
@ -79,6 +80,7 @@ class PolicySerializer(ModelSerializer, MetaNameSerializer):
class PolicyViewSet( class PolicyViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -1,6 +1,7 @@
"""Dummy Policy API Views""" """Dummy Policy API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.dummy.models import DummyPolicy from authentik.policies.dummy.models import DummyPolicy
@ -13,7 +14,7 @@ class DummyPolicySerializer(PolicySerializer):
fields = PolicySerializer.Meta.fields + ["result", "wait_min", "wait_max"] fields = PolicySerializer.Meta.fields + ["result", "wait_min", "wait_max"]
class DummyPolicyViewSet(ModelViewSet): class DummyPolicyViewSet(UsedByMixin, ModelViewSet):
"""Dummy Viewset""" """Dummy Viewset"""
queryset = DummyPolicy.objects.all() queryset = DummyPolicy.objects.all()

View File

@ -1,6 +1,7 @@
"""Event Matcher Policy API""" """Event Matcher Policy API"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.event_matcher.models import EventMatcherPolicy from authentik.policies.event_matcher.models import EventMatcherPolicy
@ -17,7 +18,7 @@ class EventMatcherPolicySerializer(PolicySerializer):
] ]
class EventMatcherPolicyViewSet(ModelViewSet): class EventMatcherPolicyViewSet(UsedByMixin, ModelViewSet):
"""Event Matcher Policy Viewset""" """Event Matcher Policy Viewset"""
queryset = EventMatcherPolicy.objects.all() queryset = EventMatcherPolicy.objects.all()

View File

@ -1,6 +1,7 @@
"""Password Expiry Policy API Views""" """Password Expiry Policy API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.expiry.models import PasswordExpiryPolicy from authentik.policies.expiry.models import PasswordExpiryPolicy
@ -13,7 +14,7 @@ class PasswordExpiryPolicySerializer(PolicySerializer):
fields = PolicySerializer.Meta.fields + ["days", "deny_only"] fields = PolicySerializer.Meta.fields + ["days", "deny_only"]
class PasswordExpiryPolicyViewSet(ModelViewSet): class PasswordExpiryPolicyViewSet(UsedByMixin, ModelViewSet):
"""Password Expiry Viewset""" """Password Expiry Viewset"""
queryset = PasswordExpiryPolicy.objects.all() queryset = PasswordExpiryPolicy.objects.all()

View File

@ -1,6 +1,7 @@
"""Expression Policy API""" """Expression Policy API"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.expression.evaluator import PolicyEvaluator from authentik.policies.expression.evaluator import PolicyEvaluator
from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.expression.models import ExpressionPolicy
@ -20,7 +21,7 @@ class ExpressionPolicySerializer(PolicySerializer):
fields = PolicySerializer.Meta.fields + ["expression"] fields = PolicySerializer.Meta.fields + ["expression"]
class ExpressionPolicyViewSet(ModelViewSet): class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet):
"""Source Viewset""" """Source Viewset"""
queryset = ExpressionPolicy.objects.all() queryset = ExpressionPolicy.objects.all()

View File

@ -1,6 +1,7 @@
"""Source API Views""" """Source API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.hibp.models import HaveIBeenPwendPolicy from authentik.policies.hibp.models import HaveIBeenPwendPolicy
@ -13,7 +14,7 @@ class HaveIBeenPwendPolicySerializer(PolicySerializer):
fields = PolicySerializer.Meta.fields + ["password_field", "allowed_count"] fields = PolicySerializer.Meta.fields + ["password_field", "allowed_count"]
class HaveIBeenPwendPolicyViewSet(ModelViewSet): class HaveIBeenPwendPolicyViewSet(UsedByMixin, ModelViewSet):
"""Source Viewset""" """Source Viewset"""
queryset = HaveIBeenPwendPolicy.objects.all() queryset = HaveIBeenPwendPolicy.objects.all()

View File

@ -1,6 +1,7 @@
"""Password Policy API Views""" """Password Policy API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.password.models import PasswordPolicy from authentik.policies.password.models import PasswordPolicy
@ -21,7 +22,7 @@ class PasswordPolicySerializer(PolicySerializer):
] ]
class PasswordPolicyViewSet(ModelViewSet): class PasswordPolicyViewSet(UsedByMixin, ModelViewSet):
"""Password Policy Viewset""" """Password Policy Viewset"""
queryset = PasswordPolicy.objects.all() queryset = PasswordPolicy.objects.all()

View File

@ -3,6 +3,7 @@ from rest_framework import mixins
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer from authentik.policies.api.policies import PolicySerializer
from authentik.policies.reputation.models import ( from authentik.policies.reputation.models import (
IPReputation, IPReputation,
@ -23,7 +24,7 @@ class ReputationPolicySerializer(PolicySerializer):
] ]
class ReputationPolicyViewSet(ModelViewSet): class ReputationPolicyViewSet(UsedByMixin, ModelViewSet):
"""Reputation Policy Viewset""" """Reputation Policy Viewset"""
queryset = ReputationPolicy.objects.all() queryset = ReputationPolicy.objects.all()
@ -46,6 +47,7 @@ class IPReputationSerializer(ModelSerializer):
class IPReputationViewSet( class IPReputationViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):
@ -74,6 +76,7 @@ class UserReputationSerializer(ModelSerializer):
class UserReputationViewSet( class UserReputationViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -4,6 +4,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.providers.ldap.models import LDAPProvider from authentik.providers.ldap.models import LDAPProvider
@ -19,7 +20,7 @@ class LDAPProviderSerializer(ProviderSerializer):
] ]
class LDAPProviderViewSet(ModelViewSet): class LDAPProviderViewSet(UsedByMixin, ModelViewSet):
"""LDAPProvider Viewset""" """LDAPProvider Viewset"""
queryset = LDAPProvider.objects.all() queryset = LDAPProvider.objects.all()

View File

@ -11,6 +11,7 @@ from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider
@ -61,7 +62,7 @@ class OAuth2ProviderSetupURLs(PassiveSerializer):
logout = ReadOnlyField() logout = ReadOnlyField()
class OAuth2ProviderViewSet(ModelViewSet): class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
"""OAuth2Provider Viewset""" """OAuth2Provider Viewset"""
queryset = OAuth2Provider.objects.all() queryset = OAuth2Provider.objects.all()

View File

@ -2,6 +2,7 @@
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.providers.oauth2.models import ScopeMapping from authentik.providers.oauth2.models import ScopeMapping
@ -17,7 +18,7 @@ class ScopeMappingSerializer(PropertyMappingSerializer):
] ]
class ScopeMappingViewSet(ModelViewSet): class ScopeMappingViewSet(UsedByMixin, ModelViewSet):
"""ScopeMapping Viewset""" """ScopeMapping Viewset"""
queryset = ScopeMapping.objects.all() queryset = ScopeMapping.objects.all()

View File

@ -10,6 +10,7 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import MetaNameSerializer from authentik.core.api.utils import MetaNameSerializer
from authentik.providers.oauth2.api.provider import OAuth2ProviderSerializer from authentik.providers.oauth2.api.provider import OAuth2ProviderSerializer
@ -57,6 +58,7 @@ class RefreshTokenModelSerializer(ExpiringBaseGrantModelSerializer):
class AuthorizationCodeViewSet( class AuthorizationCodeViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):
@ -82,6 +84,7 @@ class AuthorizationCodeViewSet(
class RefreshTokenViewSet( class RefreshTokenViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.3 on 2021-06-09 21:52
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_providers_oauth2", "0013_alter_authorizationcode_nonce"),
]
operations = [
migrations.AlterField(
model_name="oauth2provider",
name="rsa_key",
field=models.ForeignKey(
help_text="Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_crypto.certificatekeypair",
verbose_name="RSA Key",
),
),
]

View File

@ -215,8 +215,7 @@ class OAuth2Provider(Provider):
rsa_key = models.ForeignKey( rsa_key = models.ForeignKey(
CertificateKeyPair, CertificateKeyPair,
verbose_name=_("RSA Key"), verbose_name=_("RSA Key"),
on_delete=models.CASCADE, on_delete=models.SET_NULL,
blank=True,
null=True, null=True,
help_text=_( help_text=_(
"Key used to sign the tokens. Only required when JWT Algorithm is set to RS256." "Key used to sign the tokens. Only required when JWT Algorithm is set to RS256."

View File

@ -8,6 +8,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.providers.oauth2.views.provider import ProviderInfoView from authentik.providers.oauth2.views.provider import ProviderInfoView
from authentik.providers.proxy.models import ProxyMode, ProxyProvider from authentik.providers.proxy.models import ProxyMode, ProxyProvider
@ -76,7 +77,7 @@ class ProxyProviderSerializer(ProviderSerializer):
] ]
class ProxyProviderViewSet(ModelViewSet): class ProxyProviderViewSet(UsedByMixin, ModelViewSet):
"""ProxyProvider Viewset""" """ProxyProvider Viewset"""
queryset = ProxyProvider.objects.all() queryset = ProxyProvider.objects.all()

View File

@ -167,3 +167,4 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
verbose_name = _("Proxy Provider") verbose_name = _("Proxy Provider")
verbose_name_plural = _("Proxy Providers") verbose_name_plural = _("Proxy Providers")
authentik_used_by_shadows = ["authentik_providers_oauth2.oauth2provider"]

View File

@ -21,6 +21,7 @@ from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.flows.models import Flow, FlowDesignation from authentik.flows.models import Flow, FlowDesignation
@ -75,7 +76,7 @@ class SAMLProviderImportSerializer(PassiveSerializer):
file = FileField() file = FileField()
class SAMLProviderViewSet(ModelViewSet): class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
"""SAMLProvider Viewset""" """SAMLProvider Viewset"""
queryset = SAMLProvider.objects.all() queryset = SAMLProvider.objects.all()
@ -166,7 +167,7 @@ class SAMLPropertyMappingSerializer(PropertyMappingSerializer):
] ]
class SAMLPropertyMappingViewSet(ModelViewSet): class SAMLPropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""SAMLPropertyMapping Viewset""" """SAMLPropertyMapping Viewset"""
queryset = SAMLPropertyMapping.objects.all() queryset = SAMLPropertyMapping.objects.all()

View File

@ -10,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.admin.api.tasks import TaskSerializer from authentik.admin.api.tasks import TaskSerializer
from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.events.monitored_tasks import TaskInfo from authentik.events.monitored_tasks import TaskInfo
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -41,7 +42,7 @@ class LDAPSourceSerializer(SourceSerializer):
extra_kwargs = {"bind_password": {"write_only": True}} extra_kwargs = {"bind_password": {"write_only": True}}
class LDAPSourceViewSet(ModelViewSet): class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
"""LDAP Source Viewset""" """LDAP Source Viewset"""
queryset = LDAPSource.objects.all() queryset = LDAPSource.objects.all()
@ -75,7 +76,7 @@ class LDAPPropertyMappingSerializer(PropertyMappingSerializer):
] ]
class LDAPPropertyMappingViewSet(ModelViewSet): class LDAPPropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""LDAP PropertyMapping Viewset""" """LDAP PropertyMapping Viewset"""
queryset = LDAPPropertyMapping.objects.all() queryset = LDAPPropertyMapping.objects.all()

View File

@ -9,6 +9,7 @@ from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.manager import MANAGER from authentik.sources.oauth.types.manager import MANAGER
@ -78,7 +79,7 @@ class OAuthSourceSerializer(SourceSerializer):
extra_kwargs = {"consumer_secret": {"write_only": True}} extra_kwargs = {"consumer_secret": {"write_only": True}}
class OAuthSourceViewSet(ModelViewSet): class OAuthSourceViewSet(UsedByMixin, ModelViewSet):
"""Source Viewset""" """Source Viewset"""
queryset = OAuthSource.objects.all() queryset = OAuthSource.objects.all()

View File

@ -6,6 +6,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.oauth.models import UserOAuthSourceConnection from authentik.sources.oauth.models import UserOAuthSourceConnection
@ -26,6 +27,7 @@ class UserOAuthSourceConnectionViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.flows.challenge import RedirectChallenge from authentik.flows.challenge import RedirectChallenge
from authentik.flows.views import to_stage_response from authentik.flows.views import to_stage_response
@ -42,7 +43,7 @@ class PlexTokenRedeemSerializer(PassiveSerializer):
plex_token = CharField() plex_token = CharField()
class PlexSourceViewSet(ModelViewSet): class PlexSourceViewSet(UsedByMixin, ModelViewSet):
"""Plex source Viewset""" """Plex source Viewset"""
queryset = PlexSource.objects.all() queryset = PlexSource.objects.all()

View File

@ -7,6 +7,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.providers.saml.api import SAMLMetadataSerializer from authentik.providers.saml.api import SAMLMetadataSerializer
from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.metadata import MetadataProcessor from authentik.sources.saml.processors.metadata import MetadataProcessor
@ -33,7 +34,7 @@ class SAMLSourceSerializer(SourceSerializer):
] ]
class SAMLSourceViewSet(ModelViewSet): class SAMLSourceViewSet(UsedByMixin, ModelViewSet):
"""SAMLSource Viewset""" """SAMLSource Viewset"""
queryset = SAMLSource.objects.all() queryset = SAMLSource.objects.all()

View File

@ -12,6 +12,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_duo.stage import ( from authentik.stages.authenticator_duo.stage import (
@ -37,7 +38,7 @@ class AuthenticatorDuoStageSerializer(StageSerializer):
} }
class AuthenticatorDuoStageViewSet(ModelViewSet): class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorDuoStage Viewset""" """AuthenticatorDuoStage Viewset"""
queryset = AuthenticatorDuoStage.objects.all() queryset = AuthenticatorDuoStage.objects.all()
@ -78,6 +79,7 @@ class DuoDeviceViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -8,6 +8,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
@ -21,7 +22,7 @@ class AuthenticatorStaticStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + ["configure_flow", "token_count"] fields = StageSerializer.Meta.fields + ["configure_flow", "token_count"]
class AuthenticatorStaticStageViewSet(ModelViewSet): class AuthenticatorStaticStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorStaticStage Viewset""" """AuthenticatorStaticStage Viewset"""
queryset = AuthenticatorStaticStage.objects.all() queryset = AuthenticatorStaticStage.objects.all()
@ -52,6 +53,7 @@ class StaticDeviceViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -8,6 +8,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
@ -21,7 +22,7 @@ class AuthenticatorTOTPStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + ["configure_flow", "digits"] fields = StageSerializer.Meta.fields + ["configure_flow", "digits"]
class AuthenticatorTOTPStageViewSet(ModelViewSet): class AuthenticatorTOTPStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorTOTPStage Viewset""" """AuthenticatorTOTPStage Viewset"""
queryset = AuthenticatorTOTPStage.objects.all() queryset = AuthenticatorTOTPStage.objects.all()
@ -45,6 +46,7 @@ class TOTPDeviceViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -2,6 +2,7 @@
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.flows.models import NotConfiguredAction from authentik.flows.models import NotConfiguredAction
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
@ -32,7 +33,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
] ]
class AuthenticatorValidateStageViewSet(ModelViewSet): class AuthenticatorValidateStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorValidateStage Viewset""" """AuthenticatorValidateStage Viewset"""
queryset = AuthenticatorValidateStage.objects.all() queryset = AuthenticatorValidateStage.objects.all()

View File

@ -7,6 +7,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_webauthn.models import ( from authentik.stages.authenticator_webauthn.models import (
AuthenticateWebAuthnStage, AuthenticateWebAuthnStage,
@ -23,7 +24,7 @@ class AuthenticateWebAuthnStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + ["configure_flow"] fields = StageSerializer.Meta.fields + ["configure_flow"]
class AuthenticateWebAuthnStageViewSet(ModelViewSet): class AuthenticateWebAuthnStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticateWebAuthnStage Viewset""" """AuthenticateWebAuthnStage Viewset"""
queryset = AuthenticateWebAuthnStage.objects.all() queryset = AuthenticateWebAuthnStage.objects.all()
@ -44,6 +45,7 @@ class WebAuthnDeviceViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -1,6 +1,7 @@
"""CaptchaStage API Views""" """CaptchaStage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.captcha.models import CaptchaStage from authentik.stages.captcha.models import CaptchaStage
@ -15,7 +16,7 @@ class CaptchaStageSerializer(StageSerializer):
extra_kwargs = {"private_key": {"write_only": True}} extra_kwargs = {"private_key": {"write_only": True}}
class CaptchaStageViewSet(ModelViewSet): class CaptchaStageViewSet(UsedByMixin, ModelViewSet):
"""CaptchaStage Viewset""" """CaptchaStage Viewset"""
queryset = CaptchaStage.objects.all() queryset = CaptchaStage.objects.all()

View File

@ -6,6 +6,7 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.core.api.applications import ApplicationSerializer from authentik.core.api.applications import ApplicationSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer from authentik.core.api.users import UserSerializer
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.consent.models import ConsentStage, UserConsent from authentik.stages.consent.models import ConsentStage, UserConsent
@ -20,7 +21,7 @@ class ConsentStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + ["mode", "consent_expire_in"] fields = StageSerializer.Meta.fields + ["mode", "consent_expire_in"]
class ConsentStageViewSet(ModelViewSet): class ConsentStageViewSet(UsedByMixin, ModelViewSet):
"""ConsentStage Viewset""" """ConsentStage Viewset"""
queryset = ConsentStage.objects.all() queryset = ConsentStage.objects.all()
@ -42,6 +43,7 @@ class UserConsentSerializer(StageSerializer):
class UserConsentViewSet( class UserConsentViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
GenericViewSet, GenericViewSet,
): ):

View File

@ -1,6 +1,7 @@
"""deny Stage API Views""" """deny Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.deny.models import DenyStage from authentik.stages.deny.models import DenyStage
@ -14,7 +15,7 @@ class DenyStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields fields = StageSerializer.Meta.fields
class DenyStageViewSet(ModelViewSet): class DenyStageViewSet(UsedByMixin, ModelViewSet):
"""DenyStage Viewset""" """DenyStage Viewset"""
queryset = DenyStage.objects.all() queryset = DenyStage.objects.all()

View File

@ -1,6 +1,7 @@
"""DummyStage API Views""" """DummyStage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.dummy.models import DummyStage from authentik.stages.dummy.models import DummyStage
@ -14,7 +15,7 @@ class DummyStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields fields = StageSerializer.Meta.fields
class DummyStageViewSet(ModelViewSet): class DummyStageViewSet(UsedByMixin, ModelViewSet):
"""DummyStage Viewset""" """DummyStage Viewset"""
queryset = DummyStage.objects.all() queryset = DummyStage.objects.all()

View File

@ -6,6 +6,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import TypeCreateSerializer from authentik.core.api.utils import TypeCreateSerializer
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.email.models import EmailStage, get_template_choices from authentik.stages.email.models import EmailStage, get_template_choices
@ -46,7 +47,7 @@ class EmailStageSerializer(StageSerializer):
extra_kwargs = {"password": {"write_only": True}} extra_kwargs = {"password": {"write_only": True}}
class EmailStageViewSet(ModelViewSet): class EmailStageViewSet(UsedByMixin, ModelViewSet):
"""EmailStage Viewset""" """EmailStage Viewset"""
queryset = EmailStage.objects.all() queryset = EmailStage.objects.all()

View File

@ -1,6 +1,7 @@
"""Identification Stage API Views""" """Identification Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.identification.models import IdentificationStage from authentik.stages.identification.models import IdentificationStage
@ -22,7 +23,7 @@ class IdentificationStageSerializer(StageSerializer):
] ]
class IdentificationStageViewSet(ModelViewSet): class IdentificationStageViewSet(UsedByMixin, ModelViewSet):
"""IdentificationStage Viewset""" """IdentificationStage Viewset"""
queryset = IdentificationStage.objects.all() queryset = IdentificationStage.objects.all()

View File

@ -3,6 +3,7 @@ from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import is_dict from authentik.core.api.utils import is_dict
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
@ -20,7 +21,7 @@ class InvitationStageSerializer(StageSerializer):
] ]
class InvitationStageViewSet(ModelViewSet): class InvitationStageViewSet(UsedByMixin, ModelViewSet):
"""InvitationStage Viewset""" """InvitationStage Viewset"""
queryset = InvitationStage.objects.all() queryset = InvitationStage.objects.all()
@ -45,7 +46,7 @@ class InvitationSerializer(ModelSerializer):
] ]
class InvitationViewSet(ModelViewSet): class InvitationViewSet(UsedByMixin, ModelViewSet):
"""Invitation Viewset""" """Invitation Viewset"""
queryset = Invitation.objects.all() queryset = Invitation.objects.all()

View File

@ -1,6 +1,7 @@
"""PasswordStage API Views""" """PasswordStage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.password.models import PasswordStage from authentik.stages.password.models import PasswordStage
@ -18,7 +19,7 @@ class PasswordStageSerializer(StageSerializer):
] ]
class PasswordStageViewSet(ModelViewSet): class PasswordStageViewSet(UsedByMixin, ModelViewSet):
"""PasswordStage Viewset""" """PasswordStage Viewset"""
queryset = PasswordStage.objects.all() queryset = PasswordStage.objects.all()

View File

@ -3,6 +3,7 @@ from rest_framework.serializers import CharField, ModelSerializer
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.prompt.models import Prompt, PromptStage from authentik.stages.prompt.models import Prompt, PromptStage
@ -21,7 +22,7 @@ class PromptStageSerializer(StageSerializer):
] ]
class PromptStageViewSet(ModelViewSet): class PromptStageViewSet(UsedByMixin, ModelViewSet):
"""PromptStage Viewset""" """PromptStage Viewset"""
queryset = PromptStage.objects.all() queryset = PromptStage.objects.all()
@ -48,7 +49,7 @@ class PromptSerializer(ModelSerializer):
] ]
class PromptViewSet(ModelViewSet): class PromptViewSet(UsedByMixin, ModelViewSet):
"""Prompt Viewset""" """Prompt Viewset"""
queryset = Prompt.objects.all().prefetch_related("promptstage_set") queryset = Prompt.objects.all().prefetch_related("promptstage_set")

View File

@ -1,6 +1,7 @@
"""User Delete Stage API Views""" """User Delete Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_delete.models import UserDeleteStage from authentik.stages.user_delete.models import UserDeleteStage
@ -14,7 +15,7 @@ class UserDeleteStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields fields = StageSerializer.Meta.fields
class UserDeleteStageViewSet(ModelViewSet): class UserDeleteStageViewSet(UsedByMixin, ModelViewSet):
"""UserDeleteStage Viewset""" """UserDeleteStage Viewset"""
queryset = UserDeleteStage.objects.all() queryset = UserDeleteStage.objects.all()

View File

@ -1,6 +1,7 @@
"""Login Stage API Views""" """Login Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_login.models import UserLoginStage from authentik.stages.user_login.models import UserLoginStage
@ -16,7 +17,7 @@ class UserLoginStageSerializer(StageSerializer):
] ]
class UserLoginStageViewSet(ModelViewSet): class UserLoginStageViewSet(UsedByMixin, ModelViewSet):
"""UserLoginStage Viewset""" """UserLoginStage Viewset"""
queryset = UserLoginStage.objects.all() queryset = UserLoginStage.objects.all()

View File

@ -1,6 +1,7 @@
"""Logout Stage API Views""" """Logout Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_logout.models import UserLogoutStage from authentik.stages.user_logout.models import UserLogoutStage
@ -14,7 +15,7 @@ class UserLogoutStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields fields = StageSerializer.Meta.fields
class UserLogoutStageViewSet(ModelViewSet): class UserLogoutStageViewSet(UsedByMixin, ModelViewSet):
"""UserLogoutStage Viewset""" """UserLogoutStage Viewset"""
queryset = UserLogoutStage.objects.all() queryset = UserLogoutStage.objects.all()

View File

@ -1,6 +1,7 @@
"""User Write Stage API Views""" """User Write Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_write.models import UserWriteStage from authentik.stages.user_write.models import UserWriteStage
@ -14,7 +15,7 @@ class UserWriteStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields fields = StageSerializer.Meta.fields
class UserWriteStageViewSet(ModelViewSet): class UserWriteStageViewSet(UsedByMixin, ModelViewSet):
"""UserWriteStage Viewset""" """UserWriteStage Viewset"""
queryset = UserWriteStage.objects.all() queryset = UserWriteStage.objects.all()

View File

@ -8,6 +8,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -56,7 +57,7 @@ class CurrentTenantSerializer(PassiveSerializer):
flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False)
class TenantViewSet(ModelViewSet): class TenantViewSet(UsedByMixin, ModelViewSet):
"""Tenant Viewset""" """Tenant Viewset"""
queryset = Tenant.objects.all() queryset = Tenant.objects.all()

View File

@ -41,7 +41,9 @@ class Tenant(models.Model):
) )
def __str__(self) -> str: def __str__(self) -> str:
return self.domain if self.default:
return "Default tenant"
return f"Tenant {self.domain}"
class Meta: class Meta:

View File

@ -9,6 +9,7 @@ force_grid_wrap = 0
use_parentheses = true use_parentheses = true
line_length = 88 line_length = 88
src_paths = ["authentik", "tests", "lifecycle"] src_paths = ["authentik", "tests", "lifecycle"]
force_to_top = "*"
[tool.coverage.run] [tool.coverage.run]
source = ["authentik"] source = ["authentik"]

2162
schema.yml

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,30 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { customElement, html, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { EVENT_REFRESH } from "../../constants"; import { EVENT_REFRESH } from "../../constants";
import { ModalButton } from "../buttons/ModalButton"; import { ModalButton } from "../buttons/ModalButton";
import { MessageLevel } from "../messages/Message"; import { MessageLevel } from "../messages/Message";
import { showMessage } from "../messages/MessageContainer"; import { showMessage } from "../messages/MessageContainer";
import "../buttons/SpinnerButton"; import "../buttons/SpinnerButton";
import { UsedBy, UsedByActionEnum } from "authentik-api";
import PFList from "@patternfly/patternfly/components/List/list.css";
import { until } from "lit-html/directives/until";
@customElement("ak-forms-delete") @customElement("ak-forms-delete")
export class DeleteForm extends ModalButton { export class DeleteForm extends ModalButton {
static get styles(): CSSResult[] {
return super.styles.concat(PFList);
}
@property({attribute: false}) @property({attribute: false})
obj?: Record<string, unknown>; obj?: Record<string, unknown>;
@property() @property()
objectLabel?: string; objectLabel?: string;
@property({attribute: false})
usedBy?: () => Promise<UsedBy[]>;
@property({attribute: false}) @property({attribute: false})
delete!: () => Promise<unknown>; delete!: () => Promise<unknown>;
@ -69,6 +79,40 @@ export class DeleteForm extends ModalButton {
</p> </p>
</form> </form>
</section> </section>
${this.usedBy ? until(this.usedBy().then(usedBy => {
if (usedBy.length < 1) {
return html``;
}
return html`
<section class="pf-c-page__main-section">
<form class="pf-c-form pf-m-horizontal">
<p>
${t`The following objects use ${objName} `}
</p>
<ul class="pf-c-list">
${usedBy.map(ub => {
let consequence = "";
switch (ub.action) {
case UsedByActionEnum.Cascade:
consequence = t`object will be DELETED`;
break;
case UsedByActionEnum.CascadeMany:
consequence = t`connecting object will be deleted`;
break;
case UsedByActionEnum.SetDefault:
consequence = t`reference will be reset to default value`;
break;
case UsedByActionEnum.SetNull:
consequence = t`reference will be set to an empty value`;
break;
}
return html`<li>${t`${ub.name} (${consequence})`}</li>`;
})}
</ul>
</form>
</section>
`;
})) : html``}
<footer class="pf-c-modal-box__footer"> <footer class="pf-c-modal-box__footer">
<ak-spinner-button <ak-spinner-button
.callAction=${() => { .callAction=${() => {

View File

@ -44,9 +44,14 @@ export class UserOAuthCodeList extends Table<ExpiringBaseGrantModel> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Authorization Code`} objectLabel=${t`Authorization Code`}
.usedBy=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesDestroy({ return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesDestroy({
id: item.pk || 0, id: item.pk,
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -68,9 +68,14 @@ export class UserOAuthRefreshList extends Table<RefreshTokenModel> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Refresh Code`} objectLabel=${t`Refresh Code`}
.usedBy=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({ return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({
id: item.pk || 0, id: item.pk,
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -45,6 +45,11 @@ export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Session`} objectLabel=${t`Session`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsUsedByList({
uuid: item.uuid || "",
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsDestroy({ return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsDestroy({
uuid: item.uuid || "", uuid: item.uuid || "",

View File

@ -40,9 +40,14 @@ export class UserConsentList extends Table<UserConsent> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Consent`} objectLabel=${t`Consent`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUserConsentUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUserConsentDestroy({ return new CoreApi(DEFAULT_CONFIG).coreUserConsentDestroy({
id: item.pk || 0, id: item.pk,
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -1144,6 +1144,9 @@ msgid "Disable"
msgstr "Disable" msgstr "Disable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Disable Duo authenticator"
msgstr "Disable Duo authenticator"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens" msgid "Disable Static Tokens"
msgstr "Disable Static Tokens" msgstr "Disable Static Tokens"
@ -1293,11 +1296,14 @@ msgstr "Email: Text field with Email type."
msgid "Enable" msgid "Enable"
msgstr "Enable" msgstr "Enable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Enable Duo authenticator"
msgstr "Enable Duo authenticator"
#: src/pages/sources/ldap/LDAPSourceForm.ts #: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Enable StartTLS" msgid "Enable StartTLS"
msgstr "Enable StartTLS" msgstr "Enable StartTLS"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts #: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Enable Static Tokens" msgid "Enable Static Tokens"
msgstr "Enable Static Tokens" msgstr "Enable Static Tokens"
@ -2148,6 +2154,10 @@ msgstr "Matches Event's Client IP (strict matching, for network matching use an
msgid "Matches an event against a set of criteria. If any of the configured values match, the policy passes." msgid "Matches an event against a set of criteria. If any of the configured values match, the policy passes."
msgstr "Matches an event against a set of criteria. If any of the configured values match, the policy passes." msgstr "Matches an event against a set of criteria. If any of the configured values match, the policy passes."
#: src/pages/tenants/TenantForm.ts
msgid "Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match."
msgstr "Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match."
#: src/pages/policies/expiry/ExpiryPolicyForm.ts #: src/pages/policies/expiry/ExpiryPolicyForm.ts
msgid "Maximum age (in days)" msgid "Maximum age (in days)"
msgstr "Maximum age (in days)" msgstr "Maximum age (in days)"
@ -3805,6 +3815,10 @@ msgstr "The external URL you'll access the application at. Include any non-stand
msgid "The external URL you'll authenticate at. Can be the same domain as authentik." msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
msgstr "The external URL you'll authenticate at. Can be the same domain as authentik." msgstr "The external URL you'll authenticate at. Can be the same domain as authentik."
#: src/elements/forms/DeleteForm.ts
msgid "The following objects use {objName}"
msgstr "The following objects use {objName}"
#: src/pages/policies/dummy/DummyPolicyForm.ts #: src/pages/policies/dummy/DummyPolicyForm.ts
msgid "The policy takes a random time to execute. This controls the minimum time it will take." msgid "The policy takes a random time to execute. This controls the minimum time it will take."
msgstr "The policy takes a random time to execute. This controls the minimum time it will take." msgstr "The policy takes a random time to execute. This controls the minimum time it will take."
@ -4519,6 +4533,18 @@ msgstr "authentik LDAP Backend"
msgid "no tabs defined" msgid "no tabs defined"
msgstr "no tabs defined" msgstr "no tabs defined"
#: src/elements/forms/DeleteForm.ts
msgid "object will be DELETED"
msgstr "object will be DELETED"
#: src/elements/forms/DeleteForm.ts
msgid "reference will be reset to default value"
msgstr "reference will be reset to default value"
#: src/elements/forms/DeleteForm.ts
msgid "reference will be set to an empty value"
msgstr "reference will be set to an empty value"
#: src/elements/Expand.ts #: src/elements/Expand.ts
#: src/elements/Expand.ts #: src/elements/Expand.ts
msgid "{0}" msgid "{0}"
@ -4532,6 +4558,10 @@ msgstr "{0} (\"{1}\", of type {2})"
msgid "{0} ({1})" msgid "{0} ({1})"
msgstr "{0} ({1})" msgstr "{0} ({1})"
#: src/elements/forms/DeleteForm.ts
msgid "{0} ({consequence})"
msgstr "{0} ({consequence})"
#: src/elements/table/TablePagination.ts #: src/elements/table/TablePagination.ts
msgid "{0} - {1} of {2}" msgid "{0} - {1} of {2}"
msgstr "{0} - {1} of {2}" msgstr "{0} - {1} of {2}"

View File

@ -1136,6 +1136,9 @@ msgid "Disable"
msgstr "" msgstr ""
#: #:
msgid "Disable Duo authenticator"
msgstr ""
#: #:
msgid "Disable Static Tokens" msgid "Disable Static Tokens"
msgstr "" msgstr ""
@ -1286,10 +1289,13 @@ msgid "Enable"
msgstr "" msgstr ""
#: #:
msgid "Enable StartTLS" msgid "Enable Duo authenticator"
msgstr "" msgstr ""
#: #:
msgid "Enable StartTLS"
msgstr ""
#: #:
msgid "Enable Static Tokens" msgid "Enable Static Tokens"
msgstr "" msgstr ""
@ -2140,6 +2146,10 @@ msgstr ""
msgid "Matches an event against a set of criteria. If any of the configured values match, the policy passes." msgid "Matches an event against a set of criteria. If any of the configured values match, the policy passes."
msgstr "" msgstr ""
#:
msgid "Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match."
msgstr ""
#: #:
msgid "Maximum age (in days)" msgid "Maximum age (in days)"
msgstr "" msgstr ""
@ -3797,6 +3807,10 @@ msgstr ""
msgid "The external URL you'll authenticate at. Can be the same domain as authentik." msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
msgstr "" msgstr ""
#:
msgid "The following objects use {objName}"
msgstr ""
#: #:
msgid "The policy takes a random time to execute. This controls the minimum time it will take." msgid "The policy takes a random time to execute. This controls the minimum time it will take."
msgstr "" msgstr ""
@ -4505,6 +4519,18 @@ msgstr ""
msgid "no tabs defined" msgid "no tabs defined"
msgstr "" msgstr ""
#:
msgid "object will be DELETED"
msgstr ""
#:
msgid "reference will be reset to default value"
msgstr ""
#:
msgid "reference will be set to an empty value"
msgstr ""
#: #:
#: #:
msgid "{0}" msgid "{0}"
@ -4518,6 +4544,10 @@ msgstr ""
msgid "{0} ({1})" msgid "{0} ({1})"
msgstr "" msgstr ""
#:
msgid "{0} ({consequence})"
msgstr ""
#: #:
msgid "{0} - {1} of {2}" msgid "{0} - {1} of {2}"
msgstr "" msgstr ""

View File

@ -103,9 +103,14 @@ export class ApplicationListPage extends TablePage<Application> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Application`} objectLabel=${t`Application`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsUsedByList({
slug: item.slug
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsDestroy({ return new CoreApi(DEFAULT_CONFIG).coreApplicationsDestroy({
slug: item.slug || "" slug: item.slug
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -79,9 +79,14 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Certificate-Key Pair`} objectLabel=${t`Certificate-Key Pair`}
.usedBy=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsUsedByList({
kpUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsDestroy({ return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsDestroy({
kpUuid: item.pk || "" kpUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -1,7 +1,7 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { ActionEnum, FlowsApi } from "authentik-api"; import { EventMatcherPolicyActionEnum, FlowsApi } from "authentik-api";
import "../../elements/Spinner"; import "../../elements/Spinner";
import "../../elements/Expand"; import "../../elements/Expand";
import { PFSize } from "../../elements/Spinner"; import { PFSize } from "../../elements/Spinner";
@ -142,14 +142,14 @@ export class EventInfo extends LitElement {
return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`; return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`;
} }
switch (this.event?.action) { switch (this.event?.action) {
case ActionEnum.ModelCreated: case EventMatcherPolicyActionEnum.ModelCreated:
case ActionEnum.ModelUpdated: case EventMatcherPolicyActionEnum.ModelUpdated:
case ActionEnum.ModelDeleted: case EventMatcherPolicyActionEnum.ModelDeleted:
return html` return html`
<h3>${t`Affected model:`}</h3> <h3>${t`Affected model:`}</h3>
${this.getModelInfo(this.event.context?.model as EventContext)} ${this.getModelInfo(this.event.context?.model as EventContext)}
`; `;
case ActionEnum.AuthorizeApplication: case EventMatcherPolicyActionEnum.AuthorizeApplication:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Authorized application:`}</h3> <h3>${t`Authorized application:`}</h3>
@ -166,17 +166,17 @@ export class EventInfo extends LitElement {
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case ActionEnum.EmailSent: case EventMatcherPolicyActionEnum.EmailSent:
return html`<h3>${t`Email info:`}</h3> return html`<h3>${t`Email info:`}</h3>
${this.getEmailInfo(this.event.context)} ${this.getEmailInfo(this.event.context)}
<ak-expand> <ak-expand>
<iframe srcdoc=${this.event.context.body}></iframe> <iframe srcdoc=${this.event.context.body}></iframe>
</ak-expand>`; </ak-expand>`;
case ActionEnum.SecretView: case EventMatcherPolicyActionEnum.SecretView:
return html` return html`
<h3>${t`Secret:`}</h3> <h3>${t`Secret:`}</h3>
${this.getModelInfo(this.event.context.secret as EventContext)}`; ${this.getModelInfo(this.event.context.secret as EventContext)}`;
case ActionEnum.PropertyMappingException: case EventMatcherPolicyActionEnum.PropertyMappingException:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Exception`}</h3> <h3>${t`Exception`}</h3>
@ -188,7 +188,7 @@ export class EventInfo extends LitElement {
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case ActionEnum.PolicyException: case EventMatcherPolicyActionEnum.PolicyException:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Binding`}</h3> <h3>${t`Binding`}</h3>
@ -207,7 +207,7 @@ export class EventInfo extends LitElement {
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case ActionEnum.PolicyExecution: case EventMatcherPolicyActionEnum.PolicyExecution:
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
<h3>${t`Binding`}</h3> <h3>${t`Binding`}</h3>
@ -235,10 +235,10 @@ export class EventInfo extends LitElement {
</div> </div>
</div> </div>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case ActionEnum.ConfigurationError: case EventMatcherPolicyActionEnum.ConfigurationError:
return html`<h3>${this.event.context.message}</h3> return html`<h3>${this.event.context.message}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case ActionEnum.UpdateAvailable: case EventMatcherPolicyActionEnum.UpdateAvailable:
return html`<h3>${t`New version available!`}</h3> return html`<h3>${t`New version available!`}</h3>
<a <a
target="_blank" target="_blank"
@ -247,7 +247,7 @@ export class EventInfo extends LitElement {
</a>`; </a>`;
// Action types which typically don't record any extra context. // Action types which typically don't record any extra context.
// If context is not empty, we fall to the default response. // If context is not empty, we fall to the default response.
case ActionEnum.Login: case EventMatcherPolicyActionEnum.Login:
if ("using_source" in this.event.context) { if ("using_source" in this.event.context) {
return html`<div class="pf-l-flex"> return html`<div class="pf-l-flex">
<div class="pf-l-flex__item"> <div class="pf-l-flex__item">
@ -257,11 +257,11 @@ export class EventInfo extends LitElement {
</div>`; </div>`;
} }
return this.defaultResponse(); return this.defaultResponse();
case ActionEnum.LoginFailed: case EventMatcherPolicyActionEnum.LoginFailed:
return html` return html`
<h3>${t`Attempted to log in as ${this.event.context.username}`}</h3> <h3>${t`Attempted to log in as ${this.event.context.username}`}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`; <ak-expand>${this.defaultResponse()}</ak-expand>`;
case ActionEnum.Logout: case EventMatcherPolicyActionEnum.Logout:
if (this.event.context === {}) { if (this.event.context === {}) {
return html`<span>${t`No additional data available.`}</span>`; return html`<span>${t`No additional data available.`}</span>`;
} }

View File

@ -73,9 +73,14 @@ export class RuleListPage extends TablePage<NotificationRule> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Notification rule`} objectLabel=${t`Notification rule`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesUsedByList({
pbmUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesDestroy({ return new EventsApi(DEFAULT_CONFIG).eventsRulesDestroy({
pbmUuid: item.pk || "" pbmUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -77,9 +77,14 @@ export class TransportListPage extends TablePage<NotificationTransport> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Notifications Transport`} objectLabel=${t`Notifications Transport`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsUsedByList({
uuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsDestroy({ return new EventsApi(DEFAULT_CONFIG).eventsTransportsDestroy({
uuid: item.pk || "" uuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -82,9 +82,14 @@ export class BoundStagesList extends Table<FlowStageBinding> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Stage binding`} objectLabel=${t`Stage binding`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({
fsbUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsDestroy({ return new FlowsApi(DEFAULT_CONFIG).flowsBindingsDestroy({
fsbUuid: item.pk || "", fsbUuid: item.pk,
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -78,6 +78,11 @@ export class FlowListPage extends TablePage<Flow> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Flow`} objectLabel=${t`Flow`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesUsedByList({
slug: item.slug
});
}}
.delete=${() => { .delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDestroy({ return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDestroy({
slug: item.slug slug: item.slug

View File

@ -72,9 +72,14 @@ export class GroupListPage extends TablePage<Group> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Group`} objectLabel=${t`Group`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsUsedByList({
groupUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsDestroy({ return new CoreApi(DEFAULT_CONFIG).coreGroupsDestroy({
groupUuid: item.pk || "" groupUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -77,9 +77,14 @@ export class OutpostListPage extends TablePage<Outpost> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Outpost`} objectLabel=${t`Outpost`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesUsedByList({
uuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesDestroy({ return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesDestroy({
uuid: item.pk || "" uuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -93,9 +93,14 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Outpost Service-connection`} objectLabel=${t`Outpost Service-connection`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllUsedByList({
uuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllDestroy({ return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllDestroy({
uuid: item.pk || "" uuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -137,9 +137,14 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Policy binding`} objectLabel=${t`Policy binding`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
policyBindingUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({ return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
policyBindingUuid: item.pk || "", policyBindingUuid: item.pk,
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -107,9 +107,14 @@ export class PolicyListPage extends TablePage<Policy> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Policy`} objectLabel=${t`Policy`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllUsedByList({
policyUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({ return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({
policyUuid: item.pk || "" policyUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -55,6 +55,11 @@ export class IPReputationListPage extends TablePage<IPReputation> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`IP Reputation`} objectLabel=${t`IP Reputation`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsDestroy({ return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsDestroy({
id: item.pk, id: item.pk,

View File

@ -55,6 +55,11 @@ export class UserReputationListPage extends TablePage<UserReputation> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`User Reputation`} objectLabel=${t`User Reputation`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersDestroy({ return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersDestroy({
id: item.pk, id: item.pk,

View File

@ -97,9 +97,14 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Property Mapping`} objectLabel=${t`Property Mapping`}
.usedBy=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllUsedByList({
pmUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllDestroy({ return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllDestroy({
pmUuid: item.pk || "" pmUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -90,9 +90,14 @@ export class ProviderListPage extends TablePage<Provider> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Provider`} objectLabel=${t`Provider`}
.usedBy=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllDestroy({ return new ProvidersApi(DEFAULT_CONFIG).providersAllDestroy({
id: item.pk || 0 id: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -86,9 +86,14 @@ export class SourceListPage extends TablePage<Source> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Source`} objectLabel=${t`Source`}
.usedBy=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({
slug: item.slug
});
}}
.delete=${() => { .delete=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllDestroy({ return new SourcesApi(DEFAULT_CONFIG).sourcesAllDestroy({
slug: item.slug || "" slug: item.slug
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -102,9 +102,14 @@ export class StageListPage extends TablePage<Stage> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${item.verboseName || ""} objectLabel=${item.verboseName || ""}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllUsedByList({
stageUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllDestroy({ return new StagesApi(DEFAULT_CONFIG).stagesAllDestroy({
stageUuid: item.pk || "" stageUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -58,9 +58,14 @@ export class InvitationListPage extends TablePage<Invitation> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Invitation`} objectLabel=${t`Invitation`}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsUsedByList({
inviteUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsDestroy({ return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsDestroy({
inviteUuid: item.pk || "" inviteUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -77,9 +77,14 @@ export class PromptListPage extends TablePage<Prompt> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Prompt`} objectLabel=${t`Prompt`}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsUsedByList({
promptUuid: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsDestroy({ return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsDestroy({
promptUuid: item.pk || "" promptUuid: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">

View File

@ -47,6 +47,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
?required=${true} ?required=${true}
name="domain"> name="domain">
<input type="text" value="${first(this.instance?.domain, window.location.host)}" class="pf-c-form-control" required> <input type="text" value="${first(this.instance?.domain, window.location.host)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="default"> <ak-form-element-horizontal name="default">
<div class="pf-c-check"> <div class="pf-c-check">

View File

@ -68,6 +68,11 @@ export class TenantListPage extends TablePage<Tenant> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Tenant`} objectLabel=${t`Tenant`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreTenantsUsedByList({
tenantUuid: item.tenantUuid
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreTenantsDestroy({ return new CoreApi(DEFAULT_CONFIG).coreTenantsDestroy({
tenantUuid: item.tenantUuid tenantUuid: item.tenantUuid

View File

@ -58,6 +58,11 @@ export class TokenListPage extends TablePage<Token> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`Token`} objectLabel=${t`Token`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreTokensUsedByList({
identifier: item.identifier
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreTokensDestroy({ return new CoreApi(DEFAULT_CONFIG).coreTokensDestroy({
identifier: item.identifier identifier: item.identifier

View File

@ -31,7 +31,7 @@ export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
}); });
}); });
}}> }}>
${t`Disable Static Tokens`} ${t`Disable Duo authenticator`}
</button> </button>
</div>`; </div>`;
} }
@ -47,7 +47,7 @@ export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
${this.configureUrl ? ${this.configureUrl ?
html`<a href="${this.configureUrl}?next=/%23%2Fuser" html`<a href="${this.configureUrl}?next=/%23%2Fuser"
class="pf-c-button pf-m-primary">${t`Enable Static Tokens`} class="pf-c-button pf-m-primary">${t`Enable Duo authenticator`}
</a>`: html``} </a>`: html``}
</div>`; </div>`;
} }

View File

@ -111,9 +111,14 @@ export class UserListPage extends TablePage<User> {
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${t`User`} objectLabel=${t`User`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUsersUsedByList({
id: item.pk
});
}}
.delete=${() => { .delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUsersDestroy({ return new CoreApi(DEFAULT_CONFIG).coreUsersDestroy({
id: item.pk || 0 id: item.pk
}); });
}}> }}>
<button slot="trigger" class="pf-c-dropdown__menu-item"> <button slot="trigger" class="pf-c-dropdown__menu-item">