diff --git a/Pipfile.lock b/Pipfile.lock index d2743b298..f38d7dcbc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -116,18 +116,18 @@ }, "boto3": { "hashes": [ - "sha256:1709ff5feb363fee7fcaa2330e659fcbc2b4c03a14f75a884ed682ee66011fc4", - "sha256:80a761eff3b1cb0798d7e1a41b7c8e6d85c9647a8f7b6105335201a69404caa2" + "sha256:7d44cbd931c653cc68e8ccbf39f3ad8b304cb50d4e964d8c8d0936de33ff8c8b", + "sha256:b6131751e3cf2f8d4c027518373b6b82264c3897de65d3519e2d782927e8bf1e" ], "index": "pypi", - "version": "==1.17.10" + "version": "==1.17.11" }, "botocore": { "hashes": [ - "sha256:8c84eac6daf38890714e005623083106d68e9b2088e62132fdbf7d2b1228ecbd", - "sha256:a601ee5a4ae66832f328ca362b5404d22b75f1c181f6cc0934f3cfca749eb27d" + "sha256:8efd206b78269eb115279ca2d23f50eead1307dbe0bf9bcc2bba3ab2ff7bfd87", + "sha256:dd7c528c6c936d941b2c267339f0b01cce377b640856240b588d0e0d82fd29e3" ], - "version": "==1.20.10" + "version": "==1.20.11" }, "cachetools": { "hashes": [ @@ -409,11 +409,11 @@ }, "docker": { "hashes": [ - "sha256:20d71afc593486f2297bb7fb7406b03876f31894337e914a5062050c65085cab", - "sha256:67f33d4cf95182db631a17eef7d666d2c91f624c1d3fbc4df6009cb2f2a4c604" + "sha256:d4625e70e3d5a12d7cbf1fd68cef2e081ac86b83889e00e5466d975f90e50dad", + "sha256:de5753b7f6486dd541a98393e423e387579b8974a5068748b83f852cc76a89d6" ], "index": "pypi", - "version": "==4.4.2" + "version": "==4.4.3" }, "drf-yasg2": { "hashes": [ @@ -1093,11 +1093,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:9044b616ec6663cd50794fb3362efd87586e476e7b51ef2439a711d6569aede7", - "sha256:efc65e5ffd38324797a7e1dfc8a4e74b62ea6f56a59df5bb03217b84d58dff6a" + "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237", + "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b" ], "index": "pypi", - "version": "==0.20.2" + "version": "==0.20.3" }, "service-identity": { "hashes": [ @@ -1123,11 +1123,11 @@ }, "structlog": { "hashes": [ - "sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd", - "sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174" + "sha256:62f06fc0ee32fb8580f0715eea66cb87271eb7efb0eaf9af6b639cba8981de47", + "sha256:d9d2d890532e8db83c6977a2a676fb1889922ff0c26ad4dc0ecac26f9fafbc57" ], "index": "pypi", - "version": "==20.2.0" + "version": "==21.1.0" }, "swagger-spec-validator": { "hashes": [ diff --git a/authentik/admin/templates/administration/group/list.html b/authentik/admin/templates/administration/group/list.html deleted file mode 100644 index 3d1b8eb0f..000000000 --- a/authentik/admin/templates/administration/group/list.html +++ /dev/null @@ -1,114 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} - -{% block content %} -
-
-

- - {% trans 'Groups' %} -

-

{% trans "Group users together and give them permissions based on the membership." %} -

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - {% for group in object_list %} - - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Parent' %}{% trans 'Members' %}
- - {{ group.name }} - - - - {{ group.parent }} - - - - {{ group.users.all|length }} - - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Groups.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any groups." %} - {% else %} - {% trans 'Currently no group exist. Click the button below to create one.' %} - {% endif %} -
- - - {% trans 'Create' %} - -
-
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/outpost_service_connection/list.html b/authentik/admin/templates/administration/outpost_service_connection/list.html deleted file mode 100644 index b43dcf2dc..000000000 --- a/authentik/admin/templates/administration/outpost_service_connection/list.html +++ /dev/null @@ -1,153 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load humanize %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Outpost Service-Connections' %} -

-

{% trans "Outpost Service-Connections define how authentik connects to external platforms to manage and deploy Outposts." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - - - -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - - {% for sc in object_list %} - - - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Type' %}{% trans 'Local?' %}{% trans 'Status' %}
- {{ sc.name }} - - - {{ sc|verbose_name }} - - - - {{ sc.local|yesno:"Yes,No" }} - - - - {% if sc.state.healthy %} - {{ sc.state.version }} - {% else %} - {% trans 'Unhealthy' %} - {% endif %} - - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Outpost Service Connections.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any outposts." %} - {% else %} - {% trans 'Currently no service connections exist. Click the button below to create one.' %} - {% endif %} -
- - - - -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/policy/list.html b/authentik/admin/templates/administration/policy/list.html deleted file mode 100644 index 3b4d35a9a..000000000 --- a/authentik/admin/templates/administration/policy/list.html +++ /dev/null @@ -1,151 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Policies' %} -

-

{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - - - -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - {% for policy in object_list %} - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Type' %}
-
-
{{ policy.name }}
- {% if not policy.bindings.exists and not policy.promptstage_set.exists %} - - {% trans 'Warning: Policy is not assigned.' %} - {% else %} - - {% blocktrans with object_count=policy.bindings.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %} - {% endif %} -
-
- - {{ policy|verbose_name }} - - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Test' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Policies.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any policies." %} - {% else %} - {% trans 'Currently no policies exist. Click the button below to create one.' %} - {% endif %} -
- - - - -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/policy_binding/list.html b/authentik/admin/templates/administration/policy_binding/list.html deleted file mode 100644 index ee581d6c8..000000000 --- a/authentik/admin/templates/administration/policy_binding/list.html +++ /dev/null @@ -1,119 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Policy Bindings' %} -

-

{% trans "Bind existing Policies to Models accepting policies." %}

-
-
-
-
- {% if object_list %} -
-
-
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - - {% for pbm in object_list %} - - - - - - - - {% for binding in pbm.bindings %} - - - - - - - - {% endfor %} - {% endfor %} - -
{% trans 'Policy' %}{% trans 'Enabled' %}{% trans 'Order' %}{% trans 'Timeout' %}
- {{ pbm }} - - {{ pbm|fieldtype }} - -
-
{{ binding.policy }}
- - {{ binding.policy|fieldtype }} - -
-
{{ binding.enabled }}
-
-
{{ binding.order }}
-
-
{{ binding.timeout }}
-
- - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- -

- {% trans 'No Policy Bindings.' %} -

-
- {% trans 'Currently no policy bindings exist. Click the button below to create one.' %} -
- - - {% trans 'Create' %} - -
-
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/stage/list.html b/authentik/admin/templates/administration/stage/list.html deleted file mode 100644 index d71c30348..000000000 --- a/authentik/admin/templates/administration/stage/list.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Stages' %} -

-

{% trans "Stages are single steps of a Flow that a user is guided through." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - - - -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - {% for stage in object_list %} - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Flows' %}
-
-
{{ stage.name }}
- {{ stage|verbose_name }} -
-
-
    - {% for flow in stage.flow_set.all %} -
  • {{ flow.slug }}
  • - {% empty %} -
  • -
  • - {% endfor %} -
-
- - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Stages.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any stages." %} - {% else %} - {% trans 'Currently no stages exist. Click the button below to create one.' %} - {% endif %} -
- - - - -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/stage_binding/list.html b/authentik/admin/templates/administration/stage_binding/list.html deleted file mode 100644 index c4a772a62..000000000 --- a/authentik/admin/templates/administration/stage_binding/list.html +++ /dev/null @@ -1,125 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Stage Bindings' %} -

-

{% trans "Bind existing Stages to Flows." %}

-
-
-
-
- {% if object_list %} -
-
-
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - {% regroup object_list by target as grouped_bindings %} - {% for flow in grouped_bindings %} - - - - - - - {% for binding in flow.list %} - - - - - - - {% endfor %} - {% endfor %} - -
{% trans 'Order' %}{% trans 'Name' %}{% trans 'Stage Type' %}
- {% blocktrans with slug=flow.grouper.slug %} - Flow {{ slug }} - {% endblocktrans %} -
- - {{ binding.order }} - - -
-
{{ binding.target.slug }}
- - {{ binding.target.name }} - -
-
-
-
- {{ binding.stage.name }} -
- - {{ binding.stage }} - -
-
- - - {% trans 'Update' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- -

- {% trans 'No Flow-Stage Bindings.' %} -

-
- {% trans 'Currently no flow-stage bindings exist. Click the button below to create one.' %} -
- - - {% trans 'Create' %} - -
-
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/stage_invitation/list.html b/authentik/admin/templates/administration/stage_invitation/list.html deleted file mode 100644 index ec753169e..000000000 --- a/authentik/admin/templates/administration/stage_invitation/list.html +++ /dev/null @@ -1,109 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Invitations' %} -

-

{% trans "Create Invitation Links to enroll Users, and optionally force specific attributes of their account." %} -

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - {% for invitation in object_list %} - - - - - - - {% endfor %} - -
{% trans 'ID' %}{% trans 'Created by' %}{% trans 'Expiry' %}
- - {{ invitation.invite_uuid }} - - - - {{ invitation.created_by }} - - - - {{ invitation.expiry|default:"-" }} - - - - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Invitations.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any invitations." %} - {% else %} - {% trans 'Currently no invitations exist. Click the button below to create one.' %} - {% endif %} -
- - - {% trans 'Create' %} - -
-
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/stage_prompt/list.html b/authentik/admin/templates/administration/stage_prompt/list.html deleted file mode 100644 index 41b435e68..000000000 --- a/authentik/admin/templates/administration/stage_prompt/list.html +++ /dev/null @@ -1,125 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Prompts' %} -

-

{% trans "Single Prompts that can be used for Prompt Stages." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - - - {% for prompt in object_list %} - - - - - - - - - {% endfor %} - -
{% trans 'Field' %}{% trans 'Label' %}{% trans 'Type' %}{% trans 'Order' %}{% trans 'Flows' %}
-
-
{{ prompt.field_key }}
-
-
-
- {{ prompt.label }} -
-
-
- {{ prompt.type }} -
-
-
- {{ prompt.order }} -
-
-
    - {% for flow in prompt.flow_set.all %} -
  • {{ flow.slug }}
  • - {% empty %} -
  • -
  • - {% endfor %} -
-
- - - {% trans 'Update' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Stage Prompts.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any stage prompts." %} - {% else %} - {% trans 'Currently no stage prompts exist. Click the button below to create one.' %} - {% endif %} -
- {% trans 'Create' %} -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/task/list.html b/authentik/admin/templates/administration/task/list.html deleted file mode 100644 index 373bff1fc..000000000 --- a/authentik/admin/templates/administration/task/list.html +++ /dev/null @@ -1,84 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load humanize %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'System Tasks' %} -

-

{% trans "Long-running operations which authentik executes in the background." %}

-
-
-
-
-
-
- -
-
- - - - - - - - - - - - - {% for task in object_list %} - - - - - - - - - {% endfor %} - -
{% trans 'Identifier' %}{% trans 'Description' %}{% trans 'Last Run' %}{% trans 'Status' %}{% trans 'Messages' %}
- {{ task.html_name|join:"_­" }} - - - {{ task.task_description }} - - - - {{ task.finish_timestamp|naturaltime }} - - - - {% if task.result.status == task_successful %} - {% trans 'Successful' %} - {% elif task.result.status == task_warning %} - {% trans 'Warning' %} - {% elif task.result.status == task_error %} - {% trans 'Error' %} - {% else %} - {% trans 'Unknown' %} - {% endif %} - - - {% for message in task.result.messages %} -
- {{ message }} -
- {% endfor %} -
- - {% trans 'Retry Task' %} - -
-
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/token/list.html b/authentik/admin/templates/administration/token/list.html deleted file mode 100644 index 9eb7667a4..000000000 --- a/authentik/admin/templates/administration/token/list.html +++ /dev/null @@ -1,102 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Tokens' %} -

-

{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} - {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - - {% for token in object_list %} - - - - - - - - {% endfor %} - -
{% trans 'Identifier' %}{% trans 'User' %}{% trans 'Expires?' %}{% trans 'Expiry Date' %}
-
{{ token.identifier }}
-
- - {{ token.user }} - - - - {{ token.expiring|yesno:"Yes,No" }} - - - - {% if not token.expiring %} - - - {% else %} - {{ token.expires }} - {% endif %} - - - - - {% trans 'Delete' %} - -
-
- - {% trans 'Copy token' %} - -
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Tokens.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any token." %} - {% else %} - {% trans 'Currently no tokens exist.' %} - {% endif %} -
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/user/list.html b/authentik/admin/templates/administration/user/list.html deleted file mode 100644 index fe291cb51..000000000 --- a/authentik/admin/templates/administration/user/list.html +++ /dev/null @@ -1,125 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Users' %} -

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - {% for user in object_list %} - - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Active' %}{% trans 'Last Login' %}
-
-
{{ user.username }}
- {{ user.name }} -
-
- - {{ user.is_active }} - - - - {{ user.last_login }} - - - - - {% trans 'Edit' %} - -
-
- {% if user.is_active %} - - - {% trans 'Disable' %} - -
-
- {% else %} - - - {% trans 'Enable' %} - -
-
- {% endif %} - {% trans 'Reset Password' %} - {% trans 'Impersonate' %} -
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Users.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any users." %} - {% else %} - {% trans 'Currently no users exist. How did you even get here.' %} - {% endif %} -
- - - {% trans 'Create' %} - -
-
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index c35cdbc90..a8527e312 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -20,7 +20,6 @@ from authentik.admin.views import ( stages_bindings, stages_invitations, stages_prompts, - tasks, tokens, users, ) @@ -54,7 +53,6 @@ urlpatterns = [ name="application-delete", ), # Tokens - path("tokens/", tokens.TokenListView.as_view(), name="tokens"), path( "tokens//delete/", tokens.TokenDeleteView.as_view(), @@ -73,7 +71,6 @@ urlpatterns = [ name="source-delete", ), # Policies - path("policies/", policies.PolicyListView.as_view(), name="policies"), path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), path( "policies//update/", @@ -91,11 +88,6 @@ urlpatterns = [ name="policy-test", ), # Policy bindings - path( - "policies/bindings/", - policies_bindings.PolicyBindingListView.as_view(), - name="policies-bindings", - ), path( "policies/bindings/create/", policies_bindings.PolicyBindingCreateView.as_view(), @@ -133,7 +125,6 @@ urlpatterns = [ name="provider-delete", ), # Stages - path("stages/", stages.StageListView.as_view(), name="stages"), path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"), path( "stages//update/", @@ -146,11 +137,6 @@ urlpatterns = [ name="stage-delete", ), # Stage bindings - path( - "stages/bindings/", - stages_bindings.StageBindingListView.as_view(), - name="stage-bindings", - ), path( "stages/bindings/create/", stages_bindings.StageBindingCreateView.as_view(), @@ -167,11 +153,6 @@ urlpatterns = [ name="stage-binding-delete", ), # Stage Prompts - path( - "stages_prompts/", - stages_prompts.PromptListView.as_view(), - name="stage-prompts", - ), path( "stages_prompts/create/", stages_prompts.PromptCreateView.as_view(), @@ -188,11 +169,6 @@ urlpatterns = [ name="stage-prompt-delete", ), # Stage Invitations - path( - "stages/invitations/", - stages_invitations.InvitationListView.as_view(), - name="stage-invitations", - ), path( "stages/invitations/create/", stages_invitations.InvitationCreateView.as_view(), @@ -256,7 +232,6 @@ urlpatterns = [ name="property-mapping-test", ), # Users - path("users/", users.UserListView.as_view(), name="users"), path("users/create/", users.UserCreateView.as_view(), name="user-create"), path("users//update/", users.UserUpdateView.as_view(), name="user-update"), path("users//delete/", users.UserDeleteView.as_view(), name="user-delete"), @@ -270,7 +245,6 @@ urlpatterns = [ name="user-password-reset", ), # Groups - path("groups/", groups.GroupListView.as_view(), name="groups"), path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"), path( "groups//update/", @@ -320,11 +294,6 @@ urlpatterns = [ name="outpost-delete", ), # Outpost Service Connections - path( - "outpost_service_connections/", - outposts_service_connections.OutpostServiceConnectionListView.as_view(), - name="outpost-service-connections", - ), path( "outpost_service_connections/create/", outposts_service_connections.OutpostServiceConnectionCreateView.as_view(), @@ -340,12 +309,6 @@ urlpatterns = [ outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(), name="outpost-service-connection-delete", ), - # Tasks - path( - "tasks/", - tasks.TaskListView.as_view(), - name="tasks", - ), # Event Notification Transpots path( "events/transports/create/", diff --git a/authentik/admin/views/applications.py b/authentik/admin/views/applications.py index 88e72940c..f80eda369 100644 --- a/authentik/admin/views/applications.py +++ b/authentik/admin/views/applications.py @@ -11,7 +11,7 @@ from django.views.generic import UpdateView from guardian.mixins import PermissionRequiredMixin from guardian.shortcuts import get_objects_for_user -from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.admin.views.utils import DeleteMessageView from authentik.core.forms.applications import ApplicationForm from authentik.core.models import Application from authentik.lib.views import CreateAssignPermView @@ -19,7 +19,6 @@ from authentik.lib.views import CreateAssignPermView class ApplicationCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -52,7 +51,6 @@ class ApplicationCreateView( class ApplicationUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, diff --git a/authentik/admin/views/certificate_key_pair.py b/authentik/admin/views/certificate_key_pair.py index 4653cb154..1e2ea400a 100644 --- a/authentik/admin/views/certificate_key_pair.py +++ b/authentik/admin/views/certificate_key_pair.py @@ -10,7 +10,7 @@ from django.views.generic import UpdateView from django.views.generic.edit import FormView from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.admin.views.utils import DeleteMessageView from authentik.crypto.builder import CertificateBuilder from authentik.crypto.forms import ( CertificateKeyPairForm, @@ -22,7 +22,6 @@ from authentik.lib.views import CreateAssignPermView class CertificateKeyPairCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -40,7 +39,6 @@ class CertificateKeyPairCreateView( class CertificateKeyPairGenerateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, FormView, @@ -68,7 +66,6 @@ class CertificateKeyPairGenerateView( class CertificateKeyPairUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, diff --git a/authentik/admin/views/events_notifications_rules.py b/authentik/admin/views/events_notifications_rules.py index 7cb2fc390..66577dc40 100644 --- a/authentik/admin/views/events_notifications_rules.py +++ b/authentik/admin/views/events_notifications_rules.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext as _ from django.views.generic import UpdateView from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.admin.views.utils import DeleteMessageView from authentik.events.forms import NotificationRuleForm from authentik.events.models import NotificationRule from authentik.lib.views import CreateAssignPermView @@ -16,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView class NotificationRuleCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -33,7 +32,6 @@ class NotificationRuleCreateView( class NotificationRuleUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, diff --git a/authentik/admin/views/events_notifications_transports.py b/authentik/admin/views/events_notifications_transports.py index d211831ed..a5c4cd990 100644 --- a/authentik/admin/views/events_notifications_transports.py +++ b/authentik/admin/views/events_notifications_transports.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext as _ from django.views.generic import UpdateView from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.admin.views.utils import DeleteMessageView from authentik.events.forms import NotificationTransportForm from authentik.events.models import NotificationTransport from authentik.lib.views import CreateAssignPermView @@ -16,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView class NotificationTransportCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -33,7 +32,6 @@ class NotificationTransportCreateView( class NotificationTransportUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, diff --git a/authentik/admin/views/flows.py b/authentik/admin/views/flows.py index 775316245..9cfa6ffd2 100644 --- a/authentik/admin/views/flows.py +++ b/authentik/admin/views/flows.py @@ -10,7 +10,7 @@ from django.utils.translation import gettext as _ from django.views.generic import DetailView, FormView, UpdateView from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.admin.views.utils import DeleteMessageView from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.forms import FlowForm, FlowImportForm from authentik.flows.models import Flow @@ -25,7 +25,6 @@ from authentik.lib.views import CreateAssignPermView, bad_request_message class FlowCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -43,7 +42,6 @@ class FlowCreateView( class FlowUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, diff --git a/authentik/admin/views/groups.py b/authentik/admin/views/groups.py index bebd3bdb1..74a77b5f1 100644 --- a/authentik/admin/views/groups.py +++ b/authentik/admin/views/groups.py @@ -4,41 +4,18 @@ 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 ListView, UpdateView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from django.views.generic import UpdateView +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.core.forms.groups import GroupForm from authentik.core.models import Group from authentik.lib.views import CreateAssignPermView -class GroupListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all groups""" - - model = Group - permission_required = "authentik_core.view_group" - ordering = "name" - template_name = "administration/group/list.html" - search_fields = ["name", "attributes"] - - class GroupCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -50,13 +27,12 @@ class GroupCreateView( permission_required = "authentik_core.add_group" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:groups") + success_url = "/" success_message = _("Successfully created Group") class GroupUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, @@ -68,7 +44,7 @@ class GroupUpdateView( permission_required = "authentik_core.change_group" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:groups") + success_url = "/" success_message = _("Successfully updated Group") @@ -79,5 +55,5 @@ class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage permission_required = "authentik_flows.delete_group" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:groups") + success_url = "/" success_message = _("Successfully deleted Group") diff --git a/authentik/admin/views/outposts.py b/authentik/admin/views/outposts.py index 5cc7bd8eb..fc160b901 100644 --- a/authentik/admin/views/outposts.py +++ b/authentik/admin/views/outposts.py @@ -11,7 +11,7 @@ from django.utils.translation import gettext as _ from django.views.generic import UpdateView from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.admin.views.utils import DeleteMessageView from authentik.lib.views import CreateAssignPermView from authentik.outposts.forms import OutpostForm from authentik.outposts.models import Outpost, OutpostConfig @@ -19,7 +19,6 @@ from authentik.outposts.models import Outpost, OutpostConfig class OutpostCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -43,7 +42,6 @@ class OutpostCreateView( class OutpostUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, diff --git a/authentik/admin/views/outposts_service_connections.py b/authentik/admin/views/outposts_service_connections.py index a1aded022..442690bed 100644 --- a/authentik/admin/views/outposts_service_connections.py +++ b/authentik/admin/views/outposts_service_connections.py @@ -4,41 +4,19 @@ 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 guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin from authentik.admin.views.utils import ( - BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, - InheritanceListView, InheritanceUpdateView, - SearchListMixin, - UserPaginateListMixin, ) from authentik.outposts.models import OutpostServiceConnection -class OutpostServiceConnectionListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - InheritanceListView, -): - """Show list of all outpost-service-connections""" - - model = OutpostServiceConnection - permission_required = "authentik_outposts.add_outpostserviceconnection" - template_name = "administration/outpost_service_connection/list.html" - ordering = "pk" - search_fields = ["pk", "name"] - - class OutpostServiceConnectionCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, InheritanceCreateView, @@ -49,13 +27,12 @@ class OutpostServiceConnectionCreateView( permission_required = "authentik_outposts.add_outpostserviceconnection" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:outpost-service-connections") - success_message = _("Successfully created OutpostServiceConnection") + success_url = "/" + success_message = _("Successfully created Outpost Service Connection") class OutpostServiceConnectionUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, InheritanceUpdateView, @@ -66,8 +43,8 @@ class OutpostServiceConnectionUpdateView( permission_required = "authentik_outposts.change_outpostserviceconnection" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:outpost-service-connections") - success_message = _("Successfully updated OutpostServiceConnection") + success_url = "/" + success_message = _("Successfully updated Outpost Service Connection") class OutpostServiceConnectionDeleteView( @@ -79,5 +56,5 @@ class OutpostServiceConnectionDeleteView( permission_required = "authentik_outposts.delete_outpostserviceconnection" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:outpost-service-connections") - success_message = _("Successfully deleted OutpostServiceConnection") + success_url = "/" + success_message = _("Successfully deleted Outpost Service Connection") diff --git a/authentik/admin/views/policies.py b/authentik/admin/views/policies.py index 3a999f3cc..bb18a3502 100644 --- a/authentik/admin/views/policies.py +++ b/authentik/admin/views/policies.py @@ -7,45 +7,23 @@ from django.contrib.auth.mixins import ( ) from django.contrib.messages.views import SuccessMessageMixin from django.http import HttpResponse -from django.urls import reverse_lazy from django.utils.translation import gettext as _ from django.views.generic import FormView from django.views.generic.detail import DetailView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin from authentik.admin.forms.policies import PolicyTestForm from authentik.admin.views.utils import ( - BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, - InheritanceListView, InheritanceUpdateView, - SearchListMixin, - UserPaginateListMixin, ) from authentik.policies.models import Policy, PolicyBinding from authentik.policies.process import PolicyProcess, PolicyRequest -class PolicyListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - InheritanceListView, -): - """Show list of all policies""" - - model = Policy - permission_required = "authentik_policies.view_policy" - ordering = "name" - template_name = "administration/policy/list.html" - search_fields = ["name"] - - class PolicyCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, InheritanceCreateView, @@ -56,13 +34,12 @@ class PolicyCreateView( permission_required = "authentik_policies.add_policy" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:policies") + success_url = "/" success_message = _("Successfully created Policy") class PolicyUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, InheritanceUpdateView, @@ -73,7 +50,7 @@ class PolicyUpdateView( permission_required = "authentik_policies.change_policy" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:policies") + success_url = "/" success_message = _("Successfully updated Policy") @@ -84,7 +61,7 @@ class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag permission_required = "authentik_policies.delete_policy" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:policies") + success_url = "/" success_message = _("Successfully deleted Policy") diff --git a/authentik/admin/views/policies_bindings.py b/authentik/admin/views/policies_bindings.py index be19eb815..336f0dd64 100644 --- a/authentik/admin/views/policies_bindings.py +++ b/authentik/admin/views/policies_bindings.py @@ -6,55 +6,19 @@ from django.contrib.auth.mixins import ( PermissionRequiredMixin as DjangoPermissionRequiredMixin, ) from django.contrib.messages.views import SuccessMessageMixin -from django.db.models import Max, QuerySet -from django.urls import reverse_lazy +from django.db.models import Max from django.utils.translation import gettext as _ -from django.views.generic import ListView, UpdateView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin -from guardian.shortcuts import get_objects_for_user +from django.views.generic import UpdateView +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.lib.views import CreateAssignPermView from authentik.policies.forms import PolicyBindingForm from authentik.policies.models import PolicyBinding, PolicyBindingModel -class PolicyBindingListView( - LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView -): - """Show list of all policies""" - - model = PolicyBinding - permission_required = "authentik_policies.view_policybinding" - ordering = ["order", "target"] - template_name = "administration/policy_binding/list.html" - - def get_queryset(self) -> QuerySet: - # Since `select_subclasses` does not work with a foreign key, we have to do two queries here - # First, get all pbm objects that have bindings attached - objects = ( - get_objects_for_user( - self.request.user, "authentik_policies.view_policybindingmodel" - ) - .filter(policies__isnull=False) - .select_subclasses() - .select_related() - .order_by("pk") - ) - for pbm in objects: - pbm.bindings = get_objects_for_user( - self.request.user, self.permission_required - ).filter(target__pk=pbm.pbm_uuid) - return objects - - class PolicyBindingCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -66,7 +30,7 @@ class PolicyBindingCreateView( form_class = PolicyBindingForm template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:policies-bindings") + success_url = "/" success_message = _("Successfully created PolicyBinding") def get_initial(self) -> dict[str, Any]: @@ -88,7 +52,6 @@ class PolicyBindingCreateView( class PolicyBindingUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, @@ -100,7 +63,7 @@ class PolicyBindingUpdateView( form_class = PolicyBindingForm template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:policies-bindings") + success_url = "/" success_message = _("Successfully updated PolicyBinding") @@ -113,5 +76,5 @@ class PolicyBindingDeleteView( permission_required = "authentik_policies.delete_policybinding" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:policies-bindings") + success_url = "/" success_message = _("Successfully deleted PolicyBinding") diff --git a/authentik/admin/views/property_mappings.py b/authentik/admin/views/property_mappings.py index ca6b2796f..f9b2fc7d7 100644 --- a/authentik/admin/views/property_mappings.py +++ b/authentik/admin/views/property_mappings.py @@ -15,7 +15,6 @@ from guardian.mixins import PermissionRequiredMixin from authentik.admin.forms.policies import PolicyTestForm from authentik.admin.views.utils import ( - BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, InheritanceUpdateView, @@ -25,7 +24,6 @@ from authentik.core.models import PropertyMapping class PropertyMappingCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, InheritanceCreateView, @@ -41,7 +39,6 @@ class PropertyMappingCreateView( class PropertyMappingUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, InheritanceUpdateView, diff --git a/authentik/admin/views/providers.py b/authentik/admin/views/providers.py index 76eefa839..71394c18a 100644 --- a/authentik/admin/views/providers.py +++ b/authentik/admin/views/providers.py @@ -8,7 +8,6 @@ from django.utils.translation import gettext as _ from guardian.mixins import PermissionRequiredMixin from authentik.admin.views.utils import ( - BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, InheritanceUpdateView, @@ -18,7 +17,6 @@ from authentik.core.models import Provider class ProviderCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, InheritanceCreateView, @@ -34,7 +32,6 @@ class ProviderCreateView( class ProviderUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, InheritanceUpdateView, diff --git a/authentik/admin/views/sources.py b/authentik/admin/views/sources.py index 7ebe243b8..5bc002ced 100644 --- a/authentik/admin/views/sources.py +++ b/authentik/admin/views/sources.py @@ -8,7 +8,6 @@ from django.utils.translation import gettext as _ from guardian.mixins import PermissionRequiredMixin from authentik.admin.views.utils import ( - BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, InheritanceUpdateView, @@ -18,7 +17,6 @@ from authentik.core.models import Source class SourceCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, InheritanceCreateView, @@ -34,7 +32,6 @@ class SourceCreateView( class SourceUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, InheritanceUpdateView, diff --git a/authentik/admin/views/stages.py b/authentik/admin/views/stages.py index 55e7623dd..049b591e4 100644 --- a/authentik/admin/views/stages.py +++ b/authentik/admin/views/stages.py @@ -4,41 +4,19 @@ 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 guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin from authentik.admin.views.utils import ( - BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, - InheritanceListView, InheritanceUpdateView, - SearchListMixin, - UserPaginateListMixin, ) from authentik.flows.models import Stage -class StageListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - InheritanceListView, -): - """Show list of all stages""" - - model = Stage - template_name = "administration/stage/list.html" - permission_required = "authentik_flows.view_stage" - ordering = "name" - search_fields = ["name"] - - class StageCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, InheritanceCreateView, @@ -49,13 +27,12 @@ class StageCreateView( template_name = "generic/create.html" permission_required = "authentik_flows.add_stage" - success_url = reverse_lazy("authentik_admin:stages") + success_url = "/" success_message = _("Successfully created Stage") class StageUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, InheritanceUpdateView, @@ -65,7 +42,7 @@ class StageUpdateView( model = Stage permission_required = "authentik_flows.update_application" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:stages") + success_url = "/" success_message = _("Successfully updated Stage") @@ -75,5 +52,5 @@ class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage model = Stage template_name = "generic/delete.html" permission_required = "authentik_flows.delete_stage" - success_url = reverse_lazy("authentik_admin:stages") + success_url = "/" success_message = _("Successfully deleted Stage") diff --git a/authentik/admin/views/stages_bindings.py b/authentik/admin/views/stages_bindings.py index 7e416d19f..e8376e6b3 100644 --- a/authentik/admin/views/stages_bindings.py +++ b/authentik/admin/views/stages_bindings.py @@ -7,35 +7,18 @@ from django.contrib.auth.mixins import ( ) from django.contrib.messages.views import SuccessMessageMixin from django.db.models import Max -from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from django.views.generic import ListView, UpdateView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from django.views.generic import UpdateView +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.flows.forms import FlowStageBindingForm from authentik.flows.models import Flow, FlowStageBinding from authentik.lib.views import CreateAssignPermView -class StageBindingListView( - LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView -): - """Show list of all flows""" - - model = FlowStageBinding - permission_required = "authentik_flows.view_flowstagebinding" - ordering = ["target", "order"] - template_name = "administration/stage_binding/list.html" - - class StageBindingCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -47,7 +30,7 @@ class StageBindingCreateView( form_class = FlowStageBindingForm template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:stage-bindings") + success_url = "/" success_message = _("Successfully created StageBinding") def get_initial(self) -> dict[str, Any]: @@ -67,7 +50,6 @@ class StageBindingCreateView( class StageBindingUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, @@ -79,7 +61,7 @@ class StageBindingUpdateView( form_class = FlowStageBindingForm template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:stage-bindings") + success_url = "/" success_message = _("Successfully updated StageBinding") @@ -92,5 +74,5 @@ class StageBindingDeleteView( permission_required = "authentik_flows.delete_flowstagebinding" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:stage-bindings") + success_url = "/" success_message = _("Successfully deleted FlowStageBinding") diff --git a/authentik/admin/views/stages_invitations.py b/authentik/admin/views/stages_invitations.py index cf2c4bcca..3e87247cc 100644 --- a/authentik/admin/views/stages_invitations.py +++ b/authentik/admin/views/stages_invitations.py @@ -5,41 +5,17 @@ from django.contrib.auth.mixins import ( ) from django.contrib.messages.views import SuccessMessageMixin from django.http import HttpResponseRedirect -from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from django.views.generic import ListView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.lib.views import CreateAssignPermView from authentik.stages.invitation.forms import InvitationForm from authentik.stages.invitation.models import Invitation -class InvitationListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all invitations""" - - model = Invitation - permission_required = "authentik_stages_invitation.view_invitation" - template_name = "administration/stage_invitation/list.html" - ordering = "-expires" - search_fields = ["created_by__username", "expires", "fixed_data"] - - class InvitationCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -51,7 +27,7 @@ class InvitationCreateView( permission_required = "authentik_stages_invitation.add_invitation" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:stage-invitations") + success_url = "/" success_message = _("Successfully created Invitation") def form_valid(self, form): @@ -70,5 +46,5 @@ class InvitationDeleteView( permission_required = "authentik_stages_invitation.delete_invitation" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:stage-invitations") + success_url = "/" success_message = _("Successfully deleted Invitation") diff --git a/authentik/admin/views/stages_prompts.py b/authentik/admin/views/stages_prompts.py index cc59a2ba5..d61b04850 100644 --- a/authentik/admin/views/stages_prompts.py +++ b/authentik/admin/views/stages_prompts.py @@ -4,46 +4,18 @@ 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 ListView, UpdateView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from django.views.generic import UpdateView +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.lib.views import CreateAssignPermView from authentik.stages.prompt.forms import PromptAdminForm from authentik.stages.prompt.models import Prompt -class PromptListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all prompts""" - - model = Prompt - permission_required = "authentik_stages_prompt.view_prompt" - ordering = "order" - template_name = "administration/stage_prompt/list.html" - search_fields = [ - "field_key", - "label", - "type", - "placeholder", - ] - - class PromptCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -55,13 +27,12 @@ class PromptCreateView( permission_required = "authentik_stages_prompt.add_prompt" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:stage-prompts") + success_url = "/" success_message = _("Successfully created Prompt") class PromptUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, @@ -73,7 +44,7 @@ class PromptUpdateView( permission_required = "authentik_stages_prompt.change_prompt" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:stage-prompts") + success_url = "/" success_message = _("Successfully updated Prompt") @@ -84,5 +55,5 @@ class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag permission_required = "authentik_stages_prompt.delete_prompt" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:stage-prompts") + success_url = "/" success_message = _("Successfully deleted Prompt") diff --git a/authentik/admin/views/tasks.py b/authentik/admin/views/tasks.py deleted file mode 100644 index fdc69d5f4..000000000 --- a/authentik/admin/views/tasks.py +++ /dev/null @@ -1,23 +0,0 @@ -"""authentik Tasks List""" -from typing import Any - -from django.views.generic.base import TemplateView - -from authentik.admin.mixins import AdminRequiredMixin -from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus - - -class TaskListView(AdminRequiredMixin, TemplateView): - """Show list of all background tasks""" - - template_name = "administration/task/list.html" - - def get_context_data(self, **kwargs: Any) -> dict[str, Any]: - kwargs = super().get_context_data(**kwargs) - kwargs["object_list"] = sorted( - TaskInfo.all().values(), key=lambda x: x.task_name - ) - kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL - kwargs["task_warning"] = TaskResultStatus.WARNING - kwargs["task_error"] = TaskResultStatus.ERROR - return kwargs diff --git a/authentik/admin/views/tokens.py b/authentik/admin/views/tokens.py index 126dac064..0dc6ce311 100644 --- a/authentik/admin/views/tokens.py +++ b/authentik/admin/views/tokens.py @@ -1,39 +1,12 @@ """authentik Token administration""" from django.contrib.auth.mixins import LoginRequiredMixin -from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from django.views.generic import ListView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.core.models import Token -class TokenListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all tokens""" - - model = Token - permission_required = "authentik_core.view_token" - ordering = "expires" - template_name = "administration/token/list.html" - search_fields = [ - "identifier", - "intent", - "user__username", - "description", - ] - - class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): """Delete token""" @@ -41,5 +14,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage permission_required = "authentik_core.delete_token" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:tokens") + success_url = "/" success_message = _("Successfully deleted Token") diff --git a/authentik/admin/views/users.py b/authentik/admin/views/users.py index 434a8c0c2..379a8c641 100644 --- a/authentik/admin/views/users.py +++ b/authentik/admin/views/users.py @@ -7,50 +7,20 @@ from django.contrib.auth.mixins import ( from django.contrib.messages.views import SuccessMessageMixin from django.http import HttpRequest, HttpResponse from django.http.response import HttpResponseRedirect -from django.shortcuts import redirect -from django.urls import reverse, reverse_lazy +from django.shortcuts import redirect, reverse from django.utils.http import urlencode from django.utils.translation import gettext as _ -from django.views.generic import DetailView, ListView, UpdateView -from guardian.mixins import ( - PermissionListMixin, - PermissionRequiredMixin, - get_anonymous_user, -) +from django.views.generic import DetailView, UpdateView +from guardian.mixins import PermissionRequiredMixin from authentik.admin.forms.users import UserForm -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.core.models import Token, User from authentik.lib.views import CreateAssignPermView -class UserListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all users""" - - model = User - permission_required = "authentik_core.view_user" - ordering = "username" - template_name = "administration/user/list.html" - search_fields = ["username", "name", "attributes"] - - def get_queryset(self): - return super().get_queryset().exclude(pk=get_anonymous_user().pk) - - class UserCreateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, DjangoPermissionRequiredMixin, CreateAssignPermView, @@ -62,13 +32,12 @@ class UserCreateView( permission_required = "authentik_core.add_user" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:users") + success_url = "/" success_message = _("Successfully created User") class UserUpdateView( SuccessMessageMixin, - BackSuccessUrlMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView, @@ -82,7 +51,7 @@ class UserUpdateView( # 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_admin:users") + success_url = "/" success_message = _("Successfully updated User") @@ -95,13 +64,11 @@ class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageV # By default the object's name is user which is used by other checks context_object_name = "object" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:users") + success_url = "/" success_message = _("Successfully deleted User") -class UserDisableView( - LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView -): +class UserDisableView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): """Disable user""" object: User @@ -112,7 +79,7 @@ class UserDisableView( # By default the object's name is user which is used by other checks context_object_name = "object" template_name = "administration/user/disable.html" - success_url = reverse_lazy("authentik_admin:users") + success_url = "/" success_message = _("Successfully disabled User") def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: @@ -123,9 +90,7 @@ class UserDisableView( return HttpResponseRedirect(success_url) -class UserEnableView( - LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView -): +class UserEnableView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): """Enable user""" object: User @@ -135,15 +100,14 @@ class UserEnableView( # By default the object's name is user which is used by other checks context_object_name = "object" - success_url = reverse_lazy("authentik_admin:users") + success_url = "/" success_message = _("Successfully enabled User") def get(self, request: HttpRequest, *args, **kwargs): self.object: User = self.get_object() - success_url = self.get_success_url() self.object.is_active = True self.object.save() - return HttpResponseRedirect(success_url) + return HttpResponseRedirect(self.success_url) class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): @@ -165,4 +129,4 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV messages.success( request, _("Password reset link:
%(link)s
" % {"link": link}) ) - return redirect("authentik_admin:users") + return redirect("/") diff --git a/authentik/admin/views/utils.py b/authentik/admin/views/utils.py index 17c33f434..f17b1b354 100644 --- a/authentik/admin/views/utils.py +++ b/authentik/admin/views/utils.py @@ -1,15 +1,10 @@ """authentik admin util views""" -from typing import Any, Optional -from urllib.parse import urlparse +from typing import Any from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin -from django.contrib.postgres.search import SearchQuery, SearchVector -from django.db.models import QuerySet from django.http import Http404 -from django.http.request import HttpRequest -from django.views.generic import DeleteView, ListView, UpdateView -from django.views.generic.list import MultipleObjectMixin +from django.views.generic import DeleteView, UpdateView from authentik.lib.utils.reflection import all_subclasses from authentik.lib.views import CreateAssignPermView @@ -25,37 +20,6 @@ class DeleteMessageView(SuccessMessageMixin, DeleteView): return super().delete(request, *args, **kwargs) -class InheritanceListView(ListView): - """ListView for objects using InheritanceManager""" - - def get_context_data(self, **kwargs): - kwargs["types"] = {x.__name__: x for x in all_subclasses(self.model)} - return super().get_context_data(**kwargs) - - def get_queryset(self): - return super().get_queryset().select_subclasses() - - -class SearchListMixin(MultipleObjectMixin): - """Accept search query using `search` querystring parameter. Requires self.search_fields, - a list of all fields to search. Can contain special lookups like __icontains""" - - search_fields: list[str] - - def get_queryset(self) -> QuerySet: - queryset = super().get_queryset() - if "search" in self.request.GET: - raw_query = self.request.GET["search"] - if raw_query == "": - # Empty query, don't search at all - return queryset - search = SearchQuery(raw_query, search_type="websearch") - return queryset.annotate(search=SearchVector(*self.search_fields)).filter( - search=search - ) - return queryset - - class InheritanceCreateView(CreateAssignPermView): """CreateView for objects using InheritanceManager""" @@ -96,31 +60,3 @@ class InheritanceUpdateView(UpdateView): .select_subclasses() .first() ) - - -class BackSuccessUrlMixin: - """Checks if a relative URL has been given as ?back param, and redirect to it. Otherwise - default to self.success_url.""" - - request: HttpRequest - - success_url: Optional[str] - - def get_success_url(self) -> str: - """get_success_url from FormMixin""" - back_param = self.request.GET.get("back") - if back_param: - if not bool(urlparse(back_param).netloc): - return back_param - return str(self.success_url) - - -class UserPaginateListMixin: - """Get paginate_by value from user's attributes, defaulting to 15""" - - request: HttpRequest - - # pylint: disable=unused-argument - def get_paginate_by(self, queryset: QuerySet) -> int: - """get_paginate_by Function of ListView""" - return self.request.user.attributes.get("paginate_by", 15) diff --git a/authentik/api/v2/urls.py b/authentik/api/v2/urls.py index 8416166ff..df1bd26eb 100644 --- a/authentik/api/v2/urls.py +++ b/authentik/api/v2/urls.py @@ -23,12 +23,9 @@ from authentik.events.api.event import EventViewSet from authentik.events.api.notification import NotificationViewSet from authentik.events.api.notification_rule import NotificationRuleViewSet from authentik.events.api.notification_transport import NotificationTransportViewSet -from authentik.flows.api import ( - FlowCacheViewSet, - FlowStageBindingViewSet, - FlowViewSet, - StageViewSet, -) +from authentik.flows.api.bindings import FlowStageBindingViewSet +from authentik.flows.api.flows import FlowViewSet +from authentik.flows.api.stages import StageViewSet from authentik.flows.views import FlowExecutorView from authentik.outposts.api.outpost_service_connections import ( DockerServiceConnectionViewSet, @@ -36,11 +33,7 @@ from authentik.outposts.api.outpost_service_connections import ( ServiceConnectionViewSet, ) from authentik.outposts.api.outposts import OutpostViewSet -from authentik.policies.api import ( - PolicyBindingViewSet, - PolicyCacheViewSet, - PolicyViewSet, -) +from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet from authentik.policies.dummy.api import DummyPolicyViewSet from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet @@ -101,7 +94,6 @@ router.register( router.register("outposts/proxy", ProxyOutpostConfigViewSet) router.register("flows/instances", FlowViewSet) -router.register("flows/cached", FlowCacheViewSet, basename="flows_cache") router.register("flows/bindings", FlowStageBindingViewSet) router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) @@ -117,7 +109,6 @@ router.register("sources/saml", SAMLSourceViewSet) router.register("sources/oauth", OAuthSourceViewSet) router.register("policies/all", PolicyViewSet) -router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache") router.register("policies/bindings", PolicyBindingViewSet) router.register("policies/expression", ExpressionPolicyViewSet) router.register("policies/event_matcher", EventMatcherPolicyViewSet) @@ -146,8 +137,8 @@ router.register("stages/captcha", CaptchaStageViewSet) router.register("stages/consent", ConsentStageViewSet) router.register("stages/email", EmailStageViewSet) router.register("stages/identification", IdentificationStageViewSet) -router.register("stages/invitation", InvitationStageViewSet) router.register("stages/invitation/invitations", InvitationViewSet) +router.register("stages/invitation/stages", InvitationStageViewSet) router.register("stages/password", PasswordStageViewSet) router.register("stages/prompt/prompts", PromptViewSet) router.register("stages/prompt/stages", PromptStageViewSet) diff --git a/authentik/core/api/groups.py b/authentik/core/api/groups.py index fa1b8953d..152c1ea9f 100644 --- a/authentik/core/api/groups.py +++ b/authentik/core/api/groups.py @@ -19,3 +19,5 @@ class GroupViewSet(ModelViewSet): queryset = Group.objects.all() serializer_class = GroupSerializer + search_fields = ["name", "is_superuser"] + filterset_fields = ["name", "is_superuser"] diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index b87865c86..147ba44fe 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/propertymappings.py @@ -1,9 +1,16 @@ """PropertyMapping API Views""" +from django.shortcuts import reverse +from drf_yasg2.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import ReadOnlyModelViewSet -from authentik.core.api.utils import MetaNameSerializer +from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.models import PropertyMapping +from authentik.lib.templatetags.authentik_utils import verbose_name +from authentik.lib.utils.reflection import all_subclasses class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer): @@ -47,3 +54,19 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet): def get_queryset(self): return PropertyMapping.objects.select_subclasses() + + @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) + @action(detail=False) + def types(self, request: Request) -> Response: + """Get all creatable property-mapping types""" + data = [] + for subclass in all_subclasses(self.queryset.model): + data.append( + { + "name": verbose_name(subclass), + "description": subclass.__doc__, + "link": reverse("authentik_admin:property-mapping-create") + + f"?type={subclass.__name__}", + } + ) + return Response(TypeCreateSerializer(data, many=True).data) diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index f30ba4f4b..aef414bff 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -9,6 +9,7 @@ from rest_framework.response import Response from rest_framework.serializers import ModelSerializer, Serializer from rest_framework.viewsets import ModelViewSet +from authentik.core.api.users import UserSerializer from authentik.core.models import Token from authentik.events.models import Event, EventAction @@ -16,10 +17,21 @@ from authentik.events.models import Event, EventAction class TokenSerializer(ModelSerializer): """Token Serializer""" + user = UserSerializer() + class Meta: model = Token - fields = ["pk", "identifier", "intent", "user", "description"] + fields = [ + "pk", + "identifier", + "intent", + "user", + "description", + "expires", + "expiring", + ] + depth = 2 class TokenViewSerializer(Serializer): @@ -40,6 +52,19 @@ class TokenViewSet(ModelViewSet): lookup_field = "identifier" queryset = Token.filter_not_expired() serializer_class = TokenSerializer + search_fields = [ + "identifier", + "intent", + "user__username", + "description", + ] + filterset_fields = [ + "identifier", + "intent", + "user__username", + "description", + ] + ordering = ["expires"] @swagger_auto_schema(responses={200: TokenViewSerializer(many=False)}) @action(detail=True) diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index e75d451da..573e04cd1 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -28,7 +28,17 @@ class UserSerializer(ModelSerializer): class Meta: model = User - fields = ["pk", "username", "name", "is_superuser", "email", "avatar"] + fields = [ + "pk", + "username", + "name", + "is_active", + "last_login", + "is_superuser", + "email", + "avatar", + "attributes", + ] class UserViewSet(ModelViewSet): @@ -36,6 +46,8 @@ class UserViewSet(ModelViewSet): queryset = User.objects.none() serializer_class = UserSerializer + search_fields = ["username", "name", "is_active"] + filterset_fields = ["username", "name", "is_active"] def get_queryset(self): return User.objects.all().exclude(pk=get_anonymous_user().pk) diff --git a/authentik/core/api/utils.py b/authentik/core/api/utils.py index be583bdcc..b93f75155 100644 --- a/authentik/core/api/utils.py +++ b/authentik/core/api/utils.py @@ -1,6 +1,6 @@ """API Utilities""" from django.db.models import Model -from rest_framework.fields import CharField +from rest_framework.fields import CharField, IntegerField from rest_framework.serializers import Serializer, SerializerMethodField @@ -37,3 +37,15 @@ class TypeCreateSerializer(Serializer): def update(self, instance: Model, validated_data: dict) -> Model: raise NotImplementedError + + +class CacheSerializer(Serializer): + """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 diff --git a/authentik/core/templates/user/settings.html b/authentik/core/templates/user/settings.html index 047288c59..6305bc773 100644 --- a/authentik/core/templates/user/settings.html +++ b/authentik/core/templates/user/settings.html @@ -24,9 +24,7 @@
- -
-
+
{% user_stages as user_stages_loc %} {% for stage, stage_link in user_stages_loc.items %} diff --git a/authentik/core/urls.py b/authentik/core/urls.py index 9383191ab..f24a27e69 100644 --- a/authentik/core/urls.py +++ b/authentik/core/urls.py @@ -8,7 +8,6 @@ urlpatterns = [ # User views path("-/user/", user.UserSettingsView.as_view(), name="user-settings"), path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"), - path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"), path( "-/user/tokens/create/", user.TokenCreateView.as_view(), diff --git a/authentik/core/views/user.py b/authentik/core/views/user.py index 140c6e6da..13939ca4f 100644 --- a/authentik/core/views/user.py +++ b/authentik/core/views/user.py @@ -6,20 +6,15 @@ from django.contrib.auth.mixins import ( PermissionRequiredMixin as DjangoPermissionRequiredMixin, ) from django.contrib.messages.views import SuccessMessageMixin -from django.db.models.query import QuerySet from django.http.response import HttpResponse from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from django.views.generic import ListView, UpdateView +from django.views.generic import UpdateView from django.views.generic.base import TemplateView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin from guardian.shortcuts import get_objects_for_user -from authentik.admin.views.utils import ( - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import DeleteMessageView from authentik.core.forms.token import UserTokenForm from authentik.core.forms.users import UserDetailForm from authentik.core.models import Token, TokenIntents @@ -54,30 +49,6 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): return kwargs -class TokenListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all tokens""" - - model = Token - ordering = "expires" - permission_required = "authentik_core.view_token" - - template_name = "user/token_list.html" - search_fields = [ - "identifier", - "intent", - "description", - ] - - def get_queryset(self) -> QuerySet: - return super().get_queryset().filter(intent=TokenIntents.INTENT_API) - - class TokenCreateView( SuccessMessageMixin, LoginRequiredMixin, diff --git a/authentik/flows/api/__init__.py b/authentik/flows/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/flows/api/bindings.py b/authentik/flows/api/bindings.py new file mode 100644 index 000000000..32d127ffb --- /dev/null +++ b/authentik/flows/api/bindings.py @@ -0,0 +1,35 @@ +"""Flow Binding API Views""" +from rest_framework.serializers import ModelSerializer +from rest_framework.viewsets import ModelViewSet + +from authentik.flows.api.stages import StageSerializer +from authentik.flows.models import FlowStageBinding + + +class FlowStageBindingSerializer(ModelSerializer): + """FlowStageBinding Serializer""" + + stage_obj = StageSerializer(read_only=True, source="stage") + + class Meta: + + model = FlowStageBinding + fields = [ + "pk", + "policybindingmodel_ptr_id", + "target", + "stage", + "stage_obj", + "evaluate_on_plan", + "re_evaluate_policies", + "order", + "policies", + ] + + +class FlowStageBindingViewSet(ModelViewSet): + """FlowStageBinding Viewset""" + + queryset = FlowStageBinding.objects.all() + serializer_class = FlowStageBindingSerializer + filterset_fields = "__all__" diff --git a/authentik/flows/api.py b/authentik/flows/api/flows.py similarity index 64% rename from authentik/flows/api.py rename to authentik/flows/api/flows.py index dc66c5414..5979ce12f 100644 --- a/authentik/flows/api.py +++ b/authentik/flows/api/flows.py @@ -3,11 +3,10 @@ from dataclasses import dataclass from django.core.cache import cache from django.db.models import Model -from django.shortcuts import get_object_or_404, reverse +from django.shortcuts import get_object_or_404 from drf_yasg2.utils import swagger_auto_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action -from rest_framework.mixins import ListModelMixin from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ( @@ -16,13 +15,11 @@ from rest_framework.serializers import ( Serializer, SerializerMethodField, ) -from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet -from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer -from authentik.flows.models import Flow, FlowStageBinding, Stage +from authentik.core.api.utils import CacheSerializer +from authentik.flows.models import Flow from authentik.flows.planner import cache_key -from authentik.lib.templatetags.authentik_utils import verbose_name -from authentik.lib.utils.reflection import all_subclasses class FlowSerializer(ModelSerializer): @@ -84,6 +81,12 @@ class FlowViewSet(ModelViewSet): search_fields = ["name", "slug", "designation", "title"] filterset_fields = ["flow_uuid", "name", "slug", "designation"] + @swagger_auto_schema(responses={200: CacheSerializer(many=False)}) + @action(detail=False) + def cached(self, request: Request) -> Response: + """Info about cached flows""" + return Response(data={"count": len(cache.keys("flow_*"))}) + @swagger_auto_schema(responses={200: FlowDiagramSerializer()}) @action(detail=True, methods=["get"]) def diagram(self, request: Request, slug: str) -> Response: @@ -155,85 +158,3 @@ class FlowViewSet(ModelViewSet): ) diagram = "\n".join([str(x) for x in header + body + footer]) return Response({"diagram": diagram}) - - -class StageSerializer(ModelSerializer, MetaNameSerializer): - """Stage Serializer""" - - object_type = SerializerMethodField() - - def get_object_type(self, obj): - """Get object type so that we know which API Endpoint to use to get the full object""" - return obj._meta.object_name.lower().replace("stage", "") - - class Meta: - - model = Stage - fields = ["pk", "name", "object_type", "verbose_name", "verbose_name_plural"] - - -class StageViewSet(ReadOnlyModelViewSet): - """Stage Viewset""" - - queryset = Stage.objects.all() - serializer_class = StageSerializer - - def get_queryset(self): - return Stage.objects.select_subclasses() - - @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) - @action(detail=False) - def types(self, request: Request) -> Response: - """Get all creatable stage types""" - data = [] - for subclass in all_subclasses(self.queryset.model, False): - data.append( - { - "name": verbose_name(subclass), - "description": subclass.__doc__, - "link": reverse("authentik_admin:stage-create") - + f"?type={subclass.__name__}", - } - ) - data = sorted(data, key=lambda x: x["name"]) - return Response(TypeCreateSerializer(data, many=True).data) - - -class FlowStageBindingSerializer(ModelSerializer): - """FlowStageBinding Serializer""" - - stage_obj = StageSerializer(read_only=True, source="stage") - - class Meta: - - model = FlowStageBinding - fields = [ - "pk", - "policybindingmodel_ptr_id", - "target", - "stage", - "stage_obj", - "evaluate_on_plan", - "re_evaluate_policies", - "order", - "policies", - ] - - -class FlowStageBindingViewSet(ModelViewSet): - """FlowStageBinding Viewset""" - - queryset = FlowStageBinding.objects.all() - serializer_class = FlowStageBindingSerializer - filterset_fields = "__all__" - - -class FlowCacheViewSet(ListModelMixin, GenericViewSet): - """Info about cached flows""" - - queryset = Flow.objects.none() - serializer_class = Serializer - - def list(self, request: Request) -> Response: - """Info about cached flows""" - return Response(data={"pagination": {"count": len(cache.keys("flow_*"))}}) diff --git a/authentik/flows/api/stages.py b/authentik/flows/api/stages.py new file mode 100644 index 000000000..661a6e537 --- /dev/null +++ b/authentik/flows/api/stages.py @@ -0,0 +1,66 @@ +"""Flow Stage API Views""" +from django.shortcuts import reverse +from drf_yasg2.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from rest_framework.viewsets import ReadOnlyModelViewSet + +from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.flows.api.flows import FlowSerializer +from authentik.flows.models import Stage +from authentik.lib.templatetags.authentik_utils import verbose_name +from authentik.lib.utils.reflection import all_subclasses + + +class StageSerializer(ModelSerializer, MetaNameSerializer): + """Stage Serializer""" + + object_type = SerializerMethodField() + flow_set = FlowSerializer(many=True, required=False) + + def get_object_type(self, obj: Stage) -> str: + """Get object type so that we know which API Endpoint to use to get the full object""" + return obj._meta.object_name.lower().replace("stage", "") + + class Meta: + + model = Stage + fields = [ + "pk", + "name", + "object_type", + "verbose_name", + "verbose_name_plural", + "flow_set", + ] + + +class StageViewSet(ReadOnlyModelViewSet): + """Stage Viewset""" + + queryset = Stage.objects.all().select_related("flow_set") + serializer_class = StageSerializer + search_fields = ["name"] + filterset_fields = ["name"] + + def get_queryset(self): + return Stage.objects.select_subclasses() + + @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) + @action(detail=False) + def types(self, request: Request) -> Response: + """Get all creatable stage types""" + data = [] + for subclass in all_subclasses(self.queryset.model, False): + data.append( + { + "name": verbose_name(subclass), + "description": subclass.__doc__, + "link": reverse("authentik_admin:stage-create") + + f"?type={subclass.__name__}", + } + ) + data = sorted(data, key=lambda x: x["name"]) + return Response(TypeCreateSerializer(data, many=True).data) diff --git a/authentik/flows/models.py b/authentik/flows/models.py index 63702ac46..3a0acbe08 100644 --- a/authentik/flows/models.py +++ b/authentik/flows/models.py @@ -118,7 +118,7 @@ class Flow(SerializerModel, PolicyBindingModel): @property def serializer(self) -> BaseSerializer: - from authentik.flows.api import FlowSerializer + from authentik.flows.api.flows import FlowSerializer return FlowSerializer @@ -189,12 +189,12 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel): @property def serializer(self) -> BaseSerializer: - from authentik.flows.api import FlowStageBindingSerializer + from authentik.flows.api.bindings import FlowStageBindingSerializer return FlowStageBindingSerializer def __str__(self) -> str: - return f"{self.target} #{self.order} -> {self.stage}" + return f"{self.target} #{self.order}" class Meta: diff --git a/authentik/flows/tests/test_api.py b/authentik/flows/tests/test_api.py index 4aa1400aa..f242b95e8 100644 --- a/authentik/flows/tests/test_api.py +++ b/authentik/flows/tests/test_api.py @@ -3,7 +3,7 @@ from django.shortcuts import reverse from rest_framework.test import APITestCase from authentik.core.models import User -from authentik.flows.api import StageSerializer, StageViewSet +from authentik.flows.api.stages import StageSerializer, StageViewSet from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage from authentik.policies.dummy.models import DummyPolicy from authentik.policies.models import PolicyBinding diff --git a/authentik/flows/transfer/common.py b/authentik/flows/transfer/common.py index 590b15e03..c3cd629d0 100644 --- a/authentik/flows/transfer/common.py +++ b/authentik/flows/transfer/common.py @@ -22,6 +22,8 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]: "verbose_name", "verbose_name_plural", "object_type", + "flow_set", + "promptstage_set", ) for to_remove_name in to_remove: if to_remove_name in data: diff --git a/authentik/flows/transfer/importer.py b/authentik/flows/transfer/importer.py index 2839668a8..fd5ab82b6 100644 --- a/authentik/flows/transfer/importer.py +++ b/authentik/flows/transfer/importer.py @@ -65,14 +65,17 @@ class FlowImporter: return value for key, value in attrs.items(): - if isinstance(value, dict): - for idx, _inner_key in enumerate(value): - value[_inner_key] = updater(value[_inner_key]) - elif isinstance(value, list): - for idx, _inner_value in enumerate(value): - attrs[key][idx] = updater(_inner_value) - else: - attrs[key] = updater(value) + try: + if isinstance(value, dict): + for idx, _inner_key in enumerate(value): + value[_inner_key] = updater(value[_inner_key]) + elif isinstance(value, list): + for idx, _inner_value in enumerate(value): + attrs[key][idx] = updater(_inner_value) + else: + attrs[key] = updater(value) + except TypeError: + continue return attrs def __query_from_identifier(self, attrs: dict[str, Any]) -> Q: diff --git a/authentik/outposts/api/outpost_service_connections.py b/authentik/outposts/api/outpost_service_connections.py index 46f142ae6..1a5c7d947 100644 --- a/authentik/outposts/api/outpost_service_connections.py +++ b/authentik/outposts/api/outpost_service_connections.py @@ -1,7 +1,19 @@ """Outpost API Views""" -from rest_framework.serializers import ModelSerializer +from dataclasses import asdict + +from django.db.models.base import Model +from django.shortcuts import reverse +from drf_yasg2.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.viewsets import ModelViewSet +from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.lib.templatetags.authentik_utils import verbose_name +from authentik.lib.utils.reflection import all_subclasses from authentik.outposts.models import ( DockerServiceConnection, KubernetesServiceConnection, @@ -9,32 +21,81 @@ from authentik.outposts.models import ( ) -class ServiceConnectionSerializer(ModelSerializer): +class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer): """ServiceConnection Serializer""" + object_type = SerializerMethodField() + + def get_object_type(self, obj: OutpostServiceConnection) -> str: + """Get object type so that we know which API Endpoint to use to get the full object""" + return obj._meta.object_name.lower().replace("serviceconnection", "") + class Meta: model = OutpostServiceConnection - fields = ["pk", "name"] + fields = [ + "pk", + "name", + "local", + "object_type", + "verbose_name", + "verbose_name_plural", + ] + + +class ServiceConnectionStateSerializer(Serializer): + """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""" - queryset = OutpostServiceConnection.objects.all() + queryset = OutpostServiceConnection.objects.select_subclasses() serializer_class = ServiceConnectionSerializer + search_fields = ["name"] + filterset_fields = ["name"] + + @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) + @action(detail=False) + def types(self, request: Request) -> Response: + """Get all creatable service connection types""" + data = [] + for subclass in all_subclasses(self.queryset.model): + data.append( + { + "name": verbose_name(subclass), + "description": subclass.__doc__, + "link": reverse("authentik_admin:outpost-service-connection-create") + + f"?type={subclass.__name__}", + } + ) + return Response(TypeCreateSerializer(data, many=True).data) + + @swagger_auto_schema(responses={200: ServiceConnectionStateSerializer(many=False)}) + @action(detail=True) + # pylint: disable=unused-argument, invalid-name + def state(self, request: Request, pk: str) -> Response: + """Get the service connection's state""" + connection = self.get_object() + return Response(asdict(connection.state)) -class DockerServiceConnectionSerializer(ModelSerializer): +class DockerServiceConnectionSerializer(ServiceConnectionSerializer): """DockerServiceConnection Serializer""" class Meta: model = DockerServiceConnection - fields = [ - "pk", - "name", - "local", + fields = ServiceConnectionSerializer.Meta.fields + [ "url", "tls_verification", "tls_authentication", @@ -48,13 +109,13 @@ class DockerServiceConnectionViewSet(ModelViewSet): serializer_class = DockerServiceConnectionSerializer -class KubernetesServiceConnectionSerializer(ModelSerializer): +class KubernetesServiceConnectionSerializer(ServiceConnectionSerializer): """KubernetesServiceConnection Serializer""" class Meta: model = KubernetesServiceConnection - fields = ["pk", "name", "local", "kubeconfig"] + fields = ServiceConnectionSerializer.Meta.fields + ["kubeconfig"] class KubernetesServiceConnectionViewSet(ModelViewSet): diff --git a/authentik/policies/api.py b/authentik/policies/api.py index 7f179d330..9b480d6b6 100644 --- a/authentik/policies/api.py +++ b/authentik/policies/api.py @@ -1,17 +1,25 @@ """policy API Views""" from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist -from rest_framework.mixins import ListModelMixin +from django.shortcuts import reverse +from drf_yasg2.utils import swagger_auto_schema +from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ( ModelSerializer, PrimaryKeyRelatedField, - Serializer, SerializerMethodField, ) -from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet +from authentik.core.api.utils import ( + CacheSerializer, + MetaNameSerializer, + TypeCreateSerializer, +) +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 @@ -45,20 +53,27 @@ class PolicyBindingModelForeignKey(PrimaryKeyRelatedField): return correct_model.pk -class PolicySerializer(ModelSerializer): +class PolicySerializer(ModelSerializer, MetaNameSerializer): """Policy Serializer""" _resolve_inheritance: bool object_type = SerializerMethodField() + bound_to = SerializerMethodField() def __init__(self, *args, resolve_inheritance: bool = True, **kwargs): super().__init__(*args, **kwargs) self._resolve_inheritance = resolve_inheritance - def get_object_type(self, obj): + def get_object_type(self, obj: Policy) -> str: """Get object type so that we know which API Endpoint to use to get the full object""" - return obj._meta.object_name.lower().replace("provider", "") + return obj._meta.object_name.lower().replace("policy", "") + + def get_bound_to(self, obj: Policy) -> int: + """Return objects policy is bound to""" + if not obj.bindings.exists() and not obj.promptstage_set.exists(): + return 0 + return obj.bindings.count() def to_representation(self, instance: Policy): # pyright: reportGeneralTypeIssues=false @@ -71,7 +86,15 @@ class PolicySerializer(ModelSerializer): class Meta: model = Policy - fields = ["pk", "name", "execution_logging", "object_type"] + fields = [ + "pk", + "name", + "execution_logging", + "object_type", + "verbose_name", + "verbose_name_plural", + "bound_to", + ] depth = 3 @@ -84,9 +107,34 @@ class PolicyViewSet(ReadOnlyModelViewSet): "bindings": ["isnull"], "promptstage": ["isnull"], } + search_fields = ["name"] def get_queryset(self): - return Policy.objects.select_subclasses() + return Policy.objects.select_subclasses().prefetch_related( + "bindings", "promptstage_set" + ) + + @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) + @action(detail=False) + def types(self, request: Request) -> Response: + """Get all creatable policy types""" + data = [] + for subclass in all_subclasses(self.queryset.model): + data.append( + { + "name": verbose_name(subclass), + "description": subclass.__doc__, + "link": reverse("authentik_admin:policy-create") + + f"?type={subclass.__name__}", + } + ) + return Response(TypeCreateSerializer(data, many=True).data) + + @swagger_auto_schema(responses={200: CacheSerializer(many=False)}) + @action(detail=False) + def cached(self, request: Request) -> Response: + """Info about cached policies""" + return Response(data={"count": len(cache.keys("policy_*"))}) class PolicyBindingSerializer(ModelSerializer): @@ -124,14 +172,3 @@ class PolicyBindingViewSet(ModelViewSet): serializer_class = PolicyBindingSerializer filterset_fields = ["policy", "target", "enabled", "order", "timeout"] search_fields = ["policy__name"] - - -class PolicyCacheViewSet(ListModelMixin, GenericViewSet): - """Info about cached policies""" - - queryset = Policy.objects.none() - serializer_class = Serializer - - def list(self, request: Request) -> Response: - """Info about cached policies""" - return Response(data={"pagination": {"count": len(cache.keys("policy_*"))}}) diff --git a/authentik/stages/authenticator_static/api.py b/authentik/stages/authenticator_static/api.py index 0663863ca..8ac6fc8c6 100644 --- a/authentik/stages/authenticator_static/api.py +++ b/authentik/stages/authenticator_static/api.py @@ -1,7 +1,7 @@ """AuthenticatorStaticStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.authenticator_static.models import AuthenticatorStaticStage diff --git a/authentik/stages/authenticator_totp/api.py b/authentik/stages/authenticator_totp/api.py index 6a8dab0d8..b7e40fc6c 100644 --- a/authentik/stages/authenticator_totp/api.py +++ b/authentik/stages/authenticator_totp/api.py @@ -1,7 +1,7 @@ """AuthenticatorTOTPStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage diff --git a/authentik/stages/authenticator_validate/api.py b/authentik/stages/authenticator_validate/api.py index 54bfa25b0..23a0738dd 100644 --- a/authentik/stages/authenticator_validate/api.py +++ b/authentik/stages/authenticator_validate/api.py @@ -1,7 +1,7 @@ """AuthenticatorValidateStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage diff --git a/authentik/stages/authenticator_validate/forms.py b/authentik/stages/authenticator_validate/forms.py index 23e87fba0..bdc4c9316 100644 --- a/authentik/stages/authenticator_validate/forms.py +++ b/authentik/stages/authenticator_validate/forms.py @@ -19,7 +19,7 @@ class ValidationForm(forms.Form): label=_("Please enter the token from your device."), widget=forms.TextInput( attrs={ - "autocomplete": "off", + "autocomplete": "one-time-code", "placeholder": "123456", "autofocus": "autofocus", } diff --git a/authentik/stages/authenticator_webauthn/api.py b/authentik/stages/authenticator_webauthn/api.py index d3ecaed79..6fcebd119 100644 --- a/authentik/stages/authenticator_webauthn/api.py +++ b/authentik/stages/authenticator_webauthn/api.py @@ -1,7 +1,7 @@ """AuthenticateWebAuthnStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage diff --git a/authentik/stages/captcha/api.py b/authentik/stages/captcha/api.py index a60103666..6cd61185f 100644 --- a/authentik/stages/captcha/api.py +++ b/authentik/stages/captcha/api.py @@ -1,7 +1,7 @@ """CaptchaStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.captcha.models import CaptchaStage diff --git a/authentik/stages/consent/api.py b/authentik/stages/consent/api.py index 3e106c937..926c3d3b8 100644 --- a/authentik/stages/consent/api.py +++ b/authentik/stages/consent/api.py @@ -1,7 +1,7 @@ """ConsentStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.consent.models import ConsentStage diff --git a/authentik/stages/dummy/api.py b/authentik/stages/dummy/api.py index 3b677bbac..02e1c9463 100644 --- a/authentik/stages/dummy/api.py +++ b/authentik/stages/dummy/api.py @@ -1,7 +1,7 @@ """DummyStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.dummy.models import DummyStage diff --git a/authentik/stages/email/api.py b/authentik/stages/email/api.py index ffe5d6089..2a41c0f82 100644 --- a/authentik/stages/email/api.py +++ b/authentik/stages/email/api.py @@ -1,7 +1,7 @@ """EmailStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.email.models import EmailStage, get_template_choices diff --git a/authentik/stages/identification/api.py b/authentik/stages/identification/api.py index aba1629ec..677467b70 100644 --- a/authentik/stages/identification/api.py +++ b/authentik/stages/identification/api.py @@ -1,7 +1,7 @@ """Identification Stage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.identification.models import IdentificationStage diff --git a/authentik/stages/invitation/api.py b/authentik/stages/invitation/api.py index e8aadea19..8bc103c56 100644 --- a/authentik/stages/invitation/api.py +++ b/authentik/stages/invitation/api.py @@ -2,7 +2,7 @@ from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.invitation.models import Invitation, InvitationStage @@ -34,7 +34,9 @@ class InvitationSerializer(ModelSerializer): "pk", "expires", "fixed_data", + "created_by", ] + depth = 2 class InvitationViewSet(ModelViewSet): @@ -42,6 +44,9 @@ class InvitationViewSet(ModelViewSet): queryset = Invitation.objects.all() serializer_class = InvitationSerializer + order = ["-expires"] + search_fields = ["created_by__username", "expires"] + filterset_fields = ["created_by__username", "expires"] def perform_create(self, serializer: InvitationSerializer): serializer.instance.created_by = self.request.user diff --git a/authentik/stages/password/api.py b/authentik/stages/password/api.py index e5ca7dc22..48499663e 100644 --- a/authentik/stages/password/api.py +++ b/authentik/stages/password/api.py @@ -1,7 +1,7 @@ """PasswordStage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.password.models import PasswordStage diff --git a/authentik/stages/prompt/api.py b/authentik/stages/prompt/api.py index a04f021a8..a318ff646 100644 --- a/authentik/stages/prompt/api.py +++ b/authentik/stages/prompt/api.py @@ -3,7 +3,7 @@ from rest_framework.serializers import CharField, ModelSerializer from rest_framework.validators import UniqueValidator from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.prompt.models import Prompt, PromptStage @@ -31,6 +31,8 @@ class PromptStageViewSet(ModelViewSet): class PromptSerializer(ModelSerializer): """Prompt Serializer""" + promptstage_set = StageSerializer(many=True, required=False) + class Meta: model = Prompt @@ -42,11 +44,13 @@ class PromptSerializer(ModelSerializer): "required", "placeholder", "order", + "promptstage_set", ] class PromptViewSet(ModelViewSet): """Prompt Viewset""" - queryset = Prompt.objects.all() + queryset = Prompt.objects.all().prefetch_related("promptstage_set") serializer_class = PromptSerializer + filterset_fields = ["field_key", "label", "type", "placeholder"] diff --git a/authentik/stages/user_delete/api.py b/authentik/stages/user_delete/api.py index 0f60f1479..195fdcca7 100644 --- a/authentik/stages/user_delete/api.py +++ b/authentik/stages/user_delete/api.py @@ -1,7 +1,7 @@ """User Delete Stage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.user_delete.models import UserDeleteStage diff --git a/authentik/stages/user_login/api.py b/authentik/stages/user_login/api.py index 406267fb0..55ff8a00c 100644 --- a/authentik/stages/user_login/api.py +++ b/authentik/stages/user_login/api.py @@ -1,7 +1,7 @@ """Login Stage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.user_login.models import UserLoginStage diff --git a/authentik/stages/user_logout/api.py b/authentik/stages/user_logout/api.py index 8d67c7d99..89017e3b9 100644 --- a/authentik/stages/user_logout/api.py +++ b/authentik/stages/user_logout/api.py @@ -1,7 +1,7 @@ """Logout Stage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.user_logout.models import UserLogoutStage diff --git a/authentik/stages/user_write/api.py b/authentik/stages/user_write/api.py index 121285eb6..f1dadc769 100644 --- a/authentik/stages/user_write/api.py +++ b/authentik/stages/user_write/api.py @@ -1,7 +1,7 @@ """User Write Stage API Views""" from rest_framework.viewsets import ModelViewSet -from authentik.flows.api import StageSerializer +from authentik.flows.api.stages import StageSerializer from authentik.stages.user_write.models import UserWriteStage diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3adc8b401..f087d013a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,5 +1,6 @@ trigger: - master + - next resources: - repo: self diff --git a/outpost/azure-pipelines.yml b/outpost/azure-pipelines.yml index 410df0d9a..012784ce3 100644 --- a/outpost/azure-pipelines.yml +++ b/outpost/azure-pipelines.yml @@ -1,5 +1,6 @@ trigger: - master + - next variables: ${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}: diff --git a/swagger.yaml b/swagger.yaml index e6b8005c1..0aba5c0e2 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -309,6 +309,16 @@ paths: operationId: core_groups_list description: Group Viewset parameters: + - name: name + in: query + description: '' + required: false + type: string + - name: is_superuser + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -436,6 +446,26 @@ paths: operationId: core_tokens_list description: Token Viewset parameters: + - name: identifier + in: query + description: '' + required: false + type: string + - name: intent + in: query + description: '' + required: false + type: string + - name: user__username + in: query + description: '' + required: false + type: string + - name: description + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -582,6 +612,21 @@ paths: operationId: core_users_list description: User Viewset parameters: + - name: username + in: query + description: '' + required: false + type: string + - name: name + in: query + description: '' + required: false + type: string + - name: is_active + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -649,6 +694,21 @@ paths: operationId: core_users_me description: Get information about current user parameters: + - name: username + in: query + description: '' + required: false + type: string + - name: name + in: query + description: '' + required: false + type: string + - name: is_active + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -1600,59 +1660,6 @@ paths: required: true type: string format: uuid - /flows/cached/: - get: - operationId: flows_cached_list - description: Info about cached flows - parameters: - - 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: A page number within the paginated result set. - required: false - type: integer - - name: page_size - in: query - description: Number of results to return per page. - required: false - type: integer - responses: - '200': - description: '' - schema: - required: - - count - - results - type: object - properties: - count: - type: integer - next: - type: string - format: uri - x-nullable: true - previous: - type: string - format: uri - x-nullable: true - results: - type: array - items: - description: '' - type: object - properties: {} - tags: - - flows - parameters: [] /flows/instances/: get: operationId: flows_instances_list @@ -1740,6 +1747,59 @@ paths: tags: - flows parameters: [] + /flows/instances/cached/: + get: + operationId: flows_instances_cached + description: Info about cached flows + parameters: + - name: flow_uuid + in: query + description: '' + required: false + type: string + - name: name + in: query + description: '' + required: false + type: string + - name: slug + in: query + description: '' + required: false + type: string + - name: designation + 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: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: Generic cache stats for an object + schema: + $ref: '#/definitions/Cache' + tags: + - flows + parameters: [] /flows/instances/{slug}/: get: operationId: flows_instances_read @@ -2107,6 +2167,11 @@ paths: operationId: outposts_service_connections_all_list description: ServiceConnection Viewset parameters: + - name: name + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -2169,6 +2234,47 @@ paths: tags: - outposts parameters: [] + /outposts/service_connections/all/types/: + get: + operationId: outposts_service_connections_all_types + description: Get all creatable service connection types + parameters: + - name: name + 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: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: Types of an object that can be created + schema: + description: '' + type: array + items: + $ref: '#/definitions/TypeCreate' + tags: + - outposts + parameters: [] /outposts/service_connections/all/{uuid}/: get: operationId: outposts_service_connections_all_read @@ -2229,6 +2335,25 @@ paths: required: true type: string format: uuid + /outposts/service_connections/all/{uuid}/state/: + get: + operationId: outposts_service_connections_all_state + description: Get the service connection's state + parameters: [] + responses: + '200': + description: Serializer for Service connection state + schema: + $ref: '#/definitions/ServiceConnectionState' + tags: + - outposts + parameters: + - name: uuid + in: path + description: A UUID string identifying this Outpost Service-Connection. + required: true + type: string + format: uuid /outposts/service_connections/docker/: get: operationId: outposts_service_connections_docker_list @@ -2544,6 +2669,95 @@ paths: tags: - policies parameters: [] + /policies/all/cached/: + get: + operationId: policies_all_cached + description: Info about cached policies + parameters: + - name: bindings__isnull + in: query + description: '' + required: false + type: string + - name: promptstage__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: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: Generic cache stats for an object + schema: + $ref: '#/definitions/Cache' + tags: + - policies + parameters: [] + /policies/all/types/: + get: + operationId: policies_all_types + description: Get all creatable policy types + parameters: + - name: bindings__isnull + in: query + description: '' + required: false + type: string + - name: promptstage__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: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: Types of an object that can be created + schema: + description: '' + type: array + items: + $ref: '#/definitions/TypeCreate' + tags: + - policies + parameters: [] /policies/all/{policy_uuid}/: get: operationId: policies_all_read @@ -2715,59 +2929,6 @@ paths: required: true type: string format: uuid - /policies/cached/: - get: - operationId: policies_cached_list - description: Info about cached policies - parameters: - - 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: A page number within the paginated result set. - required: false - type: integer - - name: page_size - in: query - description: Number of results to return per page. - required: false - type: integer - responses: - '200': - description: '' - schema: - required: - - count - - results - type: object - properties: - count: - type: integer - next: - type: string - format: uri - x-nullable: true - previous: - type: string - format: uri - x-nullable: true - results: - type: array - items: - description: '' - type: object - properties: {} - tags: - - policies - parameters: [] /policies/dummy/: get: operationId: policies_dummy_list @@ -3840,6 +4001,47 @@ paths: tags: - propertymappings parameters: [] + /propertymappings/all/types/: + get: + operationId: propertymappings_all_types + description: Get all creatable property-mapping types + parameters: + - name: managed__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: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: Types of an object that can be created + schema: + description: '' + type: array + items: + $ref: '#/definitions/TypeCreate' + tags: + - propertymappings + parameters: [] /propertymappings/all/{pm_uuid}/: get: operationId: propertymappings_all_read @@ -5374,6 +5576,11 @@ paths: operationId: stages_all_list description: Stage Viewset parameters: + - name: name + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -5425,6 +5632,11 @@ paths: operationId: stages_all_types description: Get all creatable stage types parameters: + - name: name + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -6618,78 +6830,21 @@ paths: required: true type: string format: uuid - /stages/invitation/: - get: - operationId: stages_invitation_list - description: InvitationStage Viewset - parameters: - - 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: A page number within the paginated result set. - required: false - type: integer - - name: page_size - in: query - description: Number of results to return per page. - required: false - type: integer - responses: - '200': - description: '' - schema: - required: - - count - - results - type: object - properties: - count: - type: integer - next: - type: string - format: uri - x-nullable: true - previous: - type: string - format: uri - x-nullable: true - results: - type: array - items: - $ref: '#/definitions/InvitationStage' - tags: - - stages - post: - operationId: stages_invitation_create - description: InvitationStage Viewset - parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/InvitationStage' - responses: - '201': - description: '' - schema: - $ref: '#/definitions/InvitationStage' - tags: - - stages - parameters: [] /stages/invitation/invitations/: get: operationId: stages_invitation_invitations_list description: Invitation Viewset parameters: + - name: created_by__username + in: query + description: '' + required: false + type: string + - name: expires + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -6812,9 +6967,76 @@ paths: required: true type: string format: uuid - /stages/invitation/{stage_uuid}/: + /stages/invitation/stages/: get: - operationId: stages_invitation_read + operationId: stages_invitation_stages_list + description: InvitationStage Viewset + parameters: + - 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: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: '' + schema: + required: + - count + - results + type: object + properties: + count: + type: integer + next: + type: string + format: uri + x-nullable: true + previous: + type: string + format: uri + x-nullable: true + results: + type: array + items: + $ref: '#/definitions/InvitationStage' + tags: + - stages + post: + operationId: stages_invitation_stages_create + description: InvitationStage Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/InvitationStage' + responses: + '201': + description: '' + schema: + $ref: '#/definitions/InvitationStage' + tags: + - stages + parameters: [] + /stages/invitation/stages/{stage_uuid}/: + get: + operationId: stages_invitation_stages_read description: InvitationStage Viewset parameters: [] responses: @@ -6825,7 +7047,7 @@ paths: tags: - stages put: - operationId: stages_invitation_update + operationId: stages_invitation_stages_update description: InvitationStage Viewset parameters: - name: data @@ -6841,7 +7063,7 @@ paths: tags: - stages patch: - operationId: stages_invitation_partial_update + operationId: stages_invitation_stages_partial_update description: InvitationStage Viewset parameters: - name: data @@ -6857,7 +7079,7 @@ paths: tags: - stages delete: - operationId: stages_invitation_delete + operationId: stages_invitation_stages_delete description: InvitationStage Viewset parameters: [] responses: @@ -7004,6 +7226,26 @@ paths: operationId: stages_prompt_prompts_list description: Prompt Viewset parameters: + - name: field_key + in: query + description: '' + required: false + type: string + - name: label + in: query + description: '' + required: false + type: string + - name: type + in: query + description: '' + required: false + type: string + - name: placeholder + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -7959,6 +8201,57 @@ definitions: attributes: title: Attributes type: object + User: + title: User + description: User Serializer + required: + - username + - name + type: object + properties: + pk: + title: ID + type: integer + readOnly: true + username: + title: Username + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + type: string + pattern: ^[\w.@+-]+$ + maxLength: 150 + minLength: 1 + name: + title: Name + description: User's display name. + type: string + minLength: 1 + is_active: + title: Active + description: Designates whether this user should be treated as active. Unselect + this instead of deleting accounts. + type: boolean + last_login: + title: Last login + type: string + format: date-time + x-nullable: true + is_superuser: + title: Is superuser + type: boolean + readOnly: true + email: + title: Email address + type: string + format: email + maxLength: 254 + avatar: + title: Avatar + type: string + readOnly: true + attributes: + title: Attributes + type: object Token: description: Token Serializer required: @@ -7986,11 +8279,17 @@ definitions: - api - recovery user: - title: User - type: integer + $ref: '#/definitions/User' description: title: Description type: string + expires: + title: Expires + type: string + format: date-time + expiring: + title: Expiring + type: boolean TokenView: description: Show token's current key type: object @@ -8000,43 +8299,6 @@ definitions: type: string readOnly: true minLength: 1 - User: - description: User Serializer - required: - - username - - name - type: object - properties: - pk: - title: ID - type: integer - readOnly: true - username: - title: Username - description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ - only. - type: string - pattern: ^[\w.@+-]+$ - maxLength: 150 - minLength: 1 - name: - title: Name - description: User's display name. - type: string - minLength: 1 - is_superuser: - title: Is superuser - type: boolean - readOnly: true - email: - title: Email address - type: string - format: email - maxLength: 254 - avatar: - title: Avatar - type: string - readOnly: true CertificateKeyPair: description: CertificateKeyPair Serializer required: @@ -8338,82 +8600,6 @@ definitions: webhook_url: title: Webhook url type: string - Stage: - title: Stage obj - description: Stage Serializer - required: - - name - type: object - properties: - pk: - title: Stage uuid - type: string - format: uuid - readOnly: true - name: - title: Name - type: string - minLength: 1 - object_type: - title: Object type - type: string - readOnly: true - verbose_name: - title: Verbose name - type: string - readOnly: true - verbose_name_plural: - title: Verbose name plural - type: string - readOnly: true - FlowStageBinding: - description: FlowStageBinding Serializer - required: - - target - - stage - - order - type: object - properties: - pk: - title: Fsb uuid - type: string - format: uuid - readOnly: true - policybindingmodel_ptr_id: - title: Policybindingmodel ptr id - type: string - readOnly: true - target: - title: Target - type: string - format: uuid - stage: - title: Stage - type: string - format: uuid - stage_obj: - $ref: '#/definitions/Stage' - evaluate_on_plan: - title: Evaluate on plan - description: Evaluate policies during the Flow planning process. Disable this - for input-based policies. - type: boolean - re_evaluate_policies: - title: Re evaluate policies - description: Evaluate policies when the Stage is present to the user. - type: boolean - order: - title: Order - type: integer - maximum: 2147483647 - minimum: -2147483648 - policies: - type: array - items: - type: string - format: uuid - readOnly: true - uniqueItems: true Flow: description: Flow Serializer required: @@ -8486,6 +8672,95 @@ definitions: title: Cache count type: string readOnly: true + Stage: + title: Stage obj + description: Stage Serializer + required: + - name + type: object + properties: + pk: + title: Stage uuid + type: string + format: uuid + readOnly: true + name: + title: Name + type: string + minLength: 1 + object_type: + title: Object type + type: string + readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' + FlowStageBinding: + description: FlowStageBinding Serializer + required: + - target + - stage + - order + type: object + properties: + pk: + title: Fsb uuid + type: string + format: uuid + readOnly: true + policybindingmodel_ptr_id: + title: Policybindingmodel ptr id + type: string + readOnly: true + target: + title: Target + type: string + format: uuid + stage: + title: Stage + type: string + format: uuid + stage_obj: + $ref: '#/definitions/Stage' + evaluate_on_plan: + title: Evaluate on plan + description: Evaluate policies during the Flow planning process. Disable this + for input-based policies. + type: boolean + re_evaluate_policies: + title: Re evaluate policies + description: Evaluate policies when the Stage is present to the user. + type: boolean + order: + title: Order + type: integer + maximum: 2147483647 + minimum: -2147483648 + policies: + type: array + items: + type: string + format: uuid + readOnly: true + uniqueItems: true + Cache: + description: Generic cache stats for an object + type: object + properties: + count: + title: Count + type: integer + readOnly: true FlowDiagram: description: response of the flow's /diagram/ action type: object @@ -8712,6 +8987,55 @@ definitions: title: Name type: string minLength: 1 + local: + title: Local + description: If enabled, use the local connection. Required Docker socket/Kubernetes + Integration + type: boolean + object_type: + title: Object type + type: string + readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + TypeCreate: + description: Types of an object that can be created + type: object + properties: + name: + title: Name + type: string + readOnly: true + minLength: 1 + description: + title: Description + type: string + readOnly: true + minLength: 1 + link: + title: Link + type: string + readOnly: true + minLength: 1 + ServiceConnectionState: + description: Serializer for Service connection state + type: object + properties: + healthy: + title: Healthy + type: boolean + readOnly: true + version: + title: Version + type: string + readOnly: true + minLength: 1 DockerServiceConnection: description: DockerServiceConnection Serializer required: @@ -8733,6 +9057,18 @@ definitions: description: If enabled, use the local connection. Required Docker socket/Kubernetes Integration type: boolean + object_type: + title: Object type + type: string + readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true url: title: Url description: Can be in the format of 'unix://' when connecting to a @@ -8774,6 +9110,18 @@ definitions: description: If enabled, use the local connection. Required Docker socket/Kubernetes Integration type: boolean + object_type: + title: Object type + type: string + readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true kubeconfig: title: Kubeconfig description: Paste your kubeconfig here. authentik will automatically use @@ -8801,6 +9149,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true PolicyBinding: description: PolicyBinding Serializer required: @@ -9175,6 +9535,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true result: title: Result type: boolean @@ -9210,6 +9582,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true action: title: Action description: Match created events with this action type. When left empty, @@ -9315,6 +9699,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true expression: title: Expression type: string @@ -9341,6 +9737,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true group: title: Group type: string @@ -9368,6 +9776,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true password_field: title: Password field description: Field key to check, field keys defined in Prompt stages are available. @@ -9402,6 +9822,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true password_field: title: Password field description: Field key to check, field keys defined in Prompt stages are available. @@ -9459,6 +9891,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true days: title: Days type: integer @@ -9489,6 +9933,18 @@ definitions: title: Object type type: string readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true + bound_to: + title: Bound to + type: integer + readOnly: true check_ip: title: Check ip type: boolean @@ -9641,25 +10097,6 @@ definitions: title: Verbose name plural type: string readOnly: true - TypeCreate: - description: Types of an object that can be created - type: object - properties: - name: - title: Name - type: string - readOnly: true - minLength: 1 - description: - title: Description - type: string - readOnly: true - minLength: 1 - link: - title: Link - type: string - readOnly: true - minLength: 1 OAuth2Provider: description: OAuth2Provider Serializer required: @@ -10511,6 +10948,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' configure_flow: title: Configure flow description: Flow used by an authenticated user to configure this Stage. If @@ -10551,6 +10993,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' configure_flow: title: Configure flow description: Flow used by an authenticated user to configure this Stage. If @@ -10591,6 +11038,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' not_configured_action: title: Not configured action type: string @@ -10630,6 +11082,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' CaptchaStage: description: CaptchaStage Serializer required: @@ -10659,6 +11116,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' public_key: title: Public key description: Public key, acquired from https://www.google.com/recaptcha/intro/v3.html @@ -10696,6 +11158,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' mode: title: Mode type: string @@ -10735,6 +11202,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' EmailStage: description: EmailStage Serializer required: @@ -10762,6 +11234,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' use_global_settings: title: Use global settings description: When enabled, global Email connection settings will be used and @@ -10844,6 +11321,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' user_fields: description: '' type: array @@ -10883,6 +11365,263 @@ definitions: type: string format: uuid x-nullable: true + Invitation: + description: Invitation Serializer + type: object + properties: + pk: + title: Invite uuid + type: string + format: uuid + readOnly: true + expires: + title: Expires + type: string + format: date-time + x-nullable: true + fixed_data: + title: Fixed data + description: Optional fixed data to enforce on user enrollment. + type: object + created_by: + description: Custom User model to allow easier adding o f user-based settings + required: + - password + - username + - name + type: object + properties: + id: + title: ID + type: integer + readOnly: true + password: + title: Password + type: string + maxLength: 128 + minLength: 1 + last_login: + title: Last login + type: string + format: date-time + x-nullable: true + username: + title: Username + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + type: string + pattern: ^[\w.@+-]+$ + maxLength: 150 + minLength: 1 + first_name: + title: First name + type: string + maxLength: 150 + last_name: + title: Last name + type: string + maxLength: 150 + email: + title: Email address + type: string + format: email + maxLength: 254 + is_active: + title: Active + description: Designates whether this user should be treated as active. + Unselect this instead of deleting accounts. + type: boolean + date_joined: + title: Date joined + type: string + format: date-time + uuid: + title: Uuid + type: string + format: uuid + readOnly: true + name: + title: Name + description: User's display name. + type: string + minLength: 1 + password_change_date: + title: Password change date + type: string + format: date-time + readOnly: true + attributes: + 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 + properties: + id: + title: ID + type: integer + readOnly: true + name: + title: Name + type: string + maxLength: 150 + minLength: 1 + permissions: + type: array + items: + type: integer + 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 + - content_type + type: object + properties: + id: + title: ID + type: integer + readOnly: true + name: + title: Name + type: string + maxLength: 255 + minLength: 1 + codename: + title: Codename + type: string + maxLength: 100 + minLength: 1 + content_type: + title: Content type + 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 + type: object + properties: + pbm_uuid: + title: Pbm uuid + type: string + format: uuid + readOnly: true + name: + title: Name + description: Source's display Name. + type: string + minLength: 1 + slug: + title: Slug + description: Internal source name, used in URLs. + type: string + format: slug + pattern: ^[-a-zA-Z0-9_]+$ + maxLength: 50 + minLength: 1 + enabled: + title: Enabled + type: boolean + authentication_flow: + title: Authentication flow + description: Flow to use when authenticating existing users. + type: string + format: uuid + x-nullable: true + enrollment_flow: + title: Enrollment flow + description: Flow to use when enrolling new users. + type: string + format: uuid + x-nullable: true + policies: + type: array + items: + type: string + format: uuid + readOnly: true + uniqueItems: true + property_mappings: + type: array + items: + type: string + format: uuid + uniqueItems: true + readOnly: true + ak_groups: + description: '' + type: array + items: + 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 InvitationStage: description: InvitationStage Serializer required: @@ -10910,30 +11649,17 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' continue_flow_without_invitation: title: Continue flow without invitation description: If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given. type: boolean - Invitation: - description: Invitation Serializer - type: object - properties: - pk: - title: Invite uuid - type: string - format: uuid - readOnly: true - expires: - title: Expires - type: string - format: date-time - x-nullable: true - fixed_data: - title: Fixed data - description: Optional fixed data to enforce on user enrollment. - type: object PasswordStage: description: PasswordStage Serializer required: @@ -10962,6 +11688,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' backends: description: '' type: array @@ -11034,6 +11765,11 @@ definitions: type: integer maximum: 2147483647 minimum: -2147483648 + promptstage_set: + description: '' + type: array + items: + $ref: '#/definitions/Stage' PromptStage: description: PromptStage Serializer required: @@ -11062,6 +11798,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' fields: type: array items: @@ -11101,6 +11842,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' UserLoginStage: description: UserLoginStage Serializer required: @@ -11128,6 +11874,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' session_duration: title: Session duration description: 'Determines how long a session lasts. Default of 0 means that @@ -11161,6 +11912,11 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' UserWriteStage: description: UserWriteStage Serializer required: @@ -11188,3 +11944,8 @@ definitions: title: Verbose name plural type: string readOnly: true + flow_set: + description: '' + type: array + items: + $ref: '#/definitions/Flow' diff --git a/web/azure-pipelines.yml b/web/azure-pipelines.yml index 32aaec773..45ba50d2f 100644 --- a/web/azure-pipelines.yml +++ b/web/azure-pipelines.yml @@ -1,5 +1,6 @@ trigger: - master + - next variables: ${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}: diff --git a/web/package-lock.json b/web/package-lock.json index 40ecca3c6..954ed0718 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -144,30 +144,16 @@ } }, "@sentry/browser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.1.0.tgz", - "integrity": "sha512-t3y2TLXDWgvfknyH8eKj/9mghJfSEqItFyp74zPu1Src6kOPjkd4Sa7o4+bdkNgA8dIIOrDAhRUbB2sq4sWMCA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.0.tgz", + "integrity": "sha512-4r3paHcHXLemj471BtNDhUs2kvJxk5XDRplz1dbC/LHXN5PWEXP4anhGILxOlxqi4y33r53PIZu3xXFjznaVZA==", "requires": { - "@sentry/core": "6.1.0", - "@sentry/types": "6.1.0", - "@sentry/utils": "6.1.0", + "@sentry/core": "6.2.0", + "@sentry/types": "6.2.0", + "@sentry/utils": "6.2.0", "tslib": "^1.9.3" }, "dependencies": { - "@sentry/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.1.0.tgz", - "integrity": "sha512-kIaN52Fw5K+2mKRaHE2YluJ+F/qMGSUzZXIFDNdC6OUMXQ4TM8gZTrITXs8CLDm7cK8iCqFCtzKOjKK6KyOKAg==" - }, - "@sentry/utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.1.0.tgz", - "integrity": "sha512-6JAplzUOS6bEwfX0PDRZBbYRvn9EN22kZfcL0qGHtM9L0QQ5ybjbbVwOpbXgRkiZx++dQbzLFtelxnDhsbFG+Q==", - "requires": { - "@sentry/types": "6.1.0", - "tslib": "^1.9.3" - } - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -176,51 +162,17 @@ } }, "@sentry/core": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.1.0.tgz", - "integrity": "sha512-57mXkp3NoyxRycXrL+Ec6bYS6UYJZp9tYX0lUp5Ry2M0FxDZ3Q4drkjr8MIQOhBaQXP2ukSX4QTVLGMPm60zMw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.0.tgz", + "integrity": "sha512-oTr2b25l+0bv/+d6IgMamPuGleWV7OgJb0NFfd+WZhw6UDRgr7CdEJy2gW6tK8SerwXgPHdn4ervxsT3WIBiXw==", "requires": { - "@sentry/hub": "6.1.0", - "@sentry/minimal": "6.1.0", - "@sentry/types": "6.1.0", - "@sentry/utils": "6.1.0", + "@sentry/hub": "6.2.0", + "@sentry/minimal": "6.2.0", + "@sentry/types": "6.2.0", + "@sentry/utils": "6.2.0", "tslib": "^1.9.3" }, "dependencies": { - "@sentry/hub": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.1.0.tgz", - "integrity": "sha512-JnBSCgNg3VHiMojUl5tCHU8iWPVuE+qqENIzG9A722oJms1kKWBvWl+yQzhWBNdgk5qeAY3F5UzKWJZkbJ6xow==", - "requires": { - "@sentry/types": "6.1.0", - "@sentry/utils": "6.1.0", - "tslib": "^1.9.3" - } - }, - "@sentry/minimal": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.1.0.tgz", - "integrity": "sha512-g6sfNKenL7wnsr/tibp8nFiMv/XRH0s0Pt4p151npmNI+SmjuUz3GGYEXk8ChCyaKldYKilkNOFdVXJxUf5gZw==", - "requires": { - "@sentry/hub": "6.1.0", - "@sentry/types": "6.1.0", - "tslib": "^1.9.3" - } - }, - "@sentry/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.1.0.tgz", - "integrity": "sha512-kIaN52Fw5K+2mKRaHE2YluJ+F/qMGSUzZXIFDNdC6OUMXQ4TM8gZTrITXs8CLDm7cK8iCqFCtzKOjKK6KyOKAg==" - }, - "@sentry/utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.1.0.tgz", - "integrity": "sha512-6JAplzUOS6bEwfX0PDRZBbYRvn9EN22kZfcL0qGHtM9L0QQ5ybjbbVwOpbXgRkiZx++dQbzLFtelxnDhsbFG+Q==", - "requires": { - "@sentry/types": "6.1.0", - "tslib": "^1.9.3" - } - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -229,12 +181,12 @@ } }, "@sentry/hub": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.1.0.tgz", - "integrity": "sha512-JnBSCgNg3VHiMojUl5tCHU8iWPVuE+qqENIzG9A722oJms1kKWBvWl+yQzhWBNdgk5qeAY3F5UzKWJZkbJ6xow==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.0.tgz", + "integrity": "sha512-BDTEFK8vlJydWXp/KMX0stvv73V7od224iLi+w3k7BcPwMKXBuURBXPU8d5XIC4G8nwg8X6cnDvwL+zBBlBbkg==", "requires": { - "@sentry/types": "6.1.0", - "@sentry/utils": "6.1.0", + "@sentry/types": "6.2.0", + "@sentry/utils": "6.2.0", "tslib": "^1.9.3" }, "dependencies": { @@ -246,12 +198,12 @@ } }, "@sentry/minimal": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.1.0.tgz", - "integrity": "sha512-g6sfNKenL7wnsr/tibp8nFiMv/XRH0s0Pt4p151npmNI+SmjuUz3GGYEXk8ChCyaKldYKilkNOFdVXJxUf5gZw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.0.tgz", + "integrity": "sha512-haxsx8/ZafhZUaGeeMtY7bJt9HbDlqeiaXrRMp1CxGtd0ZRQwHt60imEjl6IH1I73SEWxNfqScGsX2s3HzztMg==", "requires": { - "@sentry/hub": "6.1.0", - "@sentry/types": "6.1.0", + "@sentry/hub": "6.2.0", + "@sentry/types": "6.2.0", "tslib": "^1.9.3" }, "dependencies": { @@ -263,14 +215,14 @@ } }, "@sentry/tracing": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.1.0.tgz", - "integrity": "sha512-s6a4Ra3hHn4awiNz4fOEK6TCV2w2iLcxdppijcYEB7S/1rJpmqZgHWDicqufbOmVMOLmyKLEQ7w+pZq3TR3WgQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.0.tgz", + "integrity": "sha512-pzgM1dePPJysVnzaFCMp+BKtjM5q46HZeyShiR+KcQYvneD3fmUPJigDkkcsB2DcrY3mFvDcswjoqxaTIW7ZBQ==", "requires": { - "@sentry/hub": "6.1.0", - "@sentry/minimal": "6.1.0", - "@sentry/types": "6.1.0", - "@sentry/utils": "6.1.0", + "@sentry/hub": "6.2.0", + "@sentry/minimal": "6.2.0", + "@sentry/types": "6.2.0", + "@sentry/utils": "6.2.0", "tslib": "^1.9.3" }, "dependencies": { @@ -282,16 +234,16 @@ } }, "@sentry/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.1.0.tgz", - "integrity": "sha512-kIaN52Fw5K+2mKRaHE2YluJ+F/qMGSUzZXIFDNdC6OUMXQ4TM8gZTrITXs8CLDm7cK8iCqFCtzKOjKK6KyOKAg==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.0.tgz", + "integrity": "sha512-vN4P/a+QqAuVfWFB9G3nQ7d6bgnM9jd/RLVi49owMuqvM24pv5mTQHUk2Hk4S3k7ConrHFl69E7xH6Dv5VpQnQ==" }, "@sentry/utils": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.1.0.tgz", - "integrity": "sha512-6JAplzUOS6bEwfX0PDRZBbYRvn9EN22kZfcL0qGHtM9L0QQ5ybjbbVwOpbXgRkiZx++dQbzLFtelxnDhsbFG+Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.0.tgz", + "integrity": "sha512-YToUC7xYf2E/pIluI7upYTlj8fKXOtdwoOBkcQZifHgX/dP+qDaHibbBFe5PyZwdmU2UiLnWFsBr0gjo0QFo1g==", "requires": { - "@sentry/types": "6.1.0", + "@sentry/types": "6.2.0", "tslib": "^1.9.3" }, "dependencies": { @@ -1682,9 +1634,9 @@ } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "has-flag": { "version": "3.0.0", @@ -2729,9 +2681,9 @@ } }, "rollup-plugin-copy": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.3.0.tgz", - "integrity": "sha512-euDjCUSBXZa06nqnwCNADbkAcYDfzwowfZQkto9K/TFhiH+QG7I4PUsEMwM9tDgomGWJc//z7KLW8t+tZwxADA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz", + "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==", "requires": { "@types/fs-extra": "^8.0.1", "colorette": "^1.1.0", diff --git a/web/package.json b/web/package.json index bb2281ae1..36fa5db79 100644 --- a/web/package.json +++ b/web/package.json @@ -12,8 +12,8 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.15.2", "@patternfly/patternfly": "^4.87.3", - "@sentry/browser": "^6.1.0", - "@sentry/tracing": "^6.1.0", + "@sentry/browser": "^6.2.0", + "@sentry/tracing": "^6.2.0", "@types/chart.js": "^2.9.30", "@types/codemirror": "0.0.108", "base64-js": "^1.5.1", @@ -24,7 +24,7 @@ "lit-element": "^2.4.0", "lit-html": "^1.3.0", "rollup": "^2.39.0", - "rollup-plugin-copy": "^3.3.0", + "rollup-plugin-copy": "^3.4.0", "rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-external-globals": "^0.6.1", "tslib": "^2.1.0" diff --git a/web/src/api/Flows.ts b/web/src/api/Flows.ts index 18f604e2b..cbf4909d5 100644 --- a/web/src/api/Flows.ts +++ b/web/src/api/Flows.ts @@ -1,4 +1,4 @@ -import { DefaultClient, AKResponse, QueryArguments } from "./Client"; +import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client"; import { TypeCreate } from "./Providers"; export enum FlowDesignation { @@ -40,8 +40,8 @@ export class Flow { } static cached(): Promise { - return DefaultClient.fetch>(["flows", "cached"]).then(r => { - return r.pagination.count; + return DefaultClient.fetch<{ count: number }>(["flows", "all", "cached"]).then(r => { + return r.count; }); } static adminUrl(rest: string): string { @@ -49,16 +49,26 @@ export class Flow { } } -export class Stage { +export class Stage implements BaseInheritanceModel { pk: string; name: string; - __type__: string; + object_type: string; verbose_name: string; + verbose_name_plural: string; + flow_set: Flow[]; constructor() { throw Error(); } + static get(slug: string): Promise { + return DefaultClient.fetch(["stages", "all", slug]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["stages", "all"], filter); + } + static getTypes(): Promise { return DefaultClient.fetch(["stages", "all", "types"]); } diff --git a/web/src/api/Groups.ts b/web/src/api/Groups.ts index 36b73b48a..1d85c05b0 100644 --- a/web/src/api/Groups.ts +++ b/web/src/api/Groups.ts @@ -1,15 +1,28 @@ +import { DefaultClient, QueryArguments, AKResponse } from "./Client"; import { EventContext } from "./Events"; export class Group { - group_uuid: string; + pk: string; name: string; is_superuser: boolean; attributes: EventContext; parent?: Group; + users: number[]; constructor() { throw Error(); } + static get(pk: string): Promise { + return DefaultClient.fetch(["core", "groups", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["core", "groups"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/groups/${rest}`; + } } diff --git a/web/src/api/Invitations.ts b/web/src/api/Invitations.ts new file mode 100644 index 000000000..e28bd8c25 --- /dev/null +++ b/web/src/api/Invitations.ts @@ -0,0 +1,27 @@ +import { DefaultClient, QueryArguments, AKResponse } from "./Client"; +import { EventContext } from "./Events"; +import { User } from "./Users"; + +export class Invitation { + + pk: string; + expires: number; + fixed_date: EventContext; + created_by: User; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["stages", "invitation", "invitations", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["stages", "invitation", "invitations"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/stages/invitations/${rest}`; + } +} diff --git a/web/src/api/Outposts.ts b/web/src/api/Outposts.ts index 6a84cb833..10c60ed7e 100644 --- a/web/src/api/Outposts.ts +++ b/web/src/api/Outposts.ts @@ -1,5 +1,5 @@ import { DefaultClient, AKResponse, QueryArguments } from "./Client"; -import { Provider } from "./Providers"; +import { Provider, TypeCreate } from "./Providers"; export interface OutpostHealth { last_seen: number; @@ -38,3 +38,42 @@ export class Outpost { return `/administration/outposts/${rest}`; } } + +export interface OutpostServiceConnectionState { + version: string; + healthy: boolean; +} + +export class OutpostServiceConnection { + pk: string; + name: string; + local: boolean; + object_type: string; + verbose_name: string; + verbose_name_plural: string; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["outposts", "service_connections", "all", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["outposts", "service_connections", "all"], filter); + } + + static state(pk: string): Promise { + return DefaultClient.fetch(["outposts", "service_connections", "all", pk, "state"]); + } + + static getTypes(): Promise { + return DefaultClient.fetch(["outposts", "service_connections", "all", "types"]); + } + + static adminUrl(rest: string): string { + return `/administration/outpost_service_connections/${rest}`; + } + +} diff --git a/web/src/api/Policies.ts b/web/src/api/Policies.ts index 7e5d78eeb..d720bd559 100644 --- a/web/src/api/Policies.ts +++ b/web/src/api/Policies.ts @@ -1,15 +1,18 @@ import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client"; +import { TypeCreate } from "./Providers"; export class Policy implements BaseInheritanceModel { pk: string; name: string; + execution_logging: boolean; + object_type: string; + verbose_name: string; + verbose_name_plural: string; + bound_to: number; constructor() { throw Error(); } - object_type: string; - verbose_name: string; - verbose_name_plural: string; static get(pk: string): Promise { return DefaultClient.fetch(["policies", "all", pk]); @@ -20,8 +23,16 @@ export class Policy implements BaseInheritanceModel { } static cached(): Promise { - return DefaultClient.fetch>(["policies", "cached"]).then(r => { - return r.pagination.count; + return DefaultClient.fetch<{ count: number }>(["policies", "all", "cached"]).then(r => { + return r.count; }); } + + static getTypes(): Promise { + return DefaultClient.fetch(["policies", "all", "types"]); + } + + static adminUrl(rest: string): string { + return `/administration/policies/${rest}`; + } } diff --git a/web/src/api/Prompts.ts b/web/src/api/Prompts.ts new file mode 100644 index 000000000..b688f4812 --- /dev/null +++ b/web/src/api/Prompts.ts @@ -0,0 +1,30 @@ +import { DefaultClient, QueryArguments, AKResponse } from "./Client"; +import { Stage } from "./Flows"; + +export class Prompt { + + pk: string; + field_key: string; + label: string; + type: string; + required: boolean; + placeholder: string; + order: number; + promptstage_set: Stage[]; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["stages", "prompt", "prompts", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["stages", "prompt", "prompts"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/stages/prompts/${rest}`; + } +} diff --git a/web/src/api/PropertyMapping.ts b/web/src/api/PropertyMapping.ts index 1a61133f5..eae1f176f 100644 --- a/web/src/api/PropertyMapping.ts +++ b/web/src/api/PropertyMapping.ts @@ -1,4 +1,5 @@ import { DefaultClient, AKResponse, QueryArguments } from "./Client"; +import { TypeCreate } from "./Providers"; export class PropertyMapping { pk: string; @@ -20,6 +21,10 @@ export class PropertyMapping { return DefaultClient.fetch>(["propertymappings", "all"], filter); } + static getTypes(): Promise { + return DefaultClient.fetch(["propertymappings", "all", "types"]); + } + static adminUrl(rest: string): string { return `/administration/property-mappings/${rest}`; } diff --git a/web/src/api/SystemTask.ts b/web/src/api/SystemTask.ts new file mode 100644 index 000000000..222873286 --- /dev/null +++ b/web/src/api/SystemTask.ts @@ -0,0 +1,33 @@ +import { DefaultClient, QueryArguments } from "./Client"; + +export enum TaskStatus { + SUCCESSFUL = 1, + WARNING = 2, + ERROR = 4, +} + +export class SystemTask { + + task_name: string; + task_description: string; + task_finish_timestamp: number; + status: TaskStatus; + messages: string[]; + + constructor() { + throw Error(); + } + + static get(task_name: string): Promise { + return DefaultClient.fetch(["admin", "system_tasks", task_name]); + } + + static list(filter?: QueryArguments): Promise { + return DefaultClient.fetch(["admin", "system_tasks"], filter); + } + + static retry(task_name: string): string { + return DefaultClient.makeUrl(["admin", "system_tasks", task_name, "retry"]); + } + +} diff --git a/web/src/api/Tokens.ts b/web/src/api/Tokens.ts index 7a5e75d49..9319efa7d 100644 --- a/web/src/api/Tokens.ts +++ b/web/src/api/Tokens.ts @@ -1,11 +1,47 @@ -import { DefaultClient } from "./Client"; +import { AKResponse, DefaultClient, QueryArguments } from "./Client"; +import { User } from "./Users"; -interface TokenResponse { - key: string; +export enum TokenIntent { + INTENT_VERIFICATION = "verification", + INTENT_API = "api", + INTENT_RECOVERY = "recovery", } -export function tokenByIdentifier(identifier: string): Promise { - return DefaultClient.fetch(["core", "tokens", identifier, "view_key"]).then( - (r) => r.key - ); +export class Token { + + pk: string; + identifier: string; + intent: TokenIntent; + user: User; + description: string; + + expires: number; + expiring: boolean; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["core", "tokens", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["core", "tokens"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/tokens/${rest}`; + } + + static userUrl(rest: string): string { + return `/-/user/tokens/${rest}`; + } + + static getKey(identifier: string): Promise { + return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then( + (r) => r.key + ); + } + } diff --git a/web/src/api/Users.ts b/web/src/api/Users.ts index 196702bf2..6bf1a7604 100644 --- a/web/src/api/Users.ts +++ b/web/src/api/Users.ts @@ -1,4 +1,4 @@ -import { DefaultClient, AKResponse } from "./Client"; +import { DefaultClient, AKResponse, QueryArguments } from "./Client"; let _globalMePromise: Promise; @@ -9,11 +9,25 @@ export class User { is_superuser: boolean; email: boolean; avatar: string; + is_active: boolean; + last_login: number; constructor() { throw Error(); } + static get(pk: string): Promise { + return DefaultClient.fetch(["core", "users", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["core", "users"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/users/${rest}`; + } + static me(): Promise { if (!_globalMePromise) { _globalMePromise = DefaultClient.fetch(["core", "users", "me"]); diff --git a/web/src/authentik.css b/web/src/authentik.css index c052df2bf..7ad666e7f 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -85,10 +85,6 @@ select[multiple] { z-index: auto !important; } -.pf-c-page__main { - display: block; -} - @media (prefers-color-scheme: dark) { :root { --ak-dark-foreground: #fafafa; diff --git a/web/src/elements/buttons/SpinnerButton.ts b/web/src/elements/buttons/SpinnerButton.ts index 99aca32fa..eb80c083a 100644 --- a/web/src/elements/buttons/SpinnerButton.ts +++ b/web/src/elements/buttons/SpinnerButton.ts @@ -70,18 +70,18 @@ export class SpinnerButton extends LitElement { @click=${() => this.callAction()} > ${this.isRunning - ? html` - - - - - - ` - : ""} + ? html` + + + + + + ` + : ""} `; } diff --git a/web/src/elements/buttons/TokenCopyButton.ts b/web/src/elements/buttons/TokenCopyButton.ts index 5b68818e0..075469812 100644 --- a/web/src/elements/buttons/TokenCopyButton.ts +++ b/web/src/elements/buttons/TokenCopyButton.ts @@ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; // @ts-ignore import ButtonStyle from "@patternfly/patternfly/components/Button/button.css"; -import { tokenByIdentifier } from "../../api/Tokens"; +import { Token } from "../../api/Tokens"; import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants"; @customElement("ak-token-copy-button") @@ -35,7 +35,7 @@ export class TokenCopyButton extends LitElement { }, 1500); return; } - tokenByIdentifier(this.identifier).then((token) => { + Token.getKey(this.identifier).then((token) => { navigator.clipboard.writeText(token).then(() => { this.buttonClass = SUCCESS_CLASS; setTimeout(() => { diff --git a/web/src/elements/messages/MessageContainer.ts b/web/src/elements/messages/MessageContainer.ts index 4545a9f74..319640cf8 100644 --- a/web/src/elements/messages/MessageContainer.ts +++ b/web/src/elements/messages/MessageContainer.ts @@ -47,6 +47,7 @@ export class MessageContainer extends LitElement { this.messageSocket = new WebSocket(wsUrl); this.messageSocket.addEventListener("open", () => { console.debug(`authentik/messages: connected to ${wsUrl}`); + this.retryDelay = 200; }); this.messageSocket.addEventListener("close", (e) => { console.debug(`authentik/messages: closed ws connection: ${e}`); diff --git a/web/src/elements/notifications/NotificationTrigger.ts b/web/src/elements/notifications/NotificationTrigger.ts index db7f581e0..842a18c2e 100644 --- a/web/src/elements/notifications/NotificationTrigger.ts +++ b/web/src/elements/notifications/NotificationTrigger.ts @@ -1,8 +1,13 @@ -import { customElement, html, LitElement, TemplateResult } from "lit-element"; +import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; +import { COMMON_STYLES } from "../../common/styles"; @customElement("ak-notification-trigger") export class NotificationRule extends LitElement { + static get styles(): CSSResult[] { + return COMMON_STYLES; + } + constructor() { super(); this.addEventListener("click", () => { @@ -16,7 +21,8 @@ export class NotificationRule extends LitElement { } render(): TemplateResult { - return html``; + // TODO: Show icon with red dot when unread notifications exist + return html``; } } diff --git a/web/src/elements/router/Router404.ts b/web/src/elements/router/Router404.ts new file mode 100644 index 000000000..6dc3fb3d0 --- /dev/null +++ b/web/src/elements/router/Router404.ts @@ -0,0 +1,27 @@ +import { gettext } from "django"; +import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { COMMON_STYLES } from "../../common/styles"; + +@customElement("ak-router-404") +export class Router404 extends LitElement { + + @property() + url = ""; + + static get styles(): CSSResult[] { + return COMMON_STYLES; + } + + render(): TemplateResult { + return html`
+
+ +

${gettext("Not found")}

+
+ ${gettext(`The url '${this.url}' was not found.`)} +
+ ${gettext("Return home")} +
+
`; + } +} diff --git a/web/src/elements/router/RouterOutlet.ts b/web/src/elements/router/RouterOutlet.ts index e817ab306..513597975 100644 --- a/web/src/elements/router/RouterOutlet.ts +++ b/web/src/elements/router/RouterOutlet.ts @@ -10,6 +10,7 @@ import { ROUTES } from "../../routes"; import { RouteMatch } from "./RouteMatch"; import "../../pages/generic/SiteShell"; +import "./Router404"; @customElement("ak-router-outlet") export class RouterOutlet extends LitElement { @@ -28,6 +29,11 @@ export class RouterOutlet extends LitElement { :host { height: 100vh; } + *:first-child { + height: 100%; + display: flex; + flex-direction: column; + } `, ].concat(...COMMON_STYLES); } @@ -62,12 +68,12 @@ export class RouterOutlet extends LitElement { } }); if (!matchedRoute) { - console.debug(`authentik/router: route "${activeUrl}" not defined, defaulting to shell`); + console.debug(`authentik/router: route "${activeUrl}" not defined`); const route = new Route( RegExp(""), - html` -
-
` + html`
+ +
` ); matchedRoute = new RouteMatch(route); matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {}; @@ -77,7 +83,6 @@ export class RouterOutlet extends LitElement { } render(): TemplateResult | undefined { - // TODO: Render 404 when current Route is empty return this.current?.render(); } } diff --git a/web/src/elements/sidebar/Sidebar.ts b/web/src/elements/sidebar/Sidebar.ts index da2203027..bf639b84e 100644 --- a/web/src/elements/sidebar/Sidebar.ts +++ b/web/src/elements/sidebar/Sidebar.ts @@ -29,7 +29,7 @@ export class SidebarItem { this.condition = async () => true; this.activeMatchers = []; if (this.path) { - this.activeMatchers.push(new RegExp(`^${this.path}`)); + this.activeMatchers.push(new RegExp(`^${this.path}$`)); } } diff --git a/web/src/elements/sidebar/SidebarUser.ts b/web/src/elements/sidebar/SidebarUser.ts index 73c79f188..01a75d470 100644 --- a/web/src/elements/sidebar/SidebarUser.ts +++ b/web/src/elements/sidebar/SidebarUser.ts @@ -37,11 +37,11 @@ export class SidebarUser extends LitElement { render(): TemplateResult { return html` - ${until(User.me().then(u => { - return html``;}), html``)} + ${until(User.me().then((u) => { + return html``; + }), html``)} - diff --git a/web/src/elements/table/TablePagination.ts b/web/src/elements/table/TablePagination.ts index 5ceea8604..fbcae2f27 100644 --- a/web/src/elements/table/TablePagination.ts +++ b/web/src/elements/table/TablePagination.ts @@ -43,7 +43,7 @@ export class TablePagination extends LitElement { + + + ${super.renderToolbar()}`; + } + +} diff --git a/web/src/pages/policies/PolicyListPage.ts b/web/src/pages/policies/PolicyListPage.ts new file mode 100644 index 000000000..90ad15e08 --- /dev/null +++ b/web/src/pages/policies/PolicyListPage.ts @@ -0,0 +1,108 @@ +import { gettext } from "django"; +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/buttons/Dropdown"; +import "../../elements/buttons/SpinnerButton"; +import { TableColumn } from "../../elements/table/Table"; +import { Policy } from "../../api/Policies"; +import { until } from "lit-html/directives/until"; + +@customElement("ak-policy-list") +export class PolicyListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Policies"); + } + pageDescription(): string { + return gettext("Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-infrastructure"); + } + + @property() + order = "name"; + + apiEndpoint(page: number): Promise> { + return Policy.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Name", "name"), + new TableColumn("Type"), + new TableColumn(""), + ]; + } + + row(item: Policy): TemplateResult[] { + return [ + html`
+
${item.name}
+ ${item.bound_to > 0 ? + html` + + ${gettext(`Assigned to ${item.bound_to} objects.`)} + `: + html` + ${gettext("Warning: Policy is not assigned.")}`} +
`, + html`${item.verbose_name}`, + html` + + + ${gettext("Edit")} + +
+
+ + + ${gettext("Test")} + +
+
+ + + ${gettext("Delete")} + +
+
+ `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + + + ${super.renderToolbar()}`; + } + +} diff --git a/web/src/pages/property-mappings/PropertyMappingListPage.ts b/web/src/pages/property-mappings/PropertyMappingListPage.ts index 7fa245f5d..210059ce1 100644 --- a/web/src/pages/property-mappings/PropertyMappingListPage.ts +++ b/web/src/pages/property-mappings/PropertyMappingListPage.ts @@ -8,6 +8,7 @@ import "../../elements/buttons/ModalButton"; import "../../elements/buttons/Dropdown"; import "../../elements/buttons/SpinnerButton"; import { TableColumn } from "../../elements/table/Table"; +import { until } from "lit-html/directives/until"; @customElement("ak-property-mapping-list") export class PropertyMappingListPage extends TablePage { @@ -82,36 +83,18 @@ export class PropertyMappingListPage extends TablePage { ${super.renderToolbar()}`; diff --git a/web/src/pages/providers/ProviderListPage.ts b/web/src/pages/providers/ProviderListPage.ts index d0bc61e6f..fe6ed3bab 100644 --- a/web/src/pages/providers/ProviderListPage.ts +++ b/web/src/pages/providers/ProviderListPage.ts @@ -47,13 +47,13 @@ export class ProviderListPage extends TablePage { row(item: Provider): TemplateResult[] { return [ - html` + html` ${item.name} `, item.assigned_application_name ? html` ${gettext("Assigned to application ")} - ${item.assigned_application_name}` : + ${item.assigned_application_name}` : html` ${gettext("Warning: Provider not assigned to any application.")}`, html`${item.verbose_name}`, diff --git a/web/src/pages/providers/RelatedApplicationButton.ts b/web/src/pages/providers/RelatedApplicationButton.ts index 5abe601a3..e7194c8a2 100644 --- a/web/src/pages/providers/RelatedApplicationButton.ts +++ b/web/src/pages/providers/RelatedApplicationButton.ts @@ -14,7 +14,7 @@ export class RelatedApplicationButton extends LitElement { render(): TemplateResult { if (this.provider?.assigned_application_slug) { - return html` + return html` ${this.provider.assigned_application_name} `; } diff --git a/web/src/pages/sources/SourcesListPage.ts b/web/src/pages/sources/SourcesListPage.ts index 30511aa4a..76e9b9410 100644 --- a/web/src/pages/sources/SourcesListPage.ts +++ b/web/src/pages/sources/SourcesListPage.ts @@ -46,7 +46,7 @@ export class SourceListPage extends TablePage { row(item: Source): TemplateResult[] { return [ - html` + html`
${item.name}
${item.enabled ? html`` : html`${gettext("Disabled")}`}
`, diff --git a/web/src/pages/stages/InvitationListPage.ts b/web/src/pages/stages/InvitationListPage.ts new file mode 100644 index 000000000..e53cfd293 --- /dev/null +++ b/web/src/pages/stages/InvitationListPage.ts @@ -0,0 +1,72 @@ +import { gettext } from "django"; +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/buttons/SpinnerButton"; +import { TableColumn } from "../../elements/table/Table"; +import { Invitation } from "../../api/Invitations"; + +@customElement("ak-stage-invitation-list") +export class InvitationListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Invitations"); + } + pageDescription(): string { + return gettext("Create Invitation Links to enroll Users, and optionally force specific attributes of their account."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-migration"); + } + + @property() + order = "expires"; + + apiEndpoint(page: number): Promise> { + return Invitation.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("ID", "pk"), + new TableColumn("Created by", "created_by"), + new TableColumn("Expiry"), + new TableColumn(""), + ]; + } + + row(item: Invitation): TemplateResult[] { + return [ + html`${item.pk}`, + html`${item.created_by.username}`, + html`${new Date(item.expires * 1000).toLocaleString()}`, + html` + + + ${gettext("Delete")} + +
+
`, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Create")} + +
+
+ ${super.renderToolbar()} + `; + } +} diff --git a/web/src/pages/stages/PromptListPage.ts b/web/src/pages/stages/PromptListPage.ts new file mode 100644 index 000000000..37bc2c8f6 --- /dev/null +++ b/web/src/pages/stages/PromptListPage.ts @@ -0,0 +1,84 @@ +import { gettext } from "django"; +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/buttons/SpinnerButton"; +import { TableColumn } from "../../elements/table/Table"; +import { Prompt } from "../../api/Prompts"; + +@customElement("ak-stage-prompt-list") +export class PromptListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Prompts"); + } + pageDescription(): string { + return gettext("Single Prompts that can be used for Prompt Stages."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-plugged"); + } + + @property() + order = "order"; + + apiEndpoint(page: number): Promise> { + return Prompt.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Field", "field_key"), + new TableColumn("Label", "label"), + new TableColumn("Type", "type"), + new TableColumn("Order", "order"), + new TableColumn("Stages"), + new TableColumn(""), + ]; + } + + row(item: Prompt): TemplateResult[] { + return [ + html`${item.field_key}`, + html`${item.label}`, + html`${item.type}`, + html`${item.order}`, + html`${item.promptstage_set.map((stage) => { + return html`
  • ${stage.name}
  • `; + })}`, + html` + + + ${gettext("Edit")} + +
    +
    + + + ${gettext("Delete")} + +
    +
    `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Create")} + +
    +
    + ${super.renderToolbar()} + `; + } +} diff --git a/web/src/pages/stages/StageListPage.ts b/web/src/pages/stages/StageListPage.ts new file mode 100644 index 000000000..710512a1d --- /dev/null +++ b/web/src/pages/stages/StageListPage.ts @@ -0,0 +1,100 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { AKResponse } from "../../api/Client"; +import { TableColumn } from "../../elements/table/Table"; +import { TablePage } from "../../elements/table/TablePage"; + +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/buttons/Dropdown"; +import { until } from "lit-html/directives/until"; +import { Stage } from "../../api/Flows"; + +@customElement("ak-stage-list") +export class StageListPage extends TablePage { + pageTitle(): string { + return "Stages"; + } + pageDescription(): string | undefined { + return "Stages are single steps of a Flow that a user is guided through."; + } + pageIcon(): string { + return "pf-icon pf-icon-plugged"; + } + searchEnabled(): boolean { + return true; + } + + @property() + order = "name"; + + apiEndpoint(page: number): Promise> { + return Stage.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Name", "name"), + new TableColumn("Flows"), + new TableColumn(""), + ]; + } + + row(item: Stage): TemplateResult[] { + return [ + html`
    +
    ${item.name}
    + ${item.verbose_name} +
    `, + html`${item.flow_set.map((flow) => { + return html` + ${flow.slug} + `; + })}`, + html` + + + ${gettext("Edit")} + +
    +
    + + + ${gettext("Delete")} + +
    +
    + `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + + + ${super.renderToolbar()}`; + } + +} diff --git a/web/src/pages/system-tasks/SystemTaskListPage.ts b/web/src/pages/system-tasks/SystemTaskListPage.ts new file mode 100644 index 000000000..bce1e09d3 --- /dev/null +++ b/web/src/pages/system-tasks/SystemTaskListPage.ts @@ -0,0 +1,87 @@ +import { gettext } from "django"; +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/buttons/SpinnerButton"; +import "../../elements/buttons/ActionButton"; +import { TableColumn } from "../../elements/table/Table"; +import { SystemTask, TaskStatus } from "../../api/SystemTask"; + +@customElement("ak-system-task-list") +export class SystemTaskListPage extends TablePage { + searchEnabled(): boolean { + return false; + } + pageTitle(): string { + return gettext("System Tasks"); + } + pageDescription(): string { + return gettext("Long-running operations which authentik executes in the background."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-automation"); + } + + @property() + order = "slug"; + + apiEndpoint(page: number): Promise> { + return SystemTask.list({ + ordering: this.order, + page: page, + }).then((tasks) => { + return { + pagination: { + count: tasks.length, + total_pages: 1, + start_index: 0, + end_index: tasks.length, + current: 1, + }, + results: tasks, + }; + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Identifier", "task_name"), + new TableColumn("Description"), + new TableColumn("Last run"), + new TableColumn("Status"), + new TableColumn("Messages"), + new TableColumn(""), + ]; + } + + taskStatus(task: SystemTask): TemplateResult { + switch (task.status) { + case TaskStatus.SUCCESSFUL: + return html` ${gettext("Successful")}`; + case TaskStatus.WARNING: + return html` ${gettext("Warning")}`; + case TaskStatus.ERROR: + return html` ${gettext("Error")}`; + default: + return html` ${gettext("Unknown")}`; + } + } + + row(item: SystemTask): TemplateResult[] { + return [ + html`${item.task_name}`, + html`${item.task_description}`, + html`${new Date(item.task_finish_timestamp * 1000).toLocaleString()}`, + this.taskStatus(item), + html`${item.messages.map(m => { + return html`
  • ${m}
  • `; + })}`, + html` + ${gettext("Retry Task")} + `, + ]; + } + +} diff --git a/web/src/pages/tokens/TokenListPage.ts b/web/src/pages/tokens/TokenListPage.ts new file mode 100644 index 000000000..f96016d11 --- /dev/null +++ b/web/src/pages/tokens/TokenListPage.ts @@ -0,0 +1,68 @@ +import { gettext } from "django"; +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/buttons/Dropdown"; +import "../../elements/buttons/TokenCopyButton"; +import { TableColumn } from "../../elements/table/Table"; +import { Token } from "../../api/Tokens"; + +@customElement("ak-token-list") +export class TokenListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Tokens"); + } + pageDescription(): string { + return gettext("Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-security"); + } + + @property() + order = "expires"; + + apiEndpoint(page: number): Promise> { + return Token.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Identifier", "identifier"), + new TableColumn("User", "user"), + new TableColumn("Expires?", "expiring"), + new TableColumn("Expiry date", "expires"), + new TableColumn(""), + ]; + } + + row(item: Token): TemplateResult[] { + return [ + html`${item.identifier}`, + html`${item.user.username}`, + html`${item.expiring ? "Yes" : "No"}`, + html`${item.expiring ? new Date(item.expires * 1000).toLocaleString() : "-"}`, + html` + + + ${gettext("Delete")} + +
    +
    + + ${gettext("Copy Key")} + + `, + ]; + } + +} diff --git a/web/src/pages/tokens/UserTokenList.ts b/web/src/pages/tokens/UserTokenList.ts new file mode 100644 index 000000000..fba40547b --- /dev/null +++ b/web/src/pages/tokens/UserTokenList.ts @@ -0,0 +1,64 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { AKResponse } from "../../api/Client"; + +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/Dropdown"; +import "../../elements/buttons/TokenCopyButton"; +import { Table, TableColumn } from "../../elements/table/Table"; +import { Token } from "../../api/Tokens"; + +@customElement("ak-token-user-list") +export class UserTokenList extends Table { + searchEnabled(): boolean { + return true; + } + + @property() + order = "expires"; + + apiEndpoint(page: number): Promise> { + return Token.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Identifier", "identifier"), + new TableColumn("User", "user"), + new TableColumn("Expires?", "expiring"), + new TableColumn("Expiry date", "expires"), + new TableColumn(""), + ]; + } + + row(item: Token): TemplateResult[] { + return [ + html`${item.identifier}`, + html`${item.user.username}`, + html`${item.expiring ? "Yes" : "No"}`, + html`${item.expiring ? new Date(item.expires * 1000).toLocaleString() : "-"}`, + html` + + + ${gettext("Edit")} + +
    +
    + + + ${gettext("Delete")} + +
    +
    + + ${gettext("Copy Key")} + + `, + ]; + } + +} diff --git a/web/src/pages/users/UserListPage.ts b/web/src/pages/users/UserListPage.ts new file mode 100644 index 000000000..340a32600 --- /dev/null +++ b/web/src/pages/users/UserListPage.ts @@ -0,0 +1,113 @@ +import { gettext } from "django"; +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/buttons/Dropdown"; +import { TableColumn } from "../../elements/table/Table"; +import { User } from "../../api/Users"; + +@customElement("ak-user-list") +export class UserListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Users"); + } + pageDescription(): string { + return ""; + } + pageIcon(): string { + return gettext("pf-icon pf-icon-user"); + } + + @property() + order = "username"; + + apiEndpoint(page: number): Promise> { + return User.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Name", "username"), + new TableColumn("Active", "active"), + new TableColumn("Last login", "last_login"), + new TableColumn(""), + ]; + } + + row(item: User): TemplateResult[] { + return [ + html`
    +
    ${item.username}
    + ${item.name} +
    `, + html`${item.is_active ? "Yes" : "No"}`, + html`${new Date(item.last_login * 1000).toLocaleString()}`, + html` + + + ${gettext("Edit")} + +
    +
    + + + + + + ${gettext("Reset Password")} + + + ${gettext("Impersonate")} + `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Create")} + +
    +
    + ${super.renderToolbar()} + `; + } +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 778b7e4d1..d30eb5f0f 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -1,23 +1,32 @@ import { html } from "lit-html"; import { Route, SLUG_REGEX, ID_REGEX, UUID_REGEX } from "./elements/router/Route"; -import "./pages/LibraryPage"; import "./pages/admin-overview/AdminOverviewPage"; import "./pages/applications/ApplicationListPage"; import "./pages/applications/ApplicationViewPage"; -import "./pages/sources/SourcesListPage"; -import "./pages/sources/SourceViewPage"; +import "./pages/crypto/CertificateKeyPairListPage"; +import "./pages/events/EventInfoPage"; +import "./pages/events/EventListPage"; +import "./pages/events/RuleListPage"; +import "./pages/events/TransportListPage"; import "./pages/flows/FlowListPage"; import "./pages/flows/FlowViewPage"; -import "./pages/events/EventListPage"; -import "./pages/events/EventInfoPage"; -import "./pages/events/TransportListPage"; -import "./pages/events/RuleListPage"; +import "./pages/groups/GroupListPage"; +import "./pages/LibraryPage"; +import "./pages/outposts/OutpostListPage"; +import "./pages/outposts/OutpostServiceConnectionListPage"; +import "./pages/policies/PolicyListPage"; +import "./pages/property-mappings/PropertyMappingListPage"; import "./pages/providers/ProviderListPage"; import "./pages/providers/ProviderViewPage"; -import "./pages/property-mappings/PropertyMappingListPage"; -import "./pages/outposts/OutpostListPage"; -import "./pages/crypto/CertificateKeyPairListPage"; +import "./pages/sources/SourcesListPage"; +import "./pages/sources/SourceViewPage"; +import "./pages/stages/StageListPage"; +import "./pages/stages/InvitationListPage"; +import "./pages/stages/PromptListPage"; +import "./pages/system-tasks/SystemTaskListPage"; +import "./pages/tokens/TokenListPage"; +import "./pages/users/UserListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -25,20 +34,28 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^#.*")).redirect("/library"), new Route(new RegExp("^/library$"), html``), new Route(new RegExp("^/administration/overview$"), html``), - new Route(new RegExp("^/providers$"), html``), - new Route(new RegExp(`^/providers/(?${ID_REGEX})$`)).then((args) => { + new Route(new RegExp("^/administration/system-tasks$"), html``), + new Route(new RegExp("^/core/providers$"), html``), + new Route(new RegExp(`^/core/providers/(?${ID_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp("^/applications$"), html``), - new Route(new RegExp(`^/applications/(?${SLUG_REGEX})$`)).then((args) => { + new Route(new RegExp("^/core/applications$"), html``), + new Route(new RegExp(`^/core/applications/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp("^/sources$"), html``), - new Route(new RegExp(`^/sources/(?${SLUG_REGEX})$`)).then((args) => { + new Route(new RegExp("^/core/sources$"), html``), + new Route(new RegExp(`^/core/sources/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp("^/flows$"), html``), - new Route(new RegExp(`^/flows/(?${SLUG_REGEX})$`)).then((args) => { + new Route(new RegExp("^/policy/policies$"), html``), + new Route(new RegExp("^/identity/groups$"), html``), + new Route(new RegExp("^/identity/users$"), html``), + new Route(new RegExp("^/core/tokens$"), html``), + new Route(new RegExp("^/flow/stages/invitations$"), html``), + new Route(new RegExp("^/flow/stages/prompts$"), html``), + new Route(new RegExp("^/flow/stages$"), html``), + new Route(new RegExp("^/flow/flows$"), html``), + new Route(new RegExp(`^/flow/flows/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), new Route(new RegExp("^/events/log$"), html``), @@ -47,7 +64,8 @@ export const ROUTES: Route[] = [ }), new Route(new RegExp("^/events/transports$"), html``), new Route(new RegExp("^/events/rules$"), html``), - new Route(new RegExp("^/property-mappings$"), html``), - new Route(new RegExp("^/outposts$"), html``), + new Route(new RegExp("^/core/property-mappings$"), html``), + new Route(new RegExp("^/outpost/outposts$"), html``), + new Route(new RegExp("^/outpost/service-connections$"), html``), new Route(new RegExp("^/crypto/certificates$"), html``), ];