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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,45 +7,23 @@ from django.contrib.auth.mixins import (
)
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import FormView
from django.views.generic.detail import DetailView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.policies import PolicyTestForm
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.policies.models import Policy, PolicyBinding
from authentik.policies.process import PolicyProcess, PolicyRequest
class PolicyListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
):
"""Show list of all policies"""
model = Policy
permission_required = "authentik_policies.view_policy"
ordering = "name"
template_name = "administration/policy/list.html"
search_fields = ["name"]
class PolicyCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -56,13 +34,12 @@ class PolicyCreateView(
permission_required = "authentik_policies.add_policy"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies")
success_url = "/"
success_message = _("Successfully created Policy")
class PolicyUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,
@ -73,7 +50,7 @@ class PolicyUpdateView(
permission_required = "authentik_policies.change_policy"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies")
success_url = "/"
success_message = _("Successfully updated Policy")
@ -84,7 +61,7 @@ class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_policies.delete_policy"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies")
success_url = "/"
success_message = _("Successfully deleted Policy")

View file

@ -6,55 +6,19 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Max, QuerySet
from django.urls import reverse_lazy
from django.db.models import Max
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.policies.forms import PolicyBindingForm
from authentik.policies.models import PolicyBinding, PolicyBindingModel
class PolicyBindingListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView
):
"""Show list of all policies"""
model = PolicyBinding
permission_required = "authentik_policies.view_policybinding"
ordering = ["order", "target"]
template_name = "administration/policy_binding/list.html"
def get_queryset(self) -> QuerySet:
# Since `select_subclasses` does not work with a foreign key, we have to do two queries here
# First, get all pbm objects that have bindings attached
objects = (
get_objects_for_user(
self.request.user, "authentik_policies.view_policybindingmodel"
)
.filter(policies__isnull=False)
.select_subclasses()
.select_related()
.order_by("pk")
)
for pbm in objects:
pbm.bindings = get_objects_for_user(
self.request.user, self.permission_required
).filter(target__pk=pbm.pbm_uuid)
return objects
class PolicyBindingCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -66,7 +30,7 @@ class PolicyBindingCreateView(
form_class = PolicyBindingForm
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies-bindings")
success_url = "/"
success_message = _("Successfully created PolicyBinding")
def get_initial(self) -> dict[str, Any]:
@ -88,7 +52,6 @@ class PolicyBindingCreateView(
class PolicyBindingUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -100,7 +63,7 @@ class PolicyBindingUpdateView(
form_class = PolicyBindingForm
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies-bindings")
success_url = "/"
success_message = _("Successfully updated PolicyBinding")
@ -113,5 +76,5 @@ class PolicyBindingDeleteView(
permission_required = "authentik_policies.delete_policybinding"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies-bindings")
success_url = "/"
success_message = _("Successfully deleted PolicyBinding")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,41 +5,17 @@ from django.contrib.auth.mixins import (
)
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.stages.invitation.forms import InvitationForm
from authentik.stages.invitation.models import Invitation
class InvitationListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all invitations"""
model = Invitation
permission_required = "authentik_stages_invitation.view_invitation"
template_name = "administration/stage_invitation/list.html"
ordering = "-expires"
search_fields = ["created_by__username", "expires", "fixed_data"]
class InvitationCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -51,7 +27,7 @@ class InvitationCreateView(
permission_required = "authentik_stages_invitation.add_invitation"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-invitations")
success_url = "/"
success_message = _("Successfully created Invitation")
def form_valid(self, form):
@ -70,5 +46,5 @@ class InvitationDeleteView(
permission_required = "authentik_stages_invitation.delete_invitation"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-invitations")
success_url = "/"
success_message = _("Successfully deleted Invitation")

View file

@ -4,46 +4,18 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.stages.prompt.forms import PromptAdminForm
from authentik.stages.prompt.models import Prompt
class PromptListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all prompts"""
model = Prompt
permission_required = "authentik_stages_prompt.view_prompt"
ordering = "order"
template_name = "administration/stage_prompt/list.html"
search_fields = [
"field_key",
"label",
"type",
"placeholder",
]
class PromptCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -55,13 +27,12 @@ class PromptCreateView(
permission_required = "authentik_stages_prompt.add_prompt"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = "/"
success_message = _("Successfully created Prompt")
class PromptUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -73,7 +44,7 @@ class PromptUpdateView(
permission_required = "authentik_stages_prompt.change_prompt"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = "/"
success_message = _("Successfully updated Prompt")
@ -84,5 +55,5 @@ class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_stages_prompt.delete_prompt"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = "/"
success_message = _("Successfully deleted Prompt")

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"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.models import Token
class TokenListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all tokens"""
model = Token
permission_required = "authentik_core.view_token"
ordering = "expires"
template_name = "administration/token/list.html"
search_fields = [
"identifier",
"intent",
"user__username",
"description",
]
class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
"""Delete token"""
@ -41,5 +14,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
permission_required = "authentik_core.delete_token"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:tokens")
success_url = "/"
success_message = _("Successfully deleted Token")

View file

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

View file

@ -1,15 +1,10 @@
"""authentik admin util views"""
from typing import Any, Optional
from urllib.parse import urlparse
from typing import Any
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.postgres.search import SearchQuery, SearchVector
from django.db.models import QuerySet
from django.http import Http404
from django.http.request import HttpRequest
from django.views.generic import DeleteView, ListView, UpdateView
from django.views.generic.list import MultipleObjectMixin
from django.views.generic import DeleteView, UpdateView
from authentik.lib.utils.reflection import all_subclasses
from authentik.lib.views import CreateAssignPermView
@ -25,37 +20,6 @@ class DeleteMessageView(SuccessMessageMixin, DeleteView):
return super().delete(request, *args, **kwargs)
class InheritanceListView(ListView):
"""ListView for objects using InheritanceManager"""
def get_context_data(self, **kwargs):
kwargs["types"] = {x.__name__: x for x in all_subclasses(self.model)}
return super().get_context_data(**kwargs)
def get_queryset(self):
return super().get_queryset().select_subclasses()
class SearchListMixin(MultipleObjectMixin):
"""Accept search query using `search` querystring parameter. Requires self.search_fields,
a list of all fields to search. Can contain special lookups like __icontains"""
search_fields: list[str]
def get_queryset(self) -> QuerySet:
queryset = super().get_queryset()
if "search" in self.request.GET:
raw_query = self.request.GET["search"]
if raw_query == "":
# Empty query, don't search at all
return queryset
search = SearchQuery(raw_query, search_type="websearch")
return queryset.annotate(search=SearchVector(*self.search_fields)).filter(
search=search
)
return queryset
class InheritanceCreateView(CreateAssignPermView):
"""CreateView for objects using InheritanceManager"""
@ -96,31 +60,3 @@ class InheritanceUpdateView(UpdateView):
.select_subclasses()
.first()
)
class BackSuccessUrlMixin:
"""Checks if a relative URL has been given as ?back param, and redirect to it. Otherwise
default to self.success_url."""
request: HttpRequest
success_url: Optional[str]
def get_success_url(self) -> str:
"""get_success_url from FormMixin"""
back_param = self.request.GET.get("back")
if back_param:
if not bool(urlparse(back_param).netloc):
return back_param
return str(self.success_url)
class UserPaginateListMixin:
"""Get paginate_by value from user's attributes, defaulting to 15"""
request: HttpRequest
# pylint: disable=unused-argument
def get_paginate_by(self, queryset: QuerySet) -> int:
"""get_paginate_by Function of ListView"""
return self.request.user.attributes.get("paginate_by", 15)

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

View file

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

View file

@ -1,9 +1,16 @@
"""PropertyMapping API Views"""
from django.shortcuts import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import PropertyMapping
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
@ -47,3 +54,19 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet):
def get_queryset(self):
return PropertyMapping.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable property-mapping types"""
data = []
for subclass in all_subclasses(self.queryset.model):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:property-mapping-create")
+ f"?type={subclass.__name__}",
}
)
return Response(TypeCreateSerializer(data, many=True).data)

View file

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

View file

@ -28,7 +28,17 @@ class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ["pk", "username", "name", "is_superuser", "email", "avatar"]
fields = [
"pk",
"username",
"name",
"is_active",
"last_login",
"is_superuser",
"email",
"avatar",
"attributes",
]
class UserViewSet(ModelViewSet):
@ -36,6 +46,8 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.none()
serializer_class = UserSerializer
search_fields = ["username", "name", "is_active"]
filterset_fields = ["username", "name", "is_active"]
def get_queryset(self):
return User.objects.all().exclude(pk=get_anonymous_user().pk)

View file

@ -1,6 +1,6 @@
"""API Utilities"""
from django.db.models import Model
from rest_framework.fields import CharField
from rest_framework.fields import CharField, IntegerField
from rest_framework.serializers import Serializer, SerializerMethodField
@ -37,3 +37,15 @@ class TypeCreateSerializer(Serializer):
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class CacheSerializer(Serializer):
"""Generic cache stats for an object"""
count = IntegerField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError

View file

@ -24,9 +24,7 @@
</div>
</section>
<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' %}">
<div slot="body"></div>
</ak-site-shell>
<ak-token-user-list></ak-token-user-list>
</section>
{% user_stages as user_stages_loc %}
{% for stage, stage_link in user_stages_loc.items %}

View file

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

View file

@ -6,20 +6,15 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models.query import QuerySet
from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from django.views.generic import UpdateView
from django.views.generic.base import TemplateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user
from authentik.admin.views.utils import (
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.forms.token import UserTokenForm
from authentik.core.forms.users import UserDetailForm
from authentik.core.models import Token, TokenIntents
@ -54,30 +49,6 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
return kwargs
class TokenListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all tokens"""
model = Token
ordering = "expires"
permission_required = "authentik_core.view_token"
template_name = "user/token_list.html"
search_fields = [
"identifier",
"intent",
"description",
]
def get_queryset(self) -> QuerySet:
return super().get_queryset().filter(intent=TokenIntents.INTENT_API)
class TokenCreateView(
SuccessMessageMixin,
LoginRequiredMixin,

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.db.models import Model
from django.shortcuts import get_object_or_404, reverse
from django.shortcuts import get_object_or_404
from drf_yasg2.utils import swagger_auto_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
@ -16,13 +15,11 @@ from rest_framework.serializers import (
Serializer,
SerializerMethodField,
)
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.core.api.utils import CacheSerializer
from authentik.flows.models import Flow
from authentik.flows.planner import cache_key
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
class FlowSerializer(ModelSerializer):
@ -84,6 +81,12 @@ class FlowViewSet(ModelViewSet):
search_fields = ["name", "slug", "designation", "title"]
filterset_fields = ["flow_uuid", "name", "slug", "designation"]
@swagger_auto_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False)
def cached(self, request: Request) -> Response:
"""Info about cached flows"""
return Response(data={"count": len(cache.keys("flow_*"))})
@swagger_auto_schema(responses={200: FlowDiagramSerializer()})
@action(detail=True, methods=["get"])
def diagram(self, request: Request, slug: str) -> Response:
@ -155,85 +158,3 @@ class FlowViewSet(ModelViewSet):
)
diagram = "\n".join([str(x) for x in header + body + footer])
return Response({"diagram": diagram})
class StageSerializer(ModelSerializer, MetaNameSerializer):
"""Stage Serializer"""
object_type = SerializerMethodField()
def get_object_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("stage", "")
class Meta:
model = Stage
fields = ["pk", "name", "object_type", "verbose_name", "verbose_name_plural"]
class StageViewSet(ReadOnlyModelViewSet):
"""Stage Viewset"""
queryset = Stage.objects.all()
serializer_class = StageSerializer
def get_queryset(self):
return Stage.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable stage types"""
data = []
for subclass in all_subclasses(self.queryset.model, False):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:stage-create")
+ f"?type={subclass.__name__}",
}
)
data = sorted(data, key=lambda x: x["name"])
return Response(TypeCreateSerializer(data, many=True).data)
class FlowStageBindingSerializer(ModelSerializer):
"""FlowStageBinding Serializer"""
stage_obj = StageSerializer(read_only=True, source="stage")
class Meta:
model = FlowStageBinding
fields = [
"pk",
"policybindingmodel_ptr_id",
"target",
"stage",
"stage_obj",
"evaluate_on_plan",
"re_evaluate_policies",
"order",
"policies",
]
class FlowStageBindingViewSet(ModelViewSet):
"""FlowStageBinding Viewset"""
queryset = FlowStageBinding.objects.all()
serializer_class = FlowStageBindingSerializer
filterset_fields = "__all__"
class FlowCacheViewSet(ListModelMixin, GenericViewSet):
"""Info about cached flows"""
queryset = Flow.objects.none()
serializer_class = Serializer
def list(self, request: Request) -> Response:
"""Info about cached flows"""
return Response(data={"pagination": {"count": len(cache.keys("flow_*"))}})

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

View file

@ -3,7 +3,7 @@ from django.shortcuts import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.api import StageSerializer, StageViewSet
from authentik.flows.api.stages import StageSerializer, StageViewSet
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding

View file

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

View file

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

View file

@ -1,7 +1,19 @@
"""Outpost API Views"""
from rest_framework.serializers import ModelSerializer
from dataclasses import asdict
from django.db.models.base import Model
from django.shortcuts import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
from authentik.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,
@ -9,32 +21,81 @@ from authentik.outposts.models import (
)
class ServiceConnectionSerializer(ModelSerializer):
class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer):
"""ServiceConnection Serializer"""
object_type = SerializerMethodField()
def get_object_type(self, obj: OutpostServiceConnection) -> str:
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("serviceconnection", "")
class Meta:
model = OutpostServiceConnection
fields = ["pk", "name"]
fields = [
"pk",
"name",
"local",
"object_type",
"verbose_name",
"verbose_name_plural",
]
class ServiceConnectionStateSerializer(Serializer):
"""Serializer for Service connection state"""
healthy = BooleanField(read_only=True)
version = CharField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class ServiceConnectionViewSet(ModelViewSet):
"""ServiceConnection Viewset"""
queryset = OutpostServiceConnection.objects.all()
queryset = OutpostServiceConnection.objects.select_subclasses()
serializer_class = ServiceConnectionSerializer
search_fields = ["name"]
filterset_fields = ["name"]
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable service connection types"""
data = []
for subclass in all_subclasses(self.queryset.model):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:outpost-service-connection-create")
+ f"?type={subclass.__name__}",
}
)
return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: ServiceConnectionStateSerializer(many=False)})
@action(detail=True)
# pylint: disable=unused-argument, invalid-name
def state(self, request: Request, pk: str) -> Response:
"""Get the service connection's state"""
connection = self.get_object()
return Response(asdict(connection.state))
class DockerServiceConnectionSerializer(ModelSerializer):
class DockerServiceConnectionSerializer(ServiceConnectionSerializer):
"""DockerServiceConnection Serializer"""
class Meta:
model = DockerServiceConnection
fields = [
"pk",
"name",
"local",
fields = ServiceConnectionSerializer.Meta.fields + [
"url",
"tls_verification",
"tls_authentication",
@ -48,13 +109,13 @@ class DockerServiceConnectionViewSet(ModelViewSet):
serializer_class = DockerServiceConnectionSerializer
class KubernetesServiceConnectionSerializer(ModelSerializer):
class KubernetesServiceConnectionSerializer(ServiceConnectionSerializer):
"""KubernetesServiceConnection Serializer"""
class Meta:
model = KubernetesServiceConnection
fields = ["pk", "name", "local", "kubeconfig"]
fields = ServiceConnectionSerializer.Meta.fields + ["kubeconfig"]
class KubernetesServiceConnectionViewSet(ModelViewSet):

View file

@ -1,17 +1,25 @@
"""policy API Views"""
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.mixins import ListModelMixin
from django.shortcuts import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
ModelSerializer,
PrimaryKeyRelatedField,
Serializer,
SerializerMethodField,
)
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.core.api.utils import (
CacheSerializer,
MetaNameSerializer,
TypeCreateSerializer,
)
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel
@ -45,20 +53,27 @@ class PolicyBindingModelForeignKey(PrimaryKeyRelatedField):
return correct_model.pk
class PolicySerializer(ModelSerializer):
class PolicySerializer(ModelSerializer, MetaNameSerializer):
"""Policy Serializer"""
_resolve_inheritance: bool
object_type = SerializerMethodField()
bound_to = SerializerMethodField()
def __init__(self, *args, resolve_inheritance: bool = True, **kwargs):
super().__init__(*args, **kwargs)
self._resolve_inheritance = resolve_inheritance
def get_object_type(self, obj):
def get_object_type(self, obj: Policy) -> str:
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("provider", "")
return obj._meta.object_name.lower().replace("policy", "")
def get_bound_to(self, obj: Policy) -> int:
"""Return objects policy is bound to"""
if not obj.bindings.exists() and not obj.promptstage_set.exists():
return 0
return obj.bindings.count()
def to_representation(self, instance: Policy):
# pyright: reportGeneralTypeIssues=false
@ -71,7 +86,15 @@ class PolicySerializer(ModelSerializer):
class Meta:
model = Policy
fields = ["pk", "name", "execution_logging", "object_type"]
fields = [
"pk",
"name",
"execution_logging",
"object_type",
"verbose_name",
"verbose_name_plural",
"bound_to",
]
depth = 3
@ -84,9 +107,34 @@ class PolicyViewSet(ReadOnlyModelViewSet):
"bindings": ["isnull"],
"promptstage": ["isnull"],
}
search_fields = ["name"]
def get_queryset(self):
return Policy.objects.select_subclasses()
return Policy.objects.select_subclasses().prefetch_related(
"bindings", "promptstage_set"
)
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable policy types"""
data = []
for subclass in all_subclasses(self.queryset.model):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:policy-create")
+ f"?type={subclass.__name__}",
}
)
return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False)
def cached(self, request: Request) -> Response:
"""Info about cached policies"""
return Response(data={"count": len(cache.keys("policy_*"))})
class PolicyBindingSerializer(ModelSerializer):
@ -124,14 +172,3 @@ class PolicyBindingViewSet(ModelViewSet):
serializer_class = PolicyBindingSerializer
filterset_fields = ["policy", "target", "enabled", "order", "timeout"]
search_fields = ["policy__name"]
class PolicyCacheViewSet(ListModelMixin, GenericViewSet):
"""Info about cached policies"""
queryset = Policy.objects.none()
serializer_class = Serializer
def list(self, request: Request) -> Response:
"""Info about cached policies"""
return Response(data={"pagination": {"count": len(cache.keys("policy_*"))}})

View file

@ -1,7 +1,7 @@
"""AuthenticatorStaticStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage

