diff --git a/Dockerfile b/Dockerfile index 023d2e61b5..eb4d70f7c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ RUN apt-get update && \ chown authentik:authentik /backups COPY ./authentik/ /authentik -COPY ./pytest.ini / +COPY ./pyproject.toml / COPY ./xml /xml COPY ./manage.py / COPY ./lifecycle/ /lifecycle diff --git a/Makefile b/Makefile index 087a20ccae..e23a2c9ff0 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ test-integration: coverage run manage.py test -v 3 tests/integration test-e2e: - coverage run manage.py test -v 3 tests/e2e + coverage run manage.py test --failfast -v 3 tests/e2e coverage: coverage run manage.py test -v 3 authentik diff --git a/Pipfile b/Pipfile index 9784a56528..5706494185 100644 --- a/Pipfile +++ b/Pipfile @@ -22,7 +22,7 @@ django-storages = "*" djangorestframework = "*" djangorestframework-guardian = "*" docker = "*" -drf_yasg2 = "*" +drf_yasg = "*" facebook-sdk = "*" geoip2 = "*" gunicorn = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 3f80b1888d..ff92ebf9f8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5fce5772178e4bc782d7112fab658f5bbb21abb77bb93fc3c0a66e9db3a23a37" + "sha256": "a9d504f00ee8820017f26a4fda2938de456cb72b4bc2f8735fc8c6a6c615d46a" }, "pipfile-spec": 6, "requires": { @@ -416,13 +416,13 @@ "index": "pypi", "version": "==4.4.4" }, - "drf-yasg2": { + "drf-yasg": { "hashes": [ - "sha256:7037a8041eb5d1073fa504a284fc889685f93d0bfd008a963db1b366db786734", - "sha256:75e661ca5cf15eb44fcfab408c7b864f87c20794f564aa08b3a31817a857f19d" + "sha256:8b72e5b1875931a8d11af407be3a9a5ba8776541492947a0df5bafda6b7f8267", + "sha256:d50f197c7f02545d0b736df88c6d5cf874f8fea2507ad85ad7de6ae5bf2d9e5a" ], "index": "pypi", - "version": "==1.19.4" + "version": "==1.20.0" }, "facebook-sdk": { "hashes": [ diff --git a/authentik/admin/api/metrics.py b/authentik/admin/api/metrics.py index ad3943e165..d9d6b2dc4f 100644 --- a/authentik/admin/api/metrics.py +++ b/authentik/admin/api/metrics.py @@ -3,18 +3,18 @@ import time from collections import Counter from datetime import timedelta -from django.db.models import Count, ExpressionWrapper, F, Model +from django.db.models import Count, ExpressionWrapper, F from django.db.models.fields import DurationField from django.db.models.functions import ExtractHour from django.utils.timezone import now -from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method +from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from rest_framework.fields import IntegerField, SerializerMethodField from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import Serializer from rest_framework.viewsets import ViewSet +from authentik.core.api.utils import PassiveSerializer from authentik.events.models import Event, EventAction @@ -45,20 +45,14 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]: return results -class CoordinateSerializer(Serializer): +class CoordinateSerializer(PassiveSerializer): """Coordinates for diagrams""" x_cord = IntegerField(read_only=True) y_cord = IntegerField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - - -class LoginMetricsSerializer(Serializer): +class LoginMetricsSerializer(PassiveSerializer): """Login Metrics per 1h""" logins_per_1h = SerializerMethodField() @@ -74,12 +68,6 @@ class LoginMetricsSerializer(Serializer): """Get failed logins per hour for the last 24 hours""" return get_events_per_1h(action=EventAction.LOGIN_FAILED) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class AdministrationMetricsViewSet(ViewSet): """Login Metrics per 1h""" diff --git a/authentik/admin/api/tasks.py b/authentik/admin/api/tasks.py index d3abd5deae..55c9e18071 100644 --- a/authentik/admin/api/tasks.py +++ b/authentik/admin/api/tasks.py @@ -2,22 +2,21 @@ from importlib import import_module from django.contrib import messages -from django.db.models import Model from django.http.response import Http404 from django.utils.translation import gettext_lazy as _ -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import Serializer from rest_framework.viewsets import ViewSet +from authentik.core.api.utils import PassiveSerializer from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus -class TaskSerializer(Serializer): +class TaskSerializer(PassiveSerializer): """Serialize TaskInfo and TaskResult""" task_name = CharField() @@ -30,12 +29,6 @@ class TaskSerializer(Serializer): ) messages = ListField(source="result.messages") - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class TaskViewSet(ViewSet): """Read-only view set that returns all background tasks""" diff --git a/authentik/admin/api/version.py b/authentik/admin/api/version.py index 3aa85277a2..40fcbffdf9 100644 --- a/authentik/admin/api/version.py +++ b/authentik/admin/api/version.py @@ -2,22 +2,21 @@ from os import environ from django.core.cache import cache -from django.db.models import Model -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from packaging.version import parse from rest_framework.fields import SerializerMethodField from rest_framework.mixins import ListModelMixin from rest_framework.permissions import IsAdminUser from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import Serializer from rest_framework.viewsets import GenericViewSet from authentik import ENV_GIT_HASH_KEY, __version__ from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version +from authentik.core.api.utils import PassiveSerializer -class VersionSerializer(Serializer): +class VersionSerializer(PassiveSerializer): """Get running and latest version.""" version_current = SerializerMethodField() @@ -47,12 +46,6 @@ class VersionSerializer(Serializer): self.get_version_latest(instance) ) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class VersionViewSet(ListModelMixin, GenericViewSet): """Get running and latest version.""" diff --git a/authentik/admin/forms/users.py b/authentik/admin/forms/users.py deleted file mode 100644 index b7c3cc8d72..0000000000 --- a/authentik/admin/forms/users.py +++ /dev/null @@ -1,22 +0,0 @@ -"""authentik administrative user forms""" - -from django import forms - -from authentik.admin.fields import CodeMirrorWidget, YAMLField -from authentik.core.models import User - - -class UserForm(forms.ModelForm): - """Update User Details""" - - class Meta: - - model = User - fields = ["username", "name", "email", "is_active", "attributes"] - widgets = { - "name": forms.TextInput, - "attributes": CodeMirrorWidget, - } - field_classes = { - "attributes": YAMLField, - } diff --git a/authentik/admin/mixins.py b/authentik/admin/mixins.py deleted file mode 100644 index 97ee3c53a9..0000000000 --- a/authentik/admin/mixins.py +++ /dev/null @@ -1,9 +0,0 @@ -"""authentik admin mixins""" -from django.contrib.auth.mixins import UserPassesTestMixin - - -class AdminRequiredMixin(UserPassesTestMixin): - """Make sure user is administrator""" - - def test_func(self): - return self.request.user.is_superuser diff --git a/authentik/admin/templates/administration/certificatekeypair/generate.html b/authentik/admin/templates/administration/certificatekeypair/generate.html deleted file mode 100644 index 6af5c916c8..0000000000 --- a/authentik/admin/templates/administration/certificatekeypair/generate.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends base_template|default:"generic/form.html" %} - -{% load authentik_utils %} -{% load i18n %} - -{% block above_form %} -

- {% trans 'Generate Certificate-Key Pair' %} -

-{% endblock %} - -{% block action %} -{% trans 'Generate Certificate-Key Pair' %} -{% endblock %} diff --git a/authentik/admin/templates/administration/flow/import.html b/authentik/admin/templates/administration/flow/import.html deleted file mode 100644 index 14eca092aa..0000000000 --- a/authentik/admin/templates/administration/flow/import.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends base_template|default:"generic/form.html" %} - -{% load i18n %} - -{% block above_form %} -

-{% trans 'Import Flow' %} -

-{% endblock %} - -{% block action %} -{% trans 'Import Flow' %} -{% endblock %} diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 33d2e0a581..56b3769a8a 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -2,13 +2,6 @@ from django.urls import path from authentik.admin.views import ( - applications, - certificate_key_pair, - events_notifications_rules, - events_notifications_transports, - flows, - groups, - outposts, outposts_service_connections, policies, policies_bindings, @@ -19,22 +12,10 @@ from authentik.admin.views import ( stages_bindings, stages_invitations, stages_prompts, - users, ) from authentik.providers.saml.views.metadata import MetadataImportView urlpatterns = [ - # Applications - path( - "applications/create/", - applications.ApplicationCreateView.as_view(), - name="application-create", - ), - path( - "applications//update/", - applications.ApplicationUpdateView.as_view(), - name="application-update", - ), # Sources path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"), path( @@ -116,27 +97,6 @@ urlpatterns = [ stages_invitations.InvitationCreateView.as_view(), name="stage-invitation-create", ), - # Flows - path( - "flows/create/", - flows.FlowCreateView.as_view(), - name="flow-create", - ), - path( - "flows/import/", - flows.FlowImportView.as_view(), - name="flow-import", - ), - path( - "flows//update/", - flows.FlowUpdateView.as_view(), - name="flow-update", - ), - path( - "flows//execute/", - flows.FlowDebugExecuteView.as_view(), - name="flow-execute", - ), # Property Mappings path( "property-mappings/create/", @@ -153,48 +113,6 @@ urlpatterns = [ property_mappings.PropertyMappingTestView.as_view(), name="property-mapping-test", ), - # Users - path("users/create/", users.UserCreateView.as_view(), name="user-create"), - path("users//update/", users.UserUpdateView.as_view(), name="user-update"), - path( - "users//reset/", - users.UserPasswordResetView.as_view(), - name="user-password-reset", - ), - # Groups - path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"), - path( - "groups//update/", - groups.GroupUpdateView.as_view(), - name="group-update", - ), - # Certificate-Key Pairs - path( - "crypto/certificates/create/", - certificate_key_pair.CertificateKeyPairCreateView.as_view(), - name="certificatekeypair-create", - ), - path( - "crypto/certificates/generate/", - certificate_key_pair.CertificateKeyPairGenerateView.as_view(), - name="certificatekeypair-generate", - ), - path( - "crypto/certificates//update/", - certificate_key_pair.CertificateKeyPairUpdateView.as_view(), - name="certificatekeypair-update", - ), - # Outposts - path( - "outposts/create/", - outposts.OutpostCreateView.as_view(), - name="outpost-create", - ), - path( - "outposts//update/", - outposts.OutpostUpdateView.as_view(), - name="outpost-update", - ), # Outpost Service Connections path( "outpost_service_connections/create/", @@ -206,26 +124,4 @@ urlpatterns = [ outposts_service_connections.OutpostServiceConnectionUpdateView.as_view(), name="outpost-service-connection-update", ), - # Event Notification Transpots - path( - "events/transports/create/", - events_notifications_transports.NotificationTransportCreateView.as_view(), - name="notification-transport-create", - ), - path( - "events/transports//update/", - events_notifications_transports.NotificationTransportUpdateView.as_view(), - name="notification-transport-update", - ), - # Event Notification Rules - path( - "events/rules/create/", - events_notifications_rules.NotificationRuleCreateView.as_view(), - name="notification-rule-create", - ), - path( - "events/rules//update/", - events_notifications_rules.NotificationRuleUpdateView.as_view(), - name="notification-rule-update", - ), ] diff --git a/authentik/admin/views/applications.py b/authentik/admin/views/applications.py deleted file mode 100644 index 450e2e2d9b..0000000000 --- a/authentik/admin/views/applications.py +++ /dev/null @@ -1,66 +0,0 @@ -"""authentik Application administration""" -from typing import Any - -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView -from guardian.mixins import PermissionRequiredMixin -from guardian.shortcuts import get_objects_for_user - -from authentik.core.forms.applications import ApplicationForm -from authentik.core.models import Application -from authentik.lib.views import CreateAssignPermView - - -class ApplicationCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new Application""" - - model = Application - form_class = ApplicationForm - permission_required = "authentik_core.add_application" - - success_url = "/" - template_name = "generic/create.html" - success_message = _("Successfully created Application") - - def get_initial(self) -> dict[str, Any]: - if "provider" in self.request.GET: - try: - initial_provider_pk = int(self.request.GET["provider"]) - except ValueError: - return super().get_initial() - providers = ( - get_objects_for_user(self.request.user, "authentik_core.view_provider") - .filter(pk=initial_provider_pk) - .select_subclasses() - ) - if not providers.exists(): - return {} - return {"provider": providers.first()} - return super().get_initial() - - -class ApplicationUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update application""" - - model = Application - form_class = ApplicationForm - permission_required = "authentik_core.change_application" - - success_url = "/" - template_name = "generic/update.html" - success_message = _("Successfully updated Application") diff --git a/authentik/admin/views/certificate_key_pair.py b/authentik/admin/views/certificate_key_pair.py deleted file mode 100644 index a08d334b28..0000000000 --- a/authentik/admin/views/certificate_key_pair.py +++ /dev/null @@ -1,81 +0,0 @@ -"""authentik CertificateKeyPair administration""" -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.http.response import HttpResponse -from django.urls import reverse_lazy -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView -from django.views.generic.edit import FormView -from guardian.mixins import PermissionRequiredMixin - -from authentik.crypto.builder import CertificateBuilder -from authentik.crypto.forms import ( - CertificateKeyPairForm, - CertificateKeyPairGenerateForm, -) -from authentik.crypto.models import CertificateKeyPair -from authentik.lib.views import CreateAssignPermView - - -class CertificateKeyPairCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new CertificateKeyPair""" - - model = CertificateKeyPair - form_class = CertificateKeyPairForm - permission_required = "authentik_crypto.add_certificatekeypair" - - template_name = "generic/create.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully created Certificate-Key Pair") - - -class CertificateKeyPairGenerateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - FormView, -): - """Generate new CertificateKeyPair""" - - model = CertificateKeyPair - form_class = CertificateKeyPairGenerateForm - permission_required = "authentik_crypto.add_certificatekeypair" - - template_name = "administration/certificatekeypair/generate.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully generated Certificate-Key Pair") - - def form_valid(self, form: CertificateKeyPairGenerateForm) -> HttpResponse: - builder = CertificateBuilder() - builder.common_name = form.data["common_name"] - builder.build( - subject_alt_names=form.data.get("subject_alt_name", "").split(","), - validity_days=int(form.data["validity_days"]), - ) - builder.save() - return super().form_valid(form) - - -class CertificateKeyPairUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update certificatekeypair""" - - model = CertificateKeyPair - form_class = CertificateKeyPairForm - permission_required = "authentik_crypto.change_certificatekeypair" - - template_name = "generic/update.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully updated Certificate-Key Pair") diff --git a/authentik/admin/views/events_notifications_rules.py b/authentik/admin/views/events_notifications_rules.py deleted file mode 100644 index 48c69fb44d..0000000000 --- a/authentik/admin/views/events_notifications_rules.py +++ /dev/null @@ -1,47 +0,0 @@ -"""authentik NotificationRule administration""" -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView -from guardian.mixins import PermissionRequiredMixin - -from authentik.events.forms import NotificationRuleForm -from authentik.events.models import NotificationRule -from authentik.lib.views import CreateAssignPermView - - -class NotificationRuleCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new NotificationRule""" - - model = NotificationRule - form_class = NotificationRuleForm - permission_required = "authentik_events.add_NotificationRule" - - success_url = "/" - template_name = "generic/create.html" - success_message = _("Successfully created Notification Rule") - - -class NotificationRuleUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update application""" - - model = NotificationRule - form_class = NotificationRuleForm - permission_required = "authentik_events.change_NotificationRule" - - success_url = "/" - template_name = "generic/update.html" - success_message = _("Successfully updated Notification Rule") diff --git a/authentik/admin/views/events_notifications_transports.py b/authentik/admin/views/events_notifications_transports.py deleted file mode 100644 index 14d84ba5a4..0000000000 --- a/authentik/admin/views/events_notifications_transports.py +++ /dev/null @@ -1,45 +0,0 @@ -"""authentik NotificationTransport administration""" -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView -from guardian.mixins import PermissionRequiredMixin - -from authentik.events.forms import NotificationTransportForm -from authentik.events.models import NotificationTransport -from authentik.lib.views import CreateAssignPermView - - -class NotificationTransportCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new NotificationTransport""" - - model = NotificationTransport - form_class = NotificationTransportForm - permission_required = "authentik_events.add_notificationtransport" - success_url = "/" - template_name = "generic/create.html" - success_message = _("Successfully created Notification Transport") - - -class NotificationTransportUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update application""" - - model = NotificationTransport - form_class = NotificationTransportForm - permission_required = "authentik_events.change_notificationtransport" - success_url = "/" - template_name = "generic/update.html" - success_message = _("Successfully updated Notification Transport") diff --git a/authentik/admin/views/flows.py b/authentik/admin/views/flows.py deleted file mode 100644 index 01d1b54faa..0000000000 --- a/authentik/admin/views/flows.py +++ /dev/null @@ -1,108 +0,0 @@ -"""authentik Flow administration""" -from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.http import HttpRequest, HttpResponse -from django.urls import reverse_lazy -from django.utils.translation import gettext as _ -from django.views.generic import DetailView, FormView, UpdateView -from guardian.mixins import PermissionRequiredMixin - -from authentik.flows.exceptions import FlowNonApplicableException -from authentik.flows.forms import FlowForm, FlowImportForm -from authentik.flows.models import Flow -from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER -from authentik.flows.transfer.importer import FlowImporter -from authentik.flows.views import SESSION_KEY_PLAN, FlowPlanner -from authentik.lib.utils.urls import redirect_with_qs -from authentik.lib.views import CreateAssignPermView, bad_request_message - - -class FlowCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new Flow""" - - model = Flow - form_class = FlowForm - permission_required = "authentik_flows.add_flow" - - template_name = "generic/create.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully created Flow") - - -class FlowUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update flow""" - - model = Flow - form_class = FlowForm - permission_required = "authentik_flows.change_flow" - - template_name = "generic/update.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully updated Flow") - - -class FlowDebugExecuteView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): - """Debug exectue flow, setting the current user as pending user""" - - model = Flow - permission_required = "authentik_flows.view_flow" - - # pylint: disable=unused-argument - def get(self, request: HttpRequest, pk: str) -> HttpResponse: - """Debug exectue flow, setting the current user as pending user""" - flow: Flow = self.get_object() - planner = FlowPlanner(flow) - planner.use_cache = False - try: - plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user}) - self.request.session[SESSION_KEY_PLAN] = plan - except FlowNonApplicableException as exc: - return bad_request_message( - request, - _( - "Flow not applicable to current user/request: %(messages)s" - % {"messages": str(exc)} - ), - ) - return redirect_with_qs( - "authentik_core:if-flow", - self.request.GET, - flow_slug=flow.slug, - ) - - -class FlowImportView(LoginRequiredMixin, FormView): - """Import flow from JSON Export; only allowed for superusers - as these flows can contain python code""" - - form_class = FlowImportForm - template_name = "administration/flow/import.html" - success_url = reverse_lazy("authentik_core:if-admin") - - def dispatch(self, request, *args, **kwargs): - if not request.user.is_superuser: - return self.handle_no_permission() - return super().dispatch(request, *args, **kwargs) - - def form_valid(self, form: FlowImportForm) -> HttpResponse: - importer = FlowImporter(form.cleaned_data["flow"].read().decode()) - successful = importer.apply() - if not successful: - messages.error(self.request, _("Failed to import flow.")) - else: - messages.success(self.request, _("Successfully imported flow.")) - return super().form_valid(form) diff --git a/authentik/admin/views/groups.py b/authentik/admin/views/groups.py deleted file mode 100644 index 3022802597..0000000000 --- a/authentik/admin/views/groups.py +++ /dev/null @@ -1,48 +0,0 @@ -"""authentik Group administration""" -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.urls import reverse_lazy -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView -from guardian.mixins import PermissionRequiredMixin - -from authentik.core.forms.groups import GroupForm -from authentik.core.models import Group -from authentik.lib.views import CreateAssignPermView - - -class GroupCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new Group""" - - model = Group - form_class = GroupForm - permission_required = "authentik_core.add_group" - - template_name = "generic/create.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully created Group") - - -class GroupUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update group""" - - model = Group - form_class = GroupForm - permission_required = "authentik_core.change_group" - - template_name = "generic/update.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully updated Group") diff --git a/authentik/admin/views/outposts.py b/authentik/admin/views/outposts.py deleted file mode 100644 index fa1b93afcd..0000000000 --- a/authentik/admin/views/outposts.py +++ /dev/null @@ -1,55 +0,0 @@ -"""authentik Outpost administration""" -from dataclasses import asdict -from typing import Any - -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView -from guardian.mixins import PermissionRequiredMixin - -from authentik.lib.views import CreateAssignPermView -from authentik.outposts.forms import OutpostForm -from authentik.outposts.models import Outpost, OutpostConfig - - -class OutpostCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create new Outpost""" - - model = Outpost - form_class = OutpostForm - permission_required = "authentik_outposts.add_outpost" - success_url = "/" - template_name = "generic/create.html" - success_message = _("Successfully created Outpost") - - def get_initial(self) -> dict[str, Any]: - return { - "_config": asdict( - OutpostConfig(authentik_host=self.request.build_absolute_uri("/")) - ) - } - - -class OutpostUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update outpost""" - - model = Outpost - form_class = OutpostForm - permission_required = "authentik_outposts.change_outpost" - success_url = "/" - template_name = "generic/update.html" - success_message = _("Successfully updated Outpost") diff --git a/authentik/admin/views/users.py b/authentik/admin/views/users.py deleted file mode 100644 index 760c677258..0000000000 --- a/authentik/admin/views/users.py +++ /dev/null @@ -1,74 +0,0 @@ -"""authentik User administration""" -from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.mixins import ( - PermissionRequiredMixin as DjangoPermissionRequiredMixin, -) -from django.contrib.messages.views import SuccessMessageMixin -from django.http import HttpRequest, HttpResponse -from django.shortcuts import redirect -from django.urls import reverse_lazy -from django.utils.http import urlencode -from django.utils.translation import gettext as _ -from django.views.generic import DetailView, UpdateView -from guardian.mixins import PermissionRequiredMixin - -from authentik.admin.forms.users import UserForm -from authentik.core.models import Token, User -from authentik.lib.views import CreateAssignPermView - - -class UserCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - CreateAssignPermView, -): - """Create user""" - - model = User - form_class = UserForm - permission_required = "authentik_core.add_user" - - template_name = "generic/create.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully created User") - - -class UserUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - UpdateView, -): - """Update user""" - - model = User - form_class = UserForm - permission_required = "authentik_core.change_user" - - # By default the object's name is user which is used by other checks - context_object_name = "object" - template_name = "generic/update.html" - success_url = reverse_lazy("authentik_core:if-admin") - success_message = _("Successfully updated User") - - -class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): - """Get Password reset link for user""" - - model = User - permission_required = "authentik_core.reset_user_password" - - def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: - """Create token for user and return link""" - super().get(request, *args, **kwargs) - token, __ = Token.objects.get_or_create( - identifier="password-reset-temp", user=self.object - ) - querystring = urlencode({"token": token.key}) - link = request.build_absolute_uri( - reverse_lazy("authentik_flows:default-recovery") + f"?{querystring}" - ) - messages.success(request, _("Password reset link: %(link)s" % {"link": link})) - return redirect("/") diff --git a/authentik/admin/views/utils.py b/authentik/admin/views/utils.py index c7ebe9eafe..4c2fe7a381 100644 --- a/authentik/admin/views/utils.py +++ b/authentik/admin/views/utils.py @@ -1,26 +1,13 @@ """authentik admin util views""" from typing import Any -from django.contrib import messages -from django.contrib.messages.views import SuccessMessageMixin from django.http import Http404 -from django.urls import reverse_lazy -from django.views.generic import DeleteView, UpdateView +from django.views.generic import UpdateView from authentik.lib.utils.reflection import all_subclasses from authentik.lib.views import CreateAssignPermView -class DeleteMessageView(SuccessMessageMixin, DeleteView): - """DeleteView which shows `self.success_message` on successful deletion""" - - success_url = reverse_lazy("authentik_core:if-admin") - - def delete(self, request, *args, **kwargs): - messages.success(self.request, self.success_message) - return super().delete(request, *args, **kwargs) - - class InheritanceCreateView(CreateAssignPermView): """CreateView for objects using InheritanceManager""" diff --git a/authentik/api/decorators.py b/authentik/api/decorators.py index fd89c01dc1..00a53ed0fa 100644 --- a/authentik/api/decorators.py +++ b/authentik/api/decorators.py @@ -1,13 +1,15 @@ """API Decorators""" from functools import wraps -from typing import Callable +from typing import Callable, Optional from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -def permission_required(perm: str, *other_perms: str): +def permission_required( + perm: Optional[str] = None, other_perms: Optional[list[str]] = None +): """Check permissions for a single custom action""" def wrapper_outter(func: Callable): @@ -15,12 +17,14 @@ def permission_required(perm: str, *other_perms: str): @wraps(func) def wrapper(self: ModelViewSet, request: Request, *args, **kwargs) -> Response: - obj = self.get_object() - if not request.user.has_perm(perm, obj): - return self.permission_denied(request) - for other_perm in other_perms: - if not request.user.has_perm(other_perm): + if perm: + obj = self.get_object() + if not request.user.has_perm(perm, obj): return self.permission_denied(request) + if other_perms: + for other_perm in other_perms: + if not request.user.has_perm(other_perm): + return self.permission_denied(request) return func(self, request, *args, **kwargs) return wrapper diff --git a/authentik/api/pagination_schema.py b/authentik/api/pagination_schema.py index 057bd8b72e..3d4f86c61d 100644 --- a/authentik/api/pagination_schema.py +++ b/authentik/api/pagination_schema.py @@ -1,8 +1,8 @@ """Swagger Pagination Schema class""" from typing import OrderedDict -from drf_yasg2 import openapi -from drf_yasg2.inspectors import PaginatorInspector +from drf_yasg import openapi +from drf_yasg.inspectors import PaginatorInspector class PaginationInspector(PaginatorInspector): diff --git a/authentik/api/schema.py b/authentik/api/schema.py new file mode 100644 index 0000000000..f60b333b81 --- /dev/null +++ b/authentik/api/schema.py @@ -0,0 +1,102 @@ +"""Error Response schema, from https://github.com/axnsan12/drf-yasg/issues/224""" +from drf_yasg import openapi +from drf_yasg.inspectors.view import SwaggerAutoSchema +from drf_yasg.utils import force_real_str, is_list_view +from rest_framework import exceptions, status +from rest_framework.settings import api_settings + + +class ErrorResponseAutoSchema(SwaggerAutoSchema): + """Inspector which includes an error schema""" + + def get_generic_error_schema(self): + """Get a generic error schema""" + return openapi.Schema( + "Generic API Error", + type=openapi.TYPE_OBJECT, + properties={ + "detail": openapi.Schema( + type=openapi.TYPE_STRING, description="Error details" + ), + "code": openapi.Schema( + type=openapi.TYPE_STRING, description="Error code" + ), + }, + required=["detail"], + ) + + def get_validation_error_schema(self): + """Get a generic validation error schema""" + return openapi.Schema( + "Validation Error", + type=openapi.TYPE_OBJECT, + properties={ + api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema( + description="List of validation errors not related to any field", + type=openapi.TYPE_ARRAY, + items=openapi.Schema(type=openapi.TYPE_STRING), + ), + }, + additional_properties=openapi.Schema( + description=( + "A list of error messages for each " + "field that triggered a validation error" + ), + type=openapi.TYPE_ARRAY, + items=openapi.Schema(type=openapi.TYPE_STRING), + ), + ) + + def get_response_serializers(self): + responses = super().get_response_serializers() + definitions = self.components.with_scope( + openapi.SCHEMA_DEFINITIONS + ) # type: openapi.ReferenceResolver + + definitions.setdefault("GenericError", self.get_generic_error_schema) + definitions.setdefault("ValidationError", self.get_validation_error_schema) + definitions.setdefault("APIException", self.get_generic_error_schema) + + if self.get_request_serializer() or self.get_query_serializer(): + responses.setdefault( + exceptions.ValidationError.status_code, + openapi.Response( + description=force_real_str( + exceptions.ValidationError.default_detail + ), + schema=openapi.SchemaRef(definitions, "ValidationError"), + ), + ) + + security = self.get_security() + if security is None or len(security) > 0: + # Note: 401 error codes are coerced into 403 see + # rest_framework/views.py:433:handle_exception + # This is b/c the API uses token auth which doesn't have WWW-Authenticate header + responses.setdefault( + status.HTTP_403_FORBIDDEN, + openapi.Response( + description="Authentication credentials were invalid, absent or insufficient.", + schema=openapi.SchemaRef(definitions, "GenericError"), + ), + ) + if not is_list_view(self.path, self.method, self.view): + responses.setdefault( + exceptions.PermissionDenied.status_code, + openapi.Response( + description="Permission denied.", + schema=openapi.SchemaRef(definitions, "APIException"), + ), + ) + responses.setdefault( + exceptions.NotFound.status_code, + openapi.Response( + description=( + "Object does not exist or caller " + "has insufficient permissions to access it." + ), + schema=openapi.SchemaRef(definitions, "APIException"), + ), + ) + + return responses diff --git a/authentik/api/tests/__init__.py b/authentik/api/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/authentik/api/tests.py b/authentik/api/tests/test_auth.py similarity index 100% rename from authentik/api/tests.py rename to authentik/api/tests/test_auth.py diff --git a/authentik/api/tests/test_swagger.py b/authentik/api/tests/test_swagger.py new file mode 100644 index 0000000000..b8095a3887 --- /dev/null +++ b/authentik/api/tests/test_swagger.py @@ -0,0 +1,24 @@ +"""Swagger generation tests""" +from json import loads + +from django.urls import reverse +from rest_framework.test import APITestCase +from yaml import safe_load + + +class TestSwaggerGeneration(APITestCase): + """Generic admin tests""" + + def test_yaml(self): + """Test YAML generation""" + response = self.client.get( + reverse("authentik_api:schema-json", kwargs={"format": ".yaml"}), + ) + self.assertTrue(safe_load(response.content.decode())) + + def test_json(self): + """Test JSON generation""" + response = self.client.get( + reverse("authentik_api:schema-json", kwargs={"format": ".json"}), + ) + self.assertTrue(loads(response.content.decode())) diff --git a/authentik/api/v2/config.py b/authentik/api/v2/config.py index e2ec1b1dc3..808d0525c0 100644 --- a/authentik/api/v2/config.py +++ b/authentik/api/v2/config.py @@ -1,46 +1,33 @@ """core Configs API""" -from django.db.models import Model -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.fields import BooleanField, CharField, ListField from rest_framework.permissions import AllowAny from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import Serializer from rest_framework.viewsets import ViewSet +from authentik.core.api.utils import PassiveSerializer from authentik.lib.config import CONFIG -class LinkSerializer(Serializer): +class FooterLinkSerializer(PassiveSerializer): """Links returned in Config API""" href = CharField(read_only=True) name = CharField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - - -class ConfigSerializer(Serializer): +class ConfigSerializer(PassiveSerializer): """Serialize authentik Config into DRF Object""" branding_logo = CharField(read_only=True) branding_title = CharField(read_only=True) - ui_footer_links = ListField(child=LinkSerializer(), read_only=True) + ui_footer_links = ListField(child=FooterLinkSerializer(), read_only=True) error_reporting_enabled = BooleanField(read_only=True) error_reporting_environment = CharField(read_only=True) error_reporting_send_pii = BooleanField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class ConfigsViewSet(ViewSet): """Read-only view set that returns the current session's Configs""" diff --git a/authentik/api/v2/urls.py b/authentik/api/v2/urls.py index 4c45e56979..9ee25b0198 100644 --- a/authentik/api/v2/urls.py +++ b/authentik/api/v2/urls.py @@ -1,7 +1,7 @@ """api v2 urls""" from django.urls import path, re_path -from drf_yasg2 import openapi -from drf_yasg2.views import get_schema_view +from drf_yasg import openapi +from drf_yasg.views import get_schema_view from rest_framework import routers from rest_framework.permissions import AllowAny @@ -33,7 +33,8 @@ from authentik.outposts.api.outpost_service_connections import ( ServiceConnectionViewSet, ) from authentik.outposts.api.outposts import OutpostViewSet -from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet +from authentik.policies.api.bindings import PolicyBindingViewSet +from authentik.policies.api.policies import PolicyViewSet from authentik.policies.dummy.api import DummyPolicyViewSet from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet @@ -189,7 +190,7 @@ router.register("policies/dummy", DummyPolicyViewSet) info = openapi.Info( title="authentik API", - default_version="v2", + default_version="v2beta", contact=openapi.Contact(email="hello@beryju.org"), license=openapi.License( name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE" diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index 66076fe6cc..784497bd04 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -1,9 +1,12 @@ """Application API Views""" from django.core.cache import cache from django.db.models import QuerySet -from drf_yasg2.utils import swagger_auto_schema +from django.http.response import HttpResponseBadRequest +from drf_yasg import openapi +from drf_yasg.utils import no_body, swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import SerializerMethodField +from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ModelSerializer @@ -49,7 +52,6 @@ class ApplicationSerializer(ModelSerializer): "meta_icon", "meta_description", "meta_publisher", - "policies", ] @@ -108,8 +110,33 @@ class ApplicationViewSet(ModelViewSet): serializer = self.get_serializer(allowed_applications, many=True) return self.get_paginated_response(serializer.data) + @permission_required("authentik_core.change_application") + @swagger_auto_schema( + request_body=no_body, + manual_parameters=[ + openapi.Parameter( + name="file", + in_=openapi.IN_FORM, + type=openapi.TYPE_FILE, + required=True, + ) + ], + responses={200: "Success"}, + ) + @action(detail=True, methods=["POST"], parser_classes=(MultiPartParser,)) + # pylint: disable=unused-argument + def set_icon(self, request: Request, slug: str): + """Set application icon""" + app: Application = self.get_object() + icon = request.FILES.get("file", None) + if not icon: + return HttpResponseBadRequest() + app.meta_icon = icon + app.save() + return Response({}) + @permission_required( - "authentik_core.view_application", "authentik_events.view_event" + "authentik_core.view_application", ["authentik_events.view_event"] ) @swagger_auto_schema(responses={200: CoordinateSerializer(many=True)}) @action(detail=True) diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index ef3754b969..17a062a465 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/propertymappings.py @@ -1,6 +1,6 @@ """PropertyMapping API Views""" from django.urls import reverse -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework import mixins from rest_framework.decorators import action from rest_framework.request import Request diff --git a/authentik/core/api/providers.py b/authentik/core/api/providers.py index bbee513f92..0dd676133b 100644 --- a/authentik/core/api/providers.py +++ b/authentik/core/api/providers.py @@ -1,7 +1,7 @@ """Provider API Views""" from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import ReadOnlyField from rest_framework.request import Request diff --git a/authentik/core/api/sources.py b/authentik/core/api/sources.py index 8aee40e970..3fa14ad3e0 100644 --- a/authentik/core/api/sources.py +++ b/authentik/core/api/sources.py @@ -2,7 +2,7 @@ from typing import Iterable from django.urls import reverse -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework import mixins from rest_framework.decorators import action from rest_framework.request import Request diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index aef414bffc..3a2bb49110 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -1,15 +1,16 @@ """Tokens API Viewset""" -from django.db.models.base import Model from django.http.response import Http404 -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import CharField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, Serializer +from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet +from authentik.api.decorators import permission_required from authentik.core.api.users import UserSerializer +from authentik.core.api.utils import PassiveSerializer from authentik.core.models import Token from authentik.events.models import Event, EventAction @@ -34,17 +35,11 @@ class TokenSerializer(ModelSerializer): depth = 2 -class TokenViewSerializer(Serializer): +class TokenViewSerializer(PassiveSerializer): """Show token's current key""" key = CharField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class TokenViewSet(ModelViewSet): """Token Viewset""" @@ -66,6 +61,7 @@ class TokenViewSet(ModelViewSet): ] ordering = ["expires"] + @permission_required("authentik_core.view_token_key") @swagger_auto_schema(responses={200: TokenViewSerializer(many=False)}) @action(detail=True) # pylint: disable=unused-argument diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 2c9e0adcdc..17b0c706ee 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -1,18 +1,18 @@ """User API Views""" -from django.db.models.base import Model from django.urls import reverse_lazy from django.utils.http import urlencode -from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method +from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from guardian.utils import get_anonymous_user from rest_framework.decorators import action from rest_framework.fields import CharField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import BooleanField, ModelSerializer, Serializer +from rest_framework.serializers import BooleanField, ModelSerializer from rest_framework.viewsets import ModelViewSet from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.api.decorators import permission_required +from authentik.core.api.utils import LinkSerializer, PassiveSerializer from authentik.core.middleware import ( SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_USER, @@ -43,33 +43,15 @@ class UserSerializer(ModelSerializer): ] -class SessionUserSerializer(Serializer): +class SessionUserSerializer(PassiveSerializer): """Response for the /user/me endpoint, returns the currently active user (as `user` property) and, if this user is being impersonated, the original user in the `original` property.""" user = UserSerializer() original = UserSerializer(required=False) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - - -class UserRecoverySerializer(Serializer): - """Recovery link for a user to reset their password""" - - link = CharField() - - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - - -class UserMetricsSerializer(Serializer): +class UserMetricsSerializer(PassiveSerializer): """User Metrics""" logins_per_1h = SerializerMethodField() @@ -98,12 +80,6 @@ class UserMetricsSerializer(Serializer): action=EventAction.AUTHORIZE_APPLICATION, user__pk=request.user.pk ) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class UserViewSet(ModelViewSet): """User Viewset""" @@ -131,7 +107,7 @@ class UserViewSet(ModelViewSet): serializer.is_valid() return Response(serializer.data) - @permission_required("authentik_core.view_user", "authentik_events.view_event") + @permission_required("authentik_core.view_user", ["authentik_events.view_event"]) @swagger_auto_schema(responses={200: UserMetricsSerializer(many=False)}) @action(detail=False) def metrics(self, request: Request) -> Response: @@ -142,7 +118,7 @@ class UserViewSet(ModelViewSet): @permission_required("authentik_core.reset_user_password") @swagger_auto_schema( - responses={"200": UserRecoverySerializer(many=False)}, + responses={"200": LinkSerializer(many=False)}, ) @action(detail=True) # pylint: disable=invalid-name, unused-argument diff --git a/authentik/core/api/utils.py b/authentik/core/api/utils.py index 438efa2df0..390955a396 100644 --- a/authentik/core/api/utils.py +++ b/authentik/core/api/utils.py @@ -4,18 +4,22 @@ from rest_framework.fields import CharField, IntegerField from rest_framework.serializers import Serializer, SerializerMethodField -class MetaNameSerializer(Serializer): +class PassiveSerializer(Serializer): + """Base serializer class which doesn't implement create/update methods""" + + def create(self, validated_data: dict) -> Model: + return Model() + + def update(self, instance: Model, validated_data: dict) -> Model: + return Model() + + +class MetaNameSerializer(PassiveSerializer): """Add verbose names to response""" verbose_name = SerializerMethodField() verbose_name_plural = SerializerMethodField() - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - def get_verbose_name(self, obj: Model) -> str: """Return object's verbose_name""" return obj._meta.verbose_name @@ -25,27 +29,21 @@ class MetaNameSerializer(Serializer): return obj._meta.verbose_name_plural -class TypeCreateSerializer(Serializer): +class TypeCreateSerializer(PassiveSerializer): """Types of an object that can be created""" name = CharField(required=True) description = CharField(required=True) link = CharField(required=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - - -class CacheSerializer(Serializer): +class CacheSerializer(PassiveSerializer): """Generic cache stats for an object""" count = IntegerField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError +class LinkSerializer(PassiveSerializer): + """Returns a single link""" + + link = CharField() diff --git a/authentik/core/forms/applications.py b/authentik/core/forms/applications.py deleted file mode 100644 index 94bbfdfb9d..0000000000 --- a/authentik/core/forms/applications.py +++ /dev/null @@ -1,50 +0,0 @@ -"""authentik Core Application forms""" -from django import forms -from django.utils.translation import gettext_lazy as _ - -from authentik.core.models import Application, Provider -from authentik.lib.widgets import GroupedModelChoiceField - - -class ApplicationForm(forms.ModelForm): - """Application Form""" - - def __init__(self, *args, **kwargs): # pragma: no cover - super().__init__(*args, **kwargs) - self.fields["provider"].queryset = ( - Provider.objects.all().order_by("name").select_subclasses() - ) - - class Meta: - - model = Application - fields = [ - "name", - "slug", - "provider", - "meta_launch_url", - "meta_icon", - "meta_description", - "meta_publisher", - ] - widgets = { - "name": forms.TextInput(), - "meta_launch_url": forms.TextInput(), - "meta_publisher": forms.TextInput(), - "meta_icon": forms.FileInput(), - } - help_texts = { - "meta_launch_url": _( - ( - "If left empty, authentik will try to extract the launch URL " - "based on the selected provider." - ) - ), - } - field_classes = {"provider": GroupedModelChoiceField} - labels = { - "meta_launch_url": _("Launch URL"), - "meta_icon": _("Icon"), - "meta_description": _("Description"), - "meta_publisher": _("Publisher"), - } diff --git a/authentik/core/forms/groups.py b/authentik/core/forms/groups.py deleted file mode 100644 index 8d10f1eae9..0000000000 --- a/authentik/core/forms/groups.py +++ /dev/null @@ -1,38 +0,0 @@ -"""authentik Core Group forms""" -from django import forms - -from authentik.admin.fields import CodeMirrorWidget, YAMLField -from authentik.core.models import Group, User - - -class GroupForm(forms.ModelForm): - """Group Form""" - - members = forms.ModelMultipleChoiceField( - User.objects.all(), - required=False, - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if self.instance.pk: - self.initial["members"] = self.instance.users.values_list("pk", flat=True) - - def save(self, *args, **kwargs): - instance = super().save(*args, **kwargs) - if instance.pk: - instance.users.clear() - instance.users.add(*self.cleaned_data["members"]) - return instance - - class Meta: - - model = Group - fields = ["name", "is_superuser", "parent", "members", "attributes"] - widgets = { - "name": forms.TextInput(), - "attributes": CodeMirrorWidget, - } - field_classes = { - "attributes": YAMLField, - } diff --git a/authentik/core/forms/users.py b/authentik/core/forms/users.py deleted file mode 100644 index 36b5e33c55..0000000000 --- a/authentik/core/forms/users.py +++ /dev/null @@ -1,15 +0,0 @@ -"""authentik core user forms""" - -from django import forms - -from authentik.core.models import User - - -class UserDetailForm(forms.ModelForm): - """Update User Details""" - - class Meta: - - model = User - fields = ["username", "name", "email"] - widgets = {"name": forms.TextInput} diff --git a/authentik/core/migrations/0018_auto_20210330_1345.py b/authentik/core/migrations/0018_auto_20210330_1345.py new file mode 100644 index 0000000000..6d2756f240 --- /dev/null +++ b/authentik/core/migrations/0018_auto_20210330_1345.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.7 on 2021-03-30 13:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_core", "0017_managed"), + ] + + operations = [ + migrations.AlterModelOptions( + name="token", + options={ + "permissions": (("view_token_key", "View token's key"),), + "verbose_name": "Token", + "verbose_name_plural": "Tokens", + }, + ), + ] diff --git a/authentik/core/models.py b/authentik/core/models.py index 1393c7bd47..8385988989 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -369,6 +369,7 @@ class Token(ManagedModel, ExpiringModel): models.Index(fields=["identifier"]), models.Index(fields=["key"]), ] + permissions = (("view_token_key", "View token's key"),) class PropertyMapping(SerializerModel, ManagedModel): diff --git a/authentik/core/signals.py b/authentik/core/signals.py index 4f293af093..ec7675fe2b 100644 --- a/authentik/core/signals.py +++ b/authentik/core/signals.py @@ -17,7 +17,7 @@ def post_save_application(sender, instance, created: bool, **_): if sender != Application: return - if not created: + if not created: # pragma: no cover return # Also delete user application cache keys = cache.keys(user_app_cache_key("*")) diff --git a/authentik/core/templates/user/details.html b/authentik/core/templates/user/details.html deleted file mode 100644 index 0babe31372..0000000000 --- a/authentik/core/templates/user/details.html +++ /dev/null @@ -1,26 +0,0 @@ -{% load i18n %} - -
-
- {% trans 'Update details' %} -
-
-
- {% include 'partials/form_horizontal.html' with form=form %} - {% block beneath_form %} - {% endblock %} -
-
-
- - {% if unenrollment_enabled %} - {% - trans "Delete account" %} - {% endif %} -
-
-
-
-
-
diff --git a/authentik/core/tests/test_views_user.py b/authentik/core/tests/test_views_user.py deleted file mode 100644 index bad0ab4419..0000000000 --- a/authentik/core/tests/test_views_user.py +++ /dev/null @@ -1,30 +0,0 @@ -"""authentik user view tests""" -import string -from random import SystemRandom - -from django.test import TestCase -from django.urls import reverse - -from authentik.core.models import User - - -class TestUserViews(TestCase): - """Test User Views""" - - def setUp(self): - super().setUp() - self.user = User.objects.create_user( - username="unittest user", - email="unittest@example.com", - password="".join( - SystemRandom().choice(string.ascii_uppercase + string.digits) - for _ in range(8) - ), - ) - self.client.force_login(self.user) - - def test_user_details(self): - """Test UserDetailsView""" - self.assertEqual( - self.client.get(reverse("authentik_core:user-details")).status_code, 200 - ) diff --git a/authentik/core/types.py b/authentik/core/types.py index 07855987dd..523ce0ae82 100644 --- a/authentik/core/types.py +++ b/authentik/core/types.py @@ -2,9 +2,9 @@ from dataclasses import dataclass from typing import Optional -from django.db.models.base import Model from rest_framework.fields import CharField -from rest_framework.serializers import Serializer + +from authentik.core.api.utils import PassiveSerializer @dataclass @@ -21,29 +21,17 @@ class UILoginButton: icon_url: Optional[str] = None -class UILoginButtonSerializer(Serializer): +class UILoginButtonSerializer(PassiveSerializer): """Serializer for Login buttons of sources""" name = CharField() url = CharField() icon_url = CharField(required=False) - def create(self, validated_data: dict) -> Model: - return Model() - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() - - -class UserSettingSerializer(Serializer): +class UserSettingSerializer(PassiveSerializer): """Serializer for User settings for stages and sources""" object_uid = CharField() component = CharField() title = CharField() - - def create(self, validated_data: dict) -> Model: - return Model() - - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() diff --git a/authentik/core/urls.py b/authentik/core/urls.py index ee03d748cd..a8f2d91b62 100644 --- a/authentik/core/urls.py +++ b/authentik/core/urls.py @@ -14,7 +14,6 @@ urlpatterns = [ name="root-redirect", ), # User views - path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"), path( "-/user/tokens/create/", user.TokenCreateView.as_view(), diff --git a/authentik/core/views/user.py b/authentik/core/views/user.py index 6d630e922a..968547434e 100644 --- a/authentik/core/views/user.py +++ b/authentik/core/views/user.py @@ -1,53 +1,20 @@ """authentik core user views""" -from typing import Any - from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import ( PermissionRequiredMixin as DjangoPermissionRequiredMixin, ) from django.contrib.messages.views import SuccessMessageMixin from django.http.response import HttpResponse -from django.urls import reverse_lazy from django.utils.translation import gettext as _ from django.views.generic import UpdateView -from django.views.generic.base import TemplateView from guardian.mixins import PermissionRequiredMixin from guardian.shortcuts import get_objects_for_user from authentik.core.forms.token import UserTokenForm -from authentik.core.forms.users import UserDetailForm from authentik.core.models import Token, TokenIntents -from authentik.flows.models import Flow, FlowDesignation from authentik.lib.views import CreateAssignPermView -class UserSettingsView(TemplateView): - """Multiple SiteShells for user details and all stages""" - - template_name = "user/settings.html" - - -class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): - """Update User details""" - - template_name = "user/details.html" - form_class = UserDetailForm - - success_message = _("Successfully updated user.") - success_url = reverse_lazy("authentik_core:user-details") - - def get_object(self): - return self.request.user - - def get_context_data(self, **kwargs: Any) -> dict[str, Any]: - kwargs = super().get_context_data(**kwargs) - unenrollment_flow = Flow.with_policy( - self.request, designation=FlowDesignation.UNRENOLLMENT - ) - kwargs["unenrollment_enabled"] = bool(unenrollment_flow) - return kwargs - - class TokenCreateView( SuccessMessageMixin, LoginRequiredMixin, diff --git a/authentik/crypto/api.py b/authentik/crypto/api.py index e26cd79cff..daa43e6723 100644 --- a/authentik/crypto/api.py +++ b/authentik/crypto/api.py @@ -2,15 +2,23 @@ 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.db.models import Model -from drf_yasg2.utils import swagger_auto_schema +from django.utils.translation import gettext_lazy as _ +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action -from rest_framework.fields import CharField, DateTimeField, SerializerMethodField +from rest_framework.fields import ( + CharField, + DateTimeField, + IntegerField, + SerializerMethodField, +) from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, Serializer, ValidationError +from rest_framework.serializers import ModelSerializer, ValidationError from rest_framework.viewsets import ModelViewSet +from authentik.api.decorators import permission_required +from authentik.core.api.utils import PassiveSerializer +from authentik.crypto.builder import CertificateBuilder from authentik.crypto.models import CertificateKeyPair from authentik.events.models import Event, EventAction @@ -71,16 +79,20 @@ class CertificateKeyPairSerializer(ModelSerializer): } -class CertificateDataSerializer(Serializer): +class CertificateDataSerializer(PassiveSerializer): """Get CertificateKeyPair's data""" data = CharField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError +class CertificateGenerationSerializer(PassiveSerializer): + """Certificate generation parameters""" + + common_name = CharField() + subject_alt_name = CharField( + required=False, allow_blank=True, label=_("Subject-alt name") + ) + validity_days = IntegerField(initial=365) class CertificateKeyPairViewSet(ModelViewSet): @@ -89,6 +101,29 @@ class CertificateKeyPairViewSet(ModelViewSet): queryset = CertificateKeyPair.objects.all() serializer_class = CertificateKeyPairSerializer + @permission_required(None, ["authentik_crypto.add_certificatekeypair"]) + @swagger_auto_schema( + request_body=CertificateGenerationSerializer(), + responses={200: CertificateKeyPairSerializer}, + ) + @action(detail=False, methods=["POST"]) + def generate(self, request: Request) -> Response: + """Generate a new, self-signed certificate-key pair""" + data = CertificateGenerationSerializer(data=request.data) + if not data.is_valid(): + return Response(data.errors, status=400) + builder = CertificateBuilder() + builder.common_name = data.validated_data["common_name"] + builder.build( + subject_alt_names=data.validated_data.get("subject_alt_name", "").split( + "," + ), + validity_days=int(data.validated_data["validity_days"]), + ) + instance = builder.save() + serializer = self.get_serializer(instance) + return Response(serializer.data) + @swagger_auto_schema(responses={200: CertificateDataSerializer(many=False)}) @action(detail=True) # pylint: disable=invalid-name, unused-argument diff --git a/authentik/crypto/forms.py b/authentik/crypto/forms.py deleted file mode 100644 index f289cc2071..0000000000 --- a/authentik/crypto/forms.py +++ /dev/null @@ -1,64 +0,0 @@ -"""authentik Crypto forms""" -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 import forms -from django.utils.translation import gettext_lazy as _ - -from authentik.crypto.models import CertificateKeyPair - - -class CertificateKeyPairGenerateForm(forms.Form): - """CertificateKeyPair generation form""" - - common_name = forms.CharField() - subject_alt_name = forms.CharField(required=False, label=_("Subject-alt name")) - validity_days = forms.IntegerField(initial=365) - - -class CertificateKeyPairForm(forms.ModelForm): - """CertificateKeyPair Form""" - - def clean_certificate_data(self): - """Verify that input is a valid PEM x509 Certificate""" - certificate_data = self.cleaned_data["certificate_data"] - try: - load_pem_x509_certificate( - certificate_data.encode("utf-8"), default_backend() - ) - except ValueError: - raise forms.ValidationError("Unable to load certificate.") - return certificate_data - - def clean_key_data(self): - """Verify that input is a valid PEM RSA Key""" - key_data = self.cleaned_data["key_data"] - # Since this field is optional, data can be empty. - if key_data != "": - try: - load_pem_private_key( - str.encode("\n".join([x.strip() for x in key_data.split("\n")])), - password=None, - backend=default_backend(), - ) - except ValueError: - raise forms.ValidationError("Unable to load private key.") - return key_data - - class Meta: - - model = CertificateKeyPair - fields = [ - "name", - "certificate_data", - "key_data", - ] - widgets = { - "name": forms.TextInput(), - "certificate_data": forms.Textarea(attrs={"class": "monospaced"}), - "key_data": forms.Textarea(attrs={"class": "monospaced"}), - } - labels = { - "certificate_data": _("Certificate"), - "key_data": _("Private Key"), - } diff --git a/authentik/crypto/tests.py b/authentik/crypto/tests.py index 1b43806d2a..f42f32172b 100644 --- a/authentik/crypto/tests.py +++ b/authentik/crypto/tests.py @@ -2,31 +2,12 @@ from django.test import TestCase from authentik.crypto.api import CertificateKeyPairSerializer -from authentik.crypto.forms import CertificateKeyPairForm from authentik.crypto.models import CertificateKeyPair class TestCrypto(TestCase): """Test Crypto validation""" - def test_form(self): - """Test form validation""" - keypair = CertificateKeyPair.objects.first() - self.assertTrue( - CertificateKeyPairForm( - { - "name": keypair.name, - "certificate_data": keypair.certificate_data, - "key_data": keypair.key_data, - } - ).is_valid() - ) - self.assertFalse( - CertificateKeyPairForm( - {"name": keypair.name, "certificate_data": "test", "key_data": "test"} - ).is_valid() - ) - def test_serializer(self): """Test API Validation""" keypair = CertificateKeyPair.objects.first() diff --git a/authentik/events/api/event.py b/authentik/events/api/event.py index 3eb7230bc0..0c59cf10bd 100644 --- a/authentik/events/api/event.py +++ b/authentik/events/api/event.py @@ -2,7 +2,7 @@ import django_filters from django.db.models.aggregates import Count from django.db.models.fields.json import KeyTextTransform -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action from rest_framework.fields import CharField, DictField, IntegerField diff --git a/authentik/events/api/notification_transport.py b/authentik/events/api/notification_transport.py index b36b2dd716..7fdb5d260a 100644 --- a/authentik/events/api/notification_transport.py +++ b/authentik/events/api/notification_transport.py @@ -1,6 +1,6 @@ """NotificationTransport API Views""" from django.http.response import Http404 -from drf_yasg2.utils import no_body, swagger_auto_schema +from drf_yasg.utils import no_body, swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import CharField, ListField, SerializerMethodField from rest_framework.request import Request @@ -36,6 +36,7 @@ class NotificationTransportSerializer(ModelSerializer): "mode", "mode_verbose", "webhook_url", + "send_once", ] diff --git a/authentik/events/forms.py b/authentik/events/forms.py deleted file mode 100644 index 6b2e7acbab..0000000000 --- a/authentik/events/forms.py +++ /dev/null @@ -1,48 +0,0 @@ -"""authentik events NotificationTransport forms""" -from django import forms -from django.utils.translation import gettext_lazy as _ - -from authentik.events.models import NotificationRule, NotificationTransport - - -class NotificationTransportForm(forms.ModelForm): - """NotificationTransport Form""" - - class Meta: - - model = NotificationTransport - fields = [ - "name", - "mode", - "webhook_url", - "send_once", - ] - widgets = { - "name": forms.TextInput(), - "webhook_url": forms.TextInput(), - } - labels = { - "webhook_url": _("Webhook URL"), - } - help_texts = { - "webhook_url": _( - ("Only required when the Generic or Slack Webhook is used.") - ), - } - - -class NotificationRuleForm(forms.ModelForm): - """NotificationRule Form""" - - class Meta: - - model = NotificationRule - fields = [ - "name", - "group", - "transports", - "severity", - ] - widgets = { - "name": forms.TextInput(), - } diff --git a/authentik/events/middleware.py b/authentik/events/middleware.py index d9b8650175..bb130cb021 100644 --- a/authentik/events/middleware.py +++ b/authentik/events/middleware.py @@ -76,7 +76,9 @@ class AuditMiddleware: user: User, request: HttpRequest, sender, instance: Model, **_ ): """Signal handler for all object's pre_delete""" - if isinstance(instance, (Event, Notification, UserObjectPermission)): + if isinstance( + instance, (Event, Notification, UserObjectPermission) + ): # pragma: no cover return EventNewThread( diff --git a/authentik/flows/api/flows.py b/authentik/flows/api/flows.py index c2a886e4c4..0923d6c881 100644 --- a/authentik/flows/api/flows.py +++ b/authentik/flows/api/flows.py @@ -3,11 +3,14 @@ from dataclasses import dataclass from django.core.cache import cache from django.db.models import Model -from django.http.response import JsonResponse -from drf_yasg2 import openapi -from drf_yasg2.utils import no_body, swagger_auto_schema +from django.http.response import HttpResponseBadRequest, JsonResponse +from django.urls import reverse +from django.utils.translation import gettext as _ +from drf_yasg import openapi +from drf_yasg.utils import no_body, swagger_auto_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action +from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ( @@ -20,11 +23,15 @@ from rest_framework.viewsets import ModelViewSet from structlog.stdlib import get_logger from authentik.api.decorators import permission_required -from authentik.core.api.utils import CacheSerializer +from authentik.core.api.utils import CacheSerializer, LinkSerializer +from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.models import Flow -from authentik.flows.planner import cache_key +from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key from authentik.flows.transfer.common import DataclassEncoder from authentik.flows.transfer.exporter import FlowExporter +from authentik.flows.transfer.importer import FlowImporter +from authentik.flows.views import SESSION_KEY_PLAN +from authentik.lib.views import bad_request_message LOGGER = get_logger() @@ -56,7 +63,7 @@ class FlowSerializer(ModelSerializer): class FlowDiagramSerializer(Serializer): - """response of the flow's /diagram/ action""" + """response of the flow's diagram action""" diagram = CharField(read_only=True) @@ -88,14 +95,14 @@ class FlowViewSet(ModelViewSet): search_fields = ["name", "slug", "designation", "title"] filterset_fields = ["flow_uuid", "name", "slug", "designation"] - @permission_required("authentik_flows.view_flow_cache") + @permission_required(None, ["authentik_flows.view_flow_cache"]) @swagger_auto_schema(responses={200: CacheSerializer(many=False)}) @action(detail=False) def cache_info(self, request: Request) -> Response: """Info about cached flows""" return Response(data={"count": len(cache.keys("flow_*"))}) - @permission_required("authentik_flows.clear_flow_cache") + @permission_required(None, ["authentik_flows.clear_flow_cache"]) @swagger_auto_schema( request_body=no_body, responses={204: "Successfully cleared cache", 400: "Bad request"}, @@ -108,7 +115,61 @@ class FlowViewSet(ModelViewSet): LOGGER.debug("Cleared flow cache", keys=len(keys)) return Response(status=204) - @permission_required("authentik_flows.export_flow") + @permission_required( + None, + [ + "authentik_flows.add_flow", + "authentik_flows.change_flow", + "authentik_flows.add_flowstagebinding", + "authentik_flows.change_flowstagebinding", + "authentik_flows.add_stage", + "authentik_flows.change_stage", + "authentik_policies.add_policy", + "authentik_policies.change_policy", + "authentik_policies.add_policybinding", + "authentik_policies.change_policybinding", + "authentik_stages_prompt.add_prompt", + "authentik_stages_prompt.change_prompt", + ], + ) + @swagger_auto_schema( + request_body=no_body, + manual_parameters=[ + openapi.Parameter( + name="file", + in_=openapi.IN_FORM, + type=openapi.TYPE_FILE, + required=True, + ) + ], + responses={204: "Successfully imported flow", 400: "Bad request"}, + ) + @action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,)) + def import_flow(self, request: Request) -> Response: + """Import flow from .akflow file""" + file = request.FILES.get("file", None) + if not file: + return HttpResponseBadRequest() + importer = FlowImporter(file.read().decode()) + valid = importer.validate() + if not valid: + return HttpResponseBadRequest() + successful = importer.apply() + if not successful: + return Response(status=204) + return HttpResponseBadRequest() + + @permission_required( + "authentik_flows.export_flow", + [ + "authentik_flows.view_flow", + "authentik_flows.view_flowstagebinding", + "authentik_flows.view_stage", + "authentik_policies.view_policy", + "authentik_policies.view_policybinding", + "authentik_stages_prompt.view_prompt", + ], + ) @swagger_auto_schema( responses={ "200": openapi.Response( @@ -194,3 +255,57 @@ class FlowViewSet(ModelViewSet): ) diagram = "\n".join([str(x) for x in header + body + footer]) return Response({"diagram": diagram}) + + @permission_required("authentik_flows.change_flow") + @swagger_auto_schema( + request_body=no_body, + manual_parameters=[ + openapi.Parameter( + name="file", + in_=openapi.IN_FORM, + type=openapi.TYPE_FILE, + required=True, + ) + ], + responses={200: "Success"}, + ) + @action(detail=True, methods=["POST"], parser_classes=(MultiPartParser,)) + # pylint: disable=unused-argument + def set_background(self, request: Request, slug: str): + """Set Flow background""" + app: Flow = self.get_object() + icon = request.FILES.get("file", None) + if not icon: + return HttpResponseBadRequest() + app.background = icon + app.save() + return Response({}) + + @swagger_auto_schema( + responses={200: LinkSerializer(many=False)}, + ) + @action(detail=True) + # pylint: disable=unused-argument + def execute(self, request: Request, slug: str): + """Execute flow for current user""" + flow: Flow = self.get_object() + planner = FlowPlanner(flow) + planner.use_cache = False + try: + plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user}) + self.request.session[SESSION_KEY_PLAN] = plan + except FlowNonApplicableException as exc: + return bad_request_message( + request, + _( + "Flow not applicable to current user/request: %(messages)s" + % {"messages": str(exc)} + ), + ) + return Response( + { + "link": request._request.build_absolute_uri( + reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + ) + } + ) diff --git a/authentik/flows/api/stages.py b/authentik/flows/api/stages.py index 8073e25642..c80a426b59 100644 --- a/authentik/flows/api/stages.py +++ b/authentik/flows/api/stages.py @@ -2,7 +2,7 @@ from typing import Iterable from django.urls import reverse -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework import mixins from rest_framework.decorators import action from rest_framework.request import Request diff --git a/authentik/flows/challenge.py b/authentik/flows/challenge.py index 6cfffc7fed..e209b1d550 100644 --- a/authentik/flows/challenge.py +++ b/authentik/flows/challenge.py @@ -2,11 +2,11 @@ from enum import Enum from typing import TYPE_CHECKING, Optional -from django.db.models.base import Model from django.http import JsonResponse from rest_framework.fields import ChoiceField, DictField -from rest_framework.serializers import CharField, Serializer +from rest_framework.serializers import CharField +from authentik.core.api.utils import PassiveSerializer from authentik.flows.transfer.common import DataclassEncoder if TYPE_CHECKING: @@ -21,20 +21,14 @@ class ChallengeTypes(Enum): REDIRECT = "redirect" -class ErrorDetailSerializer(Serializer): +class ErrorDetailSerializer(PassiveSerializer): """Serializer for rest_framework's error messages""" string = CharField() code = CharField() - def create(self, validated_data: dict) -> Model: - return Model() - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() - - -class Challenge(Serializer): +class Challenge(PassiveSerializer): """Challenge that gets sent to the client based on which stage is currently active""" @@ -49,12 +43,6 @@ class Challenge(Serializer): child=ErrorDetailSerializer(many=True), allow_empty=True, required=False ) - def create(self, validated_data: dict) -> Model: - return Model() - - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() - class RedirectChallenge(Challenge): """Challenge type to redirect the client""" @@ -81,20 +69,14 @@ class AccessDeniedChallenge(Challenge): error_message = CharField(required=False) -class PermissionSerializer(Serializer): +class PermissionSerializer(PassiveSerializer): """Permission used for consent""" name = CharField() id = CharField() - def create(self, validated_data: dict) -> Model: - return Model() - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() - - -class ChallengeResponse(Serializer): +class ChallengeResponse(PassiveSerializer): """Base class for all challenge responses""" stage: Optional["StageView"] @@ -103,12 +85,6 @@ class ChallengeResponse(Serializer): self.stage = kwargs.pop("stage", None) super().__init__(instance=instance, data=data, **kwargs) - def create(self, validated_data: dict) -> Model: - return Model() - - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() - class HttpChallengeResponse(JsonResponse): """Subclass of JsonResponse that uses the `DataclassEncoder`""" diff --git a/authentik/flows/forms.py b/authentik/flows/forms.py index 2e8ca9d143..f8082aa677 100644 --- a/authentik/flows/forms.py +++ b/authentik/flows/forms.py @@ -1,35 +1,10 @@ """Flow and Stage forms""" - from django import forms -from django.core.validators import FileExtensionValidator -from django.forms import ValidationError -from django.utils.translation import gettext_lazy as _ -from authentik.flows.models import Flow, FlowStageBinding, Stage -from authentik.flows.transfer.importer import FlowImporter +from authentik.flows.models import FlowStageBinding, Stage from authentik.lib.widgets import GroupedModelChoiceField -class FlowForm(forms.ModelForm): - """Flow Form""" - - class Meta: - - model = Flow - fields = [ - "name", - "title", - "slug", - "designation", - "background", - ] - widgets = { - "name": forms.TextInput(), - "title": forms.TextInput(), - "background": forms.FileInput(), - } - - class FlowStageBindingForm(forms.ModelForm): """FlowStageBinding Form""" @@ -56,20 +31,3 @@ class FlowStageBindingForm(forms.ModelForm): widgets = { "name": forms.TextInput(), } - - -class FlowImportForm(forms.Form): - """Form used for flow importing""" - - flow = forms.FileField( - validators=[FileExtensionValidator(allowed_extensions=["akflow"])] - ) - - def clean_flow(self): - """Check if the flow is valid and rewind the file to the start""" - flow = self.cleaned_data["flow"].read() - valid = FlowImporter(flow.decode()).validate() - if not valid: - raise ValidationError(_("Flow invalid.")) - self.cleaned_data["flow"].seek(0) - return self.cleaned_data["flow"] diff --git a/authentik/flows/views.py b/authentik/flows/views.py index 42e328f4c8..f291d39f0a 100644 --- a/authentik/flows/views.py +++ b/authentik/flows/views.py @@ -10,8 +10,8 @@ from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.generic import View -from drf_yasg2 import openapi -from drf_yasg2.utils import no_body, swagger_auto_schema +from drf_yasg import openapi +from drf_yasg.utils import no_body, swagger_auto_schema from rest_framework.permissions import AllowAny from rest_framework.views import APIView from structlog.stdlib import BoundLogger, get_logger diff --git a/authentik/managed/models.py b/authentik/managed/models.py index d6dca21e62..508cc81029 100644 --- a/authentik/managed/models.py +++ b/authentik/managed/models.py @@ -1,6 +1,5 @@ """Managed Object models""" from django.db import models -from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ @@ -22,10 +21,6 @@ class ManagedModel(models.Model): unique=True, ) - def managed_objects(self) -> QuerySet: - """Get all objects which are managed""" - return self.objects.exclude(managed__isnull=True) - class Meta: abstract = True diff --git a/authentik/outposts/api/outpost_service_connections.py b/authentik/outposts/api/outpost_service_connections.py index d8ed4bd7d1..757a2798e2 100644 --- a/authentik/outposts/api/outpost_service_connections.py +++ b/authentik/outposts/api/outpost_service_connections.py @@ -1,17 +1,20 @@ """Outpost API Views""" from dataclasses import asdict -from django.db.models.base import Model from django.urls import reverse -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import BooleanField, CharField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, Serializer +from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.core.api.utils import ( + MetaNameSerializer, + PassiveSerializer, + TypeCreateSerializer, +) from authentik.lib.templatetags.authentik_utils import verbose_name from authentik.lib.utils.reflection import all_subclasses from authentik.outposts.models import ( @@ -43,18 +46,12 @@ class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer): ] -class ServiceConnectionStateSerializer(Serializer): +class ServiceConnectionStateSerializer(PassiveSerializer): """Serializer for Service connection state""" healthy = BooleanField(read_only=True) version = CharField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class ServiceConnectionViewSet(ModelViewSet): """ServiceConnection Viewset""" diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index f908e14ffc..d1589b7ac5 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -1,15 +1,15 @@ """Outpost API Views""" -from django.db.models import Model -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import BooleanField, CharField, DateTimeField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import JSONField, ModelSerializer, Serializer +from rest_framework.serializers import JSONField, ModelSerializer from rest_framework.viewsets import ModelViewSet from authentik.core.api.providers import ProviderSerializer -from authentik.outposts.models import Outpost +from authentik.core.api.utils import PassiveSerializer +from authentik.outposts.models import Outpost, default_outpost_config class OutpostSerializer(ModelSerializer): @@ -32,7 +32,13 @@ class OutpostSerializer(ModelSerializer): ] -class OutpostHealthSerializer(Serializer): +class OutpostDefaultConfigSerializer(PassiveSerializer): + """Global default outpost config""" + + config = JSONField(read_only=True) + + +class OutpostHealthSerializer(PassiveSerializer): """Outpost health status""" last_seen = DateTimeField(read_only=True) @@ -40,12 +46,6 @@ class OutpostHealthSerializer(Serializer): version_should = CharField(read_only=True) version_outdated = BooleanField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class OutpostViewSet(ModelViewSet): """Outpost Viewset""" @@ -78,3 +78,10 @@ class OutpostViewSet(ModelViewSet): } ) return Response(OutpostHealthSerializer(states, many=True).data) + + @swagger_auto_schema(responses={200: OutpostDefaultConfigSerializer(many=False)}) + @action(detail=False, methods=["GET"]) + def default_settings(self, request: Request) -> Response: + """Global default outpost config""" + host = self.request.build_absolute_uri("/") + return Response({"config": default_outpost_config(host)}) diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index a7538eab79..9b0680010d 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -80,9 +80,9 @@ class OutpostType(models.TextChoices): PROXY = "proxy" -def default_outpost_config(): +def default_outpost_config(host: Optional[str] = None): """Get default outpost config""" - return asdict(OutpostConfig(authentik_host="")) + return asdict(OutpostConfig(authentik_host=host or "")) @dataclass diff --git a/authentik/policies/api/__init__.py b/authentik/policies/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/authentik/policies/api/bindings.py b/authentik/policies/api/bindings.py new file mode 100644 index 0000000000..5ed92ccaac --- /dev/null +++ b/authentik/policies/api/bindings.py @@ -0,0 +1,80 @@ +"""policy binding API Views""" +from django.core.exceptions import ObjectDoesNotExist +from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField +from rest_framework.viewsets import ModelViewSet +from structlog.stdlib import get_logger + +from authentik.core.api.groups import GroupSerializer +from authentik.policies.models import PolicyBinding, PolicyBindingModel + +LOGGER = get_logger() + + +class PolicyBindingModelForeignKey(PrimaryKeyRelatedField): + """rest_framework PrimaryKeyRelatedField which resolves + model_manager's InheritanceQuerySet""" + + def use_pk_only_optimization(self): + return False + + # pylint: disable=inconsistent-return-statements + def to_internal_value(self, data): + if self.pk_field is not None: + data = self.pk_field.to_internal_value(data) + try: + # Due to inheritance, a direct DB lookup for the primary key + # won't return anything. This is because the direct lookup + # checks the PK of PolicyBindingModel (for example), + # but we get given the Primary Key of the inheriting class + for model in self.get_queryset().select_subclasses().all().select_related(): + if model.pk == data: + return model + # as a fallback we still try a direct lookup + return self.get_queryset().get_subclass(pk=data) + except ObjectDoesNotExist: + self.fail("does_not_exist", pk_value=data) + except (TypeError, ValueError): + self.fail("incorrect_type", data_type=type(data).__name__) + + def to_representation(self, value): + correct_model = PolicyBindingModel.objects.get_subclass(pbm_uuid=value.pbm_uuid) + return correct_model.pk + + +class PolicyBindingSerializer(ModelSerializer): + """PolicyBinding Serializer""" + + # Because we're not interested in the PolicyBindingModel's PK but rather the subclasses PK, + # we have to manually declare this field + target = PolicyBindingModelForeignKey( + queryset=PolicyBindingModel.objects.select_subclasses(), + required=True, + ) + + group = GroupSerializer(required=False) + + class Meta: + + model = PolicyBinding + fields = [ + "pk", + "policy", + "group", + "user", + "target", + "enabled", + "order", + "timeout", + ] + depth = 2 + + +class PolicyBindingViewSet(ModelViewSet): + """PolicyBinding Viewset""" + + queryset = PolicyBinding.objects.all().select_related( + "policy", "target", "group", "user" + ) + serializer_class = PolicyBindingSerializer + filterset_fields = ["policy", "target", "enabled", "order", "timeout"] + search_fields = ["policy__name"] diff --git a/authentik/policies/api/exec.py b/authentik/policies/api/exec.py new file mode 100644 index 0000000000..bb987080d0 --- /dev/null +++ b/authentik/policies/api/exec.py @@ -0,0 +1,20 @@ +"""Serializer for policy execution""" +from rest_framework.fields import BooleanField, CharField, JSONField, ListField +from rest_framework.relations import PrimaryKeyRelatedField + +from authentik.core.api.utils import PassiveSerializer +from authentik.core.models import User + + +class PolicyTestSerializer(PassiveSerializer): + """Test policy execution for a user with context""" + + user = PrimaryKeyRelatedField(queryset=User.objects.all()) + context = JSONField(required=False) + + +class PolicyTestResultSerializer(PassiveSerializer): + """result of a policy test""" + + passing = BooleanField() + messages = ListField(child=CharField(), read_only=True) diff --git a/authentik/policies/api.py b/authentik/policies/api/policies.py similarity index 62% rename from authentik/policies/api.py rename to authentik/policies/api/policies.py index c59cacfed2..39771027e9 100644 --- a/authentik/policies/api.py +++ b/authentik/policies/api/policies.py @@ -1,19 +1,16 @@ """policy API Views""" from django.core.cache import cache -from django.core.exceptions import ObjectDoesNotExist from django.http.response import HttpResponseBadRequest from django.urls import reverse -from drf_yasg2.utils import no_body, swagger_auto_schema +from drf_yasg.utils import no_body, swagger_auto_schema +from guardian.shortcuts import get_objects_for_user from rest_framework import mixins from rest_framework.decorators import action +from rest_framework.exceptions import PermissionDenied from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ( - ModelSerializer, - PrimaryKeyRelatedField, - SerializerMethodField, -) -from rest_framework.viewsets import GenericViewSet, ModelViewSet +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from rest_framework.viewsets import GenericViewSet from structlog.stdlib import get_logger from authentik.api.decorators import permission_required @@ -25,42 +22,14 @@ from authentik.core.api.utils import ( ) from authentik.lib.templatetags.authentik_utils import verbose_name from authentik.lib.utils.reflection import all_subclasses -from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel +from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer +from authentik.policies.models import Policy, PolicyBinding +from authentik.policies.process import PolicyProcess +from authentik.policies.types import PolicyRequest LOGGER = get_logger() -class PolicyBindingModelForeignKey(PrimaryKeyRelatedField): - """rest_framework PrimaryKeyRelatedField which resolves - model_manager's InheritanceQuerySet""" - - def use_pk_only_optimization(self): - return False - - # pylint: disable=inconsistent-return-statements - def to_internal_value(self, data): - if self.pk_field is not None: - data = self.pk_field.to_internal_value(data) - try: - # Due to inheritance, a direct DB lookup for the primary key - # won't return anything. This is because the direct lookup - # checks the PK of PolicyBindingModel (for example), - # but we get given the Primary Key of the inheriting class - for model in self.get_queryset().select_subclasses().all().select_related(): - if model.pk == data: - return model - # as a fallback we still try a direct lookup - return self.get_queryset().get_subclass(pk=data) - except ObjectDoesNotExist: - self.fail("does_not_exist", pk_value=data) - except (TypeError, ValueError): - self.fail("incorrect_type", data_type=type(data).__name__) - - def to_representation(self, value): - correct_model = PolicyBindingModel.objects.get_subclass(pbm_uuid=value.pbm_uuid) - return correct_model.pk - - class PolicySerializer(ModelSerializer, MetaNameSerializer): """Policy Serializer""" @@ -168,39 +137,32 @@ class PolicyViewSet( cache.delete_many(keys) return Response(status=204) - -class PolicyBindingSerializer(ModelSerializer): - """PolicyBinding Serializer""" - - # Because we're not interested in the PolicyBindingModel's PK but rather the subclasses PK, - # we have to manually declare this field - target = PolicyBindingModelForeignKey( - queryset=PolicyBindingModel.objects.select_subclasses(), - required=True, + @permission_required("authentik_policies.view_policy") + @swagger_auto_schema( + request_body=PolicyTestSerializer(), + responses={200: PolicyTestResultSerializer()}, ) + @action(detail=True, methods=["POST"]) + def test(self, request: Request) -> Response: + """Test policy""" + policy = self.get_object() + test_params = PolicyTestSerializer(request.data) + if not test_params.is_valid(): + return Response(test_params.errors, status=400) - class Meta: + # User permission check, only allow policy testing for users that are readable + users = get_objects_for_user(request.user, "authentik_core.view_user").filter( + pk=test_params["user"] + ) + if not users.exists(): + raise PermissionDenied() - model = PolicyBinding - fields = [ - "pk", - "policy", - "group", - "user", - "target", - "enabled", - "order", - "timeout", - ] - depth = 2 + p_request = PolicyRequest(users.first()) + p_request.debug = True + p_request.set_http_request(self.request) + p_request.context = test_params.validated_data.get("context", {}) - -class PolicyBindingViewSet(ModelViewSet): - """PolicyBinding Viewset""" - - queryset = PolicyBinding.objects.all().select_related( - "policy", "target", "group", "user" - ) - serializer_class = PolicyBindingSerializer - filterset_fields = ["policy", "target", "enabled", "order", "timeout"] - search_fields = ["policy__name"] + proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) + result = proc.execute() + response = PolicyTestResultSerializer(result) + return Response(response) diff --git a/authentik/policies/dummy/api.py b/authentik/policies/dummy/api.py index aa75a2b3ab..66d8370633 100644 --- a/authentik/policies/dummy/api.py +++ b/authentik/policies/dummy/api.py @@ -1,7 +1,7 @@ """Dummy Policy API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.dummy.models import DummyPolicy diff --git a/authentik/policies/event_matcher/api.py b/authentik/policies/event_matcher/api.py index df81d25f82..0dda645f12 100644 --- a/authentik/policies/event_matcher/api.py +++ b/authentik/policies/event_matcher/api.py @@ -1,7 +1,7 @@ """Event Matcher Policy API""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.event_matcher.models import EventMatcherPolicy diff --git a/authentik/policies/expiry/api.py b/authentik/policies/expiry/api.py index 3fb9c410e9..70f12a62df 100644 --- a/authentik/policies/expiry/api.py +++ b/authentik/policies/expiry/api.py @@ -1,7 +1,7 @@ """Password Expiry Policy API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.expiry.models import PasswordExpiryPolicy diff --git a/authentik/policies/expression/api.py b/authentik/policies/expression/api.py index 8728be5ea9..1ef7a53a88 100644 --- a/authentik/policies/expression/api.py +++ b/authentik/policies/expression/api.py @@ -1,7 +1,7 @@ """Expression Policy API""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.expression.models import ExpressionPolicy diff --git a/authentik/policies/hibp/api.py b/authentik/policies/hibp/api.py index 02f91f4fb7..66acd88776 100644 --- a/authentik/policies/hibp/api.py +++ b/authentik/policies/hibp/api.py @@ -1,7 +1,7 @@ """Source API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.hibp.models import HaveIBeenPwendPolicy diff --git a/authentik/policies/models.py b/authentik/policies/models.py index 7e34131c3e..9964a1dcdb 100644 --- a/authentik/policies/models.py +++ b/authentik/policies/models.py @@ -95,7 +95,7 @@ class PolicyBinding(SerializerModel): @property def serializer(self) -> BaseSerializer: - from authentik.policies.api import PolicyBindingSerializer + from authentik.policies.api.bindings import PolicyBindingSerializer return PolicyBindingSerializer diff --git a/authentik/policies/password/api.py b/authentik/policies/password/api.py index 050b41a3f6..f0171095cd 100644 --- a/authentik/policies/password/api.py +++ b/authentik/policies/password/api.py @@ -1,7 +1,7 @@ """Password Policy API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.password.models import PasswordPolicy diff --git a/authentik/policies/reputation/api.py b/authentik/policies/reputation/api.py index e75d48fac2..ce3681972c 100644 --- a/authentik/policies/reputation/api.py +++ b/authentik/policies/reputation/api.py @@ -1,7 +1,7 @@ """Source API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.policies.api import PolicySerializer +from authentik.policies.api.policies import PolicySerializer from authentik.policies.reputation.models import ( IPReputation, ReputationPolicy, diff --git a/authentik/providers/oauth2/api/provider.py b/authentik/providers/oauth2/api/provider.py index 8a5a2e271c..2e9046529e 100644 --- a/authentik/providers/oauth2/api/provider.py +++ b/authentik/providers/oauth2/api/provider.py @@ -1,6 +1,6 @@ """OAuth2Provider API Views""" from django.urls import reverse -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import ReadOnlyField from rest_framework.generics import get_object_or_404 diff --git a/authentik/providers/proxy/api.py b/authentik/providers/proxy/api.py index f9374c0b56..ff9fc94a2d 100644 --- a/authentik/providers/proxy/api.py +++ b/authentik/providers/proxy/api.py @@ -1,5 +1,5 @@ """ProxyProvider API Views""" -from drf_yasg2.utils import swagger_serializer_method +from drf_yasg.utils import swagger_serializer_method from rest_framework.fields import CharField, ListField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index 10356f600b..052de8b5c8 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -1,5 +1,5 @@ """SAMLProvider API Views""" -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import ReadOnlyField from rest_framework.request import Request diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 945b89abca..39fe5439b4 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -126,7 +126,7 @@ INSTALLED_APPS = [ "authentik.stages.user_write.apps.AuthentikStageUserWriteConfig", "rest_framework", "django_filters", - "drf_yasg2", + "drf_yasg", "guardian", "django_prometheus", "channels", @@ -137,6 +137,7 @@ INSTALLED_APPS = [ GUARDIAN_MONKEY_PATCH = False SWAGGER_SETTINGS = { + "DEFAULT_AUTO_SCHEMA_CLASS": "authentik.api.schema.ErrorResponseAutoSchema", "DEFAULT_INFO": "authentik.api.v2.urls.info", "DEFAULT_PAGINATOR_INSPECTORS": [ "authentik.api.pagination_schema.PaginationInspector", @@ -437,7 +438,7 @@ _LOGGING_HANDLER_MAP = { "kubernetes": "INFO", "asyncio": "WARNING", "aioredis": "WARNING", - "drf_yasg2.utils": "WARNING", + "drf_yasg.utils": "WARNING", } for handler_name, level in _LOGGING_HANDLER_MAP.items(): # pyright: reportGeneralTypeIssues=false diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py index 60551bc850..01aa37d1c3 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api.py @@ -3,17 +3,16 @@ from datetime import datetime from time import time from django.core.cache import cache -from django.db.models.base import Model -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.fields import DateTimeField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, Serializer +from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet from authentik.core.api.sources import SourceSerializer -from authentik.core.api.utils import MetaNameSerializer +from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource @@ -44,17 +43,11 @@ class LDAPSourceSerializer(SourceSerializer): extra_kwargs = {"bind_password": {"write_only": True}} -class LDAPSourceSyncStatusSerializer(Serializer): +class LDAPSourceSyncStatusSerializer(PassiveSerializer): """LDAP Sync status""" last_sync = DateTimeField(read_only=True) - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - class LDAPSourceViewSet(ModelViewSet): """LDAP Source Viewset""" diff --git a/authentik/sources/saml/api.py b/authentik/sources/saml/api.py index c6ca57bd1c..91a4407fb5 100644 --- a/authentik/sources/saml/api.py +++ b/authentik/sources/saml/api.py @@ -1,5 +1,5 @@ """SAMLSource API Views""" -from drf_yasg2.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response diff --git a/authentik/stages/authenticator_validate/challenge.py b/authentik/stages/authenticator_validate/challenge.py index d5781125c1..92771a06d1 100644 --- a/authentik/stages/authenticator_validate/challenge.py +++ b/authentik/stages/authenticator_validate/challenge.py @@ -1,5 +1,4 @@ """Validation stage challenge checking""" -from django.db.models import Model from django.http import HttpRequest from django.utils.translation import gettext_lazy as _ from django_otp import match_token @@ -7,7 +6,7 @@ from django_otp.models import Device from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_totp.models import TOTPDevice from rest_framework.fields import CharField, JSONField -from rest_framework.serializers import Serializer, ValidationError +from rest_framework.serializers import ValidationError from webauthn import WebAuthnAssertionOptions, WebAuthnAssertionResponse, WebAuthnUser from webauthn.webauthn import ( AuthenticationRejectedException, @@ -15,24 +14,19 @@ from webauthn.webauthn import ( WebAuthnUserDataMissing, ) +from authentik.core.api.utils import PassiveSerializer from authentik.core.models import User from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.utils import generate_challenge, get_origin -class DeviceChallenge(Serializer): +class DeviceChallenge(PassiveSerializer): """Single device challenge""" device_class = CharField() device_uid = CharField() challenge = JSONField() - def create(self, validated_data: dict) -> Model: - raise NotImplementedError - - def update(self, instance: Model, validated_data: dict) -> Model: - raise NotImplementedError - def get_challenge_for_device(request: HttpRequest, device: Device) -> dict: """Generate challenge for a single device""" diff --git a/authentik/stages/authenticator_webauthn/apps.py b/authentik/stages/authenticator_webauthn/apps.py index ec8736525c..36a2f3f0d4 100644 --- a/authentik/stages/authenticator_webauthn/apps.py +++ b/authentik/stages/authenticator_webauthn/apps.py @@ -8,4 +8,3 @@ class AuthentikStageAuthenticatorWebAuthnConfig(AppConfig): name = "authentik.stages.authenticator_webauthn" label = "authentik_stages_authenticator_webauthn" verbose_name = "authentik Stages.Authenticator.WebAuthn" - mountpoint = "-/user/authenticator/webauthn/" diff --git a/authentik/stages/authenticator_webauthn/urls.py b/authentik/stages/authenticator_webauthn/urls.py deleted file mode 100644 index 7c61488915..0000000000 --- a/authentik/stages/authenticator_webauthn/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -"""WebAuthn urls""" -from django.urls import path - -from authentik.stages.authenticator_webauthn.views import DeviceUpdateView - -urlpatterns = [ - path("devices//update/", DeviceUpdateView.as_view(), name="device-update"), -] diff --git a/authentik/stages/authenticator_webauthn/views.py b/authentik/stages/authenticator_webauthn/views.py deleted file mode 100644 index b4db562210..0000000000 --- a/authentik/stages/authenticator_webauthn/views.py +++ /dev/null @@ -1,25 +0,0 @@ -"""webauthn views""" -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.messages.views import SuccessMessageMixin -from django.http.response import Http404 -from django.utils.translation import gettext as _ -from django.views.generic import UpdateView - -from authentik.stages.authenticator_webauthn.forms import DeviceEditForm -from authentik.stages.authenticator_webauthn.models import WebAuthnDevice - - -class DeviceUpdateView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): - """Update device""" - - model = WebAuthnDevice - form_class = DeviceEditForm - template_name = "generic/update.html" - success_url = "/" - success_message = _("Successfully updated Device") - - def get_object(self) -> WebAuthnDevice: - device: WebAuthnDevice = super().get_object() - if device.user != self.request.user: - raise Http404 - return device diff --git a/authentik/stages/prompt/stage.py b/authentik/stages/prompt/stage.py index c926a20dd5..15cc9e772f 100644 --- a/authentik/stages/prompt/stage.py +++ b/authentik/stages/prompt/stage.py @@ -3,16 +3,16 @@ from email.policy import Policy from types import MethodType from typing import Any, Callable, Iterator -from django.db.models.base import Model from django.db.models.query import QuerySet from django.http import HttpRequest, HttpResponse from django.http.request import QueryDict from django.utils.translation import gettext_lazy as _ from guardian.shortcuts import get_anonymous_user from rest_framework.fields import BooleanField, CharField, IntegerField -from rest_framework.serializers import Serializer, ValidationError +from rest_framework.serializers import ValidationError from structlog.stdlib import get_logger +from authentik.core.api.utils import PassiveSerializer from authentik.core.models import User from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan @@ -26,7 +26,7 @@ LOGGER = get_logger() PLAN_CONTEXT_PROMPT = "prompt_data" -class PromptSerializer(Serializer): +class PromptSerializer(PassiveSerializer): """Serializer for a single Prompt field""" field_key = CharField() @@ -36,12 +36,6 @@ class PromptSerializer(Serializer): placeholder = CharField() order = IntegerField() - def create(self, validated_data: dict) -> Model: - return Model() - - def update(self, instance: Model, validated_data: dict) -> Model: - return Model() - class PromptChallenge(Challenge): """Initial challenge being sent, define fields""" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a36438ac26..54490ff0df 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -285,9 +285,9 @@ stages: inputs: script: | docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0 - sudo chmod 777 -R api/ + sudo chmod 777 -R web/api/ cd web - sudo chmod 777 -R api/ + cd api && npm i && cd .. npm i npm run build - task: CmdLine@2 diff --git a/swagger.yaml b/swagger.yaml index 249a997197..9dbabb7db9 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -6,7 +6,7 @@ info: license: name: GNU GPLv3 url: https://github.com/BeryJu/authentik/blob/master/LICENSE - version: v2 + version: v2beta basePath: /api/v2beta consumes: - application/json @@ -27,9 +27,13 @@ paths: parameters: [] responses: '200': - description: Login Metrics per 1h + description: '' schema: $ref: '#/definitions/LoginMetrics' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - admin parameters: [] @@ -40,12 +44,15 @@ paths: parameters: [] responses: '200': - description: Serialize TaskInfo and TaskResult + description: '' schema: - description: '' type: array items: $ref: '#/definitions/Task' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - admin parameters: [] @@ -57,6 +64,15 @@ paths: responses: '201': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - admin parameters: @@ -91,9 +107,13 @@ paths: type: integer responses: '200': - description: Get running and latest version. + description: '' schema: $ref: '#/definitions/Version' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - admin parameters: [] @@ -159,9 +179,12 @@ paths: results: type: array items: - description: '' type: object properties: {} + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - admin parameters: [] @@ -233,6 +256,10 @@ paths: type: array items: $ref: '#/definitions/StaticDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators parameters: [] @@ -246,6 +273,15 @@ paths: description: '' schema: $ref: '#/definitions/StaticDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators parameters: @@ -322,6 +358,10 @@ paths: type: array items: $ref: '#/definitions/TOTPDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators parameters: [] @@ -335,6 +375,15 @@ paths: description: '' schema: $ref: '#/definitions/TOTPDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators parameters: @@ -411,6 +460,10 @@ paths: type: array items: $ref: '#/definitions/WebAuthnDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators parameters: [] @@ -424,6 +477,15 @@ paths: description: '' schema: $ref: '#/definitions/WebAuthnDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators parameters: @@ -500,6 +562,10 @@ paths: type: array items: $ref: '#/definitions/StaticDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators post: @@ -516,6 +582,14 @@ paths: description: '' schema: $ref: '#/definitions/StaticDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators parameters: [] @@ -529,6 +603,15 @@ paths: description: '' schema: $ref: '#/definitions/StaticDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators put: @@ -545,6 +628,19 @@ paths: description: '' schema: $ref: '#/definitions/StaticDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators patch: @@ -561,6 +657,19 @@ paths: description: '' schema: $ref: '#/definitions/StaticDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators delete: @@ -570,6 +679,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators parameters: @@ -646,6 +764,10 @@ paths: type: array items: $ref: '#/definitions/TOTPDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators post: @@ -662,6 +784,14 @@ paths: description: '' schema: $ref: '#/definitions/TOTPDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators parameters: [] @@ -675,6 +805,15 @@ paths: description: '' schema: $ref: '#/definitions/TOTPDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators put: @@ -691,6 +830,19 @@ paths: description: '' schema: $ref: '#/definitions/TOTPDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators patch: @@ -707,6 +859,19 @@ paths: description: '' schema: $ref: '#/definitions/TOTPDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators delete: @@ -716,6 +881,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators parameters: @@ -792,6 +966,10 @@ paths: type: array items: $ref: '#/definitions/WebAuthnDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators post: @@ -808,6 +986,14 @@ paths: description: '' schema: $ref: '#/definitions/WebAuthnDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - authenticators parameters: [] @@ -821,6 +1007,15 @@ paths: description: '' schema: $ref: '#/definitions/WebAuthnDevice' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators put: @@ -837,6 +1032,19 @@ paths: description: '' schema: $ref: '#/definitions/WebAuthnDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators patch: @@ -853,6 +1061,19 @@ paths: description: '' schema: $ref: '#/definitions/WebAuthnDevice' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators delete: @@ -862,6 +1083,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - authenticators parameters: @@ -933,6 +1163,10 @@ paths: type: array items: $ref: '#/definitions/Application' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core post: @@ -949,6 +1183,14 @@ paths: description: '' schema: $ref: '#/definitions/Application' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -962,6 +1204,15 @@ paths: description: '' schema: $ref: '#/definitions/Application' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core put: @@ -978,6 +1229,19 @@ paths: description: '' schema: $ref: '#/definitions/Application' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core patch: @@ -994,6 +1258,19 @@ paths: description: '' schema: $ref: '#/definitions/Application' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core delete: @@ -1003,6 +1280,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1020,12 +1306,53 @@ paths: parameters: [] responses: '200': - description: Coordinates for diagrams + description: '' schema: - description: '' type: array items: $ref: '#/definitions/Coordinate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - core + parameters: + - name: slug + in: path + description: Internal application name, used in URLs. + required: true + type: string + format: slug + pattern: ^[-a-zA-Z0-9_]+$ + /core/applications/{slug}/set_icon/: + post: + operationId: core_applications_set_icon + description: Set application icon + parameters: + - name: file + in: formData + required: true + type: file + responses: + '200': + description: Success + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + consumes: + - multipart/form-data tags: - core parameters: @@ -1109,6 +1436,10 @@ paths: type: array items: $ref: '#/definitions/Group' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core post: @@ -1125,6 +1456,14 @@ paths: description: '' schema: $ref: '#/definitions/Group' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -1138,6 +1477,15 @@ paths: description: '' schema: $ref: '#/definitions/Group' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core put: @@ -1154,6 +1502,19 @@ paths: description: '' schema: $ref: '#/definitions/Group' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core patch: @@ -1170,6 +1531,19 @@ paths: description: '' schema: $ref: '#/definitions/Group' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core delete: @@ -1179,6 +1553,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1271,6 +1654,10 @@ paths: type: array items: $ref: '#/definitions/Token' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core post: @@ -1287,6 +1674,14 @@ paths: description: '' schema: $ref: '#/definitions/Token' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -1300,6 +1695,15 @@ paths: description: '' schema: $ref: '#/definitions/Token' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core put: @@ -1316,6 +1720,19 @@ paths: description: '' schema: $ref: '#/definitions/Token' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core patch: @@ -1332,6 +1749,19 @@ paths: description: '' schema: $ref: '#/definitions/Token' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core delete: @@ -1341,6 +1771,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1357,9 +1796,18 @@ paths: parameters: [] responses: '200': - description: Show token's current key + description: '' schema: $ref: '#/definitions/TokenView' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1442,6 +1890,10 @@ paths: type: array items: $ref: '#/definitions/UserConsent' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -1455,6 +1907,15 @@ paths: description: '' schema: $ref: '#/definitions/UserConsent' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core delete: @@ -1464,6 +1925,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1550,6 +2020,10 @@ paths: type: array items: $ref: '#/definitions/User' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core post: @@ -1566,6 +2040,14 @@ paths: description: '' schema: $ref: '#/definitions/User' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -1611,11 +2093,13 @@ paths: type: integer responses: '200': - description: Response for the /user/me endpoint, returns the currently active - user (as `user` property) and, if this user is being impersonated, the - original user in the `original` property. + description: '' schema: $ref: '#/definitions/SessionUser' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -1661,9 +2145,13 @@ paths: type: integer responses: '200': - description: User Metrics + description: '' schema: $ref: '#/definitions/UserMetrics' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - core parameters: [] @@ -1677,6 +2165,15 @@ paths: description: '' schema: $ref: '#/definitions/User' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core put: @@ -1693,6 +2190,19 @@ paths: description: '' schema: $ref: '#/definitions/User' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core patch: @@ -1709,6 +2219,19 @@ paths: description: '' schema: $ref: '#/definitions/User' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core delete: @@ -1718,6 +2241,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1733,9 +2265,18 @@ paths: parameters: [] responses: '200': - description: Recovery link for a user to reset their password + description: '' schema: - $ref: '#/definitions/UserRecovery' + $ref: '#/definitions/Link' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - core parameters: @@ -1807,6 +2348,10 @@ paths: type: array items: $ref: '#/definitions/CertificateKeyPair' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - crypto post: @@ -1823,6 +2368,40 @@ paths: description: '' schema: $ref: '#/definitions/CertificateKeyPair' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + tags: + - crypto + parameters: [] + /crypto/certificatekeypairs/generate/: + post: + operationId: crypto_certificatekeypairs_generate + description: Generate a new, self-signed certificate-key pair + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/CertificateGeneration' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/CertificateKeyPair' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - crypto parameters: [] @@ -1836,6 +2415,15 @@ paths: description: '' schema: $ref: '#/definitions/CertificateKeyPair' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - crypto put: @@ -1852,6 +2440,19 @@ paths: description: '' schema: $ref: '#/definitions/CertificateKeyPair' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - crypto patch: @@ -1868,6 +2469,19 @@ paths: description: '' schema: $ref: '#/definitions/CertificateKeyPair' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - crypto delete: @@ -1877,6 +2491,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - crypto parameters: @@ -1893,9 +2516,18 @@ paths: parameters: [] responses: '200': - description: Get CertificateKeyPair's data + description: '' schema: $ref: '#/definitions/CertificateData' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - crypto parameters: @@ -1912,9 +2544,18 @@ paths: parameters: [] responses: '200': - description: Get CertificateKeyPair's data + description: '' schema: $ref: '#/definitions/CertificateData' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - crypto parameters: @@ -2022,6 +2663,10 @@ paths: type: array items: $ref: '#/definitions/Event' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events parameters: [] @@ -2092,12 +2737,19 @@ paths: default: 15 responses: '200': - description: Response object of Event's top_per_user + description: '' schema: - description: '' type: array items: $ref: '#/definitions/EventTopPerUser' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events parameters: [] @@ -2111,6 +2763,15 @@ paths: description: '' schema: $ref: '#/definitions/Event' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events parameters: @@ -2208,6 +2869,10 @@ paths: type: array items: $ref: '#/definitions/Notification' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events parameters: [] @@ -2221,6 +2886,15 @@ paths: description: '' schema: $ref: '#/definitions/Notification' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events put: @@ -2237,6 +2911,19 @@ paths: description: '' schema: $ref: '#/definitions/Notification' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events patch: @@ -2253,6 +2940,19 @@ paths: description: '' schema: $ref: '#/definitions/Notification' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events delete: @@ -2262,6 +2962,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events parameters: @@ -2334,6 +3043,10 @@ paths: type: array items: $ref: '#/definitions/NotificationRule' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events post: @@ -2350,6 +3063,14 @@ paths: description: '' schema: $ref: '#/definitions/NotificationRule' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events parameters: [] @@ -2363,6 +3084,15 @@ paths: description: '' schema: $ref: '#/definitions/NotificationRule' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events put: @@ -2379,6 +3109,19 @@ paths: description: '' schema: $ref: '#/definitions/NotificationRule' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events patch: @@ -2395,6 +3138,19 @@ paths: description: '' schema: $ref: '#/definitions/NotificationRule' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events delete: @@ -2404,6 +3160,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events parameters: @@ -2476,6 +3241,10 @@ paths: type: array items: $ref: '#/definitions/NotificationTransport' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events post: @@ -2492,6 +3261,14 @@ paths: description: '' schema: $ref: '#/definitions/NotificationTransport' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - events parameters: [] @@ -2505,6 +3282,15 @@ paths: description: '' schema: $ref: '#/definitions/NotificationTransport' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events put: @@ -2521,6 +3307,19 @@ paths: description: '' schema: $ref: '#/definitions/NotificationTransport' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events patch: @@ -2537,6 +3336,19 @@ paths: description: '' schema: $ref: '#/definitions/NotificationTransport' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events delete: @@ -2546,6 +3358,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events parameters: @@ -2564,9 +3385,18 @@ paths: parameters: [] responses: '200': - description: Notification test serializer + description: '' schema: $ref: '#/definitions/NotificationTransportTest' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - events parameters: @@ -2679,6 +3509,10 @@ paths: type: array items: $ref: '#/definitions/FlowStageBinding' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - flows post: @@ -2695,6 +3529,14 @@ paths: description: '' schema: $ref: '#/definitions/FlowStageBinding' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - flows parameters: [] @@ -2708,6 +3550,15 @@ paths: description: '' schema: $ref: '#/definitions/FlowStageBinding' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows put: @@ -2724,6 +3575,19 @@ paths: description: '' schema: $ref: '#/definitions/FlowStageBinding' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows patch: @@ -2740,6 +3604,19 @@ paths: description: '' schema: $ref: '#/definitions/FlowStageBinding' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows delete: @@ -2749,6 +3626,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows parameters: @@ -2770,10 +3656,18 @@ paths: type: string responses: '200': - description: Challenge that gets sent to the client based on which stage - is currently active + description: '' schema: $ref: '#/definitions/Challenge' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows post: @@ -2793,10 +3687,22 @@ paths: type: string responses: '200': - description: Challenge that gets sent to the client based on which stage - is currently active + description: '' schema: $ref: '#/definitions/Challenge' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows parameters: @@ -2887,6 +3793,10 @@ paths: type: array items: $ref: '#/definitions/Flow' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - flows post: @@ -2903,6 +3813,14 @@ paths: description: '' schema: $ref: '#/definitions/Flow' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - flows parameters: [] @@ -2916,6 +3834,10 @@ paths: description: Successfully cleared cache '400': description: Bad request + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - flows parameters: [] @@ -2966,9 +3888,36 @@ paths: type: integer responses: '200': - description: Generic cache stats for an object + description: '' schema: $ref: '#/definitions/Cache' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + tags: + - flows + parameters: [] + /flows/instances/import_flow/: + post: + operationId: flows_instances_import_flow + description: Import flow from .akflow file + parameters: + - name: file + in: formData + required: true + type: file + responses: + '204': + description: Successfully imported flow + '400': + description: Bad request + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + consumes: + - multipart/form-data tags: - flows parameters: [] @@ -2982,6 +3931,15 @@ paths: description: '' schema: $ref: '#/definitions/Flow' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows put: @@ -2998,6 +3956,19 @@ paths: description: '' schema: $ref: '#/definitions/Flow' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows patch: @@ -3014,6 +3985,19 @@ paths: description: '' schema: $ref: '#/definitions/Flow' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows delete: @@ -3023,6 +4007,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows parameters: @@ -3041,9 +4034,47 @@ paths: parameters: [] responses: '200': - description: response of the flow's /diagram/ action + description: '' schema: $ref: '#/definitions/FlowDiagram' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - flows + parameters: + - name: slug + in: path + description: Visible in the URL. + required: true + type: string + format: slug + pattern: ^[-a-zA-Z0-9_]+$ + /flows/instances/{slug}/execute/: + get: + operationId: flows_instances_execute + description: Execute flow for current user + parameters: [] + responses: + '200': + description: '' + schema: + $ref: '#/definitions/Link' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - flows parameters: @@ -3064,6 +4095,48 @@ paths: description: File Attachment schema: type: file + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - flows + parameters: + - name: slug + in: path + description: Visible in the URL. + required: true + type: string + format: slug + pattern: ^[-a-zA-Z0-9_]+$ + /flows/instances/{slug}/set_background/: + post: + operationId: flows_instances_set_background + description: Set Flow background + parameters: + - name: file + in: formData + required: true + type: file + responses: + '200': + description: Success + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + consumes: + - multipart/form-data tags: - flows parameters: @@ -3147,6 +4220,10 @@ paths: type: array items: $ref: '#/definitions/ExpiringBaseGrantModel' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - oauth2 parameters: [] @@ -3160,6 +4237,15 @@ paths: description: '' schema: $ref: '#/definitions/ExpiringBaseGrantModel' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - oauth2 delete: @@ -3169,6 +4255,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - oauth2 parameters: @@ -3250,6 +4345,10 @@ paths: type: array items: $ref: '#/definitions/ExpiringBaseGrantModel' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - oauth2 parameters: [] @@ -3263,6 +4362,15 @@ paths: description: '' schema: $ref: '#/definitions/ExpiringBaseGrantModel' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - oauth2 delete: @@ -3272,6 +4380,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - oauth2 parameters: @@ -3348,6 +4465,10 @@ paths: type: array items: $ref: '#/definitions/Outpost' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts post: @@ -3364,6 +4485,56 @@ paths: description: '' schema: $ref: '#/definitions/Outpost' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + tags: + - outposts + parameters: [] + /outposts/outposts/default_settings/: + get: + operationId: outposts_outposts_default_settings + description: Global default outpost config + parameters: + - name: providers__isnull + in: query + description: '' + required: false + type: string + - name: ordering + in: query + description: Which field to use when ordering the results. + required: false + type: string + - name: search + in: query + description: A search term. + required: false + type: string + - name: page + in: query + description: Page Index + required: false + type: integer + - name: page_size + in: query + description: Page Size + required: false + type: integer + responses: + '200': + description: '' + schema: + $ref: '#/definitions/OutpostDefaultConfig' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts parameters: [] @@ -3377,6 +4548,15 @@ paths: description: '' schema: $ref: '#/definitions/Outpost' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts put: @@ -3393,6 +4573,19 @@ paths: description: '' schema: $ref: '#/definitions/Outpost' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts patch: @@ -3409,6 +4602,19 @@ paths: description: '' schema: $ref: '#/definitions/Outpost' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts delete: @@ -3418,6 +4624,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -3434,12 +4649,20 @@ paths: parameters: [] responses: '200': - description: Outpost health status + description: '' schema: - description: '' type: array items: $ref: '#/definitions/OutpostHealth' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -3512,6 +4735,10 @@ paths: type: array items: $ref: '#/definitions/ProxyOutpostConfig' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts post: @@ -3528,6 +4755,14 @@ paths: description: '' schema: $ref: '#/definitions/ProxyOutpostConfig' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts parameters: [] @@ -3541,6 +4776,15 @@ paths: description: '' schema: $ref: '#/definitions/ProxyOutpostConfig' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts put: @@ -3557,6 +4801,19 @@ paths: description: '' schema: $ref: '#/definitions/ProxyOutpostConfig' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts patch: @@ -3573,6 +4830,19 @@ paths: description: '' schema: $ref: '#/definitions/ProxyOutpostConfig' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts delete: @@ -3582,6 +4852,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -3658,6 +4937,10 @@ paths: type: array items: $ref: '#/definitions/ServiceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts post: @@ -3674,6 +4957,14 @@ paths: description: '' schema: $ref: '#/definitions/ServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts parameters: [] @@ -3709,12 +5000,15 @@ paths: type: integer responses: '200': - description: Types of an object that can be created + description: '' schema: - description: '' type: array items: $ref: '#/definitions/TypeCreate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts parameters: [] @@ -3728,6 +5022,15 @@ paths: description: '' schema: $ref: '#/definitions/ServiceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts put: @@ -3744,6 +5047,19 @@ paths: description: '' schema: $ref: '#/definitions/ServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts patch: @@ -3760,6 +5076,19 @@ paths: description: '' schema: $ref: '#/definitions/ServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts delete: @@ -3769,6 +5098,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -3785,9 +5123,18 @@ paths: parameters: [] responses: '200': - description: Serializer for Service connection state + description: '' schema: $ref: '#/definitions/ServiceConnectionState' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -3860,6 +5207,10 @@ paths: type: array items: $ref: '#/definitions/DockerServiceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts post: @@ -3876,6 +5227,14 @@ paths: description: '' schema: $ref: '#/definitions/DockerServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts parameters: [] @@ -3889,6 +5248,15 @@ paths: description: '' schema: $ref: '#/definitions/DockerServiceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts put: @@ -3905,6 +5273,19 @@ paths: description: '' schema: $ref: '#/definitions/DockerServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts patch: @@ -3921,6 +5302,19 @@ paths: description: '' schema: $ref: '#/definitions/DockerServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts delete: @@ -3930,6 +5324,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -4002,6 +5405,10 @@ paths: type: array items: $ref: '#/definitions/KubernetesServiceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts post: @@ -4018,6 +5425,14 @@ paths: description: '' schema: $ref: '#/definitions/KubernetesServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - outposts parameters: [] @@ -4031,6 +5446,15 @@ paths: description: '' schema: $ref: '#/definitions/KubernetesServiceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts put: @@ -4047,6 +5471,19 @@ paths: description: '' schema: $ref: '#/definitions/KubernetesServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts patch: @@ -4063,6 +5500,19 @@ paths: description: '' schema: $ref: '#/definitions/KubernetesServiceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts delete: @@ -4072,6 +5522,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - outposts parameters: @@ -4154,6 +5613,10 @@ paths: type: array items: $ref: '#/definitions/Policy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4167,6 +5630,10 @@ paths: description: Successfully cleared cache '400': description: Bad request + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4207,9 +5674,13 @@ paths: type: integer responses: '200': - description: Generic cache stats for an object + description: '' schema: $ref: '#/definitions/Cache' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4250,12 +5721,15 @@ paths: type: integer responses: '200': - description: Types of an object that can be created + description: '' schema: - description: '' type: array items: $ref: '#/definitions/TypeCreate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4269,6 +5743,15 @@ paths: description: '' schema: $ref: '#/definitions/Policy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -4278,6 +5761,52 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - policies + parameters: + - name: policy_uuid + in: path + description: A UUID string identifying this Policy. + required: true + type: string + format: uuid + /policies/all/{policy_uuid}/test/: + post: + operationId: policies_all_test + description: Test policy + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/PolicyTest' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/PolicyTestResult' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -4375,6 +5904,10 @@ paths: type: array items: $ref: '#/definitions/PolicyBinding' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -4391,6 +5924,14 @@ paths: description: '' schema: $ref: '#/definitions/PolicyBinding' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4404,6 +5945,15 @@ paths: description: '' schema: $ref: '#/definitions/PolicyBinding' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -4420,6 +5970,19 @@ paths: description: '' schema: $ref: '#/definitions/PolicyBinding' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -4436,6 +5999,19 @@ paths: description: '' schema: $ref: '#/definitions/PolicyBinding' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -4445,6 +6021,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -4517,6 +6102,10 @@ paths: type: array items: $ref: '#/definitions/DummyPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -4533,6 +6122,14 @@ paths: description: '' schema: $ref: '#/definitions/DummyPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4546,6 +6143,15 @@ paths: description: '' schema: $ref: '#/definitions/DummyPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -4562,6 +6168,19 @@ paths: description: '' schema: $ref: '#/definitions/DummyPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -4578,6 +6197,19 @@ paths: description: '' schema: $ref: '#/definitions/DummyPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -4587,6 +6219,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -4659,6 +6300,10 @@ paths: type: array items: $ref: '#/definitions/EventMatcherPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -4675,6 +6320,14 @@ paths: description: '' schema: $ref: '#/definitions/EventMatcherPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4688,6 +6341,15 @@ paths: description: '' schema: $ref: '#/definitions/EventMatcherPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -4704,6 +6366,19 @@ paths: description: '' schema: $ref: '#/definitions/EventMatcherPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -4720,6 +6395,19 @@ paths: description: '' schema: $ref: '#/definitions/EventMatcherPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -4729,6 +6417,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -4801,6 +6498,10 @@ paths: type: array items: $ref: '#/definitions/ExpressionPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -4817,6 +6518,14 @@ paths: description: '' schema: $ref: '#/definitions/ExpressionPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4830,6 +6539,15 @@ paths: description: '' schema: $ref: '#/definitions/ExpressionPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -4846,6 +6564,19 @@ paths: description: '' schema: $ref: '#/definitions/ExpressionPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -4862,6 +6593,19 @@ paths: description: '' schema: $ref: '#/definitions/ExpressionPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -4871,6 +6615,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -4943,6 +6696,10 @@ paths: type: array items: $ref: '#/definitions/HaveIBeenPwendPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -4959,6 +6716,14 @@ paths: description: '' schema: $ref: '#/definitions/HaveIBeenPwendPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -4972,6 +6737,15 @@ paths: description: '' schema: $ref: '#/definitions/HaveIBeenPwendPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -4988,6 +6762,19 @@ paths: description: '' schema: $ref: '#/definitions/HaveIBeenPwendPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -5004,6 +6791,19 @@ paths: description: '' schema: $ref: '#/definitions/HaveIBeenPwendPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -5013,6 +6813,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -5085,6 +6894,10 @@ paths: type: array items: $ref: '#/definitions/PasswordPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -5101,6 +6914,14 @@ paths: description: '' schema: $ref: '#/definitions/PasswordPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -5114,6 +6935,15 @@ paths: description: '' schema: $ref: '#/definitions/PasswordPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -5130,6 +6960,19 @@ paths: description: '' schema: $ref: '#/definitions/PasswordPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -5146,6 +6989,19 @@ paths: description: '' schema: $ref: '#/definitions/PasswordPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -5155,6 +7011,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -5227,6 +7092,10 @@ paths: type: array items: $ref: '#/definitions/PasswordExpiryPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -5243,6 +7112,14 @@ paths: description: '' schema: $ref: '#/definitions/PasswordExpiryPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -5256,6 +7133,15 @@ paths: description: '' schema: $ref: '#/definitions/PasswordExpiryPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -5272,6 +7158,19 @@ paths: description: '' schema: $ref: '#/definitions/PasswordExpiryPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -5288,6 +7187,19 @@ paths: description: '' schema: $ref: '#/definitions/PasswordExpiryPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -5297,6 +7209,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -5369,6 +7290,10 @@ paths: type: array items: $ref: '#/definitions/ReputationPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -5385,6 +7310,14 @@ paths: description: '' schema: $ref: '#/definitions/ReputationPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -5451,6 +7384,10 @@ paths: type: array items: $ref: '#/definitions/IPReputation' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -5467,6 +7404,14 @@ paths: description: '' schema: $ref: '#/definitions/IPReputation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -5480,6 +7425,15 @@ paths: description: '' schema: $ref: '#/definitions/IPReputation' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -5496,6 +7450,19 @@ paths: description: '' schema: $ref: '#/definitions/IPReputation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -5512,6 +7479,19 @@ paths: description: '' schema: $ref: '#/definitions/IPReputation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -5521,6 +7501,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -5592,6 +7581,10 @@ paths: type: array items: $ref: '#/definitions/UserReputation' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies post: @@ -5608,6 +7601,14 @@ paths: description: '' schema: $ref: '#/definitions/UserReputation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - policies parameters: [] @@ -5621,6 +7622,15 @@ paths: description: '' schema: $ref: '#/definitions/UserReputation' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -5637,6 +7647,19 @@ paths: description: '' schema: $ref: '#/definitions/UserReputation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -5653,6 +7676,19 @@ paths: description: '' schema: $ref: '#/definitions/UserReputation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -5662,6 +7698,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -5680,6 +7725,15 @@ paths: description: '' schema: $ref: '#/definitions/ReputationPolicy' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies put: @@ -5696,6 +7750,19 @@ paths: description: '' schema: $ref: '#/definitions/ReputationPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies patch: @@ -5712,6 +7779,19 @@ paths: description: '' schema: $ref: '#/definitions/ReputationPolicy' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies delete: @@ -5721,6 +7801,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - policies parameters: @@ -5798,6 +7887,10 @@ paths: type: array items: $ref: '#/definitions/PropertyMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings parameters: [] @@ -5833,12 +7926,15 @@ paths: type: integer responses: '200': - description: Types of an object that can be created + description: '' schema: - description: '' type: array items: $ref: '#/definitions/TypeCreate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings parameters: [] @@ -5852,6 +7948,15 @@ paths: description: '' schema: $ref: '#/definitions/PropertyMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings delete: @@ -5861,6 +7966,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings parameters: @@ -5933,6 +8047,10 @@ paths: type: array items: $ref: '#/definitions/LDAPPropertyMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings post: @@ -5949,6 +8067,14 @@ paths: description: '' schema: $ref: '#/definitions/LDAPPropertyMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings parameters: [] @@ -5962,6 +8088,15 @@ paths: description: '' schema: $ref: '#/definitions/LDAPPropertyMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings put: @@ -5978,6 +8113,19 @@ paths: description: '' schema: $ref: '#/definitions/LDAPPropertyMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings patch: @@ -5994,6 +8142,19 @@ paths: description: '' schema: $ref: '#/definitions/LDAPPropertyMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings delete: @@ -6003,6 +8164,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings parameters: @@ -6075,6 +8245,10 @@ paths: type: array items: $ref: '#/definitions/SAMLPropertyMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings post: @@ -6091,6 +8265,14 @@ paths: description: '' schema: $ref: '#/definitions/SAMLPropertyMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings parameters: [] @@ -6104,6 +8286,15 @@ paths: description: '' schema: $ref: '#/definitions/SAMLPropertyMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings put: @@ -6120,6 +8311,19 @@ paths: description: '' schema: $ref: '#/definitions/SAMLPropertyMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings patch: @@ -6136,6 +8340,19 @@ paths: description: '' schema: $ref: '#/definitions/SAMLPropertyMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings delete: @@ -6145,6 +8362,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings parameters: @@ -6217,6 +8443,10 @@ paths: type: array items: $ref: '#/definitions/ScopeMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings post: @@ -6233,6 +8463,14 @@ paths: description: '' schema: $ref: '#/definitions/ScopeMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - propertymappings parameters: [] @@ -6246,6 +8484,15 @@ paths: description: '' schema: $ref: '#/definitions/ScopeMapping' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings put: @@ -6262,6 +8509,19 @@ paths: description: '' schema: $ref: '#/definitions/ScopeMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings patch: @@ -6278,6 +8538,19 @@ paths: description: '' schema: $ref: '#/definitions/ScopeMapping' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings delete: @@ -6287,6 +8560,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - propertymappings parameters: @@ -6364,6 +8646,10 @@ paths: type: array items: $ref: '#/definitions/Provider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers post: @@ -6380,6 +8666,14 @@ paths: description: '' schema: $ref: '#/definitions/Provider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers parameters: [] @@ -6415,12 +8709,15 @@ paths: type: integer responses: '200': - description: Types of an object that can be created + description: '' schema: - description: '' type: array items: $ref: '#/definitions/TypeCreate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers parameters: [] @@ -6434,6 +8731,15 @@ paths: description: '' schema: $ref: '#/definitions/Provider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers put: @@ -6450,6 +8756,19 @@ paths: description: '' schema: $ref: '#/definitions/Provider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers patch: @@ -6466,6 +8785,19 @@ paths: description: '' schema: $ref: '#/definitions/Provider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers delete: @@ -6475,6 +8807,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers parameters: @@ -6546,6 +8887,10 @@ paths: type: array items: $ref: '#/definitions/OAuth2Provider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers post: @@ -6562,6 +8907,14 @@ paths: description: '' schema: $ref: '#/definitions/OAuth2Provider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers parameters: [] @@ -6575,6 +8928,15 @@ paths: description: '' schema: $ref: '#/definitions/OAuth2Provider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers put: @@ -6591,6 +8953,19 @@ paths: description: '' schema: $ref: '#/definitions/OAuth2Provider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers patch: @@ -6607,6 +8982,19 @@ paths: description: '' schema: $ref: '#/definitions/OAuth2Provider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers delete: @@ -6616,6 +9004,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers parameters: @@ -6631,9 +9028,18 @@ paths: parameters: [] responses: '200': - description: OAuth2 Provider Metadata serializer + description: '' schema: $ref: '#/definitions/OAuth2ProviderSetupURLs' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers parameters: @@ -6705,6 +9111,10 @@ paths: type: array items: $ref: '#/definitions/ProxyProvider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers post: @@ -6721,6 +9131,14 @@ paths: description: '' schema: $ref: '#/definitions/ProxyProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers parameters: [] @@ -6734,6 +9152,15 @@ paths: description: '' schema: $ref: '#/definitions/ProxyProvider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers put: @@ -6750,6 +9177,19 @@ paths: description: '' schema: $ref: '#/definitions/ProxyProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers patch: @@ -6766,6 +9206,19 @@ paths: description: '' schema: $ref: '#/definitions/ProxyProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers delete: @@ -6775,6 +9228,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers parameters: @@ -6846,6 +9308,10 @@ paths: type: array items: $ref: '#/definitions/SAMLProvider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers post: @@ -6862,6 +9328,14 @@ paths: description: '' schema: $ref: '#/definitions/SAMLProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - providers parameters: [] @@ -6875,6 +9349,15 @@ paths: description: '' schema: $ref: '#/definitions/SAMLProvider' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers put: @@ -6891,6 +9374,19 @@ paths: description: '' schema: $ref: '#/definitions/SAMLProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers patch: @@ -6907,6 +9403,19 @@ paths: description: '' schema: $ref: '#/definitions/SAMLProvider' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers delete: @@ -6916,6 +9425,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers parameters: @@ -6931,9 +9449,18 @@ paths: parameters: [] responses: '200': - description: SAML Provider Metadata serializer + description: '' schema: $ref: '#/definitions/SAMLMetadata' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - providers parameters: @@ -6949,9 +9476,13 @@ paths: parameters: [] responses: '200': - description: Serialize authentik Config into DRF Object + description: '' schema: $ref: '#/definitions/Config' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - root parameters: [] @@ -7018,6 +9549,10 @@ paths: type: array items: $ref: '#/definitions/Source' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7048,12 +9583,15 @@ paths: type: integer responses: '200': - description: Types of an object that can be created + description: '' schema: - description: '' type: array items: $ref: '#/definitions/TypeCreate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7084,12 +9622,15 @@ paths: type: integer responses: '200': - description: Serializer for User settings for stages and sources + description: '' schema: - description: '' type: array items: $ref: '#/definitions/UserSetting' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7103,6 +9644,15 @@ paths: description: '' schema: $ref: '#/definitions/Source' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources delete: @@ -7112,6 +9662,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7185,6 +9744,10 @@ paths: type: array items: $ref: '#/definitions/LDAPSource' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources post: @@ -7201,6 +9764,14 @@ paths: description: '' schema: $ref: '#/definitions/LDAPSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7214,6 +9785,15 @@ paths: description: '' schema: $ref: '#/definitions/LDAPSource' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources put: @@ -7230,6 +9810,19 @@ paths: description: '' schema: $ref: '#/definitions/LDAPSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources patch: @@ -7246,6 +9839,19 @@ paths: description: '' schema: $ref: '#/definitions/LDAPSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources delete: @@ -7255,6 +9861,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7272,9 +9887,18 @@ paths: parameters: [] responses: '200': - description: LDAP Sync status + description: '' schema: $ref: '#/definitions/LDAPSourceSyncStatus' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7348,6 +9972,10 @@ paths: type: array items: $ref: '#/definitions/OAuthSource' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources post: @@ -7364,6 +9992,14 @@ paths: description: '' schema: $ref: '#/definitions/OAuthSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7377,6 +10013,15 @@ paths: description: '' schema: $ref: '#/definitions/OAuthSource' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources put: @@ -7393,6 +10038,19 @@ paths: description: '' schema: $ref: '#/definitions/OAuthSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources patch: @@ -7409,6 +10067,19 @@ paths: description: '' schema: $ref: '#/definitions/OAuthSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources delete: @@ -7418,6 +10089,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7496,6 +10176,10 @@ paths: type: array items: $ref: '#/definitions/UserOAuthSourceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources post: @@ -7512,6 +10196,14 @@ paths: description: '' schema: $ref: '#/definitions/UserOAuthSourceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7525,6 +10217,15 @@ paths: description: '' schema: $ref: '#/definitions/UserOAuthSourceConnection' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources put: @@ -7541,6 +10242,19 @@ paths: description: '' schema: $ref: '#/definitions/UserOAuthSourceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources patch: @@ -7557,6 +10271,19 @@ paths: description: '' schema: $ref: '#/definitions/UserOAuthSourceConnection' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources delete: @@ -7566,6 +10293,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7637,6 +10373,10 @@ paths: type: array items: $ref: '#/definitions/SAMLSource' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources post: @@ -7653,6 +10393,14 @@ paths: description: '' schema: $ref: '#/definitions/SAMLSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - sources parameters: [] @@ -7666,6 +10414,15 @@ paths: description: '' schema: $ref: '#/definitions/SAMLSource' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources put: @@ -7682,6 +10439,19 @@ paths: description: '' schema: $ref: '#/definitions/SAMLSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources patch: @@ -7698,6 +10468,19 @@ paths: description: '' schema: $ref: '#/definitions/SAMLSource' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources delete: @@ -7707,6 +10490,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7724,9 +10516,18 @@ paths: parameters: [] responses: '200': - description: SAML Provider Metadata serializer + description: '' schema: $ref: '#/definitions/SAMLMetadata' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - sources parameters: @@ -7805,6 +10606,10 @@ paths: type: array items: $ref: '#/definitions/Stage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -7840,12 +10645,15 @@ paths: type: integer responses: '200': - description: Types of an object that can be created + description: '' schema: - description: '' type: array items: $ref: '#/definitions/TypeCreate' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -7881,12 +10689,15 @@ paths: type: integer responses: '200': - description: Serializer for User settings for stages and sources + description: '' schema: - description: '' type: array items: $ref: '#/definitions/UserSetting' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -7900,6 +10711,15 @@ paths: description: '' schema: $ref: '#/definitions/Stage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -7909,6 +10729,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -7981,6 +10810,10 @@ paths: type: array items: $ref: '#/definitions/AuthenticatorStaticStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -7997,6 +10830,14 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorStaticStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8010,6 +10851,15 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorStaticStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8026,6 +10876,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorStaticStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8042,6 +10905,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorStaticStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8051,6 +10927,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8123,6 +11008,10 @@ paths: type: array items: $ref: '#/definitions/AuthenticatorTOTPStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8139,6 +11028,14 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorTOTPStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8152,6 +11049,15 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorTOTPStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8168,6 +11074,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorTOTPStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8184,6 +11103,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorTOTPStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8193,6 +11125,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8265,6 +11206,10 @@ paths: type: array items: $ref: '#/definitions/AuthenticatorValidateStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8281,6 +11226,14 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorValidateStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8294,6 +11247,15 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorValidateStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8310,6 +11272,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorValidateStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8326,6 +11301,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticatorValidateStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8335,6 +11323,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8407,6 +11404,10 @@ paths: type: array items: $ref: '#/definitions/AuthenticateWebAuthnStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8423,6 +11424,14 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticateWebAuthnStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8436,6 +11445,15 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticateWebAuthnStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8452,6 +11470,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticateWebAuthnStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8468,6 +11499,19 @@ paths: description: '' schema: $ref: '#/definitions/AuthenticateWebAuthnStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8477,6 +11521,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8549,6 +11602,10 @@ paths: type: array items: $ref: '#/definitions/CaptchaStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8565,6 +11622,14 @@ paths: description: '' schema: $ref: '#/definitions/CaptchaStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8578,6 +11643,15 @@ paths: description: '' schema: $ref: '#/definitions/CaptchaStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8594,6 +11668,19 @@ paths: description: '' schema: $ref: '#/definitions/CaptchaStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8610,6 +11697,19 @@ paths: description: '' schema: $ref: '#/definitions/CaptchaStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8619,6 +11719,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8691,6 +11800,10 @@ paths: type: array items: $ref: '#/definitions/ConsentStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8707,6 +11820,14 @@ paths: description: '' schema: $ref: '#/definitions/ConsentStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8720,6 +11841,15 @@ paths: description: '' schema: $ref: '#/definitions/ConsentStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8736,6 +11866,19 @@ paths: description: '' schema: $ref: '#/definitions/ConsentStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8752,6 +11895,19 @@ paths: description: '' schema: $ref: '#/definitions/ConsentStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8761,6 +11917,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8833,6 +11998,10 @@ paths: type: array items: $ref: '#/definitions/DenyStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8849,6 +12018,14 @@ paths: description: '' schema: $ref: '#/definitions/DenyStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -8862,6 +12039,15 @@ paths: description: '' schema: $ref: '#/definitions/DenyStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -8878,6 +12064,19 @@ paths: description: '' schema: $ref: '#/definitions/DenyStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -8894,6 +12093,19 @@ paths: description: '' schema: $ref: '#/definitions/DenyStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -8903,6 +12115,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -8975,6 +12196,10 @@ paths: type: array items: $ref: '#/definitions/DummyStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -8991,6 +12216,14 @@ paths: description: '' schema: $ref: '#/definitions/DummyStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9004,6 +12237,15 @@ paths: description: '' schema: $ref: '#/definitions/DummyStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9020,6 +12262,19 @@ paths: description: '' schema: $ref: '#/definitions/DummyStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9036,6 +12291,19 @@ paths: description: '' schema: $ref: '#/definitions/DummyStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9045,6 +12313,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9117,6 +12394,10 @@ paths: type: array items: $ref: '#/definitions/EmailStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -9133,6 +12414,14 @@ paths: description: '' schema: $ref: '#/definitions/EmailStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9146,6 +12435,15 @@ paths: description: '' schema: $ref: '#/definitions/EmailStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9162,6 +12460,19 @@ paths: description: '' schema: $ref: '#/definitions/EmailStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9178,6 +12489,19 @@ paths: description: '' schema: $ref: '#/definitions/EmailStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9187,6 +12511,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9259,6 +12592,10 @@ paths: type: array items: $ref: '#/definitions/IdentificationStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -9275,6 +12612,14 @@ paths: description: '' schema: $ref: '#/definitions/IdentificationStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9288,6 +12633,15 @@ paths: description: '' schema: $ref: '#/definitions/IdentificationStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9304,6 +12658,19 @@ paths: description: '' schema: $ref: '#/definitions/IdentificationStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9320,6 +12687,19 @@ paths: description: '' schema: $ref: '#/definitions/IdentificationStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9329,6 +12709,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9411,6 +12800,10 @@ paths: type: array items: $ref: '#/definitions/Invitation' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -9427,6 +12820,14 @@ paths: description: '' schema: $ref: '#/definitions/Invitation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9440,6 +12841,15 @@ paths: description: '' schema: $ref: '#/definitions/Invitation' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9456,6 +12866,19 @@ paths: description: '' schema: $ref: '#/definitions/Invitation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9472,6 +12895,19 @@ paths: description: '' schema: $ref: '#/definitions/Invitation' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9481,6 +12917,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9553,6 +12998,10 @@ paths: type: array items: $ref: '#/definitions/InvitationStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -9569,6 +13018,14 @@ paths: description: '' schema: $ref: '#/definitions/InvitationStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9582,6 +13039,15 @@ paths: description: '' schema: $ref: '#/definitions/InvitationStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9598,6 +13064,19 @@ paths: description: '' schema: $ref: '#/definitions/InvitationStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9614,6 +13093,19 @@ paths: description: '' schema: $ref: '#/definitions/InvitationStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9623,6 +13115,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9695,6 +13196,10 @@ paths: type: array items: $ref: '#/definitions/PasswordStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -9711,6 +13216,14 @@ paths: description: '' schema: $ref: '#/definitions/PasswordStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9724,6 +13237,15 @@ paths: description: '' schema: $ref: '#/definitions/PasswordStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9740,6 +13262,19 @@ paths: description: '' schema: $ref: '#/definitions/PasswordStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9756,6 +13291,19 @@ paths: description: '' schema: $ref: '#/definitions/PasswordStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9765,6 +13313,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9857,6 +13414,10 @@ paths: type: array items: $ref: '#/definitions/Prompt' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -9873,6 +13434,14 @@ paths: description: '' schema: $ref: '#/definitions/Prompt' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -9886,6 +13455,15 @@ paths: description: '' schema: $ref: '#/definitions/Prompt' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -9902,6 +13480,19 @@ paths: description: '' schema: $ref: '#/definitions/Prompt' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -9918,6 +13509,19 @@ paths: description: '' schema: $ref: '#/definitions/Prompt' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -9927,6 +13531,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -9999,6 +13612,10 @@ paths: type: array items: $ref: '#/definitions/PromptStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -10015,6 +13632,14 @@ paths: description: '' schema: $ref: '#/definitions/PromptStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -10028,6 +13653,15 @@ paths: description: '' schema: $ref: '#/definitions/PromptStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -10044,6 +13678,19 @@ paths: description: '' schema: $ref: '#/definitions/PromptStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -10060,6 +13707,19 @@ paths: description: '' schema: $ref: '#/definitions/PromptStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -10069,6 +13729,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -10141,6 +13810,10 @@ paths: type: array items: $ref: '#/definitions/UserDeleteStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -10157,6 +13830,14 @@ paths: description: '' schema: $ref: '#/definitions/UserDeleteStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -10170,6 +13851,15 @@ paths: description: '' schema: $ref: '#/definitions/UserDeleteStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -10186,6 +13876,19 @@ paths: description: '' schema: $ref: '#/definitions/UserDeleteStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -10202,6 +13905,19 @@ paths: description: '' schema: $ref: '#/definitions/UserDeleteStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -10211,6 +13927,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -10283,6 +14008,10 @@ paths: type: array items: $ref: '#/definitions/UserLoginStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -10299,6 +14028,14 @@ paths: description: '' schema: $ref: '#/definitions/UserLoginStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -10312,6 +14049,15 @@ paths: description: '' schema: $ref: '#/definitions/UserLoginStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -10328,6 +14074,19 @@ paths: description: '' schema: $ref: '#/definitions/UserLoginStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -10344,6 +14103,19 @@ paths: description: '' schema: $ref: '#/definitions/UserLoginStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -10353,6 +14125,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -10425,6 +14206,10 @@ paths: type: array items: $ref: '#/definitions/UserLogoutStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -10441,6 +14226,14 @@ paths: description: '' schema: $ref: '#/definitions/UserLogoutStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -10454,6 +14247,15 @@ paths: description: '' schema: $ref: '#/definitions/UserLogoutStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -10470,6 +14272,19 @@ paths: description: '' schema: $ref: '#/definitions/UserLogoutStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -10486,6 +14301,19 @@ paths: description: '' schema: $ref: '#/definitions/UserLogoutStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -10495,6 +14323,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -10567,6 +14404,10 @@ paths: type: array items: $ref: '#/definitions/UserWriteStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages post: @@ -10583,6 +14424,14 @@ paths: description: '' schema: $ref: '#/definitions/UserWriteStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' tags: - stages parameters: [] @@ -10596,6 +14445,15 @@ paths: description: '' schema: $ref: '#/definitions/UserWriteStage' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages put: @@ -10612,6 +14470,19 @@ paths: description: '' schema: $ref: '#/definitions/UserWriteStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages patch: @@ -10628,6 +14499,19 @@ paths: description: '' schema: $ref: '#/definitions/UserWriteStage' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages delete: @@ -10637,6 +14521,15 @@ paths: responses: '204': description: '' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' tags: - stages parameters: @@ -10647,8 +14540,46 @@ paths: type: string format: uuid definitions: + GenericError: + title: Generic API Error + required: + - detail + type: object + properties: + detail: + description: Error details + type: string + code: + description: Error code + type: string + ValidationError: + title: Validation Error + type: object + properties: + non_field_errors: + description: List of validation errors not related to any field + type: array + items: + type: string + additionalProperties: + description: A list of error messages for each field that triggered a validation + error + type: array + items: + type: string + APIException: + title: Generic API Error + required: + - detail + type: object + properties: + detail: + description: Error details + type: string + code: + description: Error code + type: string Coordinate: - description: Coordinates for diagrams type: object properties: x_cord: @@ -10660,23 +14591,21 @@ definitions: type: integer readOnly: true LoginMetrics: - description: Login Metrics per 1h type: object properties: logins_per_1h: - description: '' + description: Get successful logins per hour for the last 24 hours type: array items: $ref: '#/definitions/Coordinate' readOnly: true logins_failed_per_1h: - description: '' + description: Get failed logins per hour for the last 24 hours type: array items: $ref: '#/definitions/Coordinate' readOnly: true Task: - description: Serialize TaskInfo and TaskResult required: - task_name - task_description @@ -10705,12 +14634,11 @@ definitions: - WARNING - ERROR messages: - description: '' type: array items: type: string + x-nullable: true Version: - description: Get running and latest version. type: object properties: version_current: @@ -10730,7 +14658,6 @@ definitions: type: boolean readOnly: true StaticDevice: - description: Serializer for static authenticator devices required: - name type: object @@ -10742,12 +14669,8 @@ definitions: maxLength: 64 minLength: 1 token_set: - description: '' type: array items: - description: 'A single token belonging to a :class:`StaticDevice`. .. attribute:: - device *ForeignKey*: A foreign key to :class:`StaticDevice`. .. attribute:: - token *CharField*: A random string up to 16 characters.' required: - token type: object @@ -10762,13 +14685,6 @@ definitions: maxLength: 16 minLength: 1 device: - description: "A static :class:`~django_otp.models.Device` simply consists\ - \ of random tokens shared by the database and the user. These are\ - \ frequently used as emergency tokens in case a user's normal device\ - \ is lost or unavailable. They can be consumed in any order; each\ - \ token will be removed from the database as soon as it is used. This\ - \ model has no fields of its own, but serves as a container for :class:`StaticToken`\ - \ objects. .. attribute:: token_set The RelatedManager for our tokens." required: - name - user @@ -10812,7 +14728,6 @@ definitions: type: integer readOnly: true TOTPDevice: - description: Serializer for totp authenticator devices required: - name type: object @@ -10828,7 +14743,6 @@ definitions: type: integer readOnly: true WebAuthnDevice: - description: Serializer for WebAuthn authenticator devices required: - name type: object @@ -10848,8 +14762,6 @@ definitions: format: date-time readOnly: true Provider: - title: Provider - description: Provider Serializer required: - name - application @@ -10899,7 +14811,6 @@ definitions: type: string readOnly: true Application: - description: Application Serializer required: - name - slug @@ -10945,15 +14856,7 @@ definitions: meta_publisher: title: Meta publisher type: string - policies: - type: array - items: - type: string - format: uuid - readOnly: true - uniqueItems: true Group: - description: Group Serializer required: - name - parent @@ -10978,6 +14881,7 @@ definitions: title: Parent type: string format: uuid + x-nullable: true users: type: array items: @@ -10987,8 +14891,6 @@ definitions: title: Attributes type: object User: - title: User - description: User Serializer required: - username - name @@ -11039,7 +14941,6 @@ definitions: title: Attributes type: object Token: - description: Token Serializer required: - identifier - user @@ -11077,7 +14978,6 @@ definitions: title: Expiring type: boolean TokenView: - description: Show token's current key type: object properties: key: @@ -11086,7 +14986,6 @@ definitions: readOnly: true minLength: 1 UserConsent: - description: UserConsent Serializer required: - user - application @@ -11105,9 +15004,6 @@ definitions: application: $ref: '#/definitions/Application' SessionUser: - description: Response for the /user/me endpoint, returns the currently active - user (as `user` property) and, if this user is being impersonated, the original - user in the `original` property. required: - user type: object @@ -11117,29 +15013,27 @@ definitions: original: $ref: '#/definitions/User' UserMetrics: - description: User Metrics type: object properties: logins_per_1h: - description: '' + description: Get successful logins per hour for the last 24 hours type: array items: $ref: '#/definitions/Coordinate' readOnly: true logins_failed_per_1h: - description: '' + description: Get failed logins per hour for the last 24 hours type: array items: $ref: '#/definitions/Coordinate' readOnly: true authorizations_per_1h: - description: '' + description: Get failed logins per hour for the last 24 hours type: array items: $ref: '#/definitions/Coordinate' readOnly: true - UserRecovery: - description: Recovery link for a user to reset their password + Link: required: - link type: object @@ -11149,7 +15043,6 @@ definitions: type: string minLength: 1 CertificateKeyPair: - description: CertificateKeyPair Serializer required: - name - certificate_data @@ -11191,8 +15084,23 @@ definitions: title: Private key available type: boolean readOnly: true + CertificateGeneration: + required: + - common_name + - validity_days + type: object + properties: + common_name: + title: Common name + type: string + minLength: 1 + subject_alt_name: + title: Subject-alt name + type: string + validity_days: + title: Validity days + type: integer CertificateData: - description: Get CertificateKeyPair's data type: object properties: data: @@ -11201,7 +15109,6 @@ definitions: readOnly: true minLength: 1 Event: - description: Event Serializer required: - action - app @@ -11241,7 +15148,6 @@ definitions: type: string format: date-time EventTopPerUser: - description: Response object of Event's top_per_user required: - application - counted_events @@ -11253,6 +15159,7 @@ definitions: type: object additionalProperties: type: string + x-nullable: true counted_events: title: Counted events type: integer @@ -11260,7 +15167,6 @@ definitions: title: Unique users type: integer Notification: - description: Notification Serializer type: object properties: pk: @@ -11287,7 +15193,6 @@ definitions: title: Seen type: boolean NotificationRule: - description: NotificationRule Serializer required: - name type: object @@ -11302,10 +15207,8 @@ definitions: type: string minLength: 1 transports: - description: '' type: array items: - description: Action which is executed when a Rule matches required: - name - mode @@ -11346,7 +15249,6 @@ definitions: - warning - alert group: - description: Custom Group model which supports a basic hierarchy required: - name type: object @@ -11369,7 +15271,6 @@ definitions: title: Attributes type: object parent: - description: Custom Group model which supports a basic hierarchy required: - name - parent @@ -11396,10 +15297,10 @@ definitions: title: Parent type: string format: uuid + x-nullable: true readOnly: true readOnly: true NotificationTransport: - description: NotificationTransport Serializer required: - name - mode @@ -11428,20 +15329,22 @@ definitions: webhook_url: title: Webhook url type: string + send_once: + title: Send once + description: Only send notification once, for example when sending a webhook + into a chat channel. + type: boolean NotificationTransportTest: - description: Notification test serializer required: - messages type: object properties: messages: - description: '' type: array items: type: string minLength: 1 Flow: - description: Flow Serializer required: - name - slug @@ -11513,8 +15416,6 @@ definitions: type: string readOnly: true Stage: - title: Stage obj - description: Stage Serializer required: - name type: object @@ -11541,12 +15442,10 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' FlowStageBinding: - description: FlowStageBinding Serializer required: - target - stage @@ -11594,7 +15493,6 @@ definitions: readOnly: true uniqueItems: true ErrorDetail: - description: Serializer for rest_framework's error messages required: - string - code @@ -11609,8 +15507,6 @@ definitions: type: string minLength: 1 Challenge: - description: Challenge that gets sent to the client based on which stage is currently - active required: - type type: object @@ -11638,16 +15534,13 @@ definitions: title: Response errors type: object additionalProperties: - description: '' type: array items: $ref: '#/definitions/ErrorDetail' ChallengeResponse: - description: Base class for all challenge responses type: object properties: {} Cache: - description: Generic cache stats for an object type: object properties: count: @@ -11655,7 +15548,6 @@ definitions: type: integer readOnly: true FlowDiagram: - description: response of the flow's /diagram/ action type: object properties: diagram: @@ -11664,8 +15556,6 @@ definitions: readOnly: true minLength: 1 OAuth2Provider: - title: Provider - description: OAuth2Provider Serializer required: - name - application @@ -11780,7 +15670,6 @@ definitions: - global - per_provider ExpiringBaseGrantModel: - description: Serializer for BaseGrantModel and ExpiringBaseGrant required: - provider - user @@ -11804,13 +15693,11 @@ definitions: type: string format: date-time scope: - description: '' type: array items: type: string minLength: 1 Outpost: - description: Outpost Serializer required: - name - providers @@ -11832,7 +15719,6 @@ definitions: type: integer uniqueItems: true providers_obj: - description: '' type: array items: $ref: '#/definitions/Provider' @@ -11851,8 +15737,14 @@ definitions: _config: title: config type: object + OutpostDefaultConfig: + type: object + properties: + config: + title: Config + type: object + readOnly: true OutpostHealth: - description: Outpost health status type: object properties: last_seen: @@ -11875,8 +15767,7 @@ definitions: type: boolean readOnly: true OpenIDConnectConfiguration: - title: Oidc configuration - description: rest_framework Serializer for OIDC Configuration + description: Embed OpenID Connect provider information required: - issuer - authorization_endpoint @@ -11920,31 +15811,26 @@ definitions: type: string minLength: 1 response_types_supported: - description: '' type: array items: type: string minLength: 1 id_token_signing_alg_values_supported: - description: '' type: array items: type: string minLength: 1 subject_types_supported: - description: '' type: array items: type: string minLength: 1 token_endpoint_auth_methods_supported: - description: '' type: array items: type: string minLength: 1 ProxyOutpostConfig: - description: ProxyProvider Serializer required: - name - internal_host @@ -12012,7 +15898,6 @@ definitions: Header. If not set, the user's Email address is used. type: string ServiceConnection: - description: ServiceConnection Serializer required: - name type: object @@ -12044,7 +15929,6 @@ definitions: type: string readOnly: true TypeCreate: - description: Types of an object that can be created required: - name - description @@ -12064,7 +15948,6 @@ definitions: type: string minLength: 1 ServiceConnectionState: - description: Serializer for Service connection state type: object properties: healthy: @@ -12077,7 +15960,6 @@ definitions: readOnly: true minLength: 1 DockerServiceConnection: - description: DockerServiceConnection Serializer required: - name - url @@ -12131,7 +16013,6 @@ definitions: format: uuid x-nullable: true KubernetesServiceConnection: - description: KubernetesServiceConnection Serializer required: - name type: object @@ -12168,7 +16049,6 @@ definitions: the currently selected context. type: object Policy: - description: Policy Serializer type: object properties: pk: @@ -12201,8 +16081,32 @@ definitions: title: Bound to type: integer readOnly: true + PolicyTest: + required: + - user + type: object + properties: + user: + title: User + type: integer + context: + title: Context + type: object + PolicyTestResult: + required: + - passing + type: object + properties: + passing: + title: Passing + type: boolean + messages: + type: array + items: + type: string + minLength: 1 + readOnly: true PolicyBinding: - description: PolicyBinding Serializer required: - target - order @@ -12214,8 +16118,6 @@ definitions: format: uuid readOnly: true policy: - description: Policies which specify if a user is authorized to use an Application. - Can be overridden by other types to add other fields, more logic, etc. type: object properties: policy_uuid: @@ -12244,60 +16146,8 @@ definitions: type: boolean readOnly: true group: - description: Custom Group model which supports a basic hierarchy - required: - - name - type: object - properties: - group_uuid: - title: Group uuid - type: string - format: uuid - readOnly: true - name: - title: Name - type: string - maxLength: 80 - minLength: 1 - is_superuser: - title: Is superuser - description: Users added to this group will be superusers. - type: boolean - attributes: - title: Attributes - type: object - parent: - description: Custom Group model which supports a basic hierarchy - required: - - name - - parent - type: object - properties: - group_uuid: - title: Group uuid - type: string - format: uuid - readOnly: true - name: - title: Name - type: string - maxLength: 80 - minLength: 1 - is_superuser: - title: Is superuser - description: Users added to this group will be superusers. - type: boolean - attributes: - title: Attributes - type: object - parent: - title: Parent - type: string - format: uuid - readOnly: true - readOnly: true + $ref: '#/definitions/Group' user: - description: Custom User model to allow easier adding of user-based settings required: - password - username @@ -12367,20 +16217,8 @@ definitions: title: Attributes type: object groups: - description: '' type: array items: - description: Groups are a generic way of categorizing users to apply - permissions, or some other label, to those users. A user can belong - to any number of groups. A user in a group automatically has all the - permissions granted to that group. For example, if the group 'Site - editors' has the permission can_edit_home_page, any user in that group - will have that permission. Beyond permissions, groups are a convenient - way to categorize users to apply some label, or extended functionality, - to them. For example, you could create a group 'Special users', and - you could write code that would do special things to those users -- - such as giving them access to a members-only portion of your site, - or sending them members-only email messages. required: - name type: object @@ -12401,25 +16239,8 @@ definitions: uniqueItems: true readOnly: true user_permissions: - description: '' type: array items: - description: "The permissions system provides a way to assign permissions\ - \ to specific users and groups of users. The permission system is\ - \ used by the Django admin site, but may also be useful in your own\ - \ code. The Django admin site uses permissions as follows: - The \"\ - add\" permission limits the user's ability to view the \"add\" form\ - \ and add an object. - The \"change\" permission limits a user's ability\ - \ to view the change list, view the \"change\" form and change an\ - \ object. - The \"delete\" permission limits the ability to delete\ - \ an object. - The \"view\" permission limits the ability to view\ - \ an object. Permissions are set globally per type of object, not\ - \ per specific object instance. It is possible to say \"Mary may change\ - \ news stories,\" but it's not currently possible to say \"Mary may\ - \ change news stories, but only the ones she created herself\" or\ - \ \"Mary may only change news stories that have a certain status or\ - \ publication date.\" The permissions listed above are automatically\ - \ created for each model." required: - name - codename @@ -12445,11 +16266,8 @@ definitions: type: integer readOnly: true sources: - description: '' type: array items: - description: Base Authentication source, i.e. an OAuth Provider, SAML - Remote or LDAP Server required: - name - slug @@ -12503,10 +16321,8 @@ definitions: uniqueItems: true readOnly: true ak_groups: - description: '' type: array items: - description: Custom Group model which supports a basic hierarchy required: - name - parent @@ -12533,6 +16349,7 @@ definitions: title: Parent type: string format: uuid + x-nullable: true readOnly: true readOnly: true target: @@ -12554,7 +16371,6 @@ definitions: maximum: 2147483647 minimum: -2147483648 DummyPolicy: - description: Dummy Policy Serializer type: object properties: pk: @@ -12601,7 +16417,6 @@ definitions: maximum: 2147483647 minimum: -2147483648 EventMatcherPolicy: - description: Event Matcher Policy Serializer type: object properties: pk: @@ -12716,7 +16531,6 @@ definitions: - authentik.managed - authentik.core ExpressionPolicy: - description: Group Membership Policy Serializer required: - expression type: object @@ -12756,7 +16570,6 @@ definitions: type: string minLength: 1 HaveIBeenPwendPolicy: - description: Have I Been Pwned Policy Serializer type: object properties: pk: @@ -12800,7 +16613,6 @@ definitions: maximum: 2147483647 minimum: -2147483648 PasswordPolicy: - description: Password Policy Serializer required: - error_message type: object @@ -12869,7 +16681,6 @@ definitions: type: string minLength: 1 PasswordExpiryPolicy: - description: Password Expiry Policy Serializer required: - days type: object @@ -12913,7 +16724,6 @@ definitions: title: Deny only type: boolean ReputationPolicy: - description: Reputation Policy Serializer type: object properties: pk: @@ -12958,7 +16768,6 @@ definitions: maximum: 2147483647 minimum: -2147483648 IPReputation: - description: IPReputation Serializer required: - ip type: object @@ -12982,7 +16791,6 @@ definitions: format: date-time readOnly: true UserReputation: - description: UserReputation Serializer required: - user type: object @@ -13005,7 +16813,6 @@ definitions: format: date-time readOnly: true PropertyMapping: - description: PropertyMapping Serializer required: - name - expression @@ -13037,7 +16844,6 @@ definitions: type: string readOnly: true LDAPPropertyMapping: - description: LDAP PropertyMapping Serializer required: - name - expression @@ -13070,7 +16876,6 @@ definitions: type: string readOnly: true SAMLPropertyMapping: - description: SAMLPropertyMapping Serializer required: - name - saml_name @@ -13107,7 +16912,6 @@ definitions: type: string readOnly: true ScopeMapping: - description: ScopeMapping Serializer required: - name - scope_name @@ -13146,7 +16950,6 @@ definitions: type: string readOnly: true OAuth2ProviderSetupURLs: - description: OAuth2 Provider Metadata serializer type: object properties: issuer: @@ -13174,7 +16977,6 @@ definitions: type: string readOnly: true ProxyProvider: - description: ProxyProvider Serializer required: - name - application @@ -13263,7 +17065,6 @@ definitions: Header. If not set, the user's Email address is used. type: string SAMLProvider: - description: SAMLProvider Serializer required: - name - application @@ -13385,15 +17186,13 @@ definitions: format: uuid x-nullable: true SAMLMetadata: - description: SAML Provider Metadata serializer type: object properties: metadata: title: Metadata type: string readOnly: true - Link: - description: Links returned in Config API + FooterLink: type: object properties: href: @@ -13407,7 +17206,6 @@ definitions: readOnly: true minLength: 1 Config: - description: Serialize authentik Config into DRF Object type: object properties: branding_logo: @@ -13421,10 +17219,9 @@ definitions: readOnly: true minLength: 1 ui_footer_links: - description: '' type: array items: - $ref: '#/definitions/Link' + $ref: '#/definitions/FooterLink' readOnly: true error_reporting_enabled: title: Error reporting enabled @@ -13440,7 +17237,6 @@ definitions: type: boolean readOnly: true Source: - description: Source Serializer required: - name - slug @@ -13492,7 +17288,6 @@ definitions: type: string readOnly: true UserSetting: - description: Serializer for User settings for stages and sources required: - object_uid - component @@ -13512,7 +17307,6 @@ definitions: type: string minLength: 1 LDAPSource: - description: LDAP Source Serializer required: - name - slug @@ -13645,7 +17439,6 @@ definitions: format: uuid uniqueItems: true LDAPSourceSyncStatus: - description: LDAP Sync status type: object properties: last_sync: @@ -13654,7 +17447,6 @@ definitions: format: date-time readOnly: true OAuthSource: - description: OAuth Source Serializer required: - name - slug @@ -13753,7 +17545,6 @@ definitions: type: string readOnly: true UserOAuthSourceConnection: - description: OAuth Source Serializer required: - user - source @@ -13776,7 +17567,6 @@ definitions: maxLength: 255 minLength: 1 SAMLSource: - description: SAMLSource Serializer required: - name - slug @@ -13907,7 +17697,6 @@ definitions: type: string minLength: 1 AuthenticatorStaticStage: - description: AuthenticatorStaticStage Serializer required: - name type: object @@ -13934,7 +17723,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -13951,7 +17739,6 @@ definitions: maximum: 2147483647 minimum: -2147483648 AuthenticatorTOTPStage: - description: AuthenticatorTOTPStage Serializer required: - name - digits @@ -13979,7 +17766,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -13997,7 +17783,6 @@ definitions: - 6 - 8 AuthenticatorValidateStage: - description: AuthenticatorValidateStage Serializer required: - name type: object @@ -14024,7 +17809,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14036,7 +17820,7 @@ definitions: - deny - configure device_classes: - description: '' + description: Device classes which can be used to authenticate type: array items: title: Device classes @@ -14051,7 +17835,6 @@ definitions: format: uuid x-nullable: true AuthenticateWebAuthnStage: - description: AuthenticateWebAuthnStage Serializer required: - name type: object @@ -14078,7 +17861,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14090,7 +17872,6 @@ definitions: format: uuid x-nullable: true CaptchaStage: - description: CaptchaStage Serializer required: - name - public_key @@ -14119,7 +17900,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14134,7 +17914,6 @@ definitions: type: string minLength: 1 ConsentStage: - description: ConsentStage Serializer required: - name type: object @@ -14161,7 +17940,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14178,7 +17956,6 @@ definitions: type: string minLength: 1 DenyStage: - description: DenyStage Serializer required: - name type: object @@ -14205,12 +17982,10 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' DummyStage: - description: DummyStage Serializer required: - name type: object @@ -14237,12 +18012,10 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' EmailStage: - description: EmailStage Serializer required: - name type: object @@ -14269,7 +18042,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14327,7 +18099,6 @@ definitions: - email/password_reset.html - email/account_confirmation.html IdentificationStage: - description: IdentificationStage Serializer required: - name - user_fields @@ -14355,12 +18126,12 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' user_fields: - description: '' + description: Fields of the user object to match against. (Hold shift to select + multiple options) type: array items: title: User fields @@ -14393,7 +18164,6 @@ definitions: format: uuid x-nullable: true Invitation: - description: Invitation Serializer type: object properties: pk: @@ -14411,7 +18181,6 @@ definitions: description: Optional fixed data to enforce on user enrollment. type: object created_by: - description: Custom User model to allow easier adding of user-based settings required: - password - username @@ -14481,20 +18250,8 @@ definitions: title: Attributes type: object groups: - description: '' type: array items: - description: Groups are a generic way of categorizing users to apply - permissions, or some other label, to those users. A user can belong - to any number of groups. A user in a group automatically has all the - permissions granted to that group. For example, if the group 'Site - editors' has the permission can_edit_home_page, any user in that group - will have that permission. Beyond permissions, groups are a convenient - way to categorize users to apply some label, or extended functionality, - to them. For example, you could create a group 'Special users', and - you could write code that would do special things to those users -- - such as giving them access to a members-only portion of your site, - or sending them members-only email messages. required: - name type: object @@ -14515,25 +18272,8 @@ definitions: uniqueItems: true readOnly: true user_permissions: - description: '' type: array items: - description: "The permissions system provides a way to assign permissions\ - \ to specific users and groups of users. The permission system is\ - \ used by the Django admin site, but may also be useful in your own\ - \ code. The Django admin site uses permissions as follows: - The \"\ - add\" permission limits the user's ability to view the \"add\" form\ - \ and add an object. - The \"change\" permission limits a user's ability\ - \ to view the change list, view the \"change\" form and change an\ - \ object. - The \"delete\" permission limits the ability to delete\ - \ an object. - The \"view\" permission limits the ability to view\ - \ an object. Permissions are set globally per type of object, not\ - \ per specific object instance. It is possible to say \"Mary may change\ - \ news stories,\" but it's not currently possible to say \"Mary may\ - \ change news stories, but only the ones she created herself\" or\ - \ \"Mary may only change news stories that have a certain status or\ - \ publication date.\" The permissions listed above are automatically\ - \ created for each model." required: - name - codename @@ -14559,11 +18299,8 @@ definitions: type: integer readOnly: true sources: - description: '' type: array items: - description: Base Authentication source, i.e. an OAuth Provider, SAML - Remote or LDAP Server required: - name - slug @@ -14617,10 +18354,8 @@ definitions: uniqueItems: true readOnly: true ak_groups: - description: '' type: array items: - description: Custom Group model which supports a basic hierarchy required: - name - parent @@ -14647,10 +18382,10 @@ definitions: title: Parent type: string format: uuid + x-nullable: true readOnly: true readOnly: true InvitationStage: - description: InvitationStage Serializer required: - name type: object @@ -14677,7 +18412,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14688,7 +18422,6 @@ definitions: no invitation is given. type: boolean PasswordStage: - description: PasswordStage Serializer required: - name - backends @@ -14716,12 +18449,11 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' backends: - description: '' + description: Selection of backends to test the password against. type: array items: title: Backends @@ -14742,7 +18474,6 @@ definitions: maximum: 2147483647 minimum: -2147483648 Prompt: - description: Prompt Serializer required: - field_key - label @@ -14793,12 +18524,10 @@ definitions: maximum: 2147483647 minimum: -2147483648 promptstage_set: - description: '' type: array items: $ref: '#/definitions/Stage' PromptStage: - description: PromptStage Serializer required: - name - fields @@ -14826,7 +18555,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14843,7 +18571,6 @@ definitions: format: uuid uniqueItems: true UserDeleteStage: - description: UserDeleteStage Serializer required: - name type: object @@ -14870,12 +18597,10 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' UserLoginStage: - description: UserLoginStage Serializer required: - name type: object @@ -14902,7 +18627,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' @@ -14913,7 +18637,6 @@ definitions: type: string minLength: 1 UserLogoutStage: - description: UserLogoutStage Serializer required: - name type: object @@ -14940,12 +18663,10 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' UserWriteStage: - description: UserWriteStage Serializer required: - name type: object @@ -14972,7 +18693,6 @@ definitions: type: string readOnly: true flow_set: - description: '' type: array items: $ref: '#/definitions/Flow' diff --git a/tests/e2e/test_flows_enroll.py b/tests/e2e/test_flows_enroll.py index abd4c976d8..2a9bef0634 100644 --- a/tests/e2e/test_flows_enroll.py +++ b/tests/e2e/test_flows_enroll.py @@ -98,7 +98,7 @@ class TestFlowsEnroll(SeleniumTestCase): wait = WebDriverWait(interface_admin, self.wait_timeout) wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar"))) - self.driver.get(self.if_admin_url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) user = User.objects.get(username="foo") self.assertEqual(user.username, "foo") @@ -198,7 +198,7 @@ class TestFlowsEnroll(SeleniumTestCase): ) wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar"))) - self.driver.get(self.if_admin_url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) self.assert_user(User.objects.get(username="foo")) diff --git a/tests/e2e/test_source_oauth.py b/tests/e2e/test_source_oauth.py index 85dbab6ab6..c3646a7f7d 100644 --- a/tests/e2e/test_source_oauth.py +++ b/tests/e2e/test_source_oauth.py @@ -15,6 +15,7 @@ from selenium.webdriver.support.wait import WebDriverWait from structlog.stdlib import get_logger from yaml import safe_dump +from authentik.core.models import User from authentik.flows.models import Flow from authentik.providers.oauth2.generators import ( generate_client_id, @@ -160,19 +161,9 @@ class TestSourceOAuth2(SeleniumTestCase): # Wait until we've logged in self.wait_for_url(self.if_admin_url("/library")) - self.driver.get(self.url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) - self.assertEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_name").get_attribute("value"), - "admin", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_email").get_attribute("value"), - "admin@example.com", - ) + self.assert_user(User(username="foo", name="admin", email="admin@example.com")) @retry() @apply_migration("authentik_core", "0003_default_user") @@ -255,19 +246,9 @@ class TestSourceOAuth2(SeleniumTestCase): # Wait until we've logged in self.wait_for_url(self.if_admin_url("/library")) - self.driver.get(self.url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) - self.assertEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_name").get_attribute("value"), - "admin", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_email").get_attribute("value"), - "admin@example.com", - ) + self.assert_user(User(username="foo", name="admin", email="admin@example.com")) @skipUnless(platform.startswith("linux"), "requires local docker") @@ -359,17 +340,8 @@ class TestSourceOAuth1(SeleniumTestCase): sleep(2) # Wait until we've logged in self.wait_for_url(self.if_admin_url("/library")) - self.driver.get(self.url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) - self.assertEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), - "example-user", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_name").get_attribute("value"), - "test name", - ) - self.assertEqual( - self.driver.find_element(By.ID, "id_email").get_attribute("value"), - "foo@example.com", + self.assert_user( + User(username="example-user", name="test name", email="foo@example.com") ) diff --git a/tests/e2e/test_source_saml.py b/tests/e2e/test_source_saml.py index 92faf70dab..8fbfbfccd6 100644 --- a/tests/e2e/test_source_saml.py +++ b/tests/e2e/test_source_saml.py @@ -5,12 +5,14 @@ from typing import Any, Optional from unittest.case import skipUnless from docker.types import Healthcheck +from guardian.utils import get_anonymous_user from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.wait import WebDriverWait from structlog.stdlib import get_logger +from authentik.core.models import User from authentik.crypto.models import CertificateKeyPair from authentik.flows.models import Flow from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource @@ -153,11 +155,12 @@ class TestSourceSAML(SeleniumTestCase): # Wait until we're logged in self.wait_for_url(self.if_admin_url("/library")) - self.driver.get(self.url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) - # Wait until we've loaded the user info page - self.assertNotEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "" + self.assert_user( + User.objects.exclude(username="akadmin") + .exclude(pk=get_anonymous_user().pk) + .first() ) @retry() @@ -233,11 +236,12 @@ class TestSourceSAML(SeleniumTestCase): # Wait until we're logged in self.wait_for_url(self.if_admin_url("/library")) - self.driver.get(self.url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) - # Wait until we've loaded the user info page - self.assertNotEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "" + self.assert_user( + User.objects.exclude(username="akadmin") + .exclude(pk=get_anonymous_user().pk) + .first() ) @retry() @@ -300,9 +304,10 @@ class TestSourceSAML(SeleniumTestCase): # Wait until we're logged in self.wait_for_url(self.if_admin_url("/library")) - self.driver.get(self.url("authentik_core:user-details")) + self.driver.get(self.if_admin_url("/user")) - # Wait until we've loaded the user info page - self.assertNotEqual( - self.driver.find_element(By.ID, "id_username").get_attribute("value"), "" + self.assert_user( + User.objects.exclude(username="akadmin") + .exclude(pk=get_anonymous_user().pk) + .first() ) diff --git a/web/package-lock.json b/web/package-lock.json index 6fd348d3c6..fcdd0f9133 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -133,6 +133,139 @@ "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz", "integrity": "sha512-Fe0C8UkzSjtacQ+fHXlFB/LHzrv/c2K4z479C6dboOgkGQE1FyB0wt1NBfxij0D++rhOy04OOYdE+Tr0JSlZKw==" }, + "@polymer/font-roboto": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@polymer/font-roboto/-/font-roboto-3.0.2.tgz", + "integrity": "sha512-tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA==" + }, + "@polymer/iron-a11y-announcer": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.1.0.tgz", + "integrity": "sha512-lc5i4NKB8kSQHH0Hwu8WS3ym93m+J69OHJWSSBxwd17FI+h2wmgxDzeG9LI4ojMMck17/uc2pLe7g/UHt5/K/A==", + "requires": { + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-a11y-keys-behavior": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-a11y-keys-behavior/-/iron-a11y-keys-behavior-3.0.1.tgz", + "integrity": "sha512-lnrjKq3ysbBPT/74l0Fj0U9H9C35Tpw2C/tpJ8a+5g8Y3YJs1WSZYnEl1yOkw6sEyaxOq/1DkzH0+60gGu5/PQ==", + "requires": { + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-ajax": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-ajax/-/iron-ajax-3.0.1.tgz", + "integrity": "sha512-7+TPEAfWsRdhj1Y8UeF1759ktpVu+c3sG16rJiUC3wF9+woQ9xI1zUm2d59i7Yc3aDEJrR/Q8Y262KlOvyGVNg==", + "requires": { + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-autogrow-textarea": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@polymer/iron-autogrow-textarea/-/iron-autogrow-textarea-3.0.3.tgz", + "integrity": "sha512-5r0VkWrIlm0JIp5E5wlnvkw7slK72lFRZXncmrsLZF+6n1dg2rI8jt7xpFzSmUWrqpcyXwyKaGaDvUjl3j4JLA==", + "requires": { + "@polymer/iron-behaviors": "^3.0.0-pre.26", + "@polymer/iron-flex-layout": "^3.0.0-pre.26", + "@polymer/iron-validatable-behavior": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-behaviors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-behaviors/-/iron-behaviors-3.0.1.tgz", + "integrity": "sha512-IMEwcv1lhf1HSQxuyWOUIL0lOBwmeaoSTpgCJeP9IBYnuB1SPQngmfRuHKgK6/m9LQ9F9miC7p3HeQQUdKAE0w==", + "requires": { + "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-flex-layout": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz", + "integrity": "sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw==", + "requires": { + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-form": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-form/-/iron-form-3.0.1.tgz", + "integrity": "sha512-JwSQXHjYALsytCeBkXlY8aRwqgZuYIqzOk3iHuugb1RXOdZ7MZHyJhMDVBbscHjxqPKu/KaVzAjrcfwNNafzEA==", + "requires": { + "@polymer/iron-ajax": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-form-element-behavior": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-form-element-behavior/-/iron-form-element-behavior-3.0.1.tgz", + "integrity": "sha512-G/e2KXyL5AY7mMjmomHkGpgS0uAf4ovNpKhkuUTRnMuMJuf589bKqE85KN4ovE1Tzhv2hJoh/igyD6ekHiYU1A==", + "requires": { + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-input/-/iron-input-3.0.1.tgz", + "integrity": "sha512-WLx13kEcbH9GKbj9+pWR6pbJkA5kxn3796ynx6eQd2rueMyUfVTR3GzOvadBKsciUuIuzrxpBWZ2+3UcueVUQQ==", + "requires": { + "@polymer/iron-a11y-announcer": "^3.0.0-pre.26", + "@polymer/iron-validatable-behavior": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-meta": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-meta/-/iron-meta-3.0.1.tgz", + "integrity": "sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA==", + "requires": { + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/iron-validatable-behavior": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/iron-validatable-behavior/-/iron-validatable-behavior-3.0.1.tgz", + "integrity": "sha512-wwpYh6wOa4fNI+jH5EYKC7TVPYQ2OfgQqocWat7GsNWcsblKYhLYbwsvEY5nO0n2xKqNfZzDLrUom5INJN7msQ==", + "requires": { + "@polymer/iron-meta": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/paper-input": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@polymer/paper-input/-/paper-input-3.2.1.tgz", + "integrity": "sha512-6ghgwQKM6mS0hAQxQqj+tkeEY1VUBqAsrasAm8V5RpNcfSWQC/hhRFxU0beGuKTAhndzezDzWYP6Zz4b8fExGg==", + "requires": { + "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26", + "@polymer/iron-autogrow-textarea": "^3.0.0-pre.26", + "@polymer/iron-behaviors": "^3.0.0-pre.26", + "@polymer/iron-form-element-behavior": "^3.0.0-pre.26", + "@polymer/iron-input": "^3.0.0-pre.26", + "@polymer/paper-styles": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/paper-styles": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@polymer/paper-styles/-/paper-styles-3.0.1.tgz", + "integrity": "sha512-y6hmObLqlCx602TQiSBKHqjwkE7xmDiFkoxdYGaNjtv4xcysOTdVJsDR/R9UHwIaxJ7gHlthMSykir1nv78++g==", + "requires": { + "@polymer/font-roboto": "^3.0.1", + "@polymer/iron-flex-layout": "^3.0.0-pre.26", + "@polymer/polymer": "^3.0.0" + } + }, + "@polymer/polymer": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.4.1.tgz", + "integrity": "sha512-KPWnhDZibtqKrUz7enIPOiO4ZQoJNOuLwqrhV2MXzIt3VVnUVJVG5ORz4Z2sgO+UZ+/UZnPD0jqY+jmw/+a9mQ==", + "requires": { + "@webcomponents/shadycss": "^1.9.1" + } + }, "@rollup/plugin-typescript": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.1.tgz", @@ -547,6 +680,11 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@webcomponents/shadycss": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.10.2.tgz", + "integrity": "sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A==" + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -3948,6 +4086,11 @@ "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", diff --git a/web/package.json b/web/package.json index 9ae0e449c5..b12475ac2f 100644 --- a/web/package.json +++ b/web/package.json @@ -12,6 +12,8 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.15.3", "@patternfly/patternfly": "^4.90.5", + "@polymer/iron-form": "^3.0.1", + "@polymer/paper-input": "^3.2.1", "@sentry/browser": "^6.2.3", "@sentry/tracing": "^6.2.3", "@types/chart.js": "^2.9.31", @@ -31,7 +33,8 @@ "rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-external-globals": "^0.6.1", "tslib": "^2.1.0", - "webcomponent-qr-code": "^1.0.5" + "webcomponent-qr-code": "^1.0.5", + "yaml": "^1.10.2" }, "devDependencies": { "@rollup/plugin-typescript": "^8.2.1", diff --git a/web/src/api/Client.ts b/web/src/api/Client.ts index 20a8efd22e..e7b79a27e2 100644 --- a/web/src/api/Client.ts +++ b/web/src/api/Client.ts @@ -1,16 +1,3 @@ -export interface QueryArguments { - page?: number; - page_size?: number; - [key: string]: number | string | boolean | undefined | null; -} - -export interface BaseInheritanceModel { - objectType: string; - - verboseName: string; - verboseNamePlural: string; -} - export interface AKPagination { next?: number; previous?: number; diff --git a/web/src/api/legacy.ts b/web/src/api/legacy.ts index c07b02f2d4..2c1855575c 100644 --- a/web/src/api/legacy.ts +++ b/web/src/api/legacy.ts @@ -1,13 +1,5 @@ export class AdminURLManager { - static applications(rest: string): string { - return `/administration/applications/${rest}`; - } - - static cryptoCertificates(rest: string): string { - return `/administration/crypto/certificates/${rest}`; - } - static policies(rest: string): string { return `/administration/policies/${rest}`; } @@ -24,18 +16,10 @@ export class AdminURLManager { return `/administration/property-mappings/${rest}`; } - static outposts(rest: string): string { - return `/administration/outposts/${rest}`; - } - static outpostServiceConnections(rest: string): string { return `/administration/outpost_service_connections/${rest}`; } - static flows(rest: string): string { - return `/administration/flows/${rest}`; - } - static stages(rest: string): string { return `/administration/stages/${rest}`; } @@ -60,21 +44,6 @@ export class AdminURLManager { return `/administration/tokens/${rest}`; } - static eventRules(rest: string): string { - return `/administration/events/rules/${rest}`; - } - - static eventTransports(rest: string): string { - return `/administration/events/transports/${rest}`; - } - - static users(rest: string): string { - return `/administration/users/${rest}`; - } - - static groups(rest: string): string { - return `/administration/groups/${rest}`; - } } export class UserURLManager { @@ -105,6 +74,10 @@ export class AppURLManager { export class FlowURLManager { + static defaultUnenrollment(): string { + return "/flows/-/default/unenrollment/"; + } + static configure(stageUuid: string, rest: string): string { return `/flows/-/configure/${stageUuid}/${rest}`; } diff --git a/web/src/authentik.css b/web/src/authentik.css index 8d5da9ba47..29559ee42c 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -88,6 +88,7 @@ body { @media (prefers-color-scheme: dark) { :root { + --ak-accent: #fd4b2d; --ak-dark-foreground: #fafafa; --ak-dark-foreground-darker: #bebebe; --ak-dark-foreground-link: #5a5cb9; @@ -100,6 +101,15 @@ body { --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); --pf-global--link--Color: var(--ak-dark-foreground-link); } + + paper-input { + /* --paper-input-container-input-color: var(--ak-dark-foreground); */ + --primary-text-color: var(--ak-dark-foreground); + } + paper-checkbox { + --primary-text-color: var(--ak-dark-foreground); + } + /* Global page background colour */ .pf-c-page { --pf-c-page--BackgroundColor: var(--ak-dark-background); diff --git a/web/src/constants.ts b/web/src/constants.ts index 04f480d9cd..e21c53ad4d 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -9,3 +9,4 @@ export const EVENT_REFRESH = "ak-refresh"; export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle"; export const EVENT_API_DRAWER_REFRESH = "ak-api-drawer-refresh"; +export const TITLE_SUFFIX = "authentik"; diff --git a/web/src/elements/CodeMirror.ts b/web/src/elements/CodeMirror.ts index f7af8524e4..897b730bee 100644 --- a/web/src/elements/CodeMirror.ts +++ b/web/src/elements/CodeMirror.ts @@ -1,4 +1,5 @@ -import { customElement, LitElement, property } from "lit-element"; +import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; import CodeMirror from "codemirror"; import "codemirror/addon/display/autorefresh"; @@ -6,6 +7,9 @@ import "codemirror/mode/xml/xml.js"; import "codemirror/mode/yaml/yaml.js"; import "codemirror/mode/javascript/javascript.js"; import "codemirror/mode/python/python.js"; +import CodeMirrorStyle from "codemirror/lib/codemirror.css"; +import CodeMirrorTheme from "codemirror/theme/monokai.css"; +import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-codemirror") export class CodeMirrorTextarea extends LitElement { @@ -15,14 +19,20 @@ export class CodeMirrorTextarea extends LitElement { @property() mode = "yaml"; + @property() + name?: string; + + @property() + value?: string; + editor?: CodeMirror.EditorFromTextArea; - createRenderRoot() : ShadowRoot | Element { - return this; + static get styles(): CSSResult[] { + return [PFForm, CodeMirrorStyle, CodeMirrorTheme]; } firstUpdated(): void { - const textarea = this.querySelector("textarea"); + const textarea = this.shadowRoot?.querySelector("textarea"); if (!textarea) { return; } @@ -37,4 +47,8 @@ export class CodeMirrorTextarea extends LitElement { this.editor?.save(); }); } + + render(): TemplateResult { + return html``; + } } diff --git a/web/src/elements/Divider.ts b/web/src/elements/Divider.ts new file mode 100644 index 0000000000..d354139f2d --- /dev/null +++ b/web/src/elements/Divider.ts @@ -0,0 +1,37 @@ +import { css, CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import AKGlobal from "../authentik.css"; + +@customElement("ak-divider") +export class Divider extends LitElement { + + static get styles(): CSSResult[] { + return [PFBase, AKGlobal, css` + .separator { + display: flex; + align-items: center; + text-align: center; + } + + .separator::before, + .separator::after { + content: ''; + flex: 1; + border-bottom: 1px solid var(--pf-global--Color--100); + } + + .separator:not(:empty)::before { + margin-right: .25em; + } + + .separator:not(:empty)::after { + margin-left: .25em; + } + `]; + } + + render(): TemplateResult { + return html`
`; + } + +} diff --git a/web/src/elements/buttons/ActionButton.ts b/web/src/elements/buttons/ActionButton.ts index 6e3826f01b..ed5e382631 100644 --- a/web/src/elements/buttons/ActionButton.ts +++ b/web/src/elements/buttons/ActionButton.ts @@ -23,8 +23,7 @@ export class ActionButton extends SpinnerButton { this.setLoading(); this.apiRequest().then(() => { this.setDone(SUCCESS_CLASS); - }) - .catch((e: Error | Response) => { + }).catch((e: Error | Response) => { if (e instanceof Error) { showMessage({ level: MessageLevel.error, diff --git a/web/src/elements/buttons/ModalButton.ts b/web/src/elements/buttons/ModalButton.ts index 34061b4511..c76e10a968 100644 --- a/web/src/elements/buttons/ModalButton.ts +++ b/web/src/elements/buttons/ModalButton.ts @@ -12,8 +12,6 @@ import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; import AKGlobal from "../../authentik.css"; -import CodeMirrorStyle from "codemirror/lib/codemirror.css"; -import CodeMirrorTheme from "codemirror/theme/monokai.css"; import { convertToSlug } from "../../utils"; import { SpinnerButton } from "./SpinnerButton"; @@ -33,7 +31,7 @@ export class ModalButton extends LitElement { modal = ""; static get styles(): CSSResult[] { - return [PFBase, PFButton, PFModalBox, PFForm, PFFormControl, PFBullseye, PFBackdrop, PFPage, PFStack, PFCard, PFContent, AKGlobal, CodeMirrorStyle, CodeMirrorTheme].concat( + return [PFBase, PFButton, PFModalBox, PFForm, PFFormControl, PFBullseye, PFBackdrop, PFPage, PFStack, PFCard, PFContent, AKGlobal].concat( css` :host { text-align: left; @@ -56,11 +54,18 @@ export class ModalButton extends LitElement { super(); window.addEventListener("keyup", (e) => { if (e.code === "Escape") { + this.resetForms(); this.open = false; } }); } + resetForms(): void { + this.querySelectorAll("[slot=form]").forEach(form => { + form.reset(); + }); + } + updateHandlers(): void { // Ensure links close the modal this.shadowRoot?.querySelectorAll("a").forEach((a) => { @@ -136,6 +141,11 @@ export class ModalButton extends LitElement { if (!this.href) { this.updateHandlers(); this.open = true; + this.querySelectorAll("*").forEach(child => { + if ("requestUpdate" in child) { + (child as LitElement).requestUpdate(); + } + }); } else { const request = new Request(this.href); fetch(request, { diff --git a/web/src/elements/forms/Form.ts b/web/src/elements/forms/Form.ts new file mode 100644 index 0000000000..c0fae4c54b --- /dev/null +++ b/web/src/elements/forms/Form.ts @@ -0,0 +1,149 @@ +import "@polymer/paper-input/paper-input"; +import "@polymer/iron-form/iron-form"; +import { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { showMessage } from "../../elements/messages/MessageContainer"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import AKGlobal from "../../authentik.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import { MessageLevel } from "../messages/Message"; +import { IronFormElement } from "@polymer/iron-form/iron-form"; +import { camelToSnake } from "../../utils"; +import { ValidationError } from "authentik-api/src"; + +export class APIError extends Error { + + constructor(public response: ValidationError) { + super(); + } + +} + +@customElement("ak-form") +export class Form extends LitElement { + + @property() + successMessage = ""; + + @property() + send!: (data: T) => Promise; + + static get styles(): CSSResult[] { + return [PFBase, PFCard, PFButton, PFForm, PFFormControl, AKGlobal, css` + select[multiple] { + height: 15em; + } + `]; + } + + getSuccessMessage(): string { + return this.successMessage; + } + + /** + * Reset the inner iron-form + */ + reset(): void { + const ironForm = this.shadowRoot?.querySelector("iron-form"); + if (!ironForm) { + return; + } + ironForm.reset(); + } + + /** + * If this form contains a file input, and the input as been filled, this function returns + * said file. + * @returns File object or undefined + */ + getFormFile(): File | undefined { + const ironForm = this.shadowRoot?.querySelector("iron-form"); + if (!ironForm) { + return; + } + const elements = ironForm._getSubmittableElements(); + for (let i = 0; i < elements.length; i++) { + const element = elements[i] as HTMLInputElement; + if (element.tagName.toLowerCase() === "input" && element.type === "file") { + if ((element.files || []).length < 1) { + continue; + } + // We already checked the length + return (element.files || [])[0]; + } + } + } + + serializeForm(form: IronFormElement): T { + const elements = form._getSubmittableElements(); + const json: { [key: string]: unknown } = {}; + for (let i = 0; i < elements.length; i++) { + const element = elements[i] as HTMLInputElement; + const values = form._serializeElementValues(element); + if (element.tagName.toLowerCase() === "select" && "multiple" in element.attributes) { + json[element.name] = values; + } else { + for (let v = 0; v < values.length; v++) { + form._addSerializedElement(json, element.name, values[v]); + } + } + } + return json as unknown as T; + } + + submit(ev: Event): Promise | undefined { + ev.preventDefault(); + const ironForm = this.shadowRoot?.querySelector("iron-form"); + if (!ironForm) { + console.warn("authentik/forms: failed to find iron-form"); + return; + } + const data = this.serializeForm(ironForm); + return this.send(data).then((r) => { + showMessage({ + level: MessageLevel.success, + message: this.getSuccessMessage() + }); + return r; + }).catch((ex: Response) => { + if (ex.status > 399 && ex.status < 500) { + return ex.json().then((errorMessage: ValidationError) => { + if (!errorMessage) return errorMessage; + if (errorMessage instanceof Error) { + throw errorMessage; + } + const elements: PaperInputElement[] = ironForm._getSubmittableElements(); + elements.forEach((element) => { + const elementName = element.name; + if (!elementName) return; + if (camelToSnake(elementName) in errorMessage) { + element.errorMessage = errorMessage[camelToSnake(elementName)].join(", "); + element.invalid = true; + } + }); + throw new APIError(errorMessage); + }); + } + throw ex; + }); + } + + renderForm(): TemplateResult { + return html``; + } + + render(): TemplateResult { + const rect = this.getBoundingClientRect(); + if (rect.x + rect.y + rect.width + rect.height === 0) { + return html``; + } + return html` { this.submit(ev); }}> + ${this.renderForm()} + `; + } + +} diff --git a/web/src/elements/forms/HorizontalFormElement.ts b/web/src/elements/forms/HorizontalFormElement.ts new file mode 100644 index 0000000000..5e7497e4ca --- /dev/null +++ b/web/src/elements/forms/HorizontalFormElement.ts @@ -0,0 +1,71 @@ +import { customElement, LitElement, CSSResult, property, css } from "lit-element"; +import { TemplateResult, html } from "lit-html"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; + +@customElement("ak-form-element-horizontal") +export class HorizontalFormElement extends LitElement { + + static get styles(): CSSResult[] { + return [PFForm, PFFormControl, css` + .pf-c-form__group { + display: grid; + grid-template-columns: var(--pf-c-form--m-horizontal__group-label--md--GridColumnWidth) var(--pf-c-form--m-horizontal__group-control--md--GridColumnWidth); + } + .pf-c-form__group-label { + padding-top: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop); + } + `]; + } + + @property() + label = ""; + + @property({ type: Boolean }) + required = false; + + @property() + errorMessage = ""; + + @property({ type: Boolean }) + invalid = false; + + @property() + name = ""; + + updated(): void { + this.querySelectorAll("input[autofocus]").forEach(input => { + input.focus(); + }); + this.querySelectorAll("*").forEach((input) => { + switch (input.tagName.toLowerCase()) { + case "input": + case "textarea": + case "select": + case "ak-codemirror": + (input as HTMLInputElement).name = this.name; + break; + default: + break; + } + }); + } + + render(): TemplateResult { + return html`
+
+ +
+
+ +
+ ${this.invalid ? html`

${this.errorMessage}

` : html``} +
+
+
`; + } + +} diff --git a/web/src/elements/forms/ModalForm.ts b/web/src/elements/forms/ModalForm.ts new file mode 100644 index 0000000000..b3d21bb5ec --- /dev/null +++ b/web/src/elements/forms/ModalForm.ts @@ -0,0 +1,68 @@ +import { gettext } from "django"; +import { customElement, html, TemplateResult } from "lit-element"; +import { EVENT_REFRESH } from "../../constants"; +import { ModalButton } from "../buttons/ModalButton"; +import { Form } from "./Form"; + +@customElement("ak-forms-modal") +export class ModalForm extends ModalButton { + + confirm(): void { + this.querySelectorAll>("[slot=form]").forEach(form => { + const formPromise = form.submit(new Event("submit")); + if (!formPromise) { + return; + } + formPromise.then(() => { + this.open = false; + form.reset(); + this.dispatchEvent( + new CustomEvent(EVENT_REFRESH, { + bubbles: true, + composed: true, + }) + ); + }).catch((e) => { + console.log(e); + }); + }); + } + + renderModalInner(): TemplateResult { + return html`
+
+

+ +

+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ { + this.confirm(); + }} + class="pf-m-primary"> + +   + { + this.open = false; + }} + class="pf-m-secondary"> + ${gettext("Cancel")} + +
`; + } + +} diff --git a/web/src/elements/messages/Middleware.ts b/web/src/elements/messages/Middleware.ts index b018619500..320cdc74fe 100644 --- a/web/src/elements/messages/Middleware.ts +++ b/web/src/elements/messages/Middleware.ts @@ -6,7 +6,7 @@ import { showMessage } from "./MessageContainer"; export class MessageMiddleware implements Middleware { post(context: ResponseContext): Promise { - if (!context.response.ok) { + if (context.response.status >= 500) { showMessage({ level: MessageLevel.error, message: gettext("API request failed"), diff --git a/web/src/elements/policies/BoundPoliciesList.ts b/web/src/elements/policies/BoundPoliciesList.ts index b145cf660f..0b35f29534 100644 --- a/web/src/elements/policies/BoundPoliciesList.ts +++ b/web/src/elements/policies/BoundPoliciesList.ts @@ -14,6 +14,9 @@ import { PAGE_SIZE } from "../../constants"; import { DEFAULT_CONFIG } from "../../api/Config"; import { AdminURLManager } from "../../api/legacy"; +import "../../elements/forms/ModalForm"; +import "../../pages/groups/GroupForm"; + @customElement("ak-bound-policies-list") export class BoundPoliciesList extends Table { @property() @@ -59,12 +62,19 @@ export class BoundPoliciesList extends Table {
`; } else if (item.group) { - return html` - + return html` + + ${gettext("Update")} + + + ${gettext("Update Group")} + + + + + `; } else if (item.user) { return html` diff --git a/web/src/elements/router/RouterOutlet.ts b/web/src/elements/router/RouterOutlet.ts index d702b389e5..5451177625 100644 --- a/web/src/elements/router/RouterOutlet.ts +++ b/web/src/elements/router/RouterOutlet.ts @@ -4,11 +4,9 @@ import { ROUTES } from "../../routes"; import { RouteMatch } from "./RouteMatch"; import AKGlobal from "../../authentik.css"; -import "../../pages/generic/SiteShell"; import "./Router404"; import { Page } from "../Page"; - -export const TITLE_SUFFIX = "authentik"; +import { TITLE_SUFFIX } from "../../constants"; @customElement("ak-router-outlet") export class RouterOutlet extends LitElement { diff --git a/web/src/flows/FlowExecutor.ts b/web/src/flows/FlowExecutor.ts index 10297e2b40..ac991d3cdb 100644 --- a/web/src/flows/FlowExecutor.ts +++ b/web/src/flows/FlowExecutor.ts @@ -39,9 +39,9 @@ import { Challenge, ChallengeTypeEnum, Config, FlowsApi, RootApi } from "authent import { DEFAULT_CONFIG } from "../api/Config"; import { ifDefined } from "lit-html/directives/if-defined"; import { until } from "lit-html/directives/until"; -import { TITLE_SUFFIX } from "../elements/router/RouterOutlet"; import { AccessDeniedChallenge } from "./access_denied/FlowAccessDenied"; import { SpinnerSize } from "../elements/Spinner"; +import { TITLE_SUFFIX } from "../constants"; @customElement("ak-flow-executor") export class FlowExecutor extends LitElement implements StageHost { @@ -172,7 +172,7 @@ export class FlowExecutor extends LitElement implements StageHost { if (!this.challenge) { return html``; } - switch (this.challenge.type) { + switch (this.challenge.type.toUpperCase()) { case ChallengeTypeEnum.Redirect: console.debug("authentik/flows: redirecting to url from server", (this.challenge as RedirectChallenge).to); window.location.assign((this.challenge as RedirectChallenge).to); diff --git a/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts b/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts index a1d0b8d8b7..d4fa8ec9e1 100644 --- a/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts +++ b/web/src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts @@ -85,7 +85,7 @@ export class AuthenticatorTOTPStage extends BaseStage { autofocus="" autocomplete="one-time-code" class="pf-c-form-control" - required=""> + required>
diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts index d5be694058..76176c8251 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts @@ -63,7 +63,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage { autocomplete="one-time-code" class="pf-c-form-control" value="${PasswordManagerPrefill.totp || ""}" - required=""> + required>
diff --git a/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts b/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts index 84b10ca901..664086260e 100644 --- a/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts +++ b/web/src/flows/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts @@ -64,9 +64,9 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage { // post the transformed credential data to the server for validation // and storing the public key try { - const formData = new FormData(); - formData.set("response", JSON.stringify(newAssertionForServer)); - await this.host?.submit(formData); + await this.host?.submit({ + response: newAssertionForServer + }); } catch (err) { throw new Error(gettext(`Server validation of credential failed: ${err}`)); } diff --git a/web/src/flows/stages/identification/IdentificationStage.ts b/web/src/flows/stages/identification/IdentificationStage.ts index 816d53bebc..e246890eb7 100644 --- a/web/src/flows/stages/identification/IdentificationStage.ts +++ b/web/src/flows/stages/identification/IdentificationStage.ts @@ -186,7 +186,7 @@ export class IdentificationStage extends BaseStage { autofocus="" autocomplete="username" class="pf-c-form-control" - required=""> + required>
diff --git a/web/src/flows/stages/password/PasswordStage.ts b/web/src/flows/stages/password/PasswordStage.ts index 0cab171e89..7ec21f2150 100644 --- a/web/src/flows/stages/password/PasswordStage.ts +++ b/web/src/flows/stages/password/PasswordStage.ts @@ -63,7 +63,7 @@ export class PasswordStage extends BaseStage { autofocus="" autocomplete="current-password" class="pf-c-form-control" - required="" + required value=${PasswordManagerPrefill.password || ""}> diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index 0418c459e5..16ebea9e52 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -1,8 +1,5 @@ import "construct-style-sheets-polyfill"; -// Elements that are used by SiteShell pages -// And can't dynamically be imported -import "../elements/CodeMirror"; import "../elements/messages/MessageContainer"; import { customElement } from "lit-element"; import { me } from "../api/Users"; @@ -18,15 +15,6 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ ).when((): Promise => { return me().then(u => u.user.isSuperuser||false); }), - new SidebarItem("Events").children( - new SidebarItem("Logs", "/events/log").activeWhen( - `^/events/log/(?${UUID_REGEX})$` - ), - new SidebarItem("Notification Rules", "/events/rules"), - new SidebarItem("Notification Transports", "/events/transports"), - ).when((): Promise => { - return me().then(u => u.user.isSuperuser||false); - }), new SidebarItem("Resources").children( new SidebarItem("Applications", "/core/applications").activeWhen( `^/core/applications/(?${SLUG_REGEX})$` @@ -42,6 +30,15 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ ).when((): Promise => { return me().then(u => u.user.isSuperuser||false); }), + new SidebarItem("Events").children( + new SidebarItem("Logs", "/events/log").activeWhen( + `^/events/log/(?${UUID_REGEX})$` + ), + new SidebarItem("Notification Rules", "/events/rules"), + new SidebarItem("Notification Transports", "/events/transports"), + ).when((): Promise => { + return me().then(u => u.user.isSuperuser || false); + }), new SidebarItem("Customisation").children( new SidebarItem("Policies", "/policy/policies"), new SidebarItem("Property Mappings", "/core/property-mappings"), diff --git a/web/src/pages/applications/ApplicationForm.ts b/web/src/pages/applications/ApplicationForm.ts new file mode 100644 index 0000000000..e194566ea9 --- /dev/null +++ b/web/src/pages/applications/ApplicationForm.ts @@ -0,0 +1,124 @@ +import { CoreApi, Application, ProvidersApi, Provider } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { until } from "lit-html/directives/until"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; +import "../../elements/CodeMirror"; + +@customElement("ak-application-form") +export class ApplicationForm extends Form { + + @property({ attribute: false }) + application?: Application; + + @property({ attribute: false }) + provider?: number; + + getSuccessMessage(): string { + if (this.application) { + return gettext("Successfully updated application."); + } else { + return gettext("Successfully created application."); + } + } + + send = (data: Application): Promise => { + let writeOp: Promise; + if (this.application) { + writeOp = new CoreApi(DEFAULT_CONFIG).coreApplicationsUpdate({ + slug: this.application.slug, + data: data + }); + } else { + writeOp = new CoreApi(DEFAULT_CONFIG).coreApplicationsCreate({ + data: data + }); + } + const icon = this.getFormFile(); + if (icon) { + return writeOp.then(app => { + return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIcon({ + slug: app.slug, + file: icon + }); + }); + } + return writeOp; + }; + + groupProviders(providers: Provider[]): TemplateResult { + const m = new Map(); + providers.forEach(p => { + if (!m.has(p.verboseName || "")) { + m.set(p.verboseName || "", []); + } + const tProviders = m.get(p.verboseName || "") || []; + tProviders.push(p); + }); + return html` + ${Array.from(m).map(([group, providers]) => { + return html` + ${providers.map(p => { + const selected = (this.application?.provider?.pk === p.pk) || (this.provider === p.pk); + return html``; + })} + `; + })} + `; + } + + renderForm(): TemplateResult { + return html`
+ + +

${gettext("Application's display Name.")}

+
+ + +

${gettext("Internal application name, used in URLs.")}

+
+ + + + + +

${gettext("If left empty, authentik will try to extract the launch URL based on the selected provider.")}

+
+ + + + + + + + + +
`; + } + +} diff --git a/web/src/pages/applications/ApplicationListPage.ts b/web/src/pages/applications/ApplicationListPage.ts index 6caa469fbc..5d457b5c23 100644 --- a/web/src/pages/applications/ApplicationListPage.ts +++ b/web/src/pages/applications/ApplicationListPage.ts @@ -1,17 +1,17 @@ import { gettext } from "django"; import { css, CSSResult, customElement, html, property, TemplateResult } from "lit-element"; +import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; import { AKResponse } from "../../api/Client"; import { TablePage } from "../../elements/table/TablePage"; -import "../../elements/buttons/ModalButton"; +import "../../elements/forms/ModalForm"; import "../../elements/forms/DeleteForm"; import "../../elements/buttons/SpinnerButton"; import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; import { Application, CoreApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; -import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; +import "./ApplicationForm"; @customElement("ak-application-list") export class ApplicationListPage extends TablePage { @@ -74,15 +74,22 @@ export class ApplicationListPage extends TablePage { ${item.metaPublisher ? html`${item.metaPublisher}` : html``} `, html`${item.slug}`, - html`${item.provider?.name}`, - html`${item.provider?.verboseName}`, + html`${item.provider?.name || "-"}`, + html`${item.provider?.verboseName || "-"}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update Application")} + + + + + { renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
+ + + ${gettext("Create Application")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/crypto/CertificateGenerateForm.ts b/web/src/pages/crypto/CertificateGenerateForm.ts new file mode 100644 index 0000000000..9de4505fe9 --- /dev/null +++ b/web/src/pages/crypto/CertificateGenerateForm.ts @@ -0,0 +1,46 @@ +import { CertificateGeneration, CryptoApi } from "authentik-api"; +import { CertificateKeyPair } from "authentik-api/src"; +import { gettext } from "django"; +import { customElement } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import "../../elements/forms/HorizontalFormElement"; + +@customElement("ak-crypto-certificate-generate-form") +export class CertificateKeyPairForm extends Form { + + getSuccessMessage(): string { + return gettext("Successfully generated certificate-key pair."); + } + + send = (data: CertificateGeneration): Promise => { + return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsGenerate({ + data: data + }); + }; + + renderForm(): TemplateResult { + return html`
+ + + + + +

${gettext("Optional, comma-separated SubjectAlt Names.")}

+
+ + + +
`; + } + +} diff --git a/web/src/pages/crypto/CertificateKeyPairForm.ts b/web/src/pages/crypto/CertificateKeyPairForm.ts new file mode 100644 index 0000000000..168cc5a041 --- /dev/null +++ b/web/src/pages/crypto/CertificateKeyPairForm.ts @@ -0,0 +1,64 @@ +import { CertificateKeyPair, CryptoApi } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; +import "../../elements/CodeMirror"; +import "../../elements/Divider"; + +@customElement("ak-crypto-certificate-form") +export class CertificateKeyPairForm extends Form { + + @property({attribute: false}) + keyPair?: CertificateKeyPair; + + getSuccessMessage(): string { + if (this.keyPair) { + return gettext("Successfully updated certificate-key pair."); + } else { + return gettext("Successfully created certificate-key pair."); + } + } + + send = (data: CertificateKeyPair): Promise => { + if (this.keyPair) { + return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsPartialUpdate({ + kpUuid: this.keyPair.pk || "", + data: data + }); + } else { + return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsCreate({ + data: data + }); + } + }; + + renderForm(): TemplateResult { + return html`
+ + + + ${this.keyPair ? html`${gettext("Only change the fields below if you want to overwrite their values.")}` : html``} + + +

${gettext("PEM-encoded Certificate data.")}

+
+ + +

${gettext("Optional Private Key. If this is set, you can use this keypair for encryption.")}

+
+
`; + } + +} diff --git a/web/src/pages/crypto/CertificateKeyPairListPage.ts b/web/src/pages/crypto/CertificateKeyPairListPage.ts index 8e0cedbb61..d08ddba345 100644 --- a/web/src/pages/crypto/CertificateKeyPairListPage.ts +++ b/web/src/pages/crypto/CertificateKeyPairListPage.ts @@ -1,16 +1,18 @@ import { gettext } from "django"; -import { customElement, html, property, TemplateResult } from "lit-element"; +import { CSSResult, customElement, html, property, TemplateResult } from "lit-element"; import { AKResponse } from "../../api/Client"; import { TablePage } from "../../elements/table/TablePage"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import { CryptoApi, CertificateKeyPair } from "authentik-api"; -import "../../elements/buttons/ModalButton"; +import "../../elements/forms/ModalForm"; import "../../elements/buttons/SpinnerButton"; import "../../elements/forms/DeleteForm"; +import "./CertificateKeyPairForm"; +import "./CertificateGenerateForm"; import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; -import { AdminURLManager } from "../../api/legacy"; import { DEFAULT_CONFIG } from "../../api/Config"; @customElement("ak-crypto-certificate-list") @@ -33,6 +35,10 @@ export class CertificateKeyPairListPage extends TablePage { @property() order = "name"; + static get styles(): CSSResult[] { + return super.styles.concat(PFDescriptionList); + } + apiEndpoint(page: number): Promise> { return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({ ordering: this.order, @@ -57,12 +63,19 @@ export class CertificateKeyPairListPage extends TablePage { html`${gettext(item.privateKeyAvailable ? "Yes" : "No")}`, html`${item.certExpiry?.toLocaleString()}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update Certificate-Key Pair")} + + + + + { renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
- - + + + ${gettext("Create Certificate-Key Pair")} + + + + + + + ${gettext("Generate")} - -
-
+ + + ${gettext("Generate Certificate-Key Pair")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/events/RuleForm.ts b/web/src/pages/events/RuleForm.ts new file mode 100644 index 0000000000..28f43d12c4 --- /dev/null +++ b/web/src/pages/events/RuleForm.ts @@ -0,0 +1,100 @@ +import { CoreApi, EventsApi, NotificationRule, NotificationRuleSeverityEnum } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; +import { until } from "lit-html/directives/until"; + +@customElement("ak-event-rule-form") +export class RuleForm extends Form { + + @property({attribute: false}) + rule?: NotificationRule; + + getSuccessMessage(): string { + if (this.rule) { + return gettext("Successfully updated rule."); + } else { + return gettext("Successfully created rule."); + } + } + + send = (data: NotificationRule): Promise => { + if (this.rule) { + return new EventsApi(DEFAULT_CONFIG).eventsRulesUpdate({ + pbmUuid: this.rule.pk || "", + data: data + }); + } else { + return new EventsApi(DEFAULT_CONFIG).eventsRulesCreate({ + data: data + }); + } + }; + + renderSeverity(): TemplateResult { + return html` + + + + `; + } + + renderForm(): TemplateResult { + return html`
+ + + + + + + + +

${gettext("Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.")}

+

${gettext("Hold control/command to select multiple items.")}

+
+ + + +
`; + } + +} diff --git a/web/src/pages/events/RuleListPage.ts b/web/src/pages/events/RuleListPage.ts index 427d70e46e..15cb27f4ba 100644 --- a/web/src/pages/events/RuleListPage.ts +++ b/web/src/pages/events/RuleListPage.ts @@ -4,14 +4,14 @@ import { AKResponse } from "../../api/Client"; import { TablePage } from "../../elements/table/TablePage"; import "../../elements/policies/BoundPoliciesList"; -import "../../elements/buttons/ModalButton"; import "../../elements/buttons/SpinnerButton"; +import "../../elements/forms/ModalForm"; import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; import { EventsApi, NotificationRule } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; import "../../elements/forms/DeleteForm"; +import "./RuleForm"; @customElement("ak-event-rule-list") export class RuleListPage extends TablePage { @@ -57,12 +57,19 @@ export class RuleListPage extends TablePage { html`${item.severity}`, html`${item.group?.name || gettext("None (rule disabled)")}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update Notification Rule")} + + + + + { renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
+ + + ${gettext("Create Notification Rule")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/events/TransportForm.ts b/web/src/pages/events/TransportForm.ts new file mode 100644 index 0000000000..4c255dc6d9 --- /dev/null +++ b/web/src/pages/events/TransportForm.ts @@ -0,0 +1,105 @@ +import { EventsApi, NotificationTransport, NotificationTransportModeEnum } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; + +@customElement("ak-event-transport-form") +export class TransportForm extends Form { + + @property({attribute: false}) + transport?: NotificationTransport; + + @property({type: Boolean}) + showWebhook = false; + + getSuccessMessage(): string { + if (this.transport) { + return gettext("Successfully updated transport."); + } else { + return gettext("Successfully created transport."); + } + } + + send = (data: NotificationTransport): Promise => { + if (this.transport) { + return new EventsApi(DEFAULT_CONFIG).eventsTransportsUpdate({ + uuid: this.transport.pk || "", + data: data + }); + } else { + return new EventsApi(DEFAULT_CONFIG).eventsTransportsCreate({ + data: data + }); + } + }; + + renderTransportModes(): TemplateResult { + return html` + + + + `; + } + + firstUpdated(): void { + if (this.transport) { + this.onModeChange(this.transport.mode); + } + } + + onModeChange(mode: string): void { + if (mode === NotificationTransportModeEnum.Webhook || mode === NotificationTransportModeEnum.WebhookSlack) { + this.showWebhook = true; + } else { + this.showWebhook = false; + } + } + + renderForm(): TemplateResult { + return html`
+ + + + + + + + + + +
+ + +
+

${gettext("Only send notification once, for example when sending a webhook into a chat channel.")}

+
+
`; + } + +} diff --git a/web/src/pages/events/TransportListPage.ts b/web/src/pages/events/TransportListPage.ts index 52b7768831..a05afa785e 100644 --- a/web/src/pages/events/TransportListPage.ts +++ b/web/src/pages/events/TransportListPage.ts @@ -4,14 +4,14 @@ import { AKResponse } from "../../api/Client"; import { TablePage } from "../../elements/table/TablePage"; import "../../elements/buttons/ActionButton"; -import "../../elements/buttons/ModalButton"; +import "../../elements/forms/ModalForm"; import "../../elements/buttons/SpinnerButton"; import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; import { EventsApi, NotificationTransport } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; import "../../elements/forms/DeleteForm"; +import "./TransportForm"; @customElement("ak-event-transport-list") export class TransportListPage extends TablePage { @@ -61,12 +61,19 @@ export class TransportListPage extends TablePage { }}> ${gettext("Test")} - - + + + ${gettext("Update")} + + + ${gettext("Update Notification Transport")} + + + + + { renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
+ + + ${gettext("Create Notification Transport")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/flows/FlowForm.ts b/web/src/pages/flows/FlowForm.ts new file mode 100644 index 0000000000..ba32c6af1a --- /dev/null +++ b/web/src/pages/flows/FlowForm.ts @@ -0,0 +1,115 @@ +import { Flow, FlowDesignationEnum, FlowsApi } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; + +@customElement("ak-flow-form") +export class FlowForm extends Form { + + @property({attribute: false}) + flow?: Flow; + + getSuccessMessage(): string { + if (this.flow) { + return gettext("Successfully updated flow."); + } else { + return gettext("Successfully created flow."); + } + } + + send = (data: Flow): Promise => { + let writeOp: Promise; + if (this.flow) { + writeOp = new FlowsApi(DEFAULT_CONFIG).flowsInstancesUpdate({ + slug: this.flow.slug, + data: data + }); + } else { + writeOp = new FlowsApi(DEFAULT_CONFIG).flowsInstancesCreate({ + data: data + }); + } + const background = this.getFormFile(); + if (background) { + return writeOp.then(flow => { + return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackground({ + slug: flow.slug, + file: background + }); + }); + } + return writeOp; + }; + + renderDesignations(): TemplateResult { + return html` + + + + + + + + `; + } + + renderForm(): TemplateResult { + return html`
+ + + + + +

${gettext("Shown as the Title in Flow pages.")}

+
+ + +

${gettext("Visible in the URL.")}

+
+ + +

${gettext("Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.")}

+
+ + +

${gettext("Background shown during execution.")}

+
+
`; + } + +} diff --git a/web/src/pages/flows/FlowImportForm.ts b/web/src/pages/flows/FlowImportForm.ts new file mode 100644 index 0000000000..48161ef8a7 --- /dev/null +++ b/web/src/pages/flows/FlowImportForm.ts @@ -0,0 +1,38 @@ +import { Flow, FlowsApi } from "authentik-api"; +import { gettext } from "django"; +import { customElement } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import "../../elements/forms/HorizontalFormElement"; + +@customElement("ak-flow-import-form") +export class FlowImportForm extends Form { + + getSuccessMessage(): string { + return gettext("Successfully imported flow."); + } + + // eslint-disable-next-line + send = (data: Flow): Promise => { + const file = this.getFormFile(); + if (!file) { + throw new Error("No form data"); + } + return new FlowsApi(DEFAULT_CONFIG).flowsInstancesImportFlow({ + file: file + }); + }; + + renderForm(): TemplateResult { + return html`
+ + +

${gettext("Background shown during execution.")}

+
+
`; + } + +} diff --git a/web/src/pages/flows/FlowListPage.ts b/web/src/pages/flows/FlowListPage.ts index 1dc88d311f..fdb6e45d50 100644 --- a/web/src/pages/flows/FlowListPage.ts +++ b/web/src/pages/flows/FlowListPage.ts @@ -6,11 +6,13 @@ import { TablePage } from "../../elements/table/TablePage"; import "../../elements/buttons/ModalButton"; import "../../elements/buttons/SpinnerButton"; import "../../elements/forms/DeleteForm"; +import "../../elements/forms/ModalForm"; +import "./FlowForm"; +import "./FlowImportForm"; import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; import { Flow, FlowsApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; @customElement("ak-flow-list") export class FlowListPage extends TablePage { @@ -60,48 +62,76 @@ export class FlowListPage extends TablePage { html`${Array.from(item.stages || []).length}`, html`${Array.from(item.policies || []).length}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update Flow")} + + + + + { return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDelete({ - slug: item.slug || "" + slug: item.slug }); }}> - + + ${gettext("Export")} - - `, + `, ]; } renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
- - + + + ${gettext("Create Flow")} + + + + + + + ${gettext("Import")} - -
-
+ + + ${gettext("Import Flow")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/flows/FlowViewPage.ts b/web/src/pages/flows/FlowViewPage.ts index 329519659b..d67f388f1c 100644 --- a/web/src/pages/flows/FlowViewPage.ts +++ b/web/src/pages/flows/FlowViewPage.ts @@ -17,7 +17,6 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css"; import AKGlobal from "../../authentik.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css"; -import { AdminURLManager } from "../../api/legacy"; @customElement("ak-flow-view") export class FlowViewPage extends LitElement { @@ -80,9 +79,17 @@ export class FlowViewPage extends LitElement {
diff --git a/web/src/pages/generic/SiteShell.ts b/web/src/pages/generic/SiteShell.ts deleted file mode 100644 index 1ec767f7e4..0000000000 --- a/web/src/pages/generic/SiteShell.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; -import { SpinnerSize } from "../../elements/Spinner"; -import { showMessage } from "../../elements/messages/MessageContainer"; -import { gettext } from "django"; -import { SentryIgnoredError } from "../../common/errors"; -import { unsafeHTML } from "lit-html/directives/unsafe-html"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFModalBox from "@patternfly/patternfly/components/ModalBox/modal-box.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; -import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; -import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css"; -import PFPage from "@patternfly/patternfly/components/Page/page.css"; -import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; -import PFCard from "@patternfly/patternfly/components/Card/card.css"; -import PFContent from "@patternfly/patternfly/components/Content/content.css"; -import AKGlobal from "../../authentik.css"; -import CodeMirrorStyle from "codemirror/lib/codemirror.css"; -import CodeMirrorTheme from "codemirror/theme/monokai.css"; -import { EVENT_REFRESH } from "../../constants"; -import { MessageLevel } from "../../elements/messages/Message"; - -@customElement("ak-site-shell") -export class SiteShell extends LitElement { - @property() - set url(value: string) { - this._url = value; - this.loadContent(); - } - - _url?: string; - - @property({type: Boolean}) - loading = false; - - @property({type: String}) - body = ""; - - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFModalBox, PFForm, PFFormControl, PFBullseye, PFBackdrop, PFPage, PFStack, PFCard, PFContent, AKGlobal, CodeMirrorStyle, CodeMirrorTheme].concat( - css` - :host, - ::slotted(*) { - height: 100%; - } - .pf-l-bullseye { - position: absolute; - top: 0; - left: 0; - width: 100%; - } - ` - ); - } - - constructor() { - super(); - this.addEventListener(EVENT_REFRESH, () => { - this.loadContent(); - }); - } - - loadContent(): void { - const bodySlot = this.querySelector("[slot=body]"); - if (!bodySlot) { - return; - } - if (!this._url) { - return; - } - if (this.loading) { - return; - } - this.loading = true; - fetch(this._url) - .then((response) => { - if (response.ok) { - return response; - } - console.debug(`authentik/site-shell: Request failed ${this._url}`); - showMessage({ - level: MessageLevel.error, - message: gettext(`Request failed: ${response.statusText}`), - }); - this.loading = false; - throw new SentryIgnoredError("Request failed"); - }) - .then((response) => response.text()) - .then((text) => { - this.body = text; - }) - .then(() => { - setTimeout(() => { - this.loading = false; - }, 100); - }); - } - - updateHandlers(): void { - // Ensure anchors only change the hash - this.shadowRoot?.querySelectorAll("a:not(.ak-root-link)").forEach((a) => { - if (a.href === "") { - return; - } - if (a.href.startsWith("#")) { - return; - } - try { - const url = new URL(a.href); - const qs = url.search || ""; - const hash = (url.hash || "#").substring(2, Infinity); - a.href = `#${url.pathname}${qs}${hash}`; - } catch (e) { - console.debug(`authentik/site-shell: error ${e}`); - a.href = `#${a.href}`; - } - }); - // Create refresh buttons - this.shadowRoot?.querySelectorAll("[role=ak-refresh]").forEach((rt) => { - rt.addEventListener("click", () => { - this.loadContent(); - }); - }); - // Make get forms (search bar) notify us on submit so we can change the hash - this.shadowRoot?.querySelectorAll("form[method=get]").forEach((form) => { - form.addEventListener("submit", (e) => { - e.preventDefault(); - const formData = new FormData(form); - const qs = new URLSearchParams((formData)).toString(); // eslint-disable-line - window.location.hash = `#${this._url}?${qs}`; - }); - }); - // Make forms with POST Method have a correct action set - this.shadowRoot?.querySelectorAll("form[method=post]").forEach((form) => { - form.addEventListener("submit", (e) => { - e.preventDefault(); - const formData = new FormData(form); - fetch(this._url ? this._url : form.action, { - method: form.method, - body: formData, - }) - .then((response) => { - return response.text(); - }) - .then((data) => { - this.body = data; - this.updateHandlers(); - }) - .catch((e) => { - showMessage({ - level: MessageLevel.error, - message: "Unexpected error" - }); - console.error(e); - }); - }); - }); - } - - render(): TemplateResult { - return html` ${this.loading ? - html`
-
- -
-
` - : ""} - ${unsafeHTML(this.body)}`; - } - - updated(): void { - this.updateHandlers(); - } -} diff --git a/web/src/pages/groups/GroupForm.ts b/web/src/pages/groups/GroupForm.ts new file mode 100644 index 0000000000..03ee52647a --- /dev/null +++ b/web/src/pages/groups/GroupForm.ts @@ -0,0 +1,95 @@ +import { CoreApi, Group } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { until } from "lit-html/directives/until"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; +import "../../elements/CodeMirror"; +import YAML from "yaml"; + +@customElement("ak-group-form") +export class GroupForm extends Form { + + @property({attribute: false}) + group?: Group; + + getSuccessMessage(): string { + if (this.group) { + return gettext("Successfully updated group."); + } else { + return gettext("Successfully created group."); + } + } + + send = (data: Group): Promise => { + if (this.group) { + return new CoreApi(DEFAULT_CONFIG).coreGroupsUpdate({ + groupUuid: this.group.pk || "", + data: data + }); + } else { + return new CoreApi(DEFAULT_CONFIG).coreGroupsCreate({ + data: data + }); + } + }; + + renderForm(): TemplateResult { + return html`
+ + + + +
+ + +
+

${gettext("Users added to this group will be superusers.")}

+
+ + + + + +

${gettext("Hold control/command to select multiple items.")}

+
+ + + + +
`; + } + +} diff --git a/web/src/pages/groups/GroupListPage.ts b/web/src/pages/groups/GroupListPage.ts index fd4bf00ae8..294750a693 100644 --- a/web/src/pages/groups/GroupListPage.ts +++ b/web/src/pages/groups/GroupListPage.ts @@ -10,7 +10,8 @@ import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; import { CoreApi, Group } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; +import "../../elements/forms/ModalForm"; +import "./GroupForm"; @customElement("ak-group-list") export class GroupListPage extends TablePage { @@ -53,15 +54,22 @@ export class GroupListPage extends TablePage { return [ html`${item.name}`, html`${item.parent || "-"}`, - html`${item.users.keys.length}`, + html`${item.users?.keys.length}`, html`${item.isSuperuser ? "Yes" : "No"}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update Group")} + + + + + { renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
+ + + ${gettext("Create Group")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/outposts/OutpostForm.ts b/web/src/pages/outposts/OutpostForm.ts new file mode 100644 index 0000000000..3f9de2e2b3 --- /dev/null +++ b/web/src/pages/outposts/OutpostForm.ts @@ -0,0 +1,102 @@ +import { Outpost, OutpostsApi, ProvidersApi } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { until } from "lit-html/directives/until"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; +import "../../elements/CodeMirror"; +import YAML from "yaml"; + +@customElement("ak-outpost-form") +export class OutpostForm extends Form { + + @property({attribute: false}) + outpost?: Outpost; + + getSuccessMessage(): string { + if (this.outpost) { + return gettext("Successfully updated outpost."); + } else { + return gettext("Successfully created outpost."); + } + } + + send = (data: Outpost): Promise => { + if (this.outpost) { + return new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsUpdate({ + uuid: this.outpost.pk || "", + data: data + }); + } else { + return new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsCreate({ + data: data + }); + } + }; + + renderForm(): TemplateResult { + return html`
+ + + + + + + + + + + +

${gettext("Hold control/command to select multiple items.")}

+
+ ${until(new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsDefaultSettings({}).then(config => { + let fc = config.config; + if (this.outpost) { + fc = this.outpost.config; + } + return html` + + `; + }))} +
`; + } + +} diff --git a/web/src/pages/outposts/OutpostListPage.ts b/web/src/pages/outposts/OutpostListPage.ts index 8b87d7e4ea..c39018c1cc 100644 --- a/web/src/pages/outposts/OutpostListPage.ts +++ b/web/src/pages/outposts/OutpostListPage.ts @@ -6,14 +6,13 @@ import { TableColumn } from "../../elements/table/Table"; import { TablePage } from "../../elements/table/TablePage"; import "./OutpostHealth"; +import "./OutpostForm"; import "../../elements/buttons/SpinnerButton"; -import "../../elements/buttons/ModalButton"; import "../../elements/buttons/TokenCopyButton"; import "../../elements/forms/DeleteForm"; import { PAGE_SIZE } from "../../constants"; import { Outpost, OutpostsApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-outpost-list") @@ -58,12 +57,19 @@ export class OutpostListPage extends TablePage { })}`, html``, html` - - + + + ${gettext("Update")} + + + ${gettext("Update Outpost")} + + + + + { renderToolbar(): TemplateResult { return html` - - + + ${gettext("Create")} - -
-
+ + + ${gettext("Create Outpost")} + + + + + ${super.renderToolbar()} `; } diff --git a/web/src/pages/providers/RelatedApplicationButton.ts b/web/src/pages/providers/RelatedApplicationButton.ts index 77f1dd1d71..17733380dc 100644 --- a/web/src/pages/providers/RelatedApplicationButton.ts +++ b/web/src/pages/providers/RelatedApplicationButton.ts @@ -1,14 +1,21 @@ import { gettext } from "django"; -import { customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { Provider } from "authentik-api"; -import { AdminURLManager } from "../../api/legacy"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; import "../../elements/buttons/ModalButton"; import "../../elements/Spinner"; +import "../../elements/forms/ModalForm"; +import "../../pages/applications/ApplicationForm"; @customElement("ak-provider-related-application") export class RelatedApplicationButton extends LitElement { + static get styles(): CSSResult[] { + return [PFBase, PFButton]; + } + @property({attribute: false}) provider?: Provider; @@ -18,12 +25,19 @@ export class RelatedApplicationButton extends LitElement { ${this.provider.assignedApplicationName} `; } - return html` - - ${gettext("Create")} - -
-
`; + return html` + + ${gettext("Create")} + + + ${gettext("Create Application")} + + + + + `; } } diff --git a/web/src/pages/providers/SAMLProviderViewPage.ts b/web/src/pages/providers/SAMLProviderViewPage.ts index 13d70f9609..e6c9927924 100644 --- a/web/src/pages/providers/SAMLProviderViewPage.ts +++ b/web/src/pages/providers/SAMLProviderViewPage.ts @@ -12,8 +12,6 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import AKGlobal from "../../authentik.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import CodeMirrorStyle from "codemirror/lib/codemirror.css"; -import CodeMirrorTheme from "codemirror/theme/monokai.css"; import "../../elements/buttons/ModalButton"; import "../../elements/buttons/SpinnerButton"; @@ -26,6 +24,7 @@ import { ProvidersApi, SAMLProvider } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; import { AdminURLManager, AppURLManager } from "../../api/legacy"; import { EVENT_REFRESH } from "../../constants"; +import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-provider-saml-view") export class SAMLProviderViewPage extends Page { @@ -55,7 +54,7 @@ export class SAMLProviderViewPage extends Page { provider?: SAMLProvider; static get styles(): CSSResult[] { - return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing,CodeMirrorStyle, CodeMirrorTheme, AKGlobal]; + return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; } constructor() { @@ -153,7 +152,7 @@ export class SAMLProviderViewPage extends Page { new ProvidersApi(DEFAULT_CONFIG).providersSamlMetadata({ id: this.provider.pk || 0, }).then(m => { - return html``; + return html``; }) )}
diff --git a/web/src/pages/sources/SAMLSourceViewPage.ts b/web/src/pages/sources/SAMLSourceViewPage.ts index 7e4ca6dfa1..d3e87052c3 100644 --- a/web/src/pages/sources/SAMLSourceViewPage.ts +++ b/web/src/pages/sources/SAMLSourceViewPage.ts @@ -12,8 +12,6 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import AKGlobal from "../../authentik.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import CodeMirrorStyle from "codemirror/lib/codemirror.css"; -import CodeMirrorTheme from "codemirror/theme/monokai.css"; import "../../elements/buttons/ModalButton"; import "../../elements/buttons/SpinnerButton"; @@ -25,6 +23,7 @@ import { SAMLSource, SourcesApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; import { AdminURLManager, AppURLManager } from "../../api/legacy"; import { EVENT_REFRESH } from "../../constants"; +import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-source-saml-view") export class SAMLSourceViewPage extends Page { @@ -51,7 +50,7 @@ export class SAMLSourceViewPage extends Page { source?: SAMLSource; static get styles(): CSSResult[] { - return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, CodeMirrorStyle, CodeMirrorTheme, AKGlobal]; + return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; } constructor() { @@ -138,7 +137,7 @@ export class SAMLSourceViewPage extends Page { ${until(new SourcesApi(DEFAULT_CONFIG).sourcesSamlMetadata({ slug: this.source.slug, }).then(m => { - return html``; + return html``; }) )}
diff --git a/web/src/pages/users/UserDetailsPage.ts b/web/src/pages/users/UserDetailsPage.ts new file mode 100644 index 0000000000..6547dbccd7 --- /dev/null +++ b/web/src/pages/users/UserDetailsPage.ts @@ -0,0 +1,97 @@ +import { gettext } from "django"; +import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import AKGlobal from "../../authentik.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import { CoreApi, User } from "authentik-api"; +import { me } from "../../api/Users"; +import "../../elements/forms/FormElement"; +import "../../elements/EmptyState"; +import { FlowURLManager } from "../../api/legacy"; +import "@polymer/paper-input/paper-input"; +import "@polymer/iron-form/iron-form"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import "../../elements/forms/Form"; + +@customElement("ak-user-details") +export class UserDetailsPage extends LitElement { + + static get styles(): CSSResult[] { + return [PFBase, PFCard, PFForm, PFFormControl, PFButton, AKGlobal]; + } + + @property({attribute: false}) + user?: User; + + firstUpdated(): void { + me().then((user) => { + this.user = user.user; + }); + } + + render(): TemplateResult { + if (!this.user) { + return html` + `; + } + return html`
+
+ ${gettext("Update details")} +
+
+ { + return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({ + id: this.user?.pk || 0, + data: data as User + }); + }}> +
+ + +

${gettext("Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.")}

+ + +

${gettext("User's display name.")}

+ + + +
+
+
+ + + ${gettext("Delete account")} + +
+
+
+
+
+
+
`; + } + +} diff --git a/web/src/pages/users/UserForm.ts b/web/src/pages/users/UserForm.ts new file mode 100644 index 0000000000..47929ab7e1 --- /dev/null +++ b/web/src/pages/users/UserForm.ts @@ -0,0 +1,80 @@ +import { CoreApi, User } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../api/Config"; +import { Form } from "../../elements/forms/Form"; +import { ifDefined } from "lit-html/directives/if-defined"; +import "../../elements/forms/HorizontalFormElement"; +import "../../elements/CodeMirror"; +import YAML from "yaml"; + +@customElement("ak-user-form") +export class UserForm extends Form { + + @property({ attribute: false }) + user?: User; + + getSuccessMessage(): string { + if (this.user) { + return gettext("Successfully updated user."); + } else { + return gettext("Successfully created user."); + } + } + + send = (data: User): Promise => { + if (this.user) { + return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({ + id: this.user.pk || 0, + data: data + }); + } else { + return new CoreApi(DEFAULT_CONFIG).coreUsersCreate({ + data: data + }); + } + }; + + renderForm(): TemplateResult { + return html`
+ + +

${gettext("Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.")}

+
+ + +

${gettext("User's display name.")}

+
+ + + + +
+ + +
+

${gettext("Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")}

+
+ + + + +
`; + } + +} diff --git a/web/src/pages/users/UserListPage.ts b/web/src/pages/users/UserListPage.ts index cdc0b43605..b0fd534866 100644 --- a/web/src/pages/users/UserListPage.ts +++ b/web/src/pages/users/UserListPage.ts @@ -3,16 +3,18 @@ import { customElement, html, property, TemplateResult } from "lit-element"; import { AKResponse } from "../../api/Client"; import { TablePage } from "../../elements/table/TablePage"; -import "../../elements/buttons/ModalButton"; +import "../../elements/forms/ModalForm"; import "../../elements/buttons/Dropdown"; import "../../elements/buttons/ActionButton"; import { TableColumn } from "../../elements/table/Table"; import { PAGE_SIZE } from "../../constants"; import { CoreApi, User } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; import "../../elements/forms/DeleteForm"; import "./UserActiveForm"; +import "./UserForm"; +import { showMessage } from "../../elements/messages/MessageContainer"; +import { MessageLevel } from "../../elements/messages/Message"; @customElement("ak-user-list") export class UserListPage extends TablePage { @@ -59,12 +61,19 @@ export class UserListPage extends TablePage { html`${item.isActive ? "Yes" : "No"}`, html`${item.lastLogin?.toLocaleString()}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update User")} + + + + + + ${super.renderToolbar()} `; } + } diff --git a/web/src/pages/users/UserSettingsPage.ts b/web/src/pages/users/UserSettingsPage.ts index d6590324ee..d2fb255b06 100644 --- a/web/src/pages/users/UserSettingsPage.ts +++ b/web/src/pages/users/UserSettingsPage.ts @@ -18,11 +18,11 @@ import { DEFAULT_CONFIG } from "../../api/Config"; import { until } from "lit-html/directives/until"; import { ifDefined } from "lit-html/directives/if-defined"; import "../../elements/Tabs"; -import "../tokens/UserTokenList"; -import "../generic/SiteShell"; +import "./UserDetailsPage"; +import "./UserTokenList"; import "./settings/UserSettingsAuthenticatorTOTP"; import "./settings/UserSettingsAuthenticatorStatic"; -import "./settings/UserSettingsAuthenticatorWebAuthnDevices"; +import "./settings/UserSettingsAuthenticatorWebAuthn"; import "./settings/UserSettingsPassword"; import "./settings/SourceSettingsOAuth"; @@ -48,7 +48,7 @@ export class UserSettingsPage extends LitElement { return html` `; default: - return html`

unsupported component ${stage.component}

`; + return html`

${gettext(`Error: unsupported stage settings: ${stage.component}`)}

`; } } @@ -58,7 +58,7 @@ export class UserSettingsPage extends LitElement { return html` `; default: - return html`

unsupported component ${source.component}

`; + return html`

${gettext(`Error: unsupported source settings: ${source.component}`)}

`; } } @@ -76,16 +76,10 @@ export class UserSettingsPage extends LitElement {
-
-
- -
-
-
-
+
- +
${until(new StagesApi(DEFAULT_CONFIG).stagesAllUserSettings({}).then((stages) => { return stages.map((stage) => { diff --git a/web/src/pages/tokens/UserTokenList.ts b/web/src/pages/users/UserTokenList.ts similarity index 97% rename from web/src/pages/tokens/UserTokenList.ts rename to web/src/pages/users/UserTokenList.ts index 56cec2b0d0..92c61c9f1c 100644 --- a/web/src/pages/tokens/UserTokenList.ts +++ b/web/src/pages/users/UserTokenList.ts @@ -12,7 +12,7 @@ import { CoreApi, Token } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; import { AdminURLManager } from "../../api/legacy"; -@customElement("ak-token-user-list") +@customElement("ak-user-token-list") export class UserTokenList extends Table { searchEnabled(): boolean { return true; @@ -41,7 +41,7 @@ export class UserTokenList extends Table { renderToolbar(): TemplateResult { return html` - + ${gettext("Create")} diff --git a/web/src/pages/users/UserViewPage.ts b/web/src/pages/users/UserViewPage.ts index a1c59160a7..e29acc7623 100644 --- a/web/src/pages/users/UserViewPage.ts +++ b/web/src/pages/users/UserViewPage.ts @@ -12,7 +12,9 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import AKGlobal from "../../authentik.css"; -import "../../elements/buttons/ModalButton"; +import "../../elements/forms/ModalForm"; +import "./UserForm"; +import "../../elements/buttons/ActionButton"; import "../../elements/buttons/SpinnerButton"; import "../../elements/CodeMirror"; import "../../elements/Tabs"; @@ -24,8 +26,9 @@ import "../../elements/charts/UserChart"; import { Page } from "../../elements/Page"; import { CoreApi, User } from "authentik-api"; import { DEFAULT_CONFIG } from "../../api/Config"; -import { AdminURLManager } from "../../api/legacy"; import { EVENT_REFRESH } from "../../constants"; +import { showMessage } from "../../elements/messages/MessageContainer"; +import { MessageLevel } from "../../elements/messages/Message"; @customElement("ak-user-view") export class UserViewPage extends Page { @@ -131,20 +134,35 @@ export class UserViewPage extends Page {