Merge branch 'master' into stage-challenge

# Conflicts:
#	authentik/api/v2/urls.py
This commit is contained in:
Jens Langhammer 2021-02-20 12:51:10 +01:00
commit 391ee10cb8
120 changed files with 2865 additions and 2598 deletions

30
Pipfile.lock generated
View file

@ -116,18 +116,18 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:1709ff5feb363fee7fcaa2330e659fcbc2b4c03a14f75a884ed682ee66011fc4", "sha256:7d44cbd931c653cc68e8ccbf39f3ad8b304cb50d4e964d8c8d0936de33ff8c8b",
"sha256:80a761eff3b1cb0798d7e1a41b7c8e6d85c9647a8f7b6105335201a69404caa2" "sha256:b6131751e3cf2f8d4c027518373b6b82264c3897de65d3519e2d782927e8bf1e"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.10" "version": "==1.17.11"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:8c84eac6daf38890714e005623083106d68e9b2088e62132fdbf7d2b1228ecbd", "sha256:8efd206b78269eb115279ca2d23f50eead1307dbe0bf9bcc2bba3ab2ff7bfd87",
"sha256:a601ee5a4ae66832f328ca362b5404d22b75f1c181f6cc0934f3cfca749eb27d" "sha256:dd7c528c6c936d941b2c267339f0b01cce377b640856240b588d0e0d82fd29e3"
], ],
"version": "==1.20.10" "version": "==1.20.11"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -409,11 +409,11 @@
}, },
"docker": { "docker": {
"hashes": [ "hashes": [
"sha256:20d71afc593486f2297bb7fb7406b03876f31894337e914a5062050c65085cab", "sha256:d4625e70e3d5a12d7cbf1fd68cef2e081ac86b83889e00e5466d975f90e50dad",
"sha256:67f33d4cf95182db631a17eef7d666d2c91f624c1d3fbc4df6009cb2f2a4c604" "sha256:de5753b7f6486dd541a98393e423e387579b8974a5068748b83f852cc76a89d6"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.4.2" "version": "==4.4.3"
}, },
"drf-yasg2": { "drf-yasg2": {
"hashes": [ "hashes": [
@ -1093,11 +1093,11 @@
}, },
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
"sha256:9044b616ec6663cd50794fb3362efd87586e476e7b51ef2439a711d6569aede7", "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237",
"sha256:efc65e5ffd38324797a7e1dfc8a4e74b62ea6f56a59df5bb03217b84d58dff6a" "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.20.2" "version": "==0.20.3"
}, },
"service-identity": { "service-identity": {
"hashes": [ "hashes": [
@ -1123,11 +1123,11 @@
}, },
"structlog": { "structlog": {
"hashes": [ "hashes": [
"sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd", "sha256:62f06fc0ee32fb8580f0715eea66cb87271eb7efb0eaf9af6b639cba8981de47",
"sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174" "sha256:d9d2d890532e8db83c6977a2a676fb1889922ff0c26ad4dc0ecac26f9fafbc57"
], ],
"index": "pypi", "index": "pypi",
"version": "==20.2.0" "version": "==21.1.0"
}, },
"swagger-spec-validator": { "swagger-spec-validator": {
"hashes": [ "hashes": [

View file

@ -1,114 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-users"></i>
{% trans 'Groups' %}
</h1>
<p>{% trans "Group users together and give them permissions based on the membership." %}
</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:group-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Parent' %}</th>
<th role="columnheader" scope="col">{% trans 'Members' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for group in object_list %}
<tr role="row">
<td role="cell">
<span>
{{ group.name }}
</span>
</td>
<td role="cell">
<span>
{{ group.parent }}
</span>
</td>
<td role="cell">
<span>
{{ group.users.all|length }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:group-update' pk=group.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:group-delete' pk=group.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-users pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Groups.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<ak-modal-button href="{% url 'authentik_admin:group-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,153 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load humanize %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon-integration"></i>
{% trans 'Outpost Service-Connections' %}
</h1>
<p>{% trans "Outpost Service-Connections define how authentik connects to external platforms to manage and deploy Outposts." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="columnheader" scope="col">{% trans 'Local?' %}</th>
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for sc in object_list %}
<tr role="row">
<th role="columnheader">
<span>{{ sc.name }}</span>
</th>
<td role="cell">
<span>
{{ sc|verbose_name }}
</span>
</td>
<td role="cell">
<span>
{{ sc.local|yesno:"Yes,No" }}
</span>
</td>
<td role="cell">
<span>
{% if sc.state.healthy %}
<i class="fas fa-check pf-m-success"></i> {{ sc.state.version }}
{% else %}
<i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
{% endif %}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-update' pk=sc.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-delete' pk=sc.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Outpost Service Connections.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,151 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-infrastructure"></i>
{% trans 'Policies' %}
</h1>
<p>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for policy in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ policy.name }}</div>
{% if not policy.bindings.exists and not policy.promptstage_set.exists %}
<i class="pf-icon pf-icon-warning-triangle"></i>
<small>{% trans 'Warning: Policy is not assigned.' %}</small>
{% else %}
<i class="pf-icon pf-icon-ok"></i>
<small>{% blocktrans with object_count=policy.bindings.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %}</small>
{% endif %}
</div>
</th>
<td role="cell">
<span>
{{ policy|verbose_name }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:policy-update' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-test' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Test' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-delete' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-infrastructure pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Policies.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,119 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-infrastructure"></i>
{% trans 'Policy Bindings' %}
</h1>
<p>{% trans "Bind existing Policies to Models accepting policies." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Policy' %}</th>
<th role="columnheader" scope="col">{% trans 'Enabled' %}</th>
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Timeout' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for pbm in object_list %}
<tr role="role">
<td>
{{ pbm }}
<small>
{{ pbm|fieldtype }}
</small>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
{% for binding in pbm.bindings %}
<tr class="row pf-c-table__expandable-row pf-m-expanded">
<th role="cell">
<div>{{ binding.policy }}</div>
<small>
{{ binding.policy|fieldtype }}
</small>
</th>
<th role="cell">
<div>{{ binding.enabled }}</div>
</th>
<th role="cell">
<div>{{ binding.order }}</div>
</th>
<th role="cell">
<div>{{ binding.timeout }}</div>
</th>
<td>
<ak-modal-button href="{% url 'authentik_admin:policy-binding-update' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-binding-delete' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Policy Bindings.' %}
</h1>
<div class="pf-c-empty-state__body">
{% trans 'Currently no policy bindings exist. Click the button below to create one.' %}
</div>
<ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,143 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-plugged"></i>
{% trans 'Stages' %}
</h1>
<p>{% trans "Stages are single steps of a Flow that a user is guided through." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Flows' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for stage in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ stage.name }}</div>
<small>{{ stage|verbose_name }}</small>
</div>
</th>
<td role="cell">
<ul>
{% for flow in stage.flow_set.all %}
<li>{{ flow.slug }}</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-update' pk=stage.stage_uuid %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-delete' pk=stage.stage_uuid %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Stages.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,125 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-infrastructure"></i>
{% trans 'Stage Bindings' %}
</h1>
<p>{% trans "Bind existing Stages to Flows." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Stage Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% regroup object_list by target as grouped_bindings %}
{% for flow in grouped_bindings %}
<tr role="role">
<td>
{% blocktrans with slug=flow.grouper.slug %}
Flow {{ slug }}
{% endblocktrans %}
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% for binding in flow.list %}
<tr class="pf-c-table__expandable-row pf-m-expanded" role="row">
<td role="cell">
<span>
{{ binding.order }}
</span>
</td>
<th role="columnheader">
<div>
<div>{{ binding.target.slug }}</div>
<small>
{{ binding.target.name }}
</small>
</div>
</th>
<td role="cell">
<div>
<div>
{{ binding.stage.name }}
</div>
<small>
{{ binding.stage }}
</small>
</div>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-binding-update' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Update' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-binding-delete' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Flow-Stage Bindings.' %}
</h1>
<div class="pf-c-empty-state__body">
{% trans 'Currently no flow-stage bindings exist. Click the button below to create one.' %}
</div>
<ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,109 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-migration"></i>
{% trans 'Invitations' %}
</h1>
<p>{% trans "Create Invitation Links to enroll Users, and optionally force specific attributes of their account." %}
</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'ID' %}</th>
<th role="columnheader" scope="col">{% trans 'Created by' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for invitation in object_list %}
<tr role="row">
<td role="cell">
<span>
{{ invitation.invite_uuid }}
</span>
</td>
<td role="cell">
<span>
{{ invitation.created_by }}
</span>
</td>
<td role="cell">
<span>
{{ invitation.expiry|default:"-" }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-delete' pk=invitation.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-migration pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Invitations.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,125 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-plugged"></i>
{% trans 'Prompts' %}
</h1>
<p>{% trans "Single Prompts that can be used for Prompt Stages." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Field' %}</th>
<th role="columnheader" scope="col">{% trans 'Label' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Flows' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for prompt in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ prompt.field_key }}</div>
</div>
</th>
<td role="cell">
<div>
{{ prompt.label }}
</div>
</td>
<td role="cell">
<div>
{{ prompt.type }}
</div>
</td>
<td role="cell">
<div>
{{ prompt.order }}
</div>
</td>
<td role="cell">
<ul>
{% for flow in prompt.flow_set.all %}
<li>{{ flow.slug }}</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-update' pk=prompt.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Update' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-delete' pk=prompt.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Stage Prompts.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<a href="{% url 'authentik_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,84 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load humanize %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-automation"></i>
{% trans 'System Tasks' %}
</h1>
<p>{% trans "Long-running operations which authentik executes in the background." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'Description' %}</th>
<th role="columnheader" scope="col">{% trans 'Last Run' %}</th>
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
<th role="columnheader" scope="col">{% trans 'Messages' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for task in object_list %}
<tr role="row">
<th role="columnheader">
<span>{{ task.html_name|join:"_&shy;" }}</span>
</th>
<td role="cell">
<span>
{{ task.task_description }}
</span>
</td>
<td role="cell">
<span>
{{ task.finish_timestamp|naturaltime }}
</span>
</td>
<td role="cell">
<span>
{% if task.result.status == task_successful %}
<i class="fas fa-check pf-m-success"></i> {% trans 'Successful' %}
{% elif task.result.status == task_warning %}
<i class="fas fa-exclamation-triangle pf-m-warning"></i> {% trans 'Warning' %}
{% elif task.result.status == task_error %}
<i class="fas fa-times pf-m-danger"></i> {% trans 'Error' %}
{% else %}
<i class="fas fa-question-circle"></i> {% trans 'Unknown' %}
{% endif %}
</span>
</td>
<td>
{% for message in task.result.messages %}
<div>
{{ message }}
</div>
{% endfor %}
</td>
<td>
<ak-action-button url="{% url 'authentik_api:admin_system_tasks-retry' pk=task.task_name %}">
{% trans 'Retry Task' %}
</ak-action-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View file

@ -1,102 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-security"></i>
{% trans 'Tokens' %}
</h1>
<p>{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'User' %}</th>
<th role="columnheader" scope="col">{% trans 'Expires?' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for token in object_list %}
<tr role="row">
<th role="columnheader">
<div>{{ token.identifier }}</div>
</th>
<td role="cell">
<span>
{{ token.user }}
</span>
</td>
<td role="cell">
<span>
{{ token.expiring|yesno:"Yes,No" }}
</span>
</td>
<td role="cell">
<span>
{% if not token.expiring %}
-
{% else %}
{{ token.expires }}
{% endif %}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:token-delete' pk=token.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-token-copy-button identifier="{{ token.identifier }}">
{% trans 'Copy token' %}
</ak-token-copy-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Tokens.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any token." %}
{% else %}
{% trans 'Currently no tokens exist.' %}
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -1,125 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-user"></i>
{% trans 'Users' %}
</h1>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:user-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Active' %}</th>
<th role="columnheader" scope="col">{% trans 'Last Login' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for user in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ user.username }}</div>
<small>{{ user.name }}</small>
</div>
</th>
<td role="cell">
<span>
{{ user.is_active }}
</span>
</td>
<td role="cell">
<span>
{{ user.last_login }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:user-update' pk=user.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
{% if user.is_active %}
<ak-modal-button href="{% url 'authentik_admin:user-disable' pk=user.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-warning">
{% trans 'Disable' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
{% else %}
<ak-modal-button href="{% url 'authentik_admin:user-delete' pk=user.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Enable' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
{% endif %}
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-user pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Users.' %}
</h1>
<div class="pf-c-empty-state__body">
{% 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 %}
</div>
<ak-modal-button href="{% url 'authentik_admin:user-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -20,7 +20,6 @@ from authentik.admin.views import (
stages_bindings, stages_bindings,
stages_invitations, stages_invitations,
stages_prompts, stages_prompts,
tasks,
tokens, tokens,
users, users,
) )
@ -54,7 +53,6 @@ urlpatterns = [
name="application-delete", name="application-delete",
), ),
# Tokens # Tokens
path("tokens/", tokens.TokenListView.as_view(), name="tokens"),
path( path(
"tokens/<uuid:pk>/delete/", "tokens/<uuid:pk>/delete/",
tokens.TokenDeleteView.as_view(), tokens.TokenDeleteView.as_view(),
@ -73,7 +71,6 @@ urlpatterns = [
name="source-delete", name="source-delete",
), ),
# Policies # Policies
path("policies/", policies.PolicyListView.as_view(), name="policies"),
path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"),
path( path(
"policies/<uuid:pk>/update/", "policies/<uuid:pk>/update/",
@ -91,11 +88,6 @@ urlpatterns = [
name="policy-test", name="policy-test",
), ),
# Policy bindings # Policy bindings
path(
"policies/bindings/",
policies_bindings.PolicyBindingListView.as_view(),
name="policies-bindings",
),
path( path(
"policies/bindings/create/", "policies/bindings/create/",
policies_bindings.PolicyBindingCreateView.as_view(), policies_bindings.PolicyBindingCreateView.as_view(),
@ -133,7 +125,6 @@ urlpatterns = [
name="provider-delete", name="provider-delete",
), ),
# Stages # Stages
path("stages/", stages.StageListView.as_view(), name="stages"),
path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"), path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"),
path( path(
"stages/<uuid:pk>/update/", "stages/<uuid:pk>/update/",
@ -146,11 +137,6 @@ urlpatterns = [
name="stage-delete", name="stage-delete",
), ),
# Stage bindings # Stage bindings
path(
"stages/bindings/",
stages_bindings.StageBindingListView.as_view(),
name="stage-bindings",
),
path( path(
"stages/bindings/create/", "stages/bindings/create/",
stages_bindings.StageBindingCreateView.as_view(), stages_bindings.StageBindingCreateView.as_view(),
@ -167,11 +153,6 @@ urlpatterns = [
name="stage-binding-delete", name="stage-binding-delete",
), ),
# Stage Prompts # Stage Prompts
path(
"stages_prompts/",
stages_prompts.PromptListView.as_view(),
name="stage-prompts",
),
path( path(
"stages_prompts/create/", "stages_prompts/create/",
stages_prompts.PromptCreateView.as_view(), stages_prompts.PromptCreateView.as_view(),
@ -188,11 +169,6 @@ urlpatterns = [
name="stage-prompt-delete", name="stage-prompt-delete",
), ),
# Stage Invitations # Stage Invitations
path(
"stages/invitations/",
stages_invitations.InvitationListView.as_view(),
name="stage-invitations",
),
path( path(
"stages/invitations/create/", "stages/invitations/create/",
stages_invitations.InvitationCreateView.as_view(), stages_invitations.InvitationCreateView.as_view(),
@ -256,7 +232,6 @@ urlpatterns = [
name="property-mapping-test", name="property-mapping-test",
), ),
# Users # Users
path("users/", users.UserListView.as_view(), name="users"),
path("users/create/", users.UserCreateView.as_view(), name="user-create"), path("users/create/", users.UserCreateView.as_view(), name="user-create"),
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"), path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"), path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
@ -270,7 +245,6 @@ urlpatterns = [
name="user-password-reset", name="user-password-reset",
), ),
# Groups # Groups
path("groups/", groups.GroupListView.as_view(), name="groups"),
path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"), path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"),
path( path(
"groups/<uuid:pk>/update/", "groups/<uuid:pk>/update/",
@ -320,11 +294,6 @@ urlpatterns = [
name="outpost-delete", name="outpost-delete",
), ),
# Outpost Service Connections # Outpost Service Connections
path(
"outpost_service_connections/",
outposts_service_connections.OutpostServiceConnectionListView.as_view(),
name="outpost-service-connections",
),
path( path(
"outpost_service_connections/create/", "outpost_service_connections/create/",
outposts_service_connections.OutpostServiceConnectionCreateView.as_view(), outposts_service_connections.OutpostServiceConnectionCreateView.as_view(),
@ -340,12 +309,6 @@ urlpatterns = [
outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(), outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(),
name="outpost-service-connection-delete", name="outpost-service-connection-delete",
), ),
# Tasks
path(
"tasks/",
tasks.TaskListView.as_view(),
name="tasks",
),
# Event Notification Transpots # Event Notification Transpots
path( path(
"events/transports/create/", "events/transports/create/",

View file

@ -11,7 +11,7 @@ from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user 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.forms.applications import ApplicationForm
from authentik.core.models import Application from authentik.core.models import Application
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
@ -19,7 +19,6 @@ from authentik.lib.views import CreateAssignPermView
class ApplicationCreateView( class ApplicationCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -52,7 +51,6 @@ class ApplicationCreateView(
class ApplicationUpdateView( class ApplicationUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,

View file

@ -10,7 +10,7 @@ from django.views.generic import UpdateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from guardian.mixins import PermissionRequiredMixin 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.builder import CertificateBuilder
from authentik.crypto.forms import ( from authentik.crypto.forms import (
CertificateKeyPairForm, CertificateKeyPairForm,
@ -22,7 +22,6 @@ from authentik.lib.views import CreateAssignPermView
class CertificateKeyPairCreateView( class CertificateKeyPairCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -40,7 +39,6 @@ class CertificateKeyPairCreateView(
class CertificateKeyPairGenerateView( class CertificateKeyPairGenerateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
FormView, FormView,
@ -68,7 +66,6 @@ class CertificateKeyPairGenerateView(
class CertificateKeyPairUpdateView( class CertificateKeyPairUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,

View file

@ -8,7 +8,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin 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.forms import NotificationRuleForm
from authentik.events.models import NotificationRule from authentik.events.models import NotificationRule
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
@ -16,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView
class NotificationRuleCreateView( class NotificationRuleCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -33,7 +32,6 @@ class NotificationRuleCreateView(
class NotificationRuleUpdateView( class NotificationRuleUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,

View file

@ -8,7 +8,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin 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.forms import NotificationTransportForm
from authentik.events.models import NotificationTransport from authentik.events.models import NotificationTransport
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
@ -16,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView
class NotificationTransportCreateView( class NotificationTransportCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -33,7 +32,6 @@ class NotificationTransportCreateView(
class NotificationTransportUpdateView( class NotificationTransportUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,

View file

@ -10,7 +10,7 @@ from django.utils.translation import gettext as _
from django.views.generic import DetailView, FormView, UpdateView from django.views.generic import DetailView, FormView, UpdateView
from guardian.mixins import PermissionRequiredMixin 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.exceptions import FlowNonApplicableException
from authentik.flows.forms import FlowForm, FlowImportForm from authentik.flows.forms import FlowForm, FlowImportForm
from authentik.flows.models import Flow from authentik.flows.models import Flow
@ -25,7 +25,6 @@ from authentik.lib.views import CreateAssignPermView, bad_request_message
class FlowCreateView( class FlowCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -43,7 +42,6 @@ class FlowCreateView(
class FlowUpdateView( class FlowUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,

View file

@ -4,41 +4,18 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.core.forms.groups import GroupForm from authentik.core.forms.groups import GroupForm
from authentik.core.models import Group from authentik.core.models import Group
from authentik.lib.views import CreateAssignPermView 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( class GroupCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -50,13 +27,12 @@ class GroupCreateView(
permission_required = "authentik_core.add_group" permission_required = "authentik_core.add_group"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:groups") success_url = "/"
success_message = _("Successfully created Group") success_message = _("Successfully created Group")
class GroupUpdateView( class GroupUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,
@ -68,7 +44,7 @@ class GroupUpdateView(
permission_required = "authentik_core.change_group" permission_required = "authentik_core.change_group"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:groups") success_url = "/"
success_message = _("Successfully updated Group") success_message = _("Successfully updated Group")
@ -79,5 +55,5 @@ class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
permission_required = "authentik_flows.delete_group" permission_required = "authentik_flows.delete_group"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:groups") success_url = "/"
success_message = _("Successfully deleted Group") success_message = _("Successfully deleted Group")

View file

@ -11,7 +11,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin 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.lib.views import CreateAssignPermView
from authentik.outposts.forms import OutpostForm from authentik.outposts.forms import OutpostForm
from authentik.outposts.models import Outpost, OutpostConfig from authentik.outposts.models import Outpost, OutpostConfig
@ -19,7 +19,6 @@ from authentik.outposts.models import Outpost, OutpostConfig
class OutpostCreateView( class OutpostCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -43,7 +42,6 @@ class OutpostCreateView(
class OutpostUpdateView( class OutpostUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,

View file

@ -4,41 +4,19 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
) )
from authentik.outposts.models import OutpostServiceConnection 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( class OutpostServiceConnectionCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
InheritanceCreateView, InheritanceCreateView,
@ -49,13 +27,12 @@ class OutpostServiceConnectionCreateView(
permission_required = "authentik_outposts.add_outpostserviceconnection" permission_required = "authentik_outposts.add_outpostserviceconnection"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:outpost-service-connections") success_url = "/"
success_message = _("Successfully created OutpostServiceConnection") success_message = _("Successfully created Outpost Service Connection")
class OutpostServiceConnectionUpdateView( class OutpostServiceConnectionUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
InheritanceUpdateView, InheritanceUpdateView,
@ -66,8 +43,8 @@ class OutpostServiceConnectionUpdateView(
permission_required = "authentik_outposts.change_outpostserviceconnection" permission_required = "authentik_outposts.change_outpostserviceconnection"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:outpost-service-connections") success_url = "/"
success_message = _("Successfully updated OutpostServiceConnection") success_message = _("Successfully updated Outpost Service Connection")
class OutpostServiceConnectionDeleteView( class OutpostServiceConnectionDeleteView(
@ -79,5 +56,5 @@ class OutpostServiceConnectionDeleteView(
permission_required = "authentik_outposts.delete_outpostserviceconnection" permission_required = "authentik_outposts.delete_outpostserviceconnection"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:outpost-service-connections") success_url = "/"
success_message = _("Successfully deleted OutpostServiceConnection") success_message = _("Successfully deleted Outpost Service Connection")

View file

@ -7,45 +7,23 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView from django.views.generic import FormView
from django.views.generic.detail import DetailView 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.forms.policies import PolicyTestForm
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
) )
from authentik.policies.models import Policy, PolicyBinding from authentik.policies.models import Policy, PolicyBinding
from authentik.policies.process import PolicyProcess, PolicyRequest 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( class PolicyCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
InheritanceCreateView, InheritanceCreateView,
@ -56,13 +34,12 @@ class PolicyCreateView(
permission_required = "authentik_policies.add_policy" permission_required = "authentik_policies.add_policy"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies") success_url = "/"
success_message = _("Successfully created Policy") success_message = _("Successfully created Policy")
class PolicyUpdateView( class PolicyUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
InheritanceUpdateView, InheritanceUpdateView,
@ -73,7 +50,7 @@ class PolicyUpdateView(
permission_required = "authentik_policies.change_policy" permission_required = "authentik_policies.change_policy"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies") success_url = "/"
success_message = _("Successfully updated Policy") success_message = _("Successfully updated Policy")
@ -84,7 +61,7 @@ class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_policies.delete_policy" permission_required = "authentik_policies.delete_policy"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies") success_url = "/"
success_message = _("Successfully deleted Policy") success_message = _("Successfully deleted Policy")

View file

@ -6,55 +6,19 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Max, QuerySet from django.db.models import Max
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import UpdateView
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 ( from authentik.admin.views.utils import DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
UserPaginateListMixin,
)
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
from authentik.policies.forms import PolicyBindingForm from authentik.policies.forms import PolicyBindingForm
from authentik.policies.models import PolicyBinding, PolicyBindingModel 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( class PolicyBindingCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -66,7 +30,7 @@ class PolicyBindingCreateView(
form_class = PolicyBindingForm form_class = PolicyBindingForm
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies-bindings") success_url = "/"
success_message = _("Successfully created PolicyBinding") success_message = _("Successfully created PolicyBinding")
def get_initial(self) -> dict[str, Any]: def get_initial(self) -> dict[str, Any]:
@ -88,7 +52,6 @@ class PolicyBindingCreateView(
class PolicyBindingUpdateView( class PolicyBindingUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,
@ -100,7 +63,7 @@ class PolicyBindingUpdateView(
form_class = PolicyBindingForm form_class = PolicyBindingForm
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies-bindings") success_url = "/"
success_message = _("Successfully updated PolicyBinding") success_message = _("Successfully updated PolicyBinding")
@ -113,5 +76,5 @@ class PolicyBindingDeleteView(
permission_required = "authentik_policies.delete_policybinding" permission_required = "authentik_policies.delete_policybinding"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies-bindings") success_url = "/"
success_message = _("Successfully deleted PolicyBinding") success_message = _("Successfully deleted PolicyBinding")

View file

@ -15,7 +15,6 @@ from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.policies import PolicyTestForm from authentik.admin.forms.policies import PolicyTestForm
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceUpdateView, InheritanceUpdateView,
@ -25,7 +24,6 @@ from authentik.core.models import PropertyMapping
class PropertyMappingCreateView( class PropertyMappingCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
InheritanceCreateView, InheritanceCreateView,
@ -41,7 +39,6 @@ class PropertyMappingCreateView(
class PropertyMappingUpdateView( class PropertyMappingUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
InheritanceUpdateView, InheritanceUpdateView,

View file

@ -8,7 +8,6 @@ from django.utils.translation import gettext as _
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceUpdateView, InheritanceUpdateView,
@ -18,7 +17,6 @@ from authentik.core.models import Provider
class ProviderCreateView( class ProviderCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
InheritanceCreateView, InheritanceCreateView,
@ -34,7 +32,6 @@ class ProviderCreateView(
class ProviderUpdateView( class ProviderUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
InheritanceUpdateView, InheritanceUpdateView,

View file

@ -8,7 +8,6 @@ from django.utils.translation import gettext as _
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceUpdateView, InheritanceUpdateView,
@ -18,7 +17,6 @@ from authentik.core.models import Source
class SourceCreateView( class SourceCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
InheritanceCreateView, InheritanceCreateView,
@ -34,7 +32,6 @@ class SourceCreateView(
class SourceUpdateView( class SourceUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
InheritanceUpdateView, InheritanceUpdateView,

View file

@ -4,41 +4,19 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
) )
from authentik.flows.models import Stage 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( class StageCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
InheritanceCreateView, InheritanceCreateView,
@ -49,13 +27,12 @@ class StageCreateView(
template_name = "generic/create.html" template_name = "generic/create.html"
permission_required = "authentik_flows.add_stage" permission_required = "authentik_flows.add_stage"
success_url = reverse_lazy("authentik_admin:stages") success_url = "/"
success_message = _("Successfully created Stage") success_message = _("Successfully created Stage")
class StageUpdateView( class StageUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
InheritanceUpdateView, InheritanceUpdateView,
@ -65,7 +42,7 @@ class StageUpdateView(
model = Stage model = Stage
permission_required = "authentik_flows.update_application" permission_required = "authentik_flows.update_application"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stages") success_url = "/"
success_message = _("Successfully updated Stage") success_message = _("Successfully updated Stage")
@ -75,5 +52,5 @@ class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
model = Stage model = Stage
template_name = "generic/delete.html" template_name = "generic/delete.html"
permission_required = "authentik_flows.delete_stage" permission_required = "authentik_flows.delete_stage"
success_url = reverse_lazy("authentik_admin:stages") success_url = "/"
success_message = _("Successfully deleted Stage") success_message = _("Successfully deleted Stage")

View file

@ -7,35 +7,18 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Max from django.db.models import Max
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
UserPaginateListMixin,
)
from authentik.flows.forms import FlowStageBindingForm from authentik.flows.forms import FlowStageBindingForm
from authentik.flows.models import Flow, FlowStageBinding from authentik.flows.models import Flow, FlowStageBinding
from authentik.lib.views import CreateAssignPermView 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( class StageBindingCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -47,7 +30,7 @@ class StageBindingCreateView(
form_class = FlowStageBindingForm form_class = FlowStageBindingForm
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-bindings") success_url = "/"
success_message = _("Successfully created StageBinding") success_message = _("Successfully created StageBinding")
def get_initial(self) -> dict[str, Any]: def get_initial(self) -> dict[str, Any]:
@ -67,7 +50,6 @@ class StageBindingCreateView(
class StageBindingUpdateView( class StageBindingUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,
@ -79,7 +61,7 @@ class StageBindingUpdateView(
form_class = FlowStageBindingForm form_class = FlowStageBindingForm
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stage-bindings") success_url = "/"
success_message = _("Successfully updated StageBinding") success_message = _("Successfully updated StageBinding")
@ -92,5 +74,5 @@ class StageBindingDeleteView(
permission_required = "authentik_flows.delete_flowstagebinding" permission_required = "authentik_flows.delete_flowstagebinding"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-bindings") success_url = "/"
success_message = _("Successfully deleted FlowStageBinding") success_message = _("Successfully deleted FlowStageBinding")

View file

@ -5,41 +5,17 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView from guardian.mixins import PermissionRequiredMixin
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
from authentik.stages.invitation.forms import InvitationForm from authentik.stages.invitation.forms import InvitationForm
from authentik.stages.invitation.models import Invitation 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( class InvitationCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -51,7 +27,7 @@ class InvitationCreateView(
permission_required = "authentik_stages_invitation.add_invitation" permission_required = "authentik_stages_invitation.add_invitation"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-invitations") success_url = "/"
success_message = _("Successfully created Invitation") success_message = _("Successfully created Invitation")
def form_valid(self, form): def form_valid(self, form):
@ -70,5 +46,5 @@ class InvitationDeleteView(
permission_required = "authentik_stages_invitation.delete_invitation" permission_required = "authentik_stages_invitation.delete_invitation"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-invitations") success_url = "/"
success_message = _("Successfully deleted Invitation") success_message = _("Successfully deleted Invitation")

View file

@ -4,46 +4,18 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
from authentik.stages.prompt.forms import PromptAdminForm from authentik.stages.prompt.forms import PromptAdminForm
from authentik.stages.prompt.models import Prompt 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( class PromptCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -55,13 +27,12 @@ class PromptCreateView(
permission_required = "authentik_stages_prompt.add_prompt" permission_required = "authentik_stages_prompt.add_prompt"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-prompts") success_url = "/"
success_message = _("Successfully created Prompt") success_message = _("Successfully created Prompt")
class PromptUpdateView( class PromptUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,
@ -73,7 +44,7 @@ class PromptUpdateView(
permission_required = "authentik_stages_prompt.change_prompt" permission_required = "authentik_stages_prompt.change_prompt"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stage-prompts") success_url = "/"
success_message = _("Successfully updated Prompt") success_message = _("Successfully updated Prompt")
@ -84,5 +55,5 @@ class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_stages_prompt.delete_prompt" permission_required = "authentik_stages_prompt.delete_prompt"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-prompts") success_url = "/"
success_message = _("Successfully deleted Prompt") success_message = _("Successfully deleted Prompt")

View file

@ -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

View file

@ -1,39 +1,12 @@
"""authentik Token administration""" """authentik Token administration"""
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView from guardian.mixins import PermissionRequiredMixin
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.core.models import Token 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): class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
"""Delete token""" """Delete token"""
@ -41,5 +14,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
permission_required = "authentik_core.delete_token" permission_required = "authentik_core.delete_token"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:tokens") success_url = "/"
success_message = _("Successfully deleted Token") success_message = _("Successfully deleted Token")

View file

@ -7,50 +7,20 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect, reverse
from django.urls import reverse, reverse_lazy
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView, UpdateView from django.views.generic import DetailView, UpdateView
from guardian.mixins import ( from guardian.mixins import PermissionRequiredMixin
PermissionListMixin,
PermissionRequiredMixin,
get_anonymous_user,
)
from authentik.admin.forms.users import UserForm from authentik.admin.forms.users import UserForm
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.core.models import Token, User from authentik.core.models import Token, User
from authentik.lib.views import CreateAssignPermView 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( class UserCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
DjangoPermissionRequiredMixin, DjangoPermissionRequiredMixin,
CreateAssignPermView, CreateAssignPermView,
@ -62,13 +32,12 @@ class UserCreateView(
permission_required = "authentik_core.add_user" permission_required = "authentik_core.add_user"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:users") success_url = "/"
success_message = _("Successfully created User") success_message = _("Successfully created User")
class UserUpdateView( class UserUpdateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
UpdateView, UpdateView,
@ -82,7 +51,7 @@ class UserUpdateView(
# By default the object's name is user which is used by other checks # By default the object's name is user which is used by other checks
context_object_name = "object" context_object_name = "object"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:users") success_url = "/"
success_message = _("Successfully updated User") 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 # By default the object's name is user which is used by other checks
context_object_name = "object" context_object_name = "object"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:users") success_url = "/"
success_message = _("Successfully deleted User") success_message = _("Successfully deleted User")
class UserDisableView( class UserDisableView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView
):
"""Disable user""" """Disable user"""
object: User object: User
@ -112,7 +79,7 @@ class UserDisableView(
# By default the object's name is user which is used by other checks # By default the object's name is user which is used by other checks
context_object_name = "object" context_object_name = "object"
template_name = "administration/user/disable.html" template_name = "administration/user/disable.html"
success_url = reverse_lazy("authentik_admin:users") success_url = "/"
success_message = _("Successfully disabled User") success_message = _("Successfully disabled User")
def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
@ -123,9 +90,7 @@ class UserDisableView(
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
class UserEnableView( class UserEnableView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView
):
"""Enable user""" """Enable user"""
object: User object: User
@ -135,15 +100,14 @@ class UserEnableView(
# By default the object's name is user which is used by other checks # By default the object's name is user which is used by other checks
context_object_name = "object" context_object_name = "object"
success_url = reverse_lazy("authentik_admin:users") success_url = "/"
success_message = _("Successfully enabled User") success_message = _("Successfully enabled User")
def get(self, request: HttpRequest, *args, **kwargs): def get(self, request: HttpRequest, *args, **kwargs):
self.object: User = self.get_object() self.object: User = self.get_object()
success_url = self.get_success_url()
self.object.is_active = True self.object.is_active = True
self.object.save() self.object.save()
return HttpResponseRedirect(success_url) return HttpResponseRedirect(self.success_url)
class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
@ -165,4 +129,4 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
messages.success( messages.success(
request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link}) request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link})
) )
return redirect("authentik_admin:users") return redirect("/")

View file

@ -1,15 +1,10 @@
"""authentik admin util views""" """authentik admin util views"""
from typing import Any, Optional from typing import Any
from urllib.parse import urlparse
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin 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 import Http404
from django.http.request import HttpRequest from django.views.generic import DeleteView, UpdateView
from django.views.generic import DeleteView, ListView, UpdateView
from django.views.generic.list import MultipleObjectMixin
from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.reflection import all_subclasses
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
@ -25,37 +20,6 @@ class DeleteMessageView(SuccessMessageMixin, DeleteView):
return super().delete(request, *args, **kwargs) 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): class InheritanceCreateView(CreateAssignPermView):
"""CreateView for objects using InheritanceManager""" """CreateView for objects using InheritanceManager"""
@ -96,31 +60,3 @@ class InheritanceUpdateView(UpdateView):
.select_subclasses() .select_subclasses()
.first() .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)

View file

@ -23,12 +23,9 @@ from authentik.events.api.event import EventViewSet
from authentik.events.api.notification import NotificationViewSet from authentik.events.api.notification import NotificationViewSet
from authentik.events.api.notification_rule import NotificationRuleViewSet from authentik.events.api.notification_rule import NotificationRuleViewSet
from authentik.events.api.notification_transport import NotificationTransportViewSet from authentik.events.api.notification_transport import NotificationTransportViewSet
from authentik.flows.api import ( from authentik.flows.api.bindings import FlowStageBindingViewSet
FlowCacheViewSet, from authentik.flows.api.flows import FlowViewSet
FlowStageBindingViewSet, from authentik.flows.api.stages import StageViewSet
FlowViewSet,
StageViewSet,
)
from authentik.flows.views import FlowExecutorView from authentik.flows.views import FlowExecutorView
from authentik.outposts.api.outpost_service_connections import ( from authentik.outposts.api.outpost_service_connections import (
DockerServiceConnectionViewSet, DockerServiceConnectionViewSet,
@ -36,11 +33,7 @@ from authentik.outposts.api.outpost_service_connections import (
ServiceConnectionViewSet, ServiceConnectionViewSet,
) )
from authentik.outposts.api.outposts import OutpostViewSet from authentik.outposts.api.outposts import OutpostViewSet
from authentik.policies.api import ( from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet
PolicyBindingViewSet,
PolicyCacheViewSet,
PolicyViewSet,
)
from authentik.policies.dummy.api import DummyPolicyViewSet from authentik.policies.dummy.api import DummyPolicyViewSet
from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet
from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet
@ -101,7 +94,6 @@ router.register(
router.register("outposts/proxy", ProxyOutpostConfigViewSet) router.register("outposts/proxy", ProxyOutpostConfigViewSet)
router.register("flows/instances", FlowViewSet) router.register("flows/instances", FlowViewSet)
router.register("flows/cached", FlowCacheViewSet, basename="flows_cache")
router.register("flows/bindings", FlowStageBindingViewSet) router.register("flows/bindings", FlowStageBindingViewSet)
router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet)
@ -117,7 +109,6 @@ router.register("sources/saml", SAMLSourceViewSet)
router.register("sources/oauth", OAuthSourceViewSet) router.register("sources/oauth", OAuthSourceViewSet)
router.register("policies/all", PolicyViewSet) router.register("policies/all", PolicyViewSet)
router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache")
router.register("policies/bindings", PolicyBindingViewSet) router.register("policies/bindings", PolicyBindingViewSet)
router.register("policies/expression", ExpressionPolicyViewSet) router.register("policies/expression", ExpressionPolicyViewSet)
router.register("policies/event_matcher", EventMatcherPolicyViewSet) router.register("policies/event_matcher", EventMatcherPolicyViewSet)
@ -146,8 +137,8 @@ router.register("stages/captcha", CaptchaStageViewSet)
router.register("stages/consent", ConsentStageViewSet) router.register("stages/consent", ConsentStageViewSet)
router.register("stages/email", EmailStageViewSet) router.register("stages/email", EmailStageViewSet)
router.register("stages/identification", IdentificationStageViewSet) router.register("stages/identification", IdentificationStageViewSet)
router.register("stages/invitation", InvitationStageViewSet)
router.register("stages/invitation/invitations", InvitationViewSet) router.register("stages/invitation/invitations", InvitationViewSet)
router.register("stages/invitation/stages", InvitationStageViewSet)
router.register("stages/password", PasswordStageViewSet) router.register("stages/password", PasswordStageViewSet)
router.register("stages/prompt/prompts", PromptViewSet) router.register("stages/prompt/prompts", PromptViewSet)
router.register("stages/prompt/stages", PromptStageViewSet) router.register("stages/prompt/stages", PromptStageViewSet)

View file

@ -19,3 +19,5 @@ class GroupViewSet(ModelViewSet):
queryset = Group.objects.all() queryset = Group.objects.all()
serializer_class = GroupSerializer serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
filterset_fields = ["name", "is_superuser"]

View file

@ -1,9 +1,16 @@
"""PropertyMapping API Views""" """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.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet 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.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): class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
@ -47,3 +54,19 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet):
def get_queryset(self): def get_queryset(self):
return PropertyMapping.objects.select_subclasses() 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)

View file

@ -9,6 +9,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.users import UserSerializer
from authentik.core.models import Token from authentik.core.models import Token
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -16,10 +17,21 @@ from authentik.events.models import Event, EventAction
class TokenSerializer(ModelSerializer): class TokenSerializer(ModelSerializer):
"""Token Serializer""" """Token Serializer"""
user = UserSerializer()
class Meta: class Meta:
model = Token model = Token
fields = ["pk", "identifier", "intent", "user", "description"] fields = [
"pk",
"identifier",
"intent",
"user",
"description",
"expires",
"expiring",
]
depth = 2
class TokenViewSerializer(Serializer): class TokenViewSerializer(Serializer):
@ -40,6 +52,19 @@ class TokenViewSet(ModelViewSet):
lookup_field = "identifier" lookup_field = "identifier"
queryset = Token.filter_not_expired() queryset = Token.filter_not_expired()
serializer_class = TokenSerializer 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)}) @swagger_auto_schema(responses={200: TokenViewSerializer(many=False)})
@action(detail=True) @action(detail=True)

View file

@ -28,7 +28,17 @@ class UserSerializer(ModelSerializer):
class Meta: class Meta:
model = User 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): class UserViewSet(ModelViewSet):
@ -36,6 +46,8 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.none() queryset = User.objects.none()
serializer_class = UserSerializer serializer_class = UserSerializer
search_fields = ["username", "name", "is_active"]
filterset_fields = ["username", "name", "is_active"]
def get_queryset(self): def get_queryset(self):
return User.objects.all().exclude(pk=get_anonymous_user().pk) return User.objects.all().exclude(pk=get_anonymous_user().pk)

View file

@ -1,6 +1,6 @@
"""API Utilities""" """API Utilities"""
from django.db.models import Model 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 from rest_framework.serializers import Serializer, SerializerMethodField
@ -37,3 +37,15 @@ class TypeCreateSerializer(Serializer):
def update(self, instance: Model, validated_data: dict) -> Model: def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError 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

View file

@ -24,9 +24,7 @@
</div> </div>
</section> </section>
<section slot="page-2" data-tab-title="{% trans 'Tokens' %}" class="pf-c-page__main-section pf-m-no-padding-mobile"> <section slot="page-2" data-tab-title="{% trans 'Tokens' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<ak-site-shell url="{% url 'authentik_core:user-tokens' %}"> <ak-token-user-list></ak-token-user-list>
<div slot="body"></div>
</ak-site-shell>
</section> </section>
{% user_stages as user_stages_loc %} {% user_stages as user_stages_loc %}
{% for stage, stage_link in user_stages_loc.items %} {% for stage, stage_link in user_stages_loc.items %}

View file

@ -8,7 +8,6 @@ urlpatterns = [
# User views # User views
path("-/user/", user.UserSettingsView.as_view(), name="user-settings"), path("-/user/", user.UserSettingsView.as_view(), name="user-settings"),
path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"), path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"),
path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"),
path( path(
"-/user/tokens/create/", "-/user/tokens/create/",
user.TokenCreateView.as_view(), user.TokenCreateView.as_view(),

View file

@ -6,20 +6,15 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models.query import QuerySet
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext as _ 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 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 guardian.shortcuts import get_objects_for_user
from authentik.admin.views.utils import ( from authentik.admin.views.utils import DeleteMessageView
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.core.forms.token import UserTokenForm from authentik.core.forms.token import UserTokenForm
from authentik.core.forms.users import UserDetailForm from authentik.core.forms.users import UserDetailForm
from authentik.core.models import Token, TokenIntents from authentik.core.models import Token, TokenIntents
@ -54,30 +49,6 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
return kwargs 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( class TokenCreateView(
SuccessMessageMixin, SuccessMessageMixin,
LoginRequiredMixin, LoginRequiredMixin,

View file

View file

@ -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__"

View file

@ -3,11 +3,10 @@ from dataclasses import dataclass
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Model 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 drf_yasg2.utils import swagger_auto_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ( from rest_framework.serializers import (
@ -16,13 +15,11 @@ from rest_framework.serializers import (
Serializer, Serializer,
SerializerMethodField, 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.core.api.utils import CacheSerializer
from authentik.flows.models import Flow, FlowStageBinding, Stage from authentik.flows.models import Flow
from authentik.flows.planner import cache_key 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): class FlowSerializer(ModelSerializer):
@ -84,6 +81,12 @@ class FlowViewSet(ModelViewSet):
search_fields = ["name", "slug", "designation", "title"] search_fields = ["name", "slug", "designation", "title"]
filterset_fields = ["flow_uuid", "name", "slug", "designation"] 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()}) @swagger_auto_schema(responses={200: FlowDiagramSerializer()})
@action(detail=True, methods=["get"]) @action(detail=True, methods=["get"])
def diagram(self, request: Request, slug: str) -> Response: 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]) diagram = "\n".join([str(x) for x in header + body + footer])
return Response({"diagram": diagram}) 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_*"))}})

View file

@ -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)

View file

@ -118,7 +118,7 @@ class Flow(SerializerModel, PolicyBindingModel):
@property @property
def serializer(self) -> BaseSerializer: def serializer(self) -> BaseSerializer:
from authentik.flows.api import FlowSerializer from authentik.flows.api.flows import FlowSerializer
return FlowSerializer return FlowSerializer
@ -189,12 +189,12 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
@property @property
def serializer(self) -> BaseSerializer: def serializer(self) -> BaseSerializer:
from authentik.flows.api import FlowStageBindingSerializer from authentik.flows.api.bindings import FlowStageBindingSerializer
return FlowStageBindingSerializer return FlowStageBindingSerializer
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.target} #{self.order} -> {self.stage}" return f"{self.target} #{self.order}"
class Meta: class Meta:

View file

@ -3,7 +3,7 @@ from django.shortcuts import reverse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import User 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.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
from authentik.policies.dummy.models import DummyPolicy from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding

View file

@ -22,6 +22,8 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"verbose_name", "verbose_name",
"verbose_name_plural", "verbose_name_plural",
"object_type", "object_type",
"flow_set",
"promptstage_set",
) )
for to_remove_name in to_remove: for to_remove_name in to_remove:
if to_remove_name in data: if to_remove_name in data:

View file

@ -65,6 +65,7 @@ class FlowImporter:
return value return value
for key, value in attrs.items(): for key, value in attrs.items():
try:
if isinstance(value, dict): if isinstance(value, dict):
for idx, _inner_key in enumerate(value): for idx, _inner_key in enumerate(value):
value[_inner_key] = updater(value[_inner_key]) value[_inner_key] = updater(value[_inner_key])
@ -73,6 +74,8 @@ class FlowImporter:
attrs[key][idx] = updater(_inner_value) attrs[key][idx] = updater(_inner_value)
else: else:
attrs[key] = updater(value) attrs[key] = updater(value)
except TypeError:
continue
return attrs return attrs
def __query_from_identifier(self, attrs: dict[str, Any]) -> Q: def __query_from_identifier(self, attrs: dict[str, Any]) -> Q:

View file

@ -1,7 +1,19 @@
"""Outpost API Views""" """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 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 ( from authentik.outposts.models import (
DockerServiceConnection, DockerServiceConnection,
KubernetesServiceConnection, KubernetesServiceConnection,
@ -9,32 +21,81 @@ from authentik.outposts.models import (
) )
class ServiceConnectionSerializer(ModelSerializer): class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer):
"""ServiceConnection Serializer""" """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: class Meta:
model = OutpostServiceConnection 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): class ServiceConnectionViewSet(ModelViewSet):
"""ServiceConnection Viewset""" """ServiceConnection Viewset"""
queryset = OutpostServiceConnection.objects.all() queryset = OutpostServiceConnection.objects.select_subclasses()
serializer_class = ServiceConnectionSerializer 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""" """DockerServiceConnection Serializer"""
class Meta: class Meta:
model = DockerServiceConnection model = DockerServiceConnection
fields = [ fields = ServiceConnectionSerializer.Meta.fields + [
"pk",
"name",
"local",
"url", "url",
"tls_verification", "tls_verification",
"tls_authentication", "tls_authentication",
@ -48,13 +109,13 @@ class DockerServiceConnectionViewSet(ModelViewSet):
serializer_class = DockerServiceConnectionSerializer serializer_class = DockerServiceConnectionSerializer
class KubernetesServiceConnectionSerializer(ModelSerializer): class KubernetesServiceConnectionSerializer(ServiceConnectionSerializer):
"""KubernetesServiceConnection Serializer""" """KubernetesServiceConnection Serializer"""
class Meta: class Meta:
model = KubernetesServiceConnection model = KubernetesServiceConnection
fields = ["pk", "name", "local", "kubeconfig"] fields = ServiceConnectionSerializer.Meta.fields + ["kubeconfig"]
class KubernetesServiceConnectionViewSet(ModelViewSet): class KubernetesServiceConnectionViewSet(ModelViewSet):

View file

@ -1,17 +1,25 @@
"""policy API Views""" """policy API Views"""
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist 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.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ( from rest_framework.serializers import (
ModelSerializer, ModelSerializer,
PrimaryKeyRelatedField, PrimaryKeyRelatedField,
Serializer,
SerializerMethodField, 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 from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel
@ -45,20 +53,27 @@ class PolicyBindingModelForeignKey(PrimaryKeyRelatedField):
return correct_model.pk return correct_model.pk
class PolicySerializer(ModelSerializer): class PolicySerializer(ModelSerializer, MetaNameSerializer):
"""Policy Serializer""" """Policy Serializer"""
_resolve_inheritance: bool _resolve_inheritance: bool
object_type = SerializerMethodField() object_type = SerializerMethodField()
bound_to = SerializerMethodField()
def __init__(self, *args, resolve_inheritance: bool = True, **kwargs): def __init__(self, *args, resolve_inheritance: bool = True, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._resolve_inheritance = resolve_inheritance 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""" """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): def to_representation(self, instance: Policy):
# pyright: reportGeneralTypeIssues=false # pyright: reportGeneralTypeIssues=false
@ -71,7 +86,15 @@ class PolicySerializer(ModelSerializer):
class Meta: class Meta:
model = Policy 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 depth = 3
@ -84,9 +107,34 @@ class PolicyViewSet(ReadOnlyModelViewSet):
"bindings": ["isnull"], "bindings": ["isnull"],
"promptstage": ["isnull"], "promptstage": ["isnull"],
} }
search_fields = ["name"]
def get_queryset(self): 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): class PolicyBindingSerializer(ModelSerializer):
@ -124,14 +172,3 @@ class PolicyBindingViewSet(ModelViewSet):
serializer_class = PolicyBindingSerializer serializer_class = PolicyBindingSerializer
filterset_fields = ["policy", "target", "enabled", "order", "timeout"] filterset_fields = ["policy", "target", "enabled", "order", "timeout"]
search_fields = ["policy__name"] 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_*"))}})

View file

@ -1,7 +1,7 @@
"""AuthenticatorStaticStage API Views""" """AuthenticatorStaticStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.authenticator_static.models import AuthenticatorStaticStage

View file

@ -1,7 +1,7 @@
"""AuthenticatorTOTPStage API Views""" """AuthenticatorTOTPStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage

View file

@ -1,7 +1,7 @@
"""AuthenticatorValidateStage API Views""" """AuthenticatorValidateStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage

View file

@ -19,7 +19,7 @@ class ValidationForm(forms.Form):
label=_("Please enter the token from your device."), label=_("Please enter the token from your device."),
widget=forms.TextInput( widget=forms.TextInput(
attrs={ attrs={
"autocomplete": "off", "autocomplete": "one-time-code",
"placeholder": "123456", "placeholder": "123456",
"autofocus": "autofocus", "autofocus": "autofocus",
} }

View file

@ -1,7 +1,7 @@
"""AuthenticateWebAuthnStage API Views""" """AuthenticateWebAuthnStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage

View file

@ -1,7 +1,7 @@
"""CaptchaStage API Views""" """CaptchaStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.captcha.models import CaptchaStage

View file

@ -1,7 +1,7 @@
"""ConsentStage API Views""" """ConsentStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.consent.models import ConsentStage

View file

@ -1,7 +1,7 @@
"""DummyStage API Views""" """DummyStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.dummy.models import DummyStage

View file

@ -1,7 +1,7 @@
"""EmailStage API Views""" """EmailStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.email.models import EmailStage, get_template_choices

View file

@ -1,7 +1,7 @@
"""Identification Stage API Views""" """Identification Stage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.identification.models import IdentificationStage

View file

@ -2,7 +2,7 @@
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.models import Invitation, InvitationStage
@ -34,7 +34,9 @@ class InvitationSerializer(ModelSerializer):
"pk", "pk",
"expires", "expires",
"fixed_data", "fixed_data",
"created_by",
] ]
depth = 2
class InvitationViewSet(ModelViewSet): class InvitationViewSet(ModelViewSet):
@ -42,6 +44,9 @@ class InvitationViewSet(ModelViewSet):
queryset = Invitation.objects.all() queryset = Invitation.objects.all()
serializer_class = InvitationSerializer serializer_class = InvitationSerializer
order = ["-expires"]
search_fields = ["created_by__username", "expires"]
filterset_fields = ["created_by__username", "expires"]
def perform_create(self, serializer: InvitationSerializer): def perform_create(self, serializer: InvitationSerializer):
serializer.instance.created_by = self.request.user serializer.instance.created_by = self.request.user

View file

@ -1,7 +1,7 @@
"""PasswordStage API Views""" """PasswordStage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.password.models import PasswordStage

View file

@ -3,7 +3,7 @@ from rest_framework.serializers import CharField, ModelSerializer
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.prompt.models import Prompt, PromptStage from authentik.stages.prompt.models import Prompt, PromptStage
@ -31,6 +31,8 @@ class PromptStageViewSet(ModelViewSet):
class PromptSerializer(ModelSerializer): class PromptSerializer(ModelSerializer):
"""Prompt Serializer""" """Prompt Serializer"""
promptstage_set = StageSerializer(many=True, required=False)
class Meta: class Meta:
model = Prompt model = Prompt
@ -42,11 +44,13 @@ class PromptSerializer(ModelSerializer):
"required", "required",
"placeholder", "placeholder",
"order", "order",
"promptstage_set",
] ]
class PromptViewSet(ModelViewSet): class PromptViewSet(ModelViewSet):
"""Prompt Viewset""" """Prompt Viewset"""
queryset = Prompt.objects.all() queryset = Prompt.objects.all().prefetch_related("promptstage_set")
serializer_class = PromptSerializer serializer_class = PromptSerializer
filterset_fields = ["field_key", "label", "type", "placeholder"]

View file

@ -1,7 +1,7 @@
"""User Delete Stage API Views""" """User Delete Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_delete.models import UserDeleteStage from authentik.stages.user_delete.models import UserDeleteStage

View file

@ -1,7 +1,7 @@
"""Login Stage API Views""" """Login Stage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.user_login.models import UserLoginStage

View file

@ -1,7 +1,7 @@
"""Logout Stage API Views""" """Logout Stage API Views"""
from rest_framework.viewsets import ModelViewSet 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 from authentik.stages.user_logout.models import UserLogoutStage

View file

@ -1,7 +1,7 @@
"""User Write Stage API Views""" """User Write Stage API Views"""
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_write.models import UserWriteStage from authentik.stages.user_write.models import UserWriteStage

View file

@ -1,5 +1,6 @@
trigger: trigger:
- master - master
- next
resources: resources:
- repo: self - repo: self

View file

@ -1,5 +1,6 @@
trigger: trigger:
- master - master
- next
variables: variables:
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}: ${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
trigger: trigger:
- master - master
- next
variables: variables:
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}: ${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:

134
web/package-lock.json generated
View file

@ -144,30 +144,16 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.0.tgz",
"integrity": "sha512-t3y2TLXDWgvfknyH8eKj/9mghJfSEqItFyp74zPu1Src6kOPjkd4Sa7o4+bdkNgA8dIIOrDAhRUbB2sq4sWMCA==", "integrity": "sha512-4r3paHcHXLemj471BtNDhUs2kvJxk5XDRplz1dbC/LHXN5PWEXP4anhGILxOlxqi4y33r53PIZu3xXFjznaVZA==",
"requires": { "requires": {
"@sentry/core": "6.1.0", "@sentry/core": "6.2.0",
"@sentry/types": "6.1.0", "@sentry/types": "6.2.0",
"@sentry/utils": "6.1.0", "@sentry/utils": "6.2.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "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": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -176,51 +162,17 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.0.tgz",
"integrity": "sha512-57mXkp3NoyxRycXrL+Ec6bYS6UYJZp9tYX0lUp5Ry2M0FxDZ3Q4drkjr8MIQOhBaQXP2ukSX4QTVLGMPm60zMw==", "integrity": "sha512-oTr2b25l+0bv/+d6IgMamPuGleWV7OgJb0NFfd+WZhw6UDRgr7CdEJy2gW6tK8SerwXgPHdn4ervxsT3WIBiXw==",
"requires": { "requires": {
"@sentry/hub": "6.1.0", "@sentry/hub": "6.2.0",
"@sentry/minimal": "6.1.0", "@sentry/minimal": "6.2.0",
"@sentry/types": "6.1.0", "@sentry/types": "6.2.0",
"@sentry/utils": "6.1.0", "@sentry/utils": "6.2.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "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": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -229,12 +181,12 @@
} }
}, },
"@sentry/hub": { "@sentry/hub": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.0.tgz",
"integrity": "sha512-JnBSCgNg3VHiMojUl5tCHU8iWPVuE+qqENIzG9A722oJms1kKWBvWl+yQzhWBNdgk5qeAY3F5UzKWJZkbJ6xow==", "integrity": "sha512-BDTEFK8vlJydWXp/KMX0stvv73V7od224iLi+w3k7BcPwMKXBuURBXPU8d5XIC4G8nwg8X6cnDvwL+zBBlBbkg==",
"requires": { "requires": {
"@sentry/types": "6.1.0", "@sentry/types": "6.2.0",
"@sentry/utils": "6.1.0", "@sentry/utils": "6.2.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -246,12 +198,12 @@
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.0.tgz",
"integrity": "sha512-g6sfNKenL7wnsr/tibp8nFiMv/XRH0s0Pt4p151npmNI+SmjuUz3GGYEXk8ChCyaKldYKilkNOFdVXJxUf5gZw==", "integrity": "sha512-haxsx8/ZafhZUaGeeMtY7bJt9HbDlqeiaXrRMp1CxGtd0ZRQwHt60imEjl6IH1I73SEWxNfqScGsX2s3HzztMg==",
"requires": { "requires": {
"@sentry/hub": "6.1.0", "@sentry/hub": "6.2.0",
"@sentry/types": "6.1.0", "@sentry/types": "6.2.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -263,14 +215,14 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.0.tgz",
"integrity": "sha512-s6a4Ra3hHn4awiNz4fOEK6TCV2w2iLcxdppijcYEB7S/1rJpmqZgHWDicqufbOmVMOLmyKLEQ7w+pZq3TR3WgQ==", "integrity": "sha512-pzgM1dePPJysVnzaFCMp+BKtjM5q46HZeyShiR+KcQYvneD3fmUPJigDkkcsB2DcrY3mFvDcswjoqxaTIW7ZBQ==",
"requires": { "requires": {
"@sentry/hub": "6.1.0", "@sentry/hub": "6.2.0",
"@sentry/minimal": "6.1.0", "@sentry/minimal": "6.2.0",
"@sentry/types": "6.1.0", "@sentry/types": "6.2.0",
"@sentry/utils": "6.1.0", "@sentry/utils": "6.2.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -282,16 +234,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.0.tgz",
"integrity": "sha512-kIaN52Fw5K+2mKRaHE2YluJ+F/qMGSUzZXIFDNdC6OUMXQ4TM8gZTrITXs8CLDm7cK8iCqFCtzKOjKK6KyOKAg==" "integrity": "sha512-vN4P/a+QqAuVfWFB9G3nQ7d6bgnM9jd/RLVi49owMuqvM24pv5mTQHUk2Hk4S3k7ConrHFl69E7xH6Dv5VpQnQ=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.0.tgz",
"integrity": "sha512-6JAplzUOS6bEwfX0PDRZBbYRvn9EN22kZfcL0qGHtM9L0QQ5ybjbbVwOpbXgRkiZx++dQbzLFtelxnDhsbFG+Q==", "integrity": "sha512-YToUC7xYf2E/pIluI7upYTlj8fKXOtdwoOBkcQZifHgX/dP+qDaHibbBFe5PyZwdmU2UiLnWFsBr0gjo0QFo1g==",
"requires": { "requires": {
"@sentry/types": "6.1.0", "@sentry/types": "6.2.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -1682,9 +1634,9 @@
} }
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.4", "version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
}, },
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
@ -2729,9 +2681,9 @@
} }
}, },
"rollup-plugin-copy": { "rollup-plugin-copy": {
"version": "3.3.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.3.0.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz",
"integrity": "sha512-euDjCUSBXZa06nqnwCNADbkAcYDfzwowfZQkto9K/TFhiH+QG7I4PUsEMwM9tDgomGWJc//z7KLW8t+tZwxADA==", "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==",
"requires": { "requires": {
"@types/fs-extra": "^8.0.1", "@types/fs-extra": "^8.0.1",
"colorette": "^1.1.0", "colorette": "^1.1.0",

View file

@ -12,8 +12,8 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2", "@fortawesome/fontawesome-free": "^5.15.2",
"@patternfly/patternfly": "^4.87.3", "@patternfly/patternfly": "^4.87.3",
"@sentry/browser": "^6.1.0", "@sentry/browser": "^6.2.0",
"@sentry/tracing": "^6.1.0", "@sentry/tracing": "^6.2.0",
"@types/chart.js": "^2.9.30", "@types/chart.js": "^2.9.30",
"@types/codemirror": "0.0.108", "@types/codemirror": "0.0.108",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
@ -24,7 +24,7 @@
"lit-element": "^2.4.0", "lit-element": "^2.4.0",
"lit-html": "^1.3.0", "lit-html": "^1.3.0",
"rollup": "^2.39.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-cssimport": "^1.0.2",
"rollup-plugin-external-globals": "^0.6.1", "rollup-plugin-external-globals": "^0.6.1",
"tslib": "^2.1.0" "tslib": "^2.1.0"

View file

@ -1,4 +1,4 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client"; import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client";
import { TypeCreate } from "./Providers"; import { TypeCreate } from "./Providers";
export enum FlowDesignation { export enum FlowDesignation {
@ -40,8 +40,8 @@ export class Flow {
} }
static cached(): Promise<number> { static cached(): Promise<number> {
return DefaultClient.fetch<AKResponse<Flow>>(["flows", "cached"]).then(r => { return DefaultClient.fetch<{ count: number }>(["flows", "all", "cached"]).then(r => {
return r.pagination.count; return r.count;
}); });
} }
static adminUrl(rest: string): string { static adminUrl(rest: string): string {
@ -49,16 +49,26 @@ export class Flow {
} }
} }
export class Stage { export class Stage implements BaseInheritanceModel {
pk: string; pk: string;
name: string; name: string;
__type__: string; object_type: string;
verbose_name: string; verbose_name: string;
verbose_name_plural: string;
flow_set: Flow[];
constructor() { constructor() {
throw Error(); throw Error();
} }
static get(slug: string): Promise<Stage> {
return DefaultClient.fetch<Stage>(["stages", "all", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Stage>> {
return DefaultClient.fetch<AKResponse<Stage>>(["stages", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> { static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["stages", "all", "types"]); return DefaultClient.fetch<TypeCreate[]>(["stages", "all", "types"]);
} }

View file

@ -1,15 +1,28 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events"; import { EventContext } from "./Events";
export class Group { export class Group {
group_uuid: string; pk: string;
name: string; name: string;
is_superuser: boolean; is_superuser: boolean;
attributes: EventContext; attributes: EventContext;
parent?: Group; parent?: Group;
users: number[];
constructor() { constructor() {
throw Error(); throw Error();
} }
static get(pk: string): Promise<Group> {
return DefaultClient.fetch<Group>(["core", "groups", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Group>> {
return DefaultClient.fetch<AKResponse<Group>>(["core", "groups"], filter);
}
static adminUrl(rest: string): string {
return `/administration/groups/${rest}`;
}
} }

View file

@ -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<Invitation> {
return DefaultClient.fetch<Invitation>(["stages", "invitation", "invitations", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Invitation>> {
return DefaultClient.fetch<AKResponse<Invitation>>(["stages", "invitation", "invitations"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/invitations/${rest}`;
}
}

View file

@ -1,5 +1,5 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client"; import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Provider } from "./Providers"; import { Provider, TypeCreate } from "./Providers";
export interface OutpostHealth { export interface OutpostHealth {
last_seen: number; last_seen: number;
@ -38,3 +38,42 @@ export class Outpost {
return `/administration/outposts/${rest}`; 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<OutpostServiceConnection> {
return DefaultClient.fetch<OutpostServiceConnection>(["outposts", "service_connections", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<OutpostServiceConnection>> {
return DefaultClient.fetch<AKResponse<OutpostServiceConnection>>(["outposts", "service_connections", "all"], filter);
}
static state(pk: string): Promise<OutpostServiceConnectionState> {
return DefaultClient.fetch<OutpostServiceConnectionState>(["outposts", "service_connections", "all", pk, "state"]);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["outposts", "service_connections", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/outpost_service_connections/${rest}`;
}
}

View file

@ -1,15 +1,18 @@
import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client"; import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Policy implements BaseInheritanceModel { export class Policy implements BaseInheritanceModel {
pk: string; pk: string;
name: string; name: string;
execution_logging: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
bound_to: number;
constructor() { constructor() {
throw Error(); throw Error();
} }
object_type: string;
verbose_name: string;
verbose_name_plural: string;
static get(pk: string): Promise<Policy> { static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]); return DefaultClient.fetch<Policy>(["policies", "all", pk]);
@ -20,8 +23,16 @@ export class Policy implements BaseInheritanceModel {
} }
static cached(): Promise<number> { static cached(): Promise<number> {
return DefaultClient.fetch<AKResponse<Policy>>(["policies", "cached"]).then(r => { return DefaultClient.fetch<{ count: number }>(["policies", "all", "cached"]).then(r => {
return r.pagination.count; return r.count;
}); });
} }
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["policies", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/policies/${rest}`;
}
} }

30
web/src/api/Prompts.ts Normal file
View file

@ -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<Prompt> {
return DefaultClient.fetch<Prompt>(["stages", "prompt", "prompts", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Prompt>> {
return DefaultClient.fetch<AKResponse<Prompt>>(["stages", "prompt", "prompts"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/prompts/${rest}`;
}
}

View file

@ -1,4 +1,5 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client"; import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class PropertyMapping { export class PropertyMapping {
pk: string; pk: string;
@ -20,6 +21,10 @@ export class PropertyMapping {
return DefaultClient.fetch<AKResponse<PropertyMapping>>(["propertymappings", "all"], filter); return DefaultClient.fetch<AKResponse<PropertyMapping>>(["propertymappings", "all"], filter);
} }
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["propertymappings", "all", "types"]);
}
static adminUrl(rest: string): string { static adminUrl(rest: string): string {
return `/administration/property-mappings/${rest}`; return `/administration/property-mappings/${rest}`;
} }

33
web/src/api/SystemTask.ts Normal file
View file

@ -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<SystemTask> {
return DefaultClient.fetch<SystemTask>(["admin", "system_tasks", task_name]);
}
static list(filter?: QueryArguments): Promise<SystemTask[]> {
return DefaultClient.fetch<SystemTask[]>(["admin", "system_tasks"], filter);
}
static retry(task_name: string): string {
return DefaultClient.makeUrl(["admin", "system_tasks", task_name, "retry"]);
}
}

View file

@ -1,11 +1,47 @@
import { DefaultClient } from "./Client"; import { AKResponse, DefaultClient, QueryArguments } from "./Client";
import { User } from "./Users";
interface TokenResponse { export enum TokenIntent {
key: string; INTENT_VERIFICATION = "verification",
INTENT_API = "api",
INTENT_RECOVERY = "recovery",
} }
export function tokenByIdentifier(identifier: string): Promise<string> { export class Token {
return DefaultClient.fetch<TokenResponse>(["core", "tokens", identifier, "view_key"]).then(
pk: string;
identifier: string;
intent: TokenIntent;
user: User;
description: string;
expires: number;
expiring: boolean;
constructor() {
throw Error();
}
static get(pk: string): Promise<User> {
return DefaultClient.fetch<User>(["core", "tokens", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Token>> {
return DefaultClient.fetch<AKResponse<Token>>(["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<string> {
return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then(
(r) => r.key (r) => r.key
); );
}
} }

View file

@ -1,4 +1,4 @@
import { DefaultClient, AKResponse } from "./Client"; import { DefaultClient, AKResponse, QueryArguments } from "./Client";
let _globalMePromise: Promise<User>; let _globalMePromise: Promise<User>;
@ -9,11 +9,25 @@ export class User {
is_superuser: boolean; is_superuser: boolean;
email: boolean; email: boolean;
avatar: string; avatar: string;
is_active: boolean;
last_login: number;
constructor() { constructor() {
throw Error(); throw Error();
} }
static get(pk: string): Promise<User> {
return DefaultClient.fetch<User>(["core", "users", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<User>> {
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], filter);
}
static adminUrl(rest: string): string {
return `/administration/users/${rest}`;
}
static me(): Promise<User> { static me(): Promise<User> {
if (!_globalMePromise) { if (!_globalMePromise) {
_globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]); _globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]);

View file

@ -85,10 +85,6 @@ select[multiple] {
z-index: auto !important; z-index: auto !important;
} }
.pf-c-page__main {
display: block;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--ak-dark-foreground: #fafafa; --ak-dark-foreground: #fafafa;

View file

@ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
// @ts-ignore // @ts-ignore
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css"; 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"; import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
@customElement("ak-token-copy-button") @customElement("ak-token-copy-button")
@ -35,7 +35,7 @@ export class TokenCopyButton extends LitElement {
}, 1500); }, 1500);
return; return;
} }
tokenByIdentifier(this.identifier).then((token) => { Token.getKey(this.identifier).then((token) => {
navigator.clipboard.writeText(token).then(() => { navigator.clipboard.writeText(token).then(() => {
this.buttonClass = SUCCESS_CLASS; this.buttonClass = SUCCESS_CLASS;
setTimeout(() => { setTimeout(() => {

View file

@ -47,6 +47,7 @@ export class MessageContainer extends LitElement {
this.messageSocket = new WebSocket(wsUrl); this.messageSocket = new WebSocket(wsUrl);
this.messageSocket.addEventListener("open", () => { this.messageSocket.addEventListener("open", () => {
console.debug(`authentik/messages: connected to ${wsUrl}`); console.debug(`authentik/messages: connected to ${wsUrl}`);
this.retryDelay = 200;
}); });
this.messageSocket.addEventListener("close", (e) => { this.messageSocket.addEventListener("close", (e) => {
console.debug(`authentik/messages: closed ws connection: ${e}`); console.debug(`authentik/messages: closed ws connection: ${e}`);

View file

@ -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") @customElement("ak-notification-trigger")
export class NotificationRule extends LitElement { export class NotificationRule extends LitElement {
static get styles(): CSSResult[] {
return COMMON_STYLES;
}
constructor() { constructor() {
super(); super();
this.addEventListener("click", () => { this.addEventListener("click", () => {
@ -16,7 +21,8 @@ export class NotificationRule extends LitElement {
} }
render(): TemplateResult { render(): TemplateResult {
return html`<slot></slot>`; // TODO: Show icon with red dot when unread notifications exist
return html`<i class="fas fa-bell pf-c-dropdown__toggle-icon" aria-hidden="true"></i>`;
} }
} }

View file

@ -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`<div class="pf-c-empty-state pf-m-full-height">
<div class="pf-c-empty-state__content">
<i class="fas fa-question-circle pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">${gettext("Not found")}</h1>
<div class="pf-c-empty-state__body">
${gettext(`The url '${this.url}' was not found.`)}
</div>
<a href="#/" class="pf-c-button pf-m-primary" type="button">${gettext("Return home")}</a>
</div>
</div>`;
}
}

View file

@ -10,6 +10,7 @@ import { ROUTES } from "../../routes";
import { RouteMatch } from "./RouteMatch"; import { RouteMatch } from "./RouteMatch";
import "../../pages/generic/SiteShell"; import "../../pages/generic/SiteShell";
import "./Router404";
@customElement("ak-router-outlet") @customElement("ak-router-outlet")
export class RouterOutlet extends LitElement { export class RouterOutlet extends LitElement {
@ -28,6 +29,11 @@ export class RouterOutlet extends LitElement {
:host { :host {
height: 100vh; height: 100vh;
} }
*:first-child {
height: 100%;
display: flex;
flex-direction: column;
}
`, `,
].concat(...COMMON_STYLES); ].concat(...COMMON_STYLES);
} }
@ -62,12 +68,12 @@ export class RouterOutlet extends LitElement {
} }
}); });
if (!matchedRoute) { 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( const route = new Route(
RegExp(""), RegExp(""),
html`<ak-site-shell url=${activeUrl}> html`<div class="pf-c-page__main">
<div slot="body"></div> <ak-router-404 url=${activeUrl}></ak-router-404>
</ak-site-shell>` </div>`
); );
matchedRoute = new RouteMatch(route); matchedRoute = new RouteMatch(route);
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {}; matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
@ -77,7 +83,6 @@ export class RouterOutlet extends LitElement {
} }
render(): TemplateResult | undefined { render(): TemplateResult | undefined {
// TODO: Render 404 when current Route is empty
return this.current?.render(); return this.current?.render();
} }
} }

View file

@ -29,7 +29,7 @@ export class SidebarItem {
this.condition = async () => true; this.condition = async () => true;
this.activeMatchers = []; this.activeMatchers = [];
if (this.path) { if (this.path) {
this.activeMatchers.push(new RegExp(`^${this.path}`)); this.activeMatchers.push(new RegExp(`^${this.path}$`));
} }
} }

View file

@ -37,11 +37,11 @@ export class SidebarUser extends LitElement {
render(): TemplateResult { render(): TemplateResult {
return html` return html`
<a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings"> <a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings">
${until(User.me().then(u => { ${until(User.me().then((u) => {
return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;}), html``)} return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;
}), html``)}
</a> </a>
<ak-notification-trigger class="pf-c-nav__link user-notifications"> <ak-notification-trigger class="pf-c-nav__link user-notifications">
<i class="fas fa-bell pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</ak-notification-trigger> </ak-notification-trigger>
<a href="/flows/-/default/invalidation/" class="pf-c-nav__link user-logout" id="logout"> <a href="/flows/-/default/invalidation/" class="pf-c-nav__link user-logout" id="logout">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i> <i class="fas fa-sign-out-alt" aria-hidden="true"></i>

View file

@ -43,7 +43,7 @@ export class TablePagination extends LitElement {
<button <button
class="pf-c-button pf-m-plain" class="pf-c-button pf-m-plain"
@click=${() => { this.pageChangeHandler(this.pages?.next || 0); }} @click=${() => { this.pageChangeHandler(this.pages?.next || 0); }}
?disabled="${(this.pages?.next || 0) < 0}" ?disabled="${(this.pages?.next || 0) <= 0}"
aria-label="${gettext("Go to next page")}" aria-label="${gettext("Go to next page")}"
> >
<i class="fas fa-angle-right" aria-hidden="true"></i> <i class="fas fa-angle-right" aria-hidden="true"></i>

View file

@ -8,7 +8,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Library", "/library"), new SidebarItem("Library", "/library"),
new SidebarItem("Monitor").children( new SidebarItem("Monitor").children(
new SidebarItem("Overview", "/administration/overview"), new SidebarItem("Overview", "/administration/overview"),
new SidebarItem("System Tasks", "/administration/tasks/"), new SidebarItem("System Tasks", "/administration/system-tasks"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
@ -20,37 +20,37 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
new SidebarItem("Resources").children( new SidebarItem("Resources").children(
new SidebarItem("Applications", "/applications").activeWhen( new SidebarItem("Applications", "/core/applications").activeWhen(
`^/applications/(?<slug>${SLUG_REGEX})$` `^/core/applications/(?<slug>${SLUG_REGEX})$`
), ),
new SidebarItem("Sources", "/sources").activeWhen( new SidebarItem("Sources", "/core/sources").activeWhen(
`^/sources/(?<slug>${SLUG_REGEX})$`, `^/core/sources/(?<slug>${SLUG_REGEX})$`,
), ),
new SidebarItem("Providers", "/providers"), new SidebarItem("Providers", "/core/providers"),
new SidebarItem("Outposts", "/outposts"), new SidebarItem("Outposts", "/outpost/outposts"),
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"), new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
new SidebarItem("Customisation").children( new SidebarItem("Customisation").children(
new SidebarItem("Policies", "/administration/policies/"), new SidebarItem("Policies", "/policy/policies"),
new SidebarItem("Property Mappings", "/property-mappings"), new SidebarItem("Property Mappings", "/core/property-mappings"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
new SidebarItem("Flows").children( new SidebarItem("Flows").children(
new SidebarItem("Flows", "/flows").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})$`), new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
new SidebarItem("Stages", "/administration/stages/"), new SidebarItem("Stages", "/flow/stages"),
new SidebarItem("Prompts", "/administration/stages_prompts/"), new SidebarItem("Prompts", "/flow/stages/prompts"),
new SidebarItem("Invitations", "/administration/stages/invitations/"), new SidebarItem("Invitations", "/flow/stages/invitations"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
new SidebarItem("Identity & Cryptography").children( new SidebarItem("Identity & Cryptography").children(
new SidebarItem("User", "/administration/users/"), new SidebarItem("User", "/identity/users"),
new SidebarItem("Groups", "/administration/groups/"), new SidebarItem("Groups", "/identity/groups"),
new SidebarItem("Certificates", "/crypto/certificates"), new SidebarItem("Certificates", "/crypto/certificates"),
new SidebarItem("Tokens", "/administration/tokens/"), new SidebarItem("Tokens", "/core/tokens"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),

View file

@ -28,6 +28,7 @@ import "./pages/admin-overview/AdminOverviewPage";
import "./pages/admin-overview/TopApplicationsTable"; import "./pages/admin-overview/TopApplicationsTable";
import "./pages/applications/ApplicationListPage"; import "./pages/applications/ApplicationListPage";
import "./pages/applications/ApplicationViewPage"; import "./pages/applications/ApplicationViewPage";
import "./pages/tokens/UserTokenList";
import "./pages/LibraryPage"; import "./pages/LibraryPage";
import "./elements/stages/authenticator_webauthn/WebAuthnRegister"; import "./elements/stages/authenticator_webauthn/WebAuthnRegister";

View file

@ -14,6 +14,10 @@ export class LibraryApplication extends LitElement {
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return COMMON_STYLES.concat( return COMMON_STYLES.concat(
css` css`
:host,
main {
height: 100%;
}
a { a {
height: 100%; height: 100%;
} }

View file

@ -36,7 +36,7 @@ export class AdminOverviewPage extends LitElement {
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;"> <ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
<ak-top-applications-table></ak-top-applications-table> <ak-top-applications-table></ak-top-applications-table>
</ak-aggregate-card> </ak-aggregate-card>
<ak-admin-status-card-provider class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/providers/"> <ak-admin-status-card-provider class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/core/providers/">
</ak-admin-status-card-provider> </ak-admin-status-card-provider>
<ak-admin-status-card-policy-unbound class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-infrastructure" header="Policies" headerLink="#/administration/policies/"> <ak-admin-status-card-policy-unbound class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-infrastructure" header="Policies" headerLink="#/administration/policies/">
</ak-admin-status-card-policy-unbound> </ak-admin-status-card-policy-unbound>

View file

@ -50,7 +50,7 @@ export class ApplicationListPage extends TablePage<Application> {
item.meta_icon ? item.meta_icon ?
html`<img class="app-icon pf-c-avatar" src="${item.meta_icon}" alt="${gettext("Application Icon")}">` : html`<img class="app-icon pf-c-avatar" src="${item.meta_icon}" alt="${gettext("Application Icon")}">` :
html`<i class="pf-icon pf-icon-arrow"></i>`, html`<i class="pf-icon pf-icon-arrow"></i>`,
html`<a href="#/applications/${item.slug}"> html`<a href="#/core/applications/${item.slug}">
<div> <div>
${item.name} ${item.name}
</div> </div>

Some files were not shown because too many files have changed in this diff Show more