View file

@ -1,7 +1,7 @@
"""AuthenticatorTOTPStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage

View file

@ -1,7 +1,7 @@
"""AuthenticatorValidateStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage

View file

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

View file

@ -1,7 +1,7 @@
"""AuthenticateWebAuthnStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage

View file

@ -1,7 +1,7 @@
"""CaptchaStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.captcha.models import CaptchaStage

View file

@ -1,7 +1,7 @@
"""ConsentStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.consent.models import ConsentStage

View file

@ -1,7 +1,7 @@
"""DummyStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.dummy.models import DummyStage

View file

@ -1,7 +1,7 @@
"""EmailStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.email.models import EmailStage, get_template_choices

View file

@ -1,7 +1,7 @@
"""Identification Stage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.identification.models import IdentificationStage

View file

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

View file

@ -1,7 +1,7 @@
"""PasswordStage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.password.models import PasswordStage

View file

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

View file

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

View file

@ -1,7 +1,7 @@
"""Login Stage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_login.models import UserLoginStage

View file

@ -1,7 +1,7 @@
"""Logout Stage API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api import StageSerializer
from authentik.flows.api.stages import StageSerializer
from authentik.stages.user_logout.models import UserLogoutStage

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

134
web/package-lock.json generated
View file

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

View file

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

View file

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

View file

@ -1,15 +1,28 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events";
export class Group {
group_uuid: string;
pk: string;
name: string;
is_superuser: boolean;
attributes: EventContext;
parent?: Group;
users: number[];
constructor() {
throw Error();
}
static get(pk: string): Promise<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 { Provider } from "./Providers";
import { Provider, TypeCreate } from "./Providers";
export interface OutpostHealth {
last_seen: number;
@ -38,3 +38,42 @@ export class Outpost {
return `/administration/outposts/${rest}`;
}
}
export interface OutpostServiceConnectionState {
version: string;
healthy: boolean;
}
export class OutpostServiceConnection {
pk: string;
name: string;
local: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<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 { TypeCreate } from "./Providers";
export class Policy implements BaseInheritanceModel {
pk: string;
name: string;
execution_logging: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
bound_to: number;
constructor() {
throw Error();
}
object_type: string;
verbose_name: string;
verbose_name_plural: string;
static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
@ -20,8 +23,16 @@ export class Policy implements BaseInheritanceModel {
}
static cached(): Promise<number> {
return DefaultClient.fetch<AKResponse<Policy>>(["policies", "cached"]).then(r => {
return r.pagination.count;
return DefaultClient.fetch<{ count: number }>(["policies", "all", "cached"]).then(r => {
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 { TypeCreate } from "./Providers";
export class PropertyMapping {
pk: string;
@ -20,6 +21,10 @@ export class PropertyMapping {
return DefaultClient.fetch<AKResponse<PropertyMapping>>(["propertymappings", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["propertymappings", "all", "types"]);
}
static adminUrl(rest: string): string {
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 {
key: string;
export enum TokenIntent {
INTENT_VERIFICATION = "verification",
INTENT_API = "api",
INTENT_RECOVERY = "recovery",
}
export function tokenByIdentifier(identifier: string): Promise<string> {
return DefaultClient.fetch<TokenResponse>(["core", "tokens", identifier, "view_key"]).then(
export class Token {
pk: string;
identifier: string;
intent: TokenIntent;
user: User;
description: string;
expires: number;
expiring: boolean;
constructor() {
throw Error();
}
static get(pk: string): Promise<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
);
}
}

View file

@ -1,4 +1,4 @@
import { DefaultClient, AKResponse } from "./Client";
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
let _globalMePromise: Promise<User>;
@ -9,11 +9,25 @@ export class User {
is_superuser: boolean;
email: boolean;
avatar: string;
is_active: boolean;
last_login: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<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> {
if (!_globalMePromise) {
_globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]);

View file

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

View file

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

View file

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

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

View file

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

View file

@ -37,11 +37,11 @@ export class SidebarUser extends LitElement {
render(): TemplateResult {
return html`
<a href="#/-/user/" class="pf-c-nav__link user-avatar" id="user-settings">
${until(User.me().then(u => {
return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;}), html``)}
${until(User.me().then((u) => {
return html`<img class="pf-c-avatar" src="${u.avatar}" alt="" />`;
}), html``)}
</a>
<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>
<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>

View file

@ -43,7 +43,7 @@ export class TablePagination extends LitElement {
<button
class="pf-c-button pf-m-plain"
@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")}"
>
<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("Monitor").children(
new SidebarItem("Overview", "/administration/overview"),
new SidebarItem("System Tasks", "/administration/tasks/"),
new SidebarItem("System Tasks", "/administration/system-tasks"),
).when((): Promise<boolean> => {
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);
}),
new SidebarItem("Resources").children(
new SidebarItem("Applications", "/applications").activeWhen(
`^/applications/(?<slug>${SLUG_REGEX})$`
new SidebarItem("Applications", "/core/applications").activeWhen(
`^/core/applications/(?<slug>${SLUG_REGEX})$`
),
new SidebarItem("Sources", "/sources").activeWhen(
`^/sources/(?<slug>${SLUG_REGEX})$`,
new SidebarItem("Sources", "/core/sources").activeWhen(
`^/core/sources/(?<slug>${SLUG_REGEX})$`,
),
new SidebarItem("Providers", "/providers"),
new SidebarItem("Outposts", "/outposts"),
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"),
new SidebarItem("Providers", "/core/providers"),
new SidebarItem("Outposts", "/outpost/outposts"),
new SidebarItem("Outpost Service Connections", "/outpost/service-connections"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
}),
new SidebarItem("Customisation").children(
new SidebarItem("Policies", "/administration/policies/"),
new SidebarItem("Property Mappings", "/property-mappings"),
new SidebarItem("Policies", "/policy/policies"),
new SidebarItem("Property Mappings", "/core/property-mappings"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
}),
new SidebarItem("Flows").children(
new SidebarItem("Flows", "/flows").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})$`),
new SidebarItem("Stages", "/administration/stages/"),
new SidebarItem("Prompts", "/administration/stages_prompts/"),
new SidebarItem("Invitations", "/administration/stages/invitations/"),
new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
new SidebarItem("Stages", "/flow/stages"),
new SidebarItem("Prompts", "/flow/stages/prompts"),
new SidebarItem("Invitations", "/flow/stages/invitations"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
}),
new SidebarItem("Identity & Cryptography").children(
new SidebarItem("User", "/administration/users/"),
new SidebarItem("Groups", "/administration/groups/"),
new SidebarItem("User", "/identity/users"),
new SidebarItem("Groups", "/identity/groups"),
new SidebarItem("Certificates", "/crypto/certificates"),
new SidebarItem("Tokens", "/administration/tokens/"),
new SidebarItem("Tokens", "/core/tokens"),
).when((): Promise<boolean> => {
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/applications/ApplicationListPage";
import "./pages/applications/ApplicationViewPage";
import "./pages/tokens/UserTokenList";
import "./pages/LibraryPage";
import "./elements/stages/authenticator_webauthn/WebAuthnRegister";

View file

@ -14,6 +14,10 @@ export class LibraryApplication extends LitElement {
static get styles(): CSSResult[] {
return COMMON_STYLES.concat(
css`
:host,
main {
height: 100%;
}
a {
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-top-applications-table></ak-top-applications-table>
</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-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>

View file

@ -50,7 +50,7 @@ export class ApplicationListPage extends TablePage<Application> {
item.meta_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`<a href="#/applications/${item.slug}">
html`<a href="#/core/applications/${item.slug}">
<div>
${item.name}
</div>

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