Merge branch 'master' into version-2021.2

This commit is contained in:
Jens Langhammer 2021-02-08 21:33:58 +01:00
commit db113c5e8f
41 changed files with 623 additions and 555 deletions

View file

@ -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",
]

View file

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

View file

@ -3,7 +3,6 @@
{% load i18n %}
{% load humanize %}
{% load authentik_utils %}
{% load admin_reflection %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">

View file

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

View file

@ -2,7 +2,6 @@
{% load i18n %}
{% load authentik_utils %}
{% load admin_reflection %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
@ -63,7 +62,7 @@
{% for source in object_list %}
<tr role="row">
<th role="columnheader">
<a href="/sources/{{ source.slug }}/">
<a href="/sources/{{ source.slug }}">
<div>{{ source.name }}</div>
{% if not source.enabled %}
<small>{% trans 'Disabled' %}</small>
@ -93,10 +92,6 @@
</ak-spinner-button>
<div slot="modal"></div>
</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>
</tr>
{% endfor %}

View file

@ -2,7 +2,6 @@
{% load i18n %}
{% load authentik_utils %}
{% load admin_reflection %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
@ -88,10 +87,6 @@
</ak-spinner-button>
<div slot="modal"></div>
</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>
</tr>
{% endfor %}

View file

@ -2,7 +2,6 @@
{% load i18n %}
{% load authentik_utils %}
{% load admin_reflection %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
@ -90,10 +89,6 @@
</ak-spinner-button>
<div slot="modal"></div>
</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>
</tr>
{% endfor %}

View file

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

View file

@ -169,22 +169,22 @@ urlpatterns = [
),
# Stage Prompts
path(
"stages/prompts/",
"stages_prompts/",
stages_prompts.PromptListView.as_view(),
name="stage-prompts",
),
path(
"stages/prompts/create/",
"stages_prompts/create/",
stages_prompts.PromptCreateView.as_view(),
name="stage-prompt-create",
),
path(
"stages/prompts/<uuid:pk>/update/",
"stages_prompts/<uuid:pk>/update/",
stages_prompts.PromptUpdateView.as_view(),
name="stage-prompt-update",
),
path(
"stages/prompts/<uuid:pk>/delete/",
"stages_prompts/<uuid:pk>/delete/",
stages_prompts.PromptDeleteView.as_view(),
name="stage-prompt-delete",
),
@ -311,11 +311,6 @@ urlpatterns = [
name="certificatekeypair-delete",
),
# Outposts
path(
"outposts/",
outposts.OutpostListView.as_view(),
name="outposts",
),
path(
"outposts/create/",
outposts.OutpostCreateView.as_view(),

View file

@ -9,36 +9,15 @@ from django.contrib.auth.mixins import (
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 BackSuccessUrlMixin, DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.outposts.forms import OutpostForm
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(
SuccessMessageMixin,
BackSuccessUrlMixin,
@ -53,7 +32,7 @@ class OutpostCreateView(
permission_required = "authentik_outposts.add_outpost"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:outposts")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Outpost")
def get_initial(self) -> Dict[str, Any]:
@ -78,7 +57,7 @@ class OutpostUpdateView(
permission_required = "authentik_outposts.change_outpost"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:outposts")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Outpost")
@ -89,5 +68,5 @@ class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessa
permission_required = "authentik_outposts.delete_outpost"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:outposts")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Outpost")

View file

@ -29,11 +29,12 @@ from authentik.flows.api import (
FlowViewSet,
StageViewSet,
)
from authentik.outposts.api import (
from authentik.outposts.api.outpost_service_connections import (
DockerServiceConnectionViewSet,
KubernetesServiceConnectionViewSet,
OutpostViewSet,
ServiceConnectionViewSet,
)
from authentik.outposts.api.outposts import OutpostViewSet
from authentik.policies.api import (
PolicyBindingViewSet,
PolicyCacheViewSet,
@ -88,6 +89,7 @@ router.register("core/users", UserViewSet)
router.register("core/tokens", TokenViewSet)
router.register("outposts/outposts", OutpostViewSet)
router.register("outposts/service_connections/all", ServiceConnectionViewSet)
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
router.register(
"outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet

View file

@ -2,7 +2,6 @@
from rest_framework.serializers import ModelSerializer, SerializerMethodField
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.models import Source
@ -10,22 +9,26 @@ from authentik.core.models import Source
class SourceSerializer(ModelSerializer, MetaNameSerializer):
"""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"""
return obj._meta.object_name.lower().replace("source", "")
def to_representation(self, instance: Source):
# pyright: reportGeneralTypeIssues=false
if instance.__class__ == Source:
return super().to_representation(instance)
return instance.serializer(instance=instance).data
return obj._meta.object_name.lower().replace("provider", "")
class Meta:
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):

View file

@ -1,30 +1,28 @@
"""Outpost API Views"""
from rest_framework.serializers import JSONField, ModelSerializer
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,
Outpost,
OutpostServiceConnection,
)
class OutpostSerializer(ModelSerializer):
"""Outpost Serializer"""
_config = JSONField()
class ServiceConnectionSerializer(ModelSerializer):
"""ServiceConnection Serializer"""
class Meta:
model = Outpost
fields = ["pk", "name", "providers", "service_connection", "_config"]
model = OutpostServiceConnection
fields = ["pk", "name"]
class OutpostViewSet(ModelViewSet):
"""Outpost Viewset"""
class ServiceConnectionViewSet(ModelViewSet):
"""ServiceConnection Viewset"""
queryset = Outpost.objects.all()
serializer_class = OutpostSerializer
queryset = OutpostServiceConnection.objects.all()
serializer_class = ServiceConnectionSerializer
class DockerServiceConnectionSerializer(ModelSerializer):

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

View file

@ -9,7 +9,6 @@ from django.core.cache import cache
from django.db import models, transaction
from django.db.models.base import Model
from django.forms.models import ModelForm
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from docker.client import DockerClient
from docker.errors import DockerException
@ -33,7 +32,6 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.template import render_to_string
from authentik.outposts.docker_tls import DockerInlineTLS
OUR_VERSION = parse(__version__)
@ -378,13 +376,6 @@ class Outpost(models.Model):
objects.append(provider)
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:
return f"Outpost {self.name}"

View file

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

View file

@ -59,11 +59,11 @@ class OAuth2ProviderViewSet(ModelViewSet):
queryset = OAuth2Provider.objects.all()
serializer_class = OAuth2ProviderSerializer
@action(methods=["GET"], detail=True)
@swagger_auto_schema(responses={200: OAuth2ProviderSetupURLs(many=False)})
@action(methods=["GET"], detail=True)
# pylint: disable=invalid-name
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)
data = {
"issuer": provider.get_issuer(request),

View file

@ -23,6 +23,8 @@ return {
"family_name": "",
"preferred_username": 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()],
}
"""

View file

@ -253,6 +253,7 @@ class OAuthFulfillmentStage(StageView):
EventAction.AUTHORIZE_APPLICATION,
authorized_application=application,
flow=self.executor.plan.flow_pk,
scopes=", ".join(self.params.scope),
).from_http(self.request)
return redirect(self.create_response_uri())
except (ClientIdError, RedirectUriError) as error:

View file

@ -54,10 +54,10 @@ class SAMLProviderViewSet(ModelViewSet):
queryset = SAMLProvider.objects.all()
serializer_class = SAMLProviderSerializer
@action(methods=["GET"], detail=True)
@swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)})
@action(methods=["GET"], detail=True)
# 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"""
provider = get_object_or_404(SAMLProvider, pk=pk)
metadata = DescriptorDownloadView.get_metadata(request, provider)

View file

@ -2,17 +2,17 @@
from rest_framework.serializers import ModelSerializer
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.ldap.models import LDAPPropertyMapping, LDAPSource
class LDAPSourceSerializer(ModelSerializer, MetaNameSerializer):
class LDAPSourceSerializer(SourceSerializer):
"""LDAP Source Serializer"""
class Meta:
model = LDAPSource
fields = SOURCE_SERIALIZER_FIELDS + [
fields = SourceSerializer.Meta.fields + [
"server_uri",
"bind_cn",
"bind_password",

View file

@ -1,18 +1,16 @@
"""OAuth Source Serializer"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.sources import SourceSerializer
from authentik.sources.oauth.models import OAuthSource
class OAuthSourceSerializer(ModelSerializer, MetaNameSerializer):
class OAuthSourceSerializer(SourceSerializer):
"""OAuth Source Serializer"""
class Meta:
model = OAuthSource
fields = SOURCE_SERIALIZER_FIELDS + [
fields = SourceSerializer.Meta.fields + [
"provider_type",
"request_token_url",
"authorization_url",

View file

@ -2,7 +2,6 @@
from django import forms
from authentik.admin.forms.source import SOURCE_FORM_FIELDS
from authentik.flows.models import Flow, FlowDesignation
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.manager import MANAGER
@ -27,7 +26,12 @@ class OAuthSourceForm(forms.ModelForm):
class Meta:
model = OAuthSource
fields = SOURCE_FORM_FIELDS + [
fields = [
"name",
"slug",
"enabled",
"authentication_flow",
"enrollment_flow",
"provider_type",
"request_token_url",
"authorization_url",

View file

@ -1,19 +1,17 @@
"""SAMLSource API Views"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.admin.forms.source import SOURCE_FORM_FIELDS
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.sources import SourceSerializer
from authentik.sources.saml.models import SAMLSource
class SAMLSourceSerializer(ModelSerializer, MetaNameSerializer):
class SAMLSourceSerializer(SourceSerializer):
"""SAMLSource Serializer"""
class Meta:
model = SAMLSource
fields = SOURCE_FORM_FIELDS + [
fields = SourceSerializer.Meta.fields + [
"issuer",
"sso_url",
"slo_url",

View file

@ -2,7 +2,6 @@
from django import forms
from authentik.admin.forms.source import SOURCE_FORM_FIELDS
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow, FlowDesignation
from authentik.sources.saml.models import SAMLSource
@ -28,7 +27,12 @@ class SAMLSourceForm(forms.ModelForm):
class Meta:
model = SAMLSource
fields = SOURCE_FORM_FIELDS + [
fields = [
"name",
"slug",
"enabled",
"authentication_flow",
"enrollment_flow",
"issuer",
"sso_url",
"slo_url",

View file

@ -24,7 +24,7 @@ require (
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pkg/errors v0.9.1
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/spf13/afero v1.5.1 // indirect
github.com/spf13/cast v1.3.1 // indirect

View file

@ -69,23 +69,10 @@ func (ac *APIController) Shutdown() {
}
func (ac *APIController) startWSHandler() {
notConnectedBackoff := 1
logger := ac.logger.WithField("loop", "ws-handler")
for {
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
} else {
// When we're connected, reset backoff to 1
notConnectedBackoff = 1
}
var wsMsg websocketMessage
err := ac.wsConn.ReadJSON(&wsMsg)

View file

@ -1789,6 +1789,11 @@ paths:
operationId: outposts_outposts_list
description: Outpost Viewset
parameters:
- name: providers__isnull
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
@ -1911,6 +1916,28 @@ paths:
required: true
type: string
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/:
get:
operationId: outposts_proxy_list
@ -2037,6 +2064,133 @@ paths:
description: A unique integer value identifying this Proxy Provider.
required: true
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/:
get:
operationId: outposts_service_connections_docker_list
@ -4308,7 +4462,7 @@ paths:
/providers/oauth2/{id}/setup_urls/:
get:
operationId: providers_oauth2_setup_urls
description: Return metadata as XML string
description: Get Providers setup URLs
parameters: []
responses:
'200':
@ -8023,6 +8177,12 @@ definitions:
items:
type: integer
uniqueItems: true
providers_obj:
description: ''
type: array
items:
$ref: '#/definitions/Provider'
readOnly: true
service_connection:
title: Service connection
description: Select Service-Connection authentik should use to manage this
@ -8030,9 +8190,36 @@ definitions:
type: string
format: uuid
x-nullable: true
token_identifier:
title: Token identifier
type: string
readOnly: true
_config:
title: config
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:
title: 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
Header. If not set, the user's Email address is used.
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:
description: DockerServiceConnection Serializer
required:
@ -9157,6 +9359,10 @@ definitions:
type: string
format: uuid
x-nullable: true
object_type:
title: Object type
type: string
readOnly: true
verbose_name:
title: Verbose name
type: string
@ -9165,10 +9371,6 @@ definitions:
title: Verbose name plural
type: string
readOnly: true
__type__:
title: 'type '
type: string
readOnly: true
LDAPSource:
description: LDAP Source Serializer
required:
@ -9213,6 +9415,10 @@ definitions:
type: string
format: uuid
x-nullable: true
object_type:
title: Object type
type: string
readOnly: true
verbose_name:
title: Verbose name
type: string
@ -9344,6 +9550,10 @@ definitions:
type: string
format: uuid
x-nullable: true
object_type:
title: Object type
type: string
readOnly: true
verbose_name:
title: Verbose name
type: string
@ -9397,6 +9607,11 @@ definitions:
- sso_url
type: object
properties:
pk:
title: Pbm uuid
type: string
format: uuid
readOnly: true
name:
title: Name
description: Source's display Name.
@ -9425,6 +9640,18 @@ definitions:
type: string
format: uuid
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:
title: Issuer
description: Also known as Entity ID. Defaults the Metadata URL.

6
web/package-lock.json generated
View file

@ -899,9 +899,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"construct-style-sheets-polyfill": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.6.tgz",
"integrity": "sha512-lU0to7dFDjKslMF+M5NUa4s0RQMBRVyZMXvD/vp7vmjdEPgziTkHSfZHQxfoIvVWajWRJUVJMLfrMwcx8fTh4A=="
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.9.tgz",
"integrity": "sha512-kPXZXxsp7CTr/Vs29+omUA29wTrFplkdY6jqxyv0DDWC5Ro79WmwpboH2M9KiOclbtn8r81GCFtc7+t7OjRnCw=="
},
"copy-descriptor": {
"version": "0.1.1",

View file

@ -18,7 +18,7 @@
"@types/codemirror": "0.0.108",
"chart.js": "^2.9.4",
"codemirror": "^5.59.2",
"construct-style-sheets-polyfill": "^2.4.6",
"construct-style-sheets-polyfill": "^2.4.9",
"flowchart.js": "^1.15.0",
"lit-element": "^2.4.0",
"lit-html": "^1.3.0",

View file

@ -7,6 +7,13 @@ export interface QueryArguments {
[key: string]: number | string | boolean | null;
}
export interface BaseInheritanceModel {
verbose_name: string;
verbose_name_plural: string;
}
export class Client {
makeUrl(url: string[], query?: QueryArguments): string {
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;

40
web/src/api/Outposts.ts Normal file
View 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}`;
}
}

View file

@ -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;
name: string;
constructor() {
throw Error();
}
verbose_name: string;
verbose_name_plural: string;
static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]);

View file

@ -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;
name: string;
authorization_flow: string;

View file

@ -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;
name: string;
slug: string;
@ -11,6 +11,8 @@ export class Source {
constructor() {
throw Error();
}
verbose_name: string;
verbose_name_plural: string;
static get(slug: string): Promise<Source> {
return DefaultClient.fetch<Source>(["sources", "all", slug]);
@ -19,4 +21,8 @@ export class Source {
static list(filter?: QueryArguments): Promise<PBResponse<Source>> {
return DefaultClient.fetch<PBResponse<Source>>(["sources", "all"], filter);
}
static adminUrl(rest: string): string {
return `/administration/sources/${rest}`;
}
}

View file

@ -27,7 +27,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
`^/sources/(?<slug>${SLUG_REGEX})$`,
),
new SidebarItem("Providers", "/providers"),
new SidebarItem("Outposts", "/administration/outposts/"),
new SidebarItem("Outposts", "/outposts"),
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
@ -41,7 +41,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Flows").children(
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})$`),
new SidebarItem("Stages", "/administration/stages/"),
new SidebarItem("Prompts", "/administration/stages/prompts/"),
new SidebarItem("Prompts", "/administration/stages_prompts/"),
new SidebarItem("Invitations", "/administration/stages/invitations/"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);

View file

@ -65,13 +65,13 @@ export class EventInfo extends LitElement {
case "model_updated":
case "model_deleted":
return html`
<h3>${gettext("Affected model:")}</h3><hr>
<h3>${gettext("Affected model:")}</h3>
${this.getModelInfo(this.event.context.model as EventContext)}
`;
case "authorize_application":
return html`<div class="pf-l-flex">
<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)}
</div>
<div class="pf-l-flex__item">
@ -83,14 +83,15 @@ export class EventInfo extends LitElement {
}), html`<ak-spinner size=${SpinnerSize.Medium}></ak-spinner>`)}
</span>
</div>
</div>`;
</div>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case "login_failed":
return html`
<h3>${gettext(`Attempted to log in as ${this.event.context.username}`)}</h3>
<ak-expand>${this.defaultResponse()}</ak-expand>`;
case "token_view":
return html`
<h3>${gettext("Token:")}</h3><hr>
<h3>${gettext("Token:")}</h3>
${this.getModelInfo(this.event.context.token as EventContext)}`;
case "property_mapping_exception":
return html`<div class="pf-l-flex">

View 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>&nbsp;${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>&nbsp;${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>&nbsp;
${gettext(`${h.version}, should be ${h.version_should}`)}` :
html`<i class="fas fa-check pf-m-success"></i>&nbsp;${gettext(`Version: ${h.version}`)}`}
</li>
</ul>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}</ul>`;
}
}

View 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()}
`;
}
}

View file

@ -14,6 +14,7 @@ import "./pages/events/RuleListPage";
import "./pages/providers/ProviderListPage";
import "./pages/providers/ProviderViewPage";
import "./pages/property-mappings/PropertyMappingListPage";
import "./pages/outposts/OutpostListPage";
export const ROUTES: Route[] = [
// 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/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("^/outposts$"), html`<ak-outpost-list></ak-outpost-list>`),
];