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.api.decorators import permission_required
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Application, User
from authentik.events.models import EventAction
from authentik.policies.api.exec import PolicyTestResultSerializer
@ -73,7 +74,7 @@ class ApplicationSerializer(ModelSerializer):
}
class ApplicationViewSet(ModelViewSet):
class ApplicationViewSet(UsedByMixin, ModelViewSet):
"""Application Viewset"""
queryset = Application.objects.all()

View file

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

View file

@ -5,6 +5,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
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.models import Group
@ -20,7 +21,7 @@ class GroupSerializer(ModelSerializer):
fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"]
class GroupViewSet(ModelViewSet):
class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset"""
queryset = Group.objects.all()

View file

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

View file

@ -9,6 +9,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
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.models import Provider
from authentik.lib.utils.reflection import all_subclasses
@ -48,6 +49,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
class ProviderViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

@ -10,6 +10,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet
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.models import Source
from authentik.core.types import UserSettingSerializer
@ -52,6 +53,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
class SourceViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

@ -9,6 +9,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
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.utils import PassiveSerializer
from authentik.core.models import Token, TokenIntents
@ -43,7 +44,7 @@ class TokenViewSerializer(PassiveSerializer):
key = CharField(read_only=True)
class TokenViewSet(ModelViewSet):
class TokenViewSet(UsedByMixin, ModelViewSet):
"""Token Viewset"""
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.api.decorators import permission_required
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.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER,
@ -131,7 +132,7 @@ class UsersFilter(FilterSet):
fields = ["username", "name", "is_active", "is_superuser", "attributes"]
class UserViewSet(ModelViewSet):
class UserViewSet(UsedByMixin, ModelViewSet):
"""User Viewset"""
queryset = User.objects.none()

View file

@ -5,6 +5,7 @@ from typing import Any, Optional, Type
from urllib.parse import urlencode
from uuid import uuid4
import django.db.models.options as options
from django.conf import settings
from django.contrib.auth.models import AbstractUser
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")
options.DEFAULT_NAMES = options.DEFAULT_NAMES + ("authentik_used_by_shadows",)
def default_token_duration():
"""Default duration a Token is valid"""
return now() + timedelta(minutes=30)

View file

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

View file

@ -4,10 +4,14 @@ import datetime
from django.test import TestCase
from django.urls import reverse
from authentik.core.api.used_by import DeleteAction
from authentik.core.models import User
from authentik.crypto.api import CertificateKeyPairSerializer
from authentik.crypto.builder import CertificateBuilder
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):
@ -91,3 +95,35 @@ class TestCrypto(TestCase):
)
self.assertEqual(200, response.status_code)
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 authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.events.api.event import EventSerializer
from authentik.events.models import Notification
@ -35,6 +36,7 @@ class NotificationViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
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.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
@ -94,7 +95,7 @@ class DiagramElement:
return f"{self.identifier}=>{self.type}: {self.rest}"
class FlowViewSet(ModelViewSet):
class FlowViewSet(UsedByMixin, ModelViewSet):
"""Flow Viewset"""
queryset = Flow.objects.all()

View file

@ -11,6 +11,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet
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.types import UserSettingSerializer
from authentik.flows.api.flows import FlowSerializer
@ -49,6 +50,7 @@ class StageSerializer(ModelSerializer, MetaNameSerializer):
class StageViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

@ -72,7 +72,7 @@ class Stage(SerializerModel):
def __str__(self):
if hasattr(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:
@ -212,7 +212,7 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
return FlowStageBindingSerializer
def __str__(self) -> str:
return f"{self.target} #{self.order}"
return f"Flow-stage binding #{self.order} to {self.target}"
class Meta:

View file

@ -11,6 +11,7 @@ from rest_framework.serializers import JSONField, ModelSerializer, ValidationErr
from rest_framework.viewsets import ModelViewSet
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.models import Provider
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
@ -95,7 +96,7 @@ class OutpostHealthSerializer(PassiveSerializer):
version_outdated = BooleanField(read_only=True)
class OutpostViewSet(ModelViewSet):
class OutpostViewSet(UsedByMixin, ModelViewSet):
"""Outpost Viewset"""
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.viewsets import GenericViewSet, ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import (
MetaNameSerializer,
PassiveSerializer,
@ -55,6 +56,7 @@ class ServiceConnectionStateSerializer(PassiveSerializer):
class ServiceConnectionViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
@ -105,7 +107,7 @@ class DockerServiceConnectionSerializer(ServiceConnectionSerializer):
]
class DockerServiceConnectionViewSet(ModelViewSet):
class DockerServiceConnectionViewSet(UsedByMixin, ModelViewSet):
"""DockerServiceConnection Viewset"""
queryset = DockerServiceConnection.objects.all()
@ -139,7 +141,7 @@ class KubernetesServiceConnectionSerializer(ServiceConnectionSerializer):
fields = ServiceConnectionSerializer.Meta.fields + ["kubeconfig"]
class KubernetesServiceConnectionViewSet(ModelViewSet):
class KubernetesServiceConnectionViewSet(UsedByMixin, ModelViewSet):
"""KubernetesServiceConnection Viewset"""
queryset = KubernetesServiceConnection.objects.all()

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
"""Event Matcher Policy API"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer
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"""
queryset = EventMatcherPolicy.objects.all()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,6 +11,7 @@ from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet
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.models import Provider
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider
@ -61,7 +62,7 @@ class OAuth2ProviderSetupURLs(PassiveSerializer):
logout = ReadOnlyField()
class OAuth2ProviderViewSet(ModelViewSet):
class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
"""OAuth2Provider Viewset"""
queryset = OAuth2Provider.objects.all()

View file

@ -2,6 +2,7 @@
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.providers.oauth2.models import ScopeMapping
@ -17,7 +18,7 @@ class ScopeMappingSerializer(PropertyMappingSerializer):
]
class ScopeMappingViewSet(ModelViewSet):
class ScopeMappingViewSet(UsedByMixin, ModelViewSet):
"""ScopeMapping Viewset"""
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.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import MetaNameSerializer
from authentik.providers.oauth2.api.provider import OAuth2ProviderSerializer
@ -57,6 +58,7 @@ class RefreshTokenModelSerializer(ExpiringBaseGrantModelSerializer):
class AuthorizationCodeViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
@ -82,6 +84,7 @@ class AuthorizationCodeViewSet(
class RefreshTokenViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
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(
CertificateKeyPair,
verbose_name=_("RSA Key"),
on_delete=models.CASCADE,
blank=True,
on_delete=models.SET_NULL,
null=True,
help_text=_(
"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 authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.providers.oauth2.views.provider import ProviderInfoView
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
@ -76,7 +77,7 @@ class ProxyProviderSerializer(ProviderSerializer):
]
class ProxyProviderViewSet(ModelViewSet):
class ProxyProviderViewSet(UsedByMixin, ModelViewSet):
"""ProxyProvider Viewset"""
queryset = ProxyProvider.objects.all()

View file

@ -167,3 +167,4 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
verbose_name = _("Proxy Provider")
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.core.api.propertymappings import PropertyMappingSerializer
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.models import Provider
from authentik.flows.models import Flow, FlowDesignation
@ -75,7 +76,7 @@ class SAMLProviderImportSerializer(PassiveSerializer):
file = FileField()
class SAMLProviderViewSet(ModelViewSet):
class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
"""SAMLProvider Viewset"""
queryset = SAMLProvider.objects.all()
@ -166,7 +167,7 @@ class SAMLPropertyMappingSerializer(PropertyMappingSerializer):
]
class SAMLPropertyMappingViewSet(ModelViewSet):
class SAMLPropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""SAMLPropertyMapping Viewset"""
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.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.events.monitored_tasks import TaskInfo
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -41,7 +42,7 @@ class LDAPSourceSerializer(SourceSerializer):
extra_kwargs = {"bind_password": {"write_only": True}}
class LDAPSourceViewSet(ModelViewSet):
class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
"""LDAP Source Viewset"""
queryset = LDAPSource.objects.all()
@ -75,7 +76,7 @@ class LDAPPropertyMappingSerializer(PropertyMappingSerializer):
]
class LDAPPropertyMappingViewSet(ModelViewSet):
class LDAPPropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""LDAP PropertyMapping Viewset"""
queryset = LDAPPropertyMapping.objects.all()

View file

@ -9,6 +9,7 @@ from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.manager import MANAGER
@ -78,7 +79,7 @@ class OAuthSourceSerializer(SourceSerializer):
extra_kwargs = {"consumer_secret": {"write_only": True}}
class OAuthSourceViewSet(ModelViewSet):
class OAuthSourceViewSet(UsedByMixin, ModelViewSet):
"""Source Viewset"""
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.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.oauth.models import UserOAuthSourceConnection
@ -26,6 +27,7 @@ class UserOAuthSourceConnectionViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

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

View file

@ -7,6 +7,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.providers.saml.api import SAMLMetadataSerializer
from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.metadata import MetadataProcessor
@ -33,7 +34,7 @@ class SAMLSourceSerializer(SourceSerializer):
]
class SAMLSourceViewSet(ModelViewSet):
class SAMLSourceViewSet(UsedByMixin, ModelViewSet):
"""SAMLSource Viewset"""
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 authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_duo.stage import (
@ -37,7 +38,7 @@ class AuthenticatorDuoStageSerializer(StageSerializer):
}
class AuthenticatorDuoStageViewSet(ModelViewSet):
class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorDuoStage Viewset"""
queryset = AuthenticatorDuoStage.objects.all()
@ -78,6 +79,7 @@ class DuoDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

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

