Merge branch 'master' into version-2021.2
This commit is contained in:
commit
db113c5e8f
|
@ -1,19 +0,0 @@
|
||||||
"""authentik core source form fields"""
|
|
||||||
|
|
||||||
SOURCE_FORM_FIELDS = [
|
|
||||||
"name",
|
|
||||||
"slug",
|
|
||||||
"enabled",
|
|
||||||
"authentication_flow",
|
|
||||||
"enrollment_flow",
|
|
||||||
]
|
|
||||||
SOURCE_SERIALIZER_FIELDS = [
|
|
||||||
"pk",
|
|
||||||
"name",
|
|
||||||
"slug",
|
|
||||||
"enabled",
|
|
||||||
"authentication_flow",
|
|
||||||
"enrollment_flow",
|
|
||||||
"verbose_name",
|
|
||||||
"verbose_name_plural",
|
|
||||||
]
|
|
|
@ -1,149 +0,0 @@
|
||||||
{% extends "administration/base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
{% load humanize %}
|
|
||||||
{% load authentik_utils %}
|
|
||||||
{% load admin_reflection %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
|
||||||
<div class="pf-c-content">
|
|
||||||
<h1>
|
|
||||||
<i class="pf-icon pf-icon-zone"></i>
|
|
||||||
{% trans 'Outposts' %}
|
|
||||||
</h1>
|
|
||||||
<p>{% trans "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies." %}</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:outpost-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 'Providers' %}</th>
|
|
||||||
<th role="columnheader" scope="col">{% trans 'Health' %}</th>
|
|
||||||
<th role="columnheader" scope="col">{% trans 'Version' %}</th>
|
|
||||||
<th role="cell"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody role="rowgroup">
|
|
||||||
{% for outpost in object_list %}
|
|
||||||
<tr role="row">
|
|
||||||
<th role="columnheader">
|
|
||||||
<span>{{ outpost.name }}</span>
|
|
||||||
</th>
|
|
||||||
<td role="cell">
|
|
||||||
<span>
|
|
||||||
{{ outpost.providers.all.select_subclasses|join:", " }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
{% with states=outpost.state %}
|
|
||||||
{% if states|length > 0 %}
|
|
||||||
<td role="cell">
|
|
||||||
{% for state in states %}
|
|
||||||
<div>
|
|
||||||
{% if state.last_seen %}
|
|
||||||
<i class="fas fa-check pf-m-success"></i> {{ state.last_seen|naturaltime }}
|
|
||||||
{% else %}
|
|
||||||
<i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td role="cell">
|
|
||||||
{% for state in states %}
|
|
||||||
<div>
|
|
||||||
{% if not state.version %}
|
|
||||||
<i class="fas fa-question-circle"></i>
|
|
||||||
{% elif state.version_outdated %}
|
|
||||||
<i class="fas fa-times pf-m-danger"></i> {% blocktrans with is=state.version should=state.version_should %}{{ is }}, should be {{ should }}{% endblocktrans %}
|
|
||||||
{% else %}
|
|
||||||
<i class="fas fa-check pf-m-success"></i> {{ state.version }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
{% else %}
|
|
||||||
<td role="cell">
|
|
||||||
<i class="fas fa-question-circle"></i>
|
|
||||||
</td>
|
|
||||||
<td role="cell">
|
|
||||||
<i class="fas fa-question-circle"></i>
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
<td>
|
|
||||||
<ak-modal-button href="{% url 'authentik_admin:outpost-update' pk=outpost.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-delete' pk=outpost.pk %}">
|
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
|
||||||
{% trans 'Delete' %}
|
|
||||||
</ak-spinner-button>
|
|
||||||
<div slot="modal"></div>
|
|
||||||
</ak-modal-button>
|
|
||||||
{% get_htmls outpost as htmls %}
|
|
||||||
{% for html in htmls %}
|
|
||||||
{{ html|safe }}
|
|
||||||
{% endfor %}
|
|
||||||
</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 Outposts.' %}
|
|
||||||
</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 outposts exist. Click the button below to create one.' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<ak-modal-button href="{% url 'authentik_admin:outpost-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 %}
|
|
|
@ -3,7 +3,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load authentik_utils %}
|
{% load authentik_utils %}
|
||||||
{% load admin_reflection %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
|
|
@ -1,139 +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-blueprint"></i>
|
|
||||||
{% trans 'Property Mappings' %}
|
|
||||||
</h1>
|
|
||||||
<p>{% trans "Control how authentik exposes and interprets information." %}
|
|
||||||
</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:property-mapping-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 property_mapping in object_list %}
|
|
||||||
<tr role="row">
|
|
||||||
<td role="cell">
|
|
||||||
<span>
|
|
||||||
{{ property_mapping.name }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td role="cell">
|
|
||||||
<span>
|
|
||||||
{{ property_mapping|verbose_name }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ak-modal-button href="{% url 'authentik_admin:property-mapping-update' pk=property_mapping.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:property-mapping-delete' pk=property_mapping.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-blueprint pf-c-empty-state__icon" aria-hidden="true"></i>
|
|
||||||
<h1 class="pf-c-title pf-m-lg">
|
|
||||||
{% trans 'No Property Mappings.' %}
|
|
||||||
</h1>
|
|
||||||
<div class="pf-c-empty-state__body">
|
|
||||||
{% if request.GET.search != "" %}
|
|
||||||
{% trans "Your search query doesn't match any property mappings." %}
|
|
||||||
{% else %}
|
|
||||||
{% trans 'Currently no property mappings 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:property-mapping-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 %}
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load authentik_utils %}
|
{% load authentik_utils %}
|
||||||
{% load admin_reflection %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
@ -63,7 +62,7 @@
|
||||||
{% for source in object_list %}
|
{% for source in object_list %}
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<th role="columnheader">
|
<th role="columnheader">
|
||||||
<a href="/sources/{{ source.slug }}/">
|
<a href="/sources/{{ source.slug }}">
|
||||||
<div>{{ source.name }}</div>
|
<div>{{ source.name }}</div>
|
||||||
{% if not source.enabled %}
|
{% if not source.enabled %}
|
||||||
<small>{% trans 'Disabled' %}</small>
|
<small>{% trans 'Disabled' %}</small>
|
||||||
|
@ -93,10 +92,6 @@
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
<div slot="modal"></div>
|
<div slot="modal"></div>
|
||||||
</ak-modal-button>
|
</ak-modal-button>
|
||||||
{% get_links source as links %}
|
|
||||||
{% for name, href in links %}
|
|
||||||
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load authentik_utils %}
|
{% load authentik_utils %}
|
||||||
{% load admin_reflection %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
@ -88,10 +87,6 @@
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
<div slot="modal"></div>
|
<div slot="modal"></div>
|
||||||
</ak-modal-button>
|
</ak-modal-button>
|
||||||
{% get_links stage as links %}
|
|
||||||
{% for name, href in links.items %}
|
|
||||||
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load authentik_utils %}
|
{% load authentik_utils %}
|
||||||
{% load admin_reflection %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
@ -90,10 +89,6 @@
|
||||||
</ak-spinner-button>
|
</ak-spinner-button>
|
||||||
<div slot="modal"></div>
|
<div slot="modal"></div>
|
||||||
</ak-modal-button>
|
</ak-modal-button>
|
||||||
{% get_links prompt as links %}
|
|
||||||
{% for name, href in links.items %}
|
|
||||||
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
"""authentik admin templatetags"""
|
|
||||||
from django import template
|
|
||||||
from django.db.models import Model
|
|
||||||
from django.utils.html import mark_safe
|
|
||||||
from structlog.stdlib import get_logger
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
LOGGER = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
|
||||||
def get_links(model_instance):
|
|
||||||
"""Find all link_ methods on an object instance, run them and return as dict"""
|
|
||||||
prefix = "link_"
|
|
||||||
links = {}
|
|
||||||
|
|
||||||
if not isinstance(model_instance, Model):
|
|
||||||
LOGGER.warning("Model is not instance of Model", model_instance=model_instance)
|
|
||||||
return links
|
|
||||||
|
|
||||||
try:
|
|
||||||
for name in dir(model_instance):
|
|
||||||
if not name.startswith(prefix):
|
|
||||||
continue
|
|
||||||
value = getattr(model_instance, name)
|
|
||||||
if not callable(value):
|
|
||||||
continue
|
|
||||||
human_name = name.replace(prefix, "").replace("_", " ").capitalize()
|
|
||||||
link = value()
|
|
||||||
if link:
|
|
||||||
links[human_name] = link
|
|
||||||
except NotImplementedError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return links
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
|
||||||
def get_htmls(context, model_instance):
|
|
||||||
"""Find all html_ methods on an object instance, run them and return as dict"""
|
|
||||||
prefix = "html_"
|
|
||||||
htmls = []
|
|
||||||
|
|
||||||
if not isinstance(model_instance, Model):
|
|
||||||
LOGGER.warning("Model is not instance of Model", model_instance=model_instance)
|
|
||||||
return htmls
|
|
||||||
|
|
||||||
try:
|
|
||||||
for name in dir(model_instance):
|
|
||||||
if not name.startswith(prefix):
|
|
||||||
continue
|
|
||||||
value = getattr(model_instance, name)
|
|
||||||
if not callable(value):
|
|
||||||
continue
|
|
||||||
if name.startswith(prefix):
|
|
||||||
html = value(context.get("request"))
|
|
||||||
if html:
|
|
||||||
htmls.append(mark_safe(html))
|
|
||||||
except NotImplementedError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return htmls
|
|
|
@ -169,22 +169,22 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
# Stage Prompts
|
# Stage Prompts
|
||||||
path(
|
path(
|
||||||
"stages/prompts/",
|
"stages_prompts/",
|
||||||
stages_prompts.PromptListView.as_view(),
|
stages_prompts.PromptListView.as_view(),
|
||||||
name="stage-prompts",
|
name="stage-prompts",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"stages/prompts/create/",
|
"stages_prompts/create/",
|
||||||
stages_prompts.PromptCreateView.as_view(),
|
stages_prompts.PromptCreateView.as_view(),
|
||||||
name="stage-prompt-create",
|
name="stage-prompt-create",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"stages/prompts/<uuid:pk>/update/",
|
"stages_prompts/<uuid:pk>/update/",
|
||||||
stages_prompts.PromptUpdateView.as_view(),
|
stages_prompts.PromptUpdateView.as_view(),
|
||||||
name="stage-prompt-update",
|
name="stage-prompt-update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"stages/prompts/<uuid:pk>/delete/",
|
"stages_prompts/<uuid:pk>/delete/",
|
||||||
stages_prompts.PromptDeleteView.as_view(),
|
stages_prompts.PromptDeleteView.as_view(),
|
||||||
name="stage-prompt-delete",
|
name="stage-prompt-delete",
|
||||||
),
|
),
|
||||||
|
@ -311,11 +311,6 @@ urlpatterns = [
|
||||||
name="certificatekeypair-delete",
|
name="certificatekeypair-delete",
|
||||||
),
|
),
|
||||||
# Outposts
|
# Outposts
|
||||||
path(
|
|
||||||
"outposts/",
|
|
||||||
outposts.OutpostListView.as_view(),
|
|
||||||
name="outposts",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"outposts/create/",
|
"outposts/create/",
|
||||||
outposts.OutpostCreateView.as_view(),
|
outposts.OutpostCreateView.as_view(),
|
||||||
|
|
|
@ -9,36 +9,15 @@ from django.contrib.auth.mixins import (
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import ListView, UpdateView
|
from django.views.generic import UpdateView
|
||||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
from guardian.mixins import PermissionRequiredMixin
|
||||||
|
|
||||||
from authentik.admin.views.utils import (
|
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
|
||||||
BackSuccessUrlMixin,
|
|
||||||
DeleteMessageView,
|
|
||||||
SearchListMixin,
|
|
||||||
UserPaginateListMixin,
|
|
||||||
)
|
|
||||||
from authentik.lib.views import CreateAssignPermView
|
from authentik.lib.views import CreateAssignPermView
|
||||||
from authentik.outposts.forms import OutpostForm
|
from authentik.outposts.forms import OutpostForm
|
||||||
from authentik.outposts.models import Outpost, OutpostConfig
|
from authentik.outposts.models import Outpost, OutpostConfig
|
||||||
|
|
||||||
|
|
||||||
class OutpostListView(
|
|
||||||
LoginRequiredMixin,
|
|
||||||
PermissionListMixin,
|
|
||||||
UserPaginateListMixin,
|
|
||||||
SearchListMixin,
|
|
||||||
ListView,
|
|
||||||
):
|
|
||||||
"""Show list of all outposts"""
|
|
||||||
|
|
||||||
model = Outpost
|
|
||||||
permission_required = "authentik_outposts.view_outpost"
|
|
||||||
ordering = "name"
|
|
||||||
template_name = "administration/outpost/list.html"
|
|
||||||
search_fields = ["name", "_config"]
|
|
||||||
|
|
||||||
|
|
||||||
class OutpostCreateView(
|
class OutpostCreateView(
|
||||||
SuccessMessageMixin,
|
SuccessMessageMixin,
|
||||||
BackSuccessUrlMixin,
|
BackSuccessUrlMixin,
|
||||||
|
@ -53,7 +32,7 @@ class OutpostCreateView(
|
||||||
permission_required = "authentik_outposts.add_outpost"
|
permission_required = "authentik_outposts.add_outpost"
|
||||||
|
|
||||||
template_name = "generic/create.html"
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy("authentik_admin:outposts")
|
success_url = reverse_lazy("authentik_core:shell")
|
||||||
success_message = _("Successfully created Outpost")
|
success_message = _("Successfully created Outpost")
|
||||||
|
|
||||||
def get_initial(self) -> Dict[str, Any]:
|
def get_initial(self) -> Dict[str, Any]:
|
||||||
|
@ -78,7 +57,7 @@ class OutpostUpdateView(
|
||||||
permission_required = "authentik_outposts.change_outpost"
|
permission_required = "authentik_outposts.change_outpost"
|
||||||
|
|
||||||
template_name = "generic/update.html"
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy("authentik_admin:outposts")
|
success_url = reverse_lazy("authentik_core:shell")
|
||||||
success_message = _("Successfully updated Outpost")
|
success_message = _("Successfully updated Outpost")
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,5 +68,5 @@ class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessa
|
||||||
permission_required = "authentik_outposts.delete_outpost"
|
permission_required = "authentik_outposts.delete_outpost"
|
||||||
|
|
||||||
template_name = "generic/delete.html"
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy("authentik_admin:outposts")
|
success_url = reverse_lazy("authentik_core:shell")
|
||||||
success_message = _("Successfully deleted Outpost")
|
success_message = _("Successfully deleted Outpost")
|
||||||
|
|
|
@ -29,11 +29,12 @@ from authentik.flows.api import (
|
||||||
FlowViewSet,
|
FlowViewSet,
|
||||||
StageViewSet,
|
StageViewSet,
|
||||||
)
|
)
|
||||||
from authentik.outposts.api import (
|
from authentik.outposts.api.outpost_service_connections import (
|
||||||
DockerServiceConnectionViewSet,
|
DockerServiceConnectionViewSet,
|
||||||
KubernetesServiceConnectionViewSet,
|
KubernetesServiceConnectionViewSet,
|
||||||
OutpostViewSet,
|
ServiceConnectionViewSet,
|
||||||
)
|
)
|
||||||
|
from authentik.outposts.api.outposts import OutpostViewSet
|
||||||
from authentik.policies.api import (
|
from authentik.policies.api import (
|
||||||
PolicyBindingViewSet,
|
PolicyBindingViewSet,
|
||||||
PolicyCacheViewSet,
|
PolicyCacheViewSet,
|
||||||
|
@ -88,6 +89,7 @@ router.register("core/users", UserViewSet)
|
||||||
router.register("core/tokens", TokenViewSet)
|
router.register("core/tokens", TokenViewSet)
|
||||||
|
|
||||||
router.register("outposts/outposts", OutpostViewSet)
|
router.register("outposts/outposts", OutpostViewSet)
|
||||||
|
router.register("outposts/service_connections/all", ServiceConnectionViewSet)
|
||||||
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
|
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
|
||||||
router.register(
|
router.register(
|
||||||
"outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet
|
"outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
|
||||||
from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS
|
|
||||||
from authentik.core.api.utils import MetaNameSerializer
|
from authentik.core.api.utils import MetaNameSerializer
|
||||||
from authentik.core.models import Source
|
from authentik.core.models import Source
|
||||||
|
|
||||||
|
@ -10,22 +9,26 @@ from authentik.core.models import Source
|
||||||
class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||||
"""Source Serializer"""
|
"""Source Serializer"""
|
||||||
|
|
||||||
__type__ = SerializerMethodField(method_name="get_type")
|
object_type = SerializerMethodField()
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_object_type(self, obj):
|
||||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||||
return obj._meta.object_name.lower().replace("source", "")
|
return obj._meta.object_name.lower().replace("provider", "")
|
||||||
|
|
||||||
def to_representation(self, instance: Source):
|
|
||||||
# pyright: reportGeneralTypeIssues=false
|
|
||||||
if instance.__class__ == Source:
|
|
||||||
return super().to_representation(instance)
|
|
||||||
return instance.serializer(instance=instance).data
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Source
|
model = Source
|
||||||
fields = SOURCE_SERIALIZER_FIELDS + ["__type__"]
|
fields = SOURCE_SERIALIZER_FIELDS = [
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"enabled",
|
||||||
|
"authentication_flow",
|
||||||
|
"enrollment_flow",
|
||||||
|
"object_type",
|
||||||
|
"verbose_name",
|
||||||
|
"verbose_name_plural",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SourceViewSet(ReadOnlyModelViewSet):
|
class SourceViewSet(ReadOnlyModelViewSet):
|
||||||
|
|
|
@ -1,30 +1,28 @@
|
||||||
"""Outpost API Views"""
|
"""Outpost API Views"""
|
||||||
from rest_framework.serializers import JSONField, ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.outposts.models import (
|
from authentik.outposts.models import (
|
||||||
DockerServiceConnection,
|
DockerServiceConnection,
|
||||||
KubernetesServiceConnection,
|
KubernetesServiceConnection,
|
||||||
Outpost,
|
OutpostServiceConnection,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OutpostSerializer(ModelSerializer):
|
class ServiceConnectionSerializer(ModelSerializer):
|
||||||
"""Outpost Serializer"""
|
"""ServiceConnection Serializer"""
|
||||||
|
|
||||||
_config = JSONField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Outpost
|
model = OutpostServiceConnection
|
||||||
fields = ["pk", "name", "providers", "service_connection", "_config"]
|
fields = ["pk", "name"]
|
||||||
|
|
||||||
|
|
||||||
class OutpostViewSet(ModelViewSet):
|
class ServiceConnectionViewSet(ModelViewSet):
|
||||||
"""Outpost Viewset"""
|
"""ServiceConnection Viewset"""
|
||||||
|
|
||||||
queryset = Outpost.objects.all()
|
queryset = OutpostServiceConnection.objects.all()
|
||||||
serializer_class = OutpostSerializer
|
serializer_class = ServiceConnectionSerializer
|
||||||
|
|
||||||
|
|
||||||
class DockerServiceConnectionSerializer(ModelSerializer):
|
class DockerServiceConnectionSerializer(ModelSerializer):
|
79
authentik/outposts/api/outposts.py
Normal file
79
authentik/outposts/api/outposts.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
"""Outpost API Views"""
|
||||||
|
from django.db.models import Model
|
||||||
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import BooleanField, CharField, DateTimeField
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.serializers import JSONField, ModelSerializer, Serializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.core.api.providers import ProviderSerializer
|
||||||
|
from authentik.outposts.models import Outpost
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostSerializer(ModelSerializer):
|
||||||
|
"""Outpost Serializer"""
|
||||||
|
|
||||||
|
_config = JSONField()
|
||||||
|
providers_obj = ProviderSerializer(source="providers", many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = Outpost
|
||||||
|
fields = [
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"providers",
|
||||||
|
"providers_obj",
|
||||||
|
"service_connection",
|
||||||
|
"token_identifier",
|
||||||
|
"_config",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostHealthSerializer(Serializer):
|
||||||
|
"""Outpost health status"""
|
||||||
|
|
||||||
|
last_seen = DateTimeField(read_only=True)
|
||||||
|
version = CharField(read_only=True)
|
||||||
|
version_should = CharField(read_only=True)
|
||||||
|
version_outdated = BooleanField(read_only=True)
|
||||||
|
|
||||||
|
def create(self, validated_data: dict) -> Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update(self, instance: Model, validated_data: dict) -> Model:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostViewSet(ModelViewSet):
|
||||||
|
"""Outpost Viewset"""
|
||||||
|
|
||||||
|
queryset = Outpost.objects.all()
|
||||||
|
serializer_class = OutpostSerializer
|
||||||
|
filterset_fields = {
|
||||||
|
"providers": ["isnull"],
|
||||||
|
}
|
||||||
|
search_fields = [
|
||||||
|
"name",
|
||||||
|
"providers__name",
|
||||||
|
]
|
||||||
|
|
||||||
|
@swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)})
|
||||||
|
@action(methods=["GET"], detail=True)
|
||||||
|
# pylint: disable=invalid-name, unused-argument
|
||||||
|
def health(self, request: Request, pk: int) -> Response:
|
||||||
|
"""Get outposts current health"""
|
||||||
|
outpost: Outpost = self.get_object()
|
||||||
|
states = []
|
||||||
|
for state in outpost.state:
|
||||||
|
states.append(
|
||||||
|
{
|
||||||
|
"last_seen": state.last_seen,
|
||||||
|
"version": state.version,
|
||||||
|
"version_should": state.version_should,
|
||||||
|
"version_outdated": state.version_outdated,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return Response(OutpostHealthSerializer(states, many=True).data)
|
|
@ -9,7 +9,6 @@ from django.core.cache import cache
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.forms.models import ModelForm
|
from django.forms.models import ModelForm
|
||||||
from django.http import HttpRequest
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from docker.client import DockerClient
|
from docker.client import DockerClient
|
||||||
from docker.errors import DockerException
|
from docker.errors import DockerException
|
||||||
|
@ -33,7 +32,6 @@ from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.models import InheritanceForeignKey
|
from authentik.lib.models import InheritanceForeignKey
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
from authentik.lib.utils.template import render_to_string
|
|
||||||
from authentik.outposts.docker_tls import DockerInlineTLS
|
from authentik.outposts.docker_tls import DockerInlineTLS
|
||||||
|
|
||||||
OUR_VERSION = parse(__version__)
|
OUR_VERSION = parse(__version__)
|
||||||
|
@ -378,13 +376,6 @@ class Outpost(models.Model):
|
||||||
objects.append(provider)
|
objects.append(provider)
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
def html_deployment_view(self, request: HttpRequest) -> Optional[str]:
|
|
||||||
"""return template and context modal to view token and other config info"""
|
|
||||||
return render_to_string(
|
|
||||||
"outposts/deployment_modal.html",
|
|
||||||
{"outpost": self, "full_url": request.build_absolute_uri("/")},
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Outpost {self.name}"
|
return f"Outpost {self.name}"
|
||||||
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<ak-modal-button>
|
|
||||||
<button slot="trigger" class="pf-c-button pf-m-tertiary">
|
|
||||||
{% trans 'View Deployment Info' %}
|
|
||||||
</button>
|
|
||||||
<div slot="modal">
|
|
||||||
<div class="pf-c-modal-box__header">
|
|
||||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Outpost Deployment Info' %}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-modal-box__body" id="modal-description">
|
|
||||||
<p><a href="https://goauthentik.io/docs/outposts/outposts/#deploy">{% trans 'View deployment documentation' %}</a></p>
|
|
||||||
<form class="pf-c-form">
|
|
||||||
<div class="pf-c-form__group">
|
|
||||||
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
|
||||||
<span class="pf-c-form__label-text">AUTHENTIK_HOST</span>
|
|
||||||
</label>
|
|
||||||
<input class="pf-c-form-control" readonly type="text" value="{{ full_url }}" />
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-form__group">
|
|
||||||
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
|
||||||
<span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span>
|
|
||||||
</label>
|
|
||||||
<div>
|
|
||||||
<ak-token-copy-button identifier="{{ outpost.token_identifier }}">
|
|
||||||
{% trans 'Click to copy token' %}
|
|
||||||
</ak-token-copy-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>{% trans 'If your authentik Instance is using a self-signed certificate, set this value.' %}</h3>
|
|
||||||
<div class="pf-c-form__group">
|
|
||||||
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
|
||||||
<span class="pf-c-form__label-text">AUTHENTIK_INSECURE</span>
|
|
||||||
</label>
|
|
||||||
<input class="pf-c-form-control" readonly type="text" value="true" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<footer class="pf-c-modal-box__footer pf-m-align-left">
|
|
||||||
<a class="pf-c-button pf-m-primary">{% trans 'Close' %}</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</ak-modal-button>
|
|
|
@ -59,11 +59,11 @@ class OAuth2ProviderViewSet(ModelViewSet):
|
||||||
queryset = OAuth2Provider.objects.all()
|
queryset = OAuth2Provider.objects.all()
|
||||||
serializer_class = OAuth2ProviderSerializer
|
serializer_class = OAuth2ProviderSerializer
|
||||||
|
|
||||||
@action(methods=["GET"], detail=True)
|
|
||||||
@swagger_auto_schema(responses={200: OAuth2ProviderSetupURLs(many=False)})
|
@swagger_auto_schema(responses={200: OAuth2ProviderSetupURLs(many=False)})
|
||||||
|
@action(methods=["GET"], detail=True)
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def setup_urls(self, request: Request, pk: int) -> str:
|
def setup_urls(self, request: Request, pk: int) -> str:
|
||||||
"""Return metadata as XML string"""
|
"""Get Providers setup URLs"""
|
||||||
provider = get_object_or_404(OAuth2Provider, pk=pk)
|
provider = get_object_or_404(OAuth2Provider, pk=pk)
|
||||||
data = {
|
data = {
|
||||||
"issuer": provider.get_issuer(request),
|
"issuer": provider.get_issuer(request),
|
||||||
|
|
|
@ -23,6 +23,8 @@ return {
|
||||||
"family_name": "",
|
"family_name": "",
|
||||||
"preferred_username": user.username,
|
"preferred_username": user.username,
|
||||||
"nickname": user.username,
|
"nickname": user.username,
|
||||||
|
# groups is not part of the official userinfo schema, but is a quasi-standard
|
||||||
|
"groups": [group.name for group in user.ak_groups.all()],
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -253,6 +253,7 @@ class OAuthFulfillmentStage(StageView):
|
||||||
EventAction.AUTHORIZE_APPLICATION,
|
EventAction.AUTHORIZE_APPLICATION,
|
||||||
authorized_application=application,
|
authorized_application=application,
|
||||||
flow=self.executor.plan.flow_pk,
|
flow=self.executor.plan.flow_pk,
|
||||||
|
scopes=", ".join(self.params.scope),
|
||||||
).from_http(self.request)
|
).from_http(self.request)
|
||||||
return redirect(self.create_response_uri())
|
return redirect(self.create_response_uri())
|
||||||
except (ClientIdError, RedirectUriError) as error:
|
except (ClientIdError, RedirectUriError) as error:
|
||||||
|
|
|
@ -54,10 +54,10 @@ class SAMLProviderViewSet(ModelViewSet):
|
||||||
queryset = SAMLProvider.objects.all()
|
queryset = SAMLProvider.objects.all()
|
||||||
serializer_class = SAMLProviderSerializer
|
serializer_class = SAMLProviderSerializer
|
||||||
|
|
||||||
@action(methods=["GET"], detail=True)
|
|
||||||
@swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)})
|
@swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)})
|
||||||
|
@action(methods=["GET"], detail=True)
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def metadata(self, request: Request, pk: int) -> str:
|
def metadata(self, request: Request, pk: int) -> Response:
|
||||||
"""Return metadata as XML string"""
|
"""Return metadata as XML string"""
|
||||||
provider = get_object_or_404(SAMLProvider, pk=pk)
|
provider = get_object_or_404(SAMLProvider, pk=pk)
|
||||||
metadata = DescriptorDownloadView.get_metadata(request, provider)
|
metadata = DescriptorDownloadView.get_metadata(request, provider)
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS
|
from authentik.core.api.sources import SourceSerializer
|
||||||
from authentik.core.api.utils import MetaNameSerializer
|
from authentik.core.api.utils import MetaNameSerializer
|
||||||
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
||||||
|
|
||||||
|
|
||||||
class LDAPSourceSerializer(ModelSerializer, MetaNameSerializer):
|
class LDAPSourceSerializer(SourceSerializer):
|
||||||
"""LDAP Source Serializer"""
|
"""LDAP Source Serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LDAPSource
|
model = LDAPSource
|
||||||
fields = SOURCE_SERIALIZER_FIELDS + [
|
fields = SourceSerializer.Meta.fields + [
|
||||||
"server_uri",
|
"server_uri",
|
||||||
"bind_cn",
|
"bind_cn",
|
||||||
"bind_password",
|
"bind_password",
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
"""OAuth Source Serializer"""
|
"""OAuth Source Serializer"""
|
||||||
from rest_framework.serializers import ModelSerializer
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS
|
from authentik.core.api.sources import SourceSerializer
|
||||||
from authentik.core.api.utils import MetaNameSerializer
|
|
||||||
from authentik.sources.oauth.models import OAuthSource
|
from authentik.sources.oauth.models import OAuthSource
|
||||||
|
|
||||||
|
|
||||||
class OAuthSourceSerializer(ModelSerializer, MetaNameSerializer):
|
class OAuthSourceSerializer(SourceSerializer):
|
||||||
"""OAuth Source Serializer"""
|
"""OAuth Source Serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OAuthSource
|
model = OAuthSource
|
||||||
fields = SOURCE_SERIALIZER_FIELDS + [
|
fields = SourceSerializer.Meta.fields + [
|
||||||
"provider_type",
|
"provider_type",
|
||||||
"request_token_url",
|
"request_token_url",
|
||||||
"authorization_url",
|
"authorization_url",
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from authentik.admin.forms.source import SOURCE_FORM_FIELDS
|
|
||||||
from authentik.flows.models import Flow, FlowDesignation
|
from authentik.flows.models import Flow, FlowDesignation
|
||||||
from authentik.sources.oauth.models import OAuthSource
|
from authentik.sources.oauth.models import OAuthSource
|
||||||
from authentik.sources.oauth.types.manager import MANAGER
|
from authentik.sources.oauth.types.manager import MANAGER
|
||||||
|
@ -27,7 +26,12 @@ class OAuthSourceForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = OAuthSource
|
model = OAuthSource
|
||||||
fields = SOURCE_FORM_FIELDS + [
|
fields = [
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"enabled",
|
||||||
|
"authentication_flow",
|
||||||
|
"enrollment_flow",
|
||||||
"provider_type",
|
"provider_type",
|
||||||
"request_token_url",
|
"request_token_url",
|
||||||
"authorization_url",
|
"authorization_url",
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
"""SAMLSource API Views"""
|
"""SAMLSource API Views"""
|
||||||
from rest_framework.serializers import ModelSerializer
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.admin.forms.source import SOURCE_FORM_FIELDS
|
from authentik.core.api.sources import SourceSerializer
|
||||||
from authentik.core.api.utils import MetaNameSerializer
|
|
||||||
from authentik.sources.saml.models import SAMLSource
|
from authentik.sources.saml.models import SAMLSource
|
||||||
|
|
||||||
|
|
||||||
class SAMLSourceSerializer(ModelSerializer, MetaNameSerializer):
|
class SAMLSourceSerializer(SourceSerializer):
|
||||||
"""SAMLSource Serializer"""
|
"""SAMLSource Serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = SAMLSource
|
model = SAMLSource
|
||||||
fields = SOURCE_FORM_FIELDS + [
|
fields = SourceSerializer.Meta.fields + [
|
||||||
"issuer",
|
"issuer",
|
||||||
"sso_url",
|
"sso_url",
|
||||||
"slo_url",
|
"slo_url",
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from authentik.admin.forms.source import SOURCE_FORM_FIELDS
|
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.flows.models import Flow, FlowDesignation
|
from authentik.flows.models import Flow, FlowDesignation
|
||||||
from authentik.sources.saml.models import SAMLSource
|
from authentik.sources.saml.models import SAMLSource
|
||||||
|
@ -28,7 +27,12 @@ class SAMLSourceForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = SAMLSource
|
model = SAMLSource
|
||||||
fields = SOURCE_FORM_FIELDS + [
|
fields = [
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"enabled",
|
||||||
|
"authentication_flow",
|
||||||
|
"enrollment_flow",
|
||||||
"issuer",
|
"issuer",
|
||||||
"sso_url",
|
"sso_url",
|
||||||
"slo_url",
|
"slo_url",
|
||||||
|
|
|
@ -24,7 +24,7 @@ require (
|
||||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect
|
||||||
github.com/recws-org/recws v1.2.2
|
github.com/recws-org/recws v1.2.1
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/spf13/afero v1.5.1 // indirect
|
github.com/spf13/afero v1.5.1 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
|
|
@ -69,23 +69,10 @@ func (ac *APIController) Shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *APIController) startWSHandler() {
|
func (ac *APIController) startWSHandler() {
|
||||||
notConnectedBackoff := 1
|
|
||||||
logger := ac.logger.WithField("loop", "ws-handler")
|
logger := ac.logger.WithField("loop", "ws-handler")
|
||||||
for {
|
for {
|
||||||
if !ac.wsConn.IsConnected() {
|
if !ac.wsConn.IsConnected() {
|
||||||
notConnectedWait := time.Duration(notConnectedBackoff) * time.Second
|
|
||||||
logger.WithField("wait", notConnectedWait).Info("Not connected, trying again...")
|
|
||||||
time.Sleep(notConnectedWait)
|
|
||||||
notConnectedBackoff += notConnectedBackoff
|
|
||||||
// Limit backoff to max 60 seconds
|
|
||||||
if notConnectedBackoff >= 60 {
|
|
||||||
notConnectedBackoff = 60
|
|
||||||
}
|
|
||||||
ac.wsConn.CloseAndReconnect()
|
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
// When we're connected, reset backoff to 1
|
|
||||||
notConnectedBackoff = 1
|
|
||||||
}
|
}
|
||||||
var wsMsg websocketMessage
|
var wsMsg websocketMessage
|
||||||
err := ac.wsConn.ReadJSON(&wsMsg)
|
err := ac.wsConn.ReadJSON(&wsMsg)
|
||||||
|
|
237
swagger.yaml
237
swagger.yaml
|
@ -1789,6 +1789,11 @@ paths:
|
||||||
operationId: outposts_outposts_list
|
operationId: outposts_outposts_list
|
||||||
description: Outpost Viewset
|
description: Outpost Viewset
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: providers__isnull
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
@ -1911,6 +1916,28 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
/outposts/outposts/{uuid}/health/:
|
||||||
|
get:
|
||||||
|
operationId: outposts_outposts_health
|
||||||
|
description: Get outposts current health
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Outpost health status
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/OutpostHealth'
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
parameters:
|
||||||
|
- name: uuid
|
||||||
|
in: path
|
||||||
|
description: A UUID string identifying this outpost.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
/outposts/proxy/:
|
/outposts/proxy/:
|
||||||
get:
|
get:
|
||||||
operationId: outposts_proxy_list
|
operationId: outposts_proxy_list
|
||||||
|
@ -2037,6 +2064,133 @@ paths:
|
||||||
description: A unique integer value identifying this Proxy Provider.
|
description: A unique integer value identifying this Proxy Provider.
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
/outposts/service_connections/all/:
|
||||||
|
get:
|
||||||
|
operationId: outposts_service_connections_all_list
|
||||||
|
description: ServiceConnection Viewset
|
||||||
|
parameters:
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
description: A page number within the paginated result set.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: page_size
|
||||||
|
in: query
|
||||||
|
description: Number of results to return per page.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
required:
|
||||||
|
- count
|
||||||
|
- results
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
next:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
previous:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
post:
|
||||||
|
operationId: outposts_service_connections_all_create
|
||||||
|
description: ServiceConnection Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
parameters: []
|
||||||
|
/outposts/service_connections/all/{uuid}/:
|
||||||
|
get:
|
||||||
|
operationId: outposts_service_connections_all_read
|
||||||
|
description: ServiceConnection Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
put:
|
||||||
|
operationId: outposts_service_connections_all_update
|
||||||
|
description: ServiceConnection Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
patch:
|
||||||
|
operationId: outposts_service_connections_all_partial_update
|
||||||
|
description: ServiceConnection Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ServiceConnection'
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
delete:
|
||||||
|
operationId: outposts_service_connections_all_delete
|
||||||
|
description: ServiceConnection Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- outposts
|
||||||
|
parameters:
|
||||||
|
- name: uuid
|
||||||
|
in: path
|
||||||
|
description: A UUID string identifying this Outpost Service-Connection.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
/outposts/service_connections/docker/:
|
/outposts/service_connections/docker/:
|
||||||
get:
|
get:
|
||||||
operationId: outposts_service_connections_docker_list
|
operationId: outposts_service_connections_docker_list
|
||||||
|
@ -4308,7 +4462,7 @@ paths:
|
||||||
/providers/oauth2/{id}/setup_urls/:
|
/providers/oauth2/{id}/setup_urls/:
|
||||||
get:
|
get:
|
||||||
operationId: providers_oauth2_setup_urls
|
operationId: providers_oauth2_setup_urls
|
||||||
description: Return metadata as XML string
|
description: Get Providers setup URLs
|
||||||
parameters: []
|
parameters: []
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
|
@ -8023,6 +8177,12 @@ definitions:
|
||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
uniqueItems: true
|
uniqueItems: true
|
||||||
|
providers_obj:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Provider'
|
||||||
|
readOnly: true
|
||||||
service_connection:
|
service_connection:
|
||||||
title: Service connection
|
title: Service connection
|
||||||
description: Select Service-Connection authentik should use to manage this
|
description: Select Service-Connection authentik should use to manage this
|
||||||
|
@ -8030,9 +8190,36 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
token_identifier:
|
||||||
|
title: Token identifier
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
_config:
|
_config:
|
||||||
title: config
|
title: config
|
||||||
type: object
|
type: object
|
||||||
|
OutpostHealth:
|
||||||
|
description: Outpost health status
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
last_seen:
|
||||||
|
title: Last seen
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
version:
|
||||||
|
title: Version
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
minLength: 1
|
||||||
|
version_should:
|
||||||
|
title: Version should
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
minLength: 1
|
||||||
|
version_outdated:
|
||||||
|
title: Version outdated
|
||||||
|
type: boolean
|
||||||
|
readOnly: true
|
||||||
OpenIDConnectConfiguration:
|
OpenIDConnectConfiguration:
|
||||||
title: Oidc configuration
|
title: Oidc configuration
|
||||||
description: rest_framework Serializer for OIDC Configuration
|
description: rest_framework Serializer for OIDC Configuration
|
||||||
|
@ -8170,6 +8357,21 @@ definitions:
|
||||||
description: User/Group Attribute used for the user part of the HTTP-Basic
|
description: User/Group Attribute used for the user part of the HTTP-Basic
|
||||||
Header. If not set, the user's Email address is used.
|
Header. If not set, the user's Email address is used.
|
||||||
type: string
|
type: string
|
||||||
|
ServiceConnection:
|
||||||
|
description: ServiceConnection Serializer
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
title: Uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
title: Name
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
DockerServiceConnection:
|
DockerServiceConnection:
|
||||||
description: DockerServiceConnection Serializer
|
description: DockerServiceConnection Serializer
|
||||||
required:
|
required:
|
||||||
|
@ -9157,6 +9359,10 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
object_type:
|
||||||
|
title: Object type
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
verbose_name:
|
verbose_name:
|
||||||
title: Verbose name
|
title: Verbose name
|
||||||
type: string
|
type: string
|
||||||
|
@ -9165,10 +9371,6 @@ definitions:
|
||||||
title: Verbose name plural
|
title: Verbose name plural
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
__type__:
|
|
||||||
title: 'type '
|
|
||||||
type: string
|
|
||||||
readOnly: true
|
|
||||||
LDAPSource:
|
LDAPSource:
|
||||||
description: LDAP Source Serializer
|
description: LDAP Source Serializer
|
||||||
required:
|
required:
|
||||||
|
@ -9213,6 +9415,10 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
object_type:
|
||||||
|
title: Object type
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
verbose_name:
|
verbose_name:
|
||||||
title: Verbose name
|
title: Verbose name
|
||||||
type: string
|
type: string
|
||||||
|
@ -9344,6 +9550,10 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
object_type:
|
||||||
|
title: Object type
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
verbose_name:
|
verbose_name:
|
||||||
title: Verbose name
|
title: Verbose name
|
||||||
type: string
|
type: string
|
||||||
|
@ -9397,6 +9607,11 @@ definitions:
|
||||||
- sso_url
|
- sso_url
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
pk:
|
||||||
|
title: Pbm uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
name:
|
name:
|
||||||
title: Name
|
title: Name
|
||||||
description: Source's display Name.
|
description: Source's display Name.
|
||||||
|
@ -9425,6 +9640,18 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
object_type:
|
||||||
|
title: Object type
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
verbose_name:
|
||||||
|
title: Verbose name
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
verbose_name_plural:
|
||||||
|
title: Verbose name plural
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
issuer:
|
issuer:
|
||||||
title: Issuer
|
title: Issuer
|
||||||
description: Also known as Entity ID. Defaults the Metadata URL.
|
description: Also known as Entity ID. Defaults the Metadata URL.
|
||||||
|
|
6
web/package-lock.json
generated
6
web/package-lock.json
generated
|
@ -899,9 +899,9 @@
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||||
},
|
},
|
||||||
"construct-style-sheets-polyfill": {
|
"construct-style-sheets-polyfill": {
|
||||||
"version": "2.4.6",
|
"version": "2.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.9.tgz",
|
||||||
"integrity": "sha512-lU0to7dFDjKslMF+M5NUa4s0RQMBRVyZMXvD/vp7vmjdEPgziTkHSfZHQxfoIvVWajWRJUVJMLfrMwcx8fTh4A=="
|
"integrity": "sha512-kPXZXxsp7CTr/Vs29+omUA29wTrFplkdY6jqxyv0DDWC5Ro79WmwpboH2M9KiOclbtn8r81GCFtc7+t7OjRnCw=="
|
||||||
},
|
},
|
||||||
"copy-descriptor": {
|
"copy-descriptor": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"@types/codemirror": "0.0.108",
|
"@types/codemirror": "0.0.108",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"codemirror": "^5.59.2",
|
"codemirror": "^5.59.2",
|
||||||
"construct-style-sheets-polyfill": "^2.4.6",
|
"construct-style-sheets-polyfill": "^2.4.9",
|
||||||
"flowchart.js": "^1.15.0",
|
"flowchart.js": "^1.15.0",
|
||||||
"lit-element": "^2.4.0",
|
"lit-element": "^2.4.0",
|
||||||
"lit-html": "^1.3.0",
|
"lit-html": "^1.3.0",
|
||||||
|
|
|
@ -7,6 +7,13 @@ export interface QueryArguments {
|
||||||
[key: string]: number | string | boolean | null;
|
[key: string]: number | string | boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BaseInheritanceModel {
|
||||||
|
|
||||||
|
verbose_name: string;
|
||||||
|
verbose_name_plural: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class Client {
|
export class Client {
|
||||||
makeUrl(url: string[], query?: QueryArguments): string {
|
makeUrl(url: string[], query?: QueryArguments): string {
|
||||||
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
|
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
|
||||||
|
|
40
web/src/api/Outposts.ts
Normal file
40
web/src/api/Outposts.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
|
||||||
|
import { Provider } from "./Providers";
|
||||||
|
|
||||||
|
export interface OutpostHealth {
|
||||||
|
last_seen: number;
|
||||||
|
version: string;
|
||||||
|
version_should: string;
|
||||||
|
version_outdated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Outpost {
|
||||||
|
|
||||||
|
pk: string;
|
||||||
|
name: string;
|
||||||
|
providers: number[];
|
||||||
|
providers_obj: Provider[];
|
||||||
|
service_connection?: string;
|
||||||
|
_config: QueryArguments;
|
||||||
|
token_identifier: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(pk: string): Promise<Outpost> {
|
||||||
|
return DefaultClient.fetch<Outpost>(["outposts", "outposts", pk]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static list(filter?: QueryArguments): Promise<PBResponse<Outpost>> {
|
||||||
|
return DefaultClient.fetch<PBResponse<Outpost>>(["outposts", "outposts"], filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
static health(pk: string): Promise<OutpostHealth[]> {
|
||||||
|
return DefaultClient.fetch<OutpostHealth[]>(["outposts", "outposts", pk, "health"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static adminUrl(rest: string): string {
|
||||||
|
return `/administration/outposts/${rest}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
|
import { DefaultClient, BaseInheritanceModel, PBResponse, QueryArguments } from "./Client";
|
||||||
|
|
||||||
export class Policy {
|
export class Policy implements BaseInheritanceModel {
|
||||||
pk: string;
|
pk: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
|
verbose_name: string;
|
||||||
|
verbose_name_plural: string;
|
||||||
|
|
||||||
static get(pk: string): Promise<Policy> {
|
static get(pk: string): Promise<Policy> {
|
||||||
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
|
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
|
import { BaseInheritanceModel, DefaultClient, PBResponse, QueryArguments } from "./Client";
|
||||||
|
|
||||||
export class Provider {
|
export class Provider implements BaseInheritanceModel {
|
||||||
pk: number;
|
pk: number;
|
||||||
name: string;
|
name: string;
|
||||||
authorization_flow: string;
|
authorization_flow: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
|
import { BaseInheritanceModel, DefaultClient, PBResponse, QueryArguments } from "./Client";
|
||||||
|
|
||||||
export class Source {
|
export class Source implements BaseInheritanceModel {
|
||||||
pk: string;
|
pk: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
@ -11,6 +11,8 @@ export class Source {
|
||||||
constructor() {
|
constructor() {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
|
verbose_name: string;
|
||||||
|
verbose_name_plural: string;
|
||||||
|
|
||||||
static get(slug: string): Promise<Source> {
|
static get(slug: string): Promise<Source> {
|
||||||
return DefaultClient.fetch<Source>(["sources", "all", slug]);
|
return DefaultClient.fetch<Source>(["sources", "all", slug]);
|
||||||
|
@ -19,4 +21,8 @@ export class Source {
|
||||||
static list(filter?: QueryArguments): Promise<PBResponse<Source>> {
|
static list(filter?: QueryArguments): Promise<PBResponse<Source>> {
|
||||||
return DefaultClient.fetch<PBResponse<Source>>(["sources", "all"], filter);
|
return DefaultClient.fetch<PBResponse<Source>>(["sources", "all"], filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static adminUrl(rest: string): string {
|
||||||
|
return `/administration/sources/${rest}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
`^/sources/(?<slug>${SLUG_REGEX})$`,
|
`^/sources/(?<slug>${SLUG_REGEX})$`,
|
||||||
),
|
),
|
||||||
new SidebarItem("Providers", "/providers"),
|
new SidebarItem("Providers", "/providers"),
|
||||||
new SidebarItem("Outposts", "/administration/outposts/"),
|
new SidebarItem("Outposts", "/outposts"),
|
||||||
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"),
|
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return User.me().then(u => u.is_superuser);
|
||||||
|
@ -41,7 +41,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||||
new SidebarItem("Flows").children(
|
new SidebarItem("Flows").children(
|
||||||
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})$`),
|
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})$`),
|
||||||
new SidebarItem("Stages", "/administration/stages/"),
|
new SidebarItem("Stages", "/administration/stages/"),
|
||||||
new SidebarItem("Prompts", "/administration/stages/prompts/"),
|
new SidebarItem("Prompts", "/administration/stages_prompts/"),
|
||||||
new SidebarItem("Invitations", "/administration/stages/invitations/"),
|
new SidebarItem("Invitations", "/administration/stages/invitations/"),
|
||||||
).when((): Promise<boolean> => {
|
).when((): Promise<boolean> => {
|
||||||
return User.me().then(u => u.is_superuser);
|
return User.me().then(u => u.is_superuser);
|
||||||
|
|
|
@ -65,13 +65,13 @@ export class EventInfo extends LitElement {
|
||||||
case "model_updated":
|
case "model_updated":
|
||||||
case "model_deleted":
|
case "model_deleted":
|
||||||
return html`
|
return html`
|
||||||
<h3>${gettext("Affected model:")}</h3><hr>
|
<h3>${gettext("Affected model:")}</h3>
|
||||||
${this.getModelInfo(this.event.context.model as EventContext)}
|
${this.getModelInfo(this.event.context.model as EventContext)}
|
||||||
`;
|
`;
|
||||||
case "authorize_application":
|
case "authorize_application":
|
||||||
return html`<div class="pf-l-flex">
|
return html`<div class="pf-l-flex">
|
||||||
<div class="pf-l-flex__item">
|
<div class="pf-l-flex__item">
|
||||||
<h3>${gettext("Authorized application:")}</h3><hr>
|
<h3>${gettext("Authorized application:")}</h3>
|
||||||
${this.getModelInfo(this.event.context.authorized_application as EventContext)}
|
${this.getModelInfo(this.event.context.authorized_application as EventContext)}
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-l-flex__item">
|
<div class="pf-l-flex__item">
|
||||||
|
@ -83,14 +83,15 @@ export class EventInfo extends LitElement {
|
||||||
}), html`<ak-spinner size=${SpinnerSize.Medium}></ak-spinner>`)}
|
}), html`<ak-spinner size=${SpinnerSize.Medium}></ak-spinner>`)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>
|
||||||
|
<ak-expand>${this.defaultResponse()}</ak-expand>`;
|
||||||
case "login_failed":
|
case "login_failed":
|
||||||
return html`
|
return html`
|
||||||
<h3>${gettext(`Attempted to log in as ${this.event.context.username}`)}</h3>
|
<h3>${gettext(`Attempted to log in as ${this.event.context.username}`)}</h3>
|
||||||
<ak-expand>${this.defaultResponse()}</ak-expand>`;
|
<ak-expand>${this.defaultResponse()}</ak-expand>`;
|
||||||
case "token_view":
|
case "token_view":
|
||||||
return html`
|
return html`
|
||||||
<h3>${gettext("Token:")}</h3><hr>
|
<h3>${gettext("Token:")}</h3>
|
||||||
${this.getModelInfo(this.event.context.token as EventContext)}`;
|
${this.getModelInfo(this.event.context.token as EventContext)}`;
|
||||||
case "property_mapping_exception":
|
case "property_mapping_exception":
|
||||||
return html`<div class="pf-l-flex">
|
return html`<div class="pf-l-flex">
|
||||||
|
|
49
web/src/pages/outposts/OutpostHealth.ts
Normal file
49
web/src/pages/outposts/OutpostHealth.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { gettext } from "django";
|
||||||
|
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||||
|
import { until } from "lit-html/directives/until";
|
||||||
|
import { Outpost } from "../../api/Outposts";
|
||||||
|
import { COMMON_STYLES } from "../../common/styles";
|
||||||
|
|
||||||
|
@customElement("ak-outpost-health")
|
||||||
|
export class OutpostHealth extends LitElement {
|
||||||
|
|
||||||
|
@property()
|
||||||
|
outpostId?: string;
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return COMMON_STYLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
if (!this.outpostId) {
|
||||||
|
return html`<ak-spinner></ak-spinner>`;
|
||||||
|
}
|
||||||
|
return html`<ul>${until(Outpost.health(this.outpostId).then((oh) => {
|
||||||
|
if (oh.length === 0) {
|
||||||
|
return html`<li>
|
||||||
|
<ul>
|
||||||
|
<li role="cell">
|
||||||
|
<i class="fas fa-question-circle"></i> ${gettext("Not available")}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>`;
|
||||||
|
}
|
||||||
|
return oh.map((h) => {
|
||||||
|
return html`<li>
|
||||||
|
<ul>
|
||||||
|
<li role="cell">
|
||||||
|
<i class="fas fa-check pf-m-success"></i> ${gettext(`Last seen: ${new Date(h.last_seen * 1000).toLocaleTimeString()}`)}
|
||||||
|
</li>
|
||||||
|
<li role="cell">
|
||||||
|
${h.version_outdated ?
|
||||||
|
html`<i class="fas fa-times pf-m-danger"></i>
|
||||||
|
${gettext(`${h.version}, should be ${h.version_should}`)}` :
|
||||||
|
html`<i class="fas fa-check pf-m-success"></i> ${gettext(`Version: ${h.version}`)}`}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>`;
|
||||||
|
});
|
||||||
|
}), html`<ak-spinner></ak-spinner>`)}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
121
web/src/pages/outposts/OutpostListPage.ts
Normal file
121
web/src/pages/outposts/OutpostListPage.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import { gettext } from "django";
|
||||||
|
import { customElement, property } from "lit-element";
|
||||||
|
import { html, TemplateResult } from "lit-html";
|
||||||
|
import { PBResponse } from "../../api/Client";
|
||||||
|
import { Outpost } from "../../api/Outposts";
|
||||||
|
import { TableColumn } from "../../elements/table/Table";
|
||||||
|
import { TablePage } from "../../elements/table/TablePage";
|
||||||
|
|
||||||
|
import "./OutpostHealth";
|
||||||
|
import "../../elements/buttons/SpinnerButton";
|
||||||
|
import "../../elements/buttons/ModalButton";
|
||||||
|
|
||||||
|
@customElement("ak-outpost-list")
|
||||||
|
export class OutpostListPage extends TablePage<Outpost> {
|
||||||
|
pageTitle(): string {
|
||||||
|
return "Outposts";
|
||||||
|
}
|
||||||
|
pageDescription(): string | undefined {
|
||||||
|
return "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies.";
|
||||||
|
}
|
||||||
|
pageIcon(): string {
|
||||||
|
return "pf-icon pf-icon-zone";
|
||||||
|
}
|
||||||
|
searchEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
apiEndpoint(page: number): Promise<PBResponse<Outpost>> {
|
||||||
|
return Outpost.list({
|
||||||
|
ordering: this.order,
|
||||||
|
page: page,
|
||||||
|
search: this.search || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
columns(): TableColumn[] {
|
||||||
|
return [
|
||||||
|
new TableColumn("Name", "name"),
|
||||||
|
new TableColumn("Providers"),
|
||||||
|
new TableColumn("Health and Version"),
|
||||||
|
new TableColumn(""),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@property()
|
||||||
|
order = "name";
|
||||||
|
|
||||||
|
row(item: Outpost): TemplateResult[] {
|
||||||
|
return [
|
||||||
|
html`${item.name}`,
|
||||||
|
html`<ul>${item.providers_obj.map((p) => {
|
||||||
|
return html`<li><a href="#/providers/${p.pk}">${p.name}</a></li>`;
|
||||||
|
})}</ul>`,
|
||||||
|
html`<ak-outpost-health outpostId=${item.pk}></ak-outpost-health>`,
|
||||||
|
html`
|
||||||
|
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/update`)}">
|
||||||
|
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||||
|
${gettext("Edit")}
|
||||||
|
</ak-spinner-button>
|
||||||
|
<div slot="modal"></div>
|
||||||
|
</ak-modal-button>
|
||||||
|
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/delete`)}">
|
||||||
|
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||||
|
${gettext("Delete")}
|
||||||
|
</ak-spinner-button>
|
||||||
|
<div slot="modal"></div>
|
||||||
|
</ak-modal-button>
|
||||||
|
<ak-modal-button>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-tertiary">
|
||||||
|
${gettext("View Deployment Info")}
|
||||||
|
</button>
|
||||||
|
<div slot="modal">
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">${gettext("Outpost Deployment Info")}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
|
<p><a href="https://goauthentik.io/docs/outposts/outposts/#deploy">${gettext("View deployment documentation")}</a></p>
|
||||||
|
<form class="pf-c-form">
|
||||||
|
<div class="pf-c-form__group">
|
||||||
|
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
||||||
|
<span class="pf-c-form__label-text">AUTHENTIK_HOST</span>
|
||||||
|
</label>
|
||||||
|
<input class="pf-c-form-control" readonly type="text" value="${document.location.toString()}" />
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-form__group">
|
||||||
|
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
||||||
|
<span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<ak-token-copy-button identifier="${item.token_identifier}">
|
||||||
|
${gettext("Click to copy token")}
|
||||||
|
</ak-token-copy-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>${gettext("If your authentik Instance is using a self-signed certificate, set this value.")}</h3>
|
||||||
|
<div class="pf-c-form__group">
|
||||||
|
<label class="pf-c-form__label" for="help-text-simple-form-name">
|
||||||
|
<span class="pf-c-form__label-text">AUTHENTIK_INSECURE</span>
|
||||||
|
</label>
|
||||||
|
<input class="pf-c-form-control" readonly type="text" value="true" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer class="pf-c-modal-box__footer pf-m-align-left">
|
||||||
|
<a class="pf-c-button pf-m-primary">${gettext("Close")}</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</ak-modal-button>`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolbar(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ak-modal-button href=${Outpost.adminUrl("create/")}>
|
||||||
|
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||||
|
${gettext("Create")}
|
||||||
|
</ak-spinner-button>
|
||||||
|
<div slot="modal"></div>
|
||||||
|
</ak-modal-button>
|
||||||
|
${super.renderToolbar()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import "./pages/events/RuleListPage";
|
||||||
import "./pages/providers/ProviderListPage";
|
import "./pages/providers/ProviderListPage";
|
||||||
import "./pages/providers/ProviderViewPage";
|
import "./pages/providers/ProviderViewPage";
|
||||||
import "./pages/property-mappings/PropertyMappingListPage";
|
import "./pages/property-mappings/PropertyMappingListPage";
|
||||||
|
import "./pages/outposts/OutpostListPage";
|
||||||
|
|
||||||
export const ROUTES: Route[] = [
|
export const ROUTES: Route[] = [
|
||||||
// Prevent infinite Shell loops
|
// Prevent infinite Shell loops
|
||||||
|
@ -42,4 +43,5 @@ export const ROUTES: Route[] = [
|
||||||
new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`),
|
new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`),
|
||||||
new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`),
|
new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`),
|
||||||
new Route(new RegExp("^/property-mappings$"), html`<ak-property-mapping-list></ak-property-mapping-list>`),
|
new Route(new RegExp("^/property-mappings$"), html`<ak-property-mapping-list></ak-property-mapping-list>`),
|
||||||
|
new Route(new RegExp("^/outposts$"), html`<ak-outpost-list></ak-outpost-list>`),
|
||||||
];
|
];
|
||||||
|
|
Reference in a new issue