View file

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

View file

@ -2,6 +2,7 @@
from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.flows.models import NotConfiguredAction
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
@ -32,7 +33,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
]
class AuthenticatorValidateStageViewSet(ModelViewSet):
class AuthenticatorValidateStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorValidateStage Viewset"""
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 authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_webauthn.models import (
AuthenticateWebAuthnStage,
@ -23,7 +24,7 @@ class AuthenticateWebAuthnStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + ["configure_flow"]
class AuthenticateWebAuthnStageViewSet(ModelViewSet):
class AuthenticateWebAuthnStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticateWebAuthnStage Viewset"""
queryset = AuthenticateWebAuthnStage.objects.all()
@ -44,6 +45,7 @@ class WebAuthnDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

@ -1,6 +1,7 @@
"""CaptchaStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.captcha.models import CaptchaStage
@ -15,7 +16,7 @@ class CaptchaStageSerializer(StageSerializer):
extra_kwargs = {"private_key": {"write_only": True}}
class CaptchaStageViewSet(ModelViewSet):
class CaptchaStageViewSet(UsedByMixin, ModelViewSet):
"""CaptchaStage Viewset"""
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 authentik.core.api.applications import ApplicationSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.consent.models import ConsentStage, UserConsent
@ -20,7 +21,7 @@ class ConsentStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + ["mode", "consent_expire_in"]
class ConsentStageViewSet(ModelViewSet):
class ConsentStageViewSet(UsedByMixin, ModelViewSet):
"""ConsentStage Viewset"""
queryset = ConsentStage.objects.all()
@ -42,6 +43,7 @@ class UserConsentSerializer(StageSerializer):
class UserConsentViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):

View file

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

View file

@ -1,6 +1,7 @@
"""DummyStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.dummy.models import DummyStage
@ -14,7 +15,7 @@ class DummyStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields
class DummyStageViewSet(ModelViewSet):
class DummyStageViewSet(UsedByMixin, ModelViewSet):
"""DummyStage Viewset"""
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.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import TypeCreateSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.email.models import EmailStage, get_template_choices
@ -46,7 +47,7 @@ class EmailStageSerializer(StageSerializer):
extra_kwargs = {"password": {"write_only": True}}
class EmailStageViewSet(ModelViewSet):
class EmailStageViewSet(UsedByMixin, ModelViewSet):
"""EmailStage Viewset"""
queryset = EmailStage.objects.all()

View file

@ -1,6 +1,7 @@
"""Identification Stage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.identification.models import IdentificationStage
@ -22,7 +23,7 @@ class IdentificationStageSerializer(StageSerializer):
]
class IdentificationStageViewSet(ModelViewSet):
class IdentificationStageViewSet(UsedByMixin, ModelViewSet):
"""IdentificationStage Viewset"""
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.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import is_dict
from authentik.flows.api.stages import StageSerializer
@ -20,7 +21,7 @@ class InvitationStageSerializer(StageSerializer):
]
class InvitationStageViewSet(ModelViewSet):
class InvitationStageViewSet(UsedByMixin, ModelViewSet):
"""InvitationStage Viewset"""
queryset = InvitationStage.objects.all()
@ -45,7 +46,7 @@ class InvitationSerializer(ModelSerializer):
]
class InvitationViewSet(ModelViewSet):
class InvitationViewSet(UsedByMixin, ModelViewSet):
"""Invitation Viewset"""
queryset = Invitation.objects.all()

View file

@ -1,6 +1,7 @@
"""PasswordStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.password.models import PasswordStage
@ -18,7 +19,7 @@ class PasswordStageSerializer(StageSerializer):
]
class PasswordStageViewSet(ModelViewSet):
class PasswordStageViewSet(UsedByMixin, ModelViewSet):
"""PasswordStage Viewset"""
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.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.prompt.models import Prompt, PromptStage
@ -21,7 +22,7 @@ class PromptStageSerializer(StageSerializer):
]
class PromptStageViewSet(ModelViewSet):
class PromptStageViewSet(UsedByMixin, ModelViewSet):
"""PromptStage Viewset"""
queryset = PromptStage.objects.all()
@ -48,7 +49,7 @@ class PromptSerializer(ModelSerializer):
]
class PromptViewSet(ModelViewSet):
class PromptViewSet(UsedByMixin, ModelViewSet):
"""Prompt Viewset"""
queryset = Prompt.objects.all().prefetch_related("promptstage_set")

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
"""User Write Stage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_write.models import UserWriteStage
@ -14,7 +15,7 @@ class UserWriteStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields
class UserWriteStageViewSet(ModelViewSet):
class UserWriteStageViewSet(UsedByMixin, ModelViewSet):
"""UserWriteStage Viewset"""
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.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.config import CONFIG
from authentik.tenants.models import Tenant
@ -56,7 +57,7 @@ class CurrentTenantSerializer(PassiveSerializer):
flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False)
class TenantViewSet(ModelViewSet):
class TenantViewSet(UsedByMixin, ModelViewSet):
"""Tenant Viewset"""
queryset = Tenant.objects.all()

View file

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

View file

@ -9,6 +9,7 @@ force_grid_wrap = 0
use_parentheses = true
line_length = 88
src_paths = ["authentik", "tests", "lifecycle"]
force_to_top = "*"
[tool.coverage.run]
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 { customElement, html, property, TemplateResult } from "lit-element";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { EVENT_REFRESH } from "../../constants";
import { ModalButton } from "../buttons/ModalButton";
import { MessageLevel } from "../messages/Message";
import { showMessage } from "../messages/MessageContainer";
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")
export class DeleteForm extends ModalButton {
static get styles(): CSSResult[] {
return super.styles.concat(PFList);
}
@property({attribute: false})
obj?: Record<string, unknown>;
@property()
objectLabel?: string;
@property({attribute: false})
usedBy?: () => Promise<UsedBy[]>;
@property({attribute: false})
delete!: () => Promise<unknown>;
@ -69,6 +79,40 @@ export class DeleteForm extends ModalButton {
</p>
</form>
</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">
<ak-spinner-button
.callAction=${() => {

View file

@ -44,9 +44,14 @@ export class UserOAuthCodeList extends Table<ExpiringBaseGrantModel> {
<ak-forms-delete
.obj=${item}
objectLabel=${t`Authorization Code`}
.usedBy=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesUsedByList({
id: item.pk
});
}}
.delete=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesDestroy({
id: item.pk || 0,
id: item.pk,
});
}}>
<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
.obj=${item}
objectLabel=${t`Refresh Code`}
.usedBy=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensUsedByList({
id: item.pk
});
}}
.delete=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({
id: item.pk || 0,
id: item.pk,
});
}}>
<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
.obj=${item}
objectLabel=${t`Session`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsUsedByList({
uuid: item.uuid || "",
});
}}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsDestroy({
uuid: item.uuid || "",

View file

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

View file

@ -1144,6 +1144,9 @@ msgid "Disable"
msgstr "Disable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Disable Duo authenticator"
msgstr "Disable Duo authenticator"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr "Disable Static Tokens"
@ -1293,11 +1296,14 @@ msgstr "Email: Text field with Email type."
msgid "Enable"
msgstr "Enable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Enable Duo authenticator"
msgstr "Enable Duo authenticator"
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Enable StartTLS"
msgstr "Enable StartTLS"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "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."
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
msgid "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."
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
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."
@ -4519,6 +4533,18 @@ msgstr "authentik LDAP Backend"
msgid "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
msgid "{0}"
@ -4532,6 +4558,10 @@ msgstr "{0} (\"{1}\", of type {2})"
msgid "{0} ({1})"
msgstr "{0} ({1})"
#: src/elements/forms/DeleteForm.ts
msgid "{0} ({consequence})"
msgstr "{0} ({consequence})"
#: src/elements/table/TablePagination.ts
msgid "{0} - {1} of {2}"
msgstr "{0} - {1} of {2}"

View file

@ -1136,6 +1136,9 @@ msgid "Disable"
msgstr ""
#:
msgid "Disable Duo authenticator"
msgstr ""
#:
msgid "Disable Static Tokens"
msgstr ""
@ -1286,10 +1289,13 @@ msgid "Enable"
msgstr ""
#:
msgid "Enable StartTLS"
msgid "Enable Duo authenticator"
msgstr ""
#:
msgid "Enable StartTLS"
msgstr ""
#:
msgid "Enable Static Tokens"
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."
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)"
msgstr ""
@ -3797,6 +3807,10 @@ msgstr ""
msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
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."
msgstr ""
@ -4505,6 +4519,18 @@ msgstr ""
msgid "no tabs defined"
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}"
@ -4518,6 +4544,10 @@ msgstr ""
msgid "{0} ({1})"
msgstr ""
#:
msgid "{0} ({consequence})"
msgstr ""
#:
msgid "{0} - {1} of {2}"
msgstr ""

View file

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

View file

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

View file

@ -73,9 +73,14 @@ export class RuleListPage extends TablePage<NotificationRule> {
<ak-forms-delete
.obj=${item}
objectLabel=${t`Notification rule`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesUsedByList({
pbmUuid: item.pk
});
}}
.delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsRulesDestroy({
pbmUuid: item.pk || ""
pbmUuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Notifications Transport`}
.usedBy=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsUsedByList({
uuid: item.pk
});
}}
.delete=${() => {
return new EventsApi(DEFAULT_CONFIG).eventsTransportsDestroy({
uuid: item.pk || ""
uuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Stage binding`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({
fsbUuid: item.pk
});
}}
.delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsDestroy({
fsbUuid: item.pk || "",
fsbUuid: item.pk,
});
}}>
<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
.obj=${item}
objectLabel=${t`Flow`}
.usedBy=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesUsedByList({
slug: item.slug
});
}}
.delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDestroy({
slug: item.slug

View file

@ -72,9 +72,14 @@ export class GroupListPage extends TablePage<Group> {
<ak-forms-delete
.obj=${item}
objectLabel=${t`Group`}
.usedBy=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsUsedByList({
groupUuid: item.pk
});
}}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsDestroy({
groupUuid: item.pk || ""
groupUuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Outpost`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesUsedByList({
uuid: item.pk
});
}}
.delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesDestroy({
uuid: item.pk || ""
uuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Outpost Service-connection`}
.usedBy=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllUsedByList({
uuid: item.pk
});
}}
.delete=${() => {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllDestroy({
uuid: item.pk || ""
uuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Policy binding`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
policyBindingUuid: item.pk
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
policyBindingUuid: item.pk || "",
policyBindingUuid: item.pk,
});
}}>
<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
.obj=${item}
objectLabel=${t`Policy`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllUsedByList({
policyUuid: item.pk
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({
policyUuid: item.pk || ""
policyUuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`IP Reputation`}
.usedBy=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsUsedByList({
id: item.pk
});
}}
.delete=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsDestroy({
id: item.pk,

View file

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

View file

@ -97,9 +97,14 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
<ak-forms-delete
.obj=${item}
objectLabel=${t`Property Mapping`}
.usedBy=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllUsedByList({
pmUuid: item.pk
});
}}
.delete=${() => {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllDestroy({
pmUuid: item.pk || ""
pmUuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Provider`}
.usedBy=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllUsedByList({
id: item.pk
});
}}
.delete=${() => {
return new ProvidersApi(DEFAULT_CONFIG).providersAllDestroy({
id: item.pk || 0
id: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Source`}
.usedBy=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({
slug: item.slug
});
}}
.delete=${() => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllDestroy({
slug: item.slug || ""
slug: item.slug
});
}}>
<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
.obj=${item}
objectLabel=${item.verboseName || ""}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllUsedByList({
stageUuid: item.pk
});
}}
.delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesAllDestroy({
stageUuid: item.pk || ""
stageUuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Invitation`}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsUsedByList({
inviteUuid: item.pk
});
}}
.delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsDestroy({
inviteUuid: item.pk || ""
inviteUuid: item.pk
});
}}>
<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
.obj=${item}
objectLabel=${t`Prompt`}
.usedBy=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsUsedByList({
promptUuid: item.pk
});
}}
.delete=${() => {
return new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsDestroy({
promptUuid: item.pk || ""
promptUuid: item.pk
});
}}>
<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}
name="domain">
<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 name="default">
<div class="pf-c-check">

View file

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

View file

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

View file

@ -31,7 +31,7 @@ export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
});
});
}}>
${t`Disable Static Tokens`}
${t`Disable Duo authenticator`}
</button>
</div>`;
}
@ -47,7 +47,7 @@ export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
<div class="pf-c-card__footer">
${this.configureUrl ?
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``}
</div>`;
}

View file

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