Merge branch 'master' into version-2021.3
|
@ -95,10 +95,10 @@
|
|||
},
|
||||
"autobahn": {
|
||||
"hashes": [
|
||||
"sha256:884f79c50fdc55ade2c315946a9caa145e8b10075eee9d2c2594ea5e8f5226aa",
|
||||
"sha256:bf7a9d302a34d0f719d43c57f65ca1f2f5c982dd6ea0c11e1e190ef6f43710fe"
|
||||
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
|
||||
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
|
||||
],
|
||||
"version": "==21.2.2"
|
||||
"version": "==21.3.1"
|
||||
},
|
||||
"automat": {
|
||||
"hashes": [
|
||||
|
@ -116,18 +116,18 @@
|
|||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:3570a3c0fbd80bcb30449f87cf9d2f7abb67fac2a5e317d002f9921c59be9b17",
|
||||
"sha256:ceff2f32ba05acc9ee35a6dd82e29ea285d63e889bed39a6ba7a700146f43749"
|
||||
"sha256:c9513a9ea00f8d17ecdc02c391ae956bf0f990aa07deec11c421607c09b294e1",
|
||||
"sha256:f84ca60e9605af69022f039c035b33d519531eeaac52724b9223a5465f4a8b6b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.17.18"
|
||||
"version": "==1.17.19"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:51900b10da4ae45be4b16045e5b2ff7d1158a7955d9d7cc5e5a9ba3170f10586",
|
||||
"sha256:b181f32d9075e5419a89fa9636ce95946c15459c9bfadfabb53ca902fc8072b8"
|
||||
"sha256:135b5f30e6662b46d804f993bf31d9c7769c6c0848321ed0aa0393f5b9c19a94",
|
||||
"sha256:8e42c78d2eb888551635309158c04ef2648a96d8c2c70dbce7712c6ce8629759"
|
||||
],
|
||||
"version": "==1.20.18"
|
||||
"version": "==1.20.19"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
|
@ -1249,10 +1249,10 @@
|
|||
},
|
||||
"websocket-client": {
|
||||
"hashes": [
|
||||
"sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
|
||||
"sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"
|
||||
"sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663",
|
||||
"sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"
|
||||
],
|
||||
"version": "==0.57.0"
|
||||
"version": "==0.58.0"
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
|
|
|
@ -7,14 +7,14 @@ from django.http.response import Http404
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField
|
||||
from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import Serializer
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from authentik.events.monitored_tasks import TaskInfo
|
||||
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
|
||||
|
||||
|
||||
class TaskSerializer(Serializer):
|
||||
|
@ -24,7 +24,10 @@ class TaskSerializer(Serializer):
|
|||
task_description = CharField()
|
||||
task_finish_timestamp = DateTimeField(source="finish_timestamp")
|
||||
|
||||
status = IntegerField(source="result.status.value")
|
||||
status = ChoiceField(
|
||||
source="result.status.name",
|
||||
choices=[(x.name, x.name) for x in TaskResultStatus],
|
||||
)
|
||||
messages = ListField(source="result.messages")
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
|
|
|
@ -55,7 +55,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet):
|
|||
def get_queryset(self): # pragma: no cover
|
||||
return None
|
||||
|
||||
@swagger_auto_schema(responses={200: VersionSerializer(many=True)})
|
||||
@swagger_auto_schema(responses={200: VersionSerializer(many=False)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Get running and latest version."""
|
||||
return Response(VersionSerializer(True).data)
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
"""Swagger Pagination Schema class"""
|
||||
from typing import OrderedDict
|
||||
|
||||
from drf_yasg2 import openapi
|
||||
from drf_yasg2.inspectors import PaginatorInspector
|
||||
|
||||
|
||||
class PaginationInspector(PaginatorInspector):
|
||||
"""Swagger Pagination Schema class"""
|
||||
|
||||
def get_paginated_response(self, paginator, response_schema):
|
||||
"""
|
||||
:param BasePagination paginator: the paginator
|
||||
:param openapi.Schema response_schema: the response schema that must be paged.
|
||||
:rtype: openapi.Schema
|
||||
"""
|
||||
|
||||
return openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties=OrderedDict(
|
||||
(
|
||||
(
|
||||
"pagination",
|
||||
openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties=OrderedDict(
|
||||
(
|
||||
("next", openapi.Schema(type=openapi.TYPE_NUMBER)),
|
||||
(
|
||||
"previous",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
("count", openapi.Schema(type=openapi.TYPE_NUMBER)),
|
||||
(
|
||||
"current",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
(
|
||||
"total_pages",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
(
|
||||
"start_index",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
(
|
||||
"end_index",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
)
|
||||
),
|
||||
required=[
|
||||
"next",
|
||||
"previous",
|
||||
"count",
|
||||
"current",
|
||||
"total_pages",
|
||||
"start_index",
|
||||
"end_index",
|
||||
],
|
||||
),
|
||||
),
|
||||
("results", response_schema),
|
||||
)
|
||||
),
|
||||
required=["results", "pagination"],
|
||||
)
|
||||
|
||||
def get_paginator_parameters(self, paginator):
|
||||
"""
|
||||
Get the pagination parameters for a single paginator **instance**.
|
||||
|
||||
Should return :data:`.NotHandled` if this inspector
|
||||
does not know how to handle the given `paginator`.
|
||||
|
||||
:param BasePagination paginator: the paginator
|
||||
:rtype: list[openapi.Parameter]
|
||||
"""
|
||||
|
||||
return [
|
||||
openapi.Parameter(
|
||||
"page",
|
||||
openapi.IN_QUERY,
|
||||
"Page Index",
|
||||
False,
|
||||
None,
|
||||
openapi.TYPE_INTEGER,
|
||||
),
|
||||
openapi.Parameter(
|
||||
"page_size",
|
||||
openapi.IN_QUERY,
|
||||
"Page Size",
|
||||
False,
|
||||
None,
|
||||
openapi.TYPE_INTEGER,
|
||||
),
|
||||
]
|
|
@ -1,10 +1,11 @@
|
|||
"""core Configs API"""
|
||||
from django.db.models import Model
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ReadOnlyField, Serializer
|
||||
from rest_framework.serializers import Serializer
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
|
@ -13,12 +14,12 @@ from authentik.lib.config import CONFIG
|
|||
class ConfigSerializer(Serializer):
|
||||
"""Serialize authentik Config into DRF Object"""
|
||||
|
||||
branding_logo = ReadOnlyField()
|
||||
branding_title = ReadOnlyField()
|
||||
branding_logo = CharField(read_only=True)
|
||||
branding_title = CharField(read_only=True)
|
||||
|
||||
error_reporting_enabled = ReadOnlyField()
|
||||
error_reporting_environment = ReadOnlyField()
|
||||
error_reporting_send_pii = ReadOnlyField()
|
||||
error_reporting_enabled = BooleanField(read_only=True)
|
||||
error_reporting_environment = CharField(read_only=True)
|
||||
error_reporting_send_pii = BooleanField(read_only=True)
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
@ -32,7 +33,7 @@ class ConfigsViewSet(ViewSet):
|
|||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(responses={200: ConfigSerializer(many=True)})
|
||||
@swagger_auto_schema(responses={200: ConfigSerializer(many=False)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Retrive public configuration options"""
|
||||
config = ConfigSerializer(
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
"""core messages API"""
|
||||
from django.contrib.messages import get_messages
|
||||
from django.db.models import Model
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ReadOnlyField, Serializer
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
|
||||
class MessageSerializer(Serializer):
|
||||
"""Serialize Django Message into DRF Object"""
|
||||
|
||||
message = ReadOnlyField()
|
||||
level = ReadOnlyField()
|
||||
tags = ReadOnlyField()
|
||||
extra_tags = ReadOnlyField()
|
||||
level_tag = ReadOnlyField()
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, instance: Model, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MessagesViewSet(ViewSet):
|
||||
"""Read-only view set that returns the current session's messages"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(responses={200: MessageSerializer(many=True)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""List current messages and pass into Serializer"""
|
||||
all_messages = list(get_messages(request))
|
||||
return Response(MessageSerializer(all_messages, many=True).data)
|
|
@ -10,7 +10,6 @@ from authentik.admin.api.tasks import TaskViewSet
|
|||
from authentik.admin.api.version import VersionViewSet
|
||||
from authentik.admin.api.workers import WorkerViewSet
|
||||
from authentik.api.v2.config import ConfigsViewSet
|
||||
from authentik.api.v2.messages import MessagesViewSet
|
||||
from authentik.core.api.applications import ApplicationViewSet
|
||||
from authentik.core.api.groups import GroupViewSet
|
||||
from authentik.core.api.propertymappings import PropertyMappingViewSet
|
||||
|
@ -77,7 +76,6 @@ from authentik.stages.user_write.api import UserWriteStageViewSet
|
|||
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
router.register("root/messages", MessagesViewSet, basename="messages")
|
||||
router.register("root/config", ConfigsViewSet, basename="configs")
|
||||
|
||||
router.register("admin/version", VersionViewSet, basename="admin_version")
|
||||
|
|
|
@ -46,8 +46,7 @@ def backup_database(self: MonitoredTask): # pragma: no cover
|
|||
TaskResult(
|
||||
TaskResultStatus.SUCCESSFUL,
|
||||
[
|
||||
f"Successfully finished database backup {naturaltime(start)}",
|
||||
out.getvalue(),
|
||||
f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}",
|
||||
],
|
||||
)
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<p>{% trans "Configure settings relevant to your user profile." %}</p>
|
||||
</div>
|
||||
</section>
|
||||
<ak-tabs>
|
||||
<ak-tabs vertical="true">
|
||||
<section slot="page-1" data-tab-title="{% trans 'User details' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
|
|
|
@ -29,7 +29,7 @@ class EventSerializer(ModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class EventTopPerUserSerialier(Serializer):
|
||||
class EventTopPerUserSerializer(Serializer):
|
||||
"""Response object of Event's top_per_user"""
|
||||
|
||||
application = DictField()
|
||||
|
@ -60,7 +60,7 @@ class EventViewSet(ReadOnlyModelViewSet):
|
|||
filterset_fields = ["action"]
|
||||
|
||||
@swagger_auto_schema(
|
||||
method="GET", responses={200: EventTopPerUserSerialier(many=True)}
|
||||
method="GET", responses={200: EventTopPerUserSerializer(many=True)}
|
||||
)
|
||||
@action(detail=False, methods=["GET"])
|
||||
def top_per_user(self, request: Request):
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Type
|
|||
from kubernetes.client import OpenApiException
|
||||
from kubernetes.client.api_client import ApiClient
|
||||
from structlog.testing import capture_logs
|
||||
from urllib3.exceptions import HTTPError
|
||||
from yaml import dump_all
|
||||
|
||||
from authentik.outposts.controllers.base import BaseController, ControllerException
|
||||
|
@ -42,7 +43,7 @@ class KubernetesController(BaseController):
|
|||
reconciler = self.reconcilers[reconcile_key](self)
|
||||
reconciler.up()
|
||||
|
||||
except OpenApiException as exc:
|
||||
except (OpenApiException, HTTPError) as exc:
|
||||
raise ControllerException from exc
|
||||
|
||||
def up_with_logs(self) -> list[str]:
|
||||
|
@ -54,7 +55,7 @@ class KubernetesController(BaseController):
|
|||
reconciler.up()
|
||||
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
|
||||
return all_logs
|
||||
except OpenApiException as exc:
|
||||
except (OpenApiException, HTTPError) as exc:
|
||||
raise ControllerException from exc
|
||||
|
||||
def down(self):
|
||||
|
|
|
@ -139,6 +139,9 @@ GUARDIAN_MONKEY_PATCH = False
|
|||
|
||||
SWAGGER_SETTINGS = {
|
||||
"DEFAULT_INFO": "authentik.api.v2.urls.info",
|
||||
"DEFAULT_PAGINATOR_INSPECTORS": [
|
||||
"authentik.api.pagination_schema.PaginationInspector",
|
||||
],
|
||||
"SECURITY_DEFINITIONS": {
|
||||
"token": {"type": "apiKey", "name": "Authorization", "in": "header"}
|
||||
},
|
||||
|
@ -147,7 +150,6 @@ SWAGGER_SETTINGS = {
|
|||
REST_FRAMEWORK = {
|
||||
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
|
||||
"PAGE_SIZE": 100,
|
||||
"DATETIME_FORMAT": "%s",
|
||||
"DEFAULT_FILTER_BACKENDS": [
|
||||
"rest_framework_guardian.filters.ObjectPermissionsFilter",
|
||||
"django_filters.rest_framework.DjangoFilterBackend",
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
"""OTP Validate stage forms"""
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_otp import match_token
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.models import NotConfiguredAction
|
||||
from authentik.stages.authenticator_validate.models import (
|
||||
AuthenticatorValidateStage,
|
||||
|
@ -11,35 +8,6 @@ from authentik.stages.authenticator_validate.models import (
|
|||
)
|
||||
|
||||
|
||||
class ValidationForm(forms.Form):
|
||||
"""OTP Validate stage forms"""
|
||||
|
||||
user: User
|
||||
|
||||
code = forms.CharField(
|
||||
label=_("Please enter the token from your device."),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"autocomplete": "one-time-code",
|
||||
"placeholder": "123456",
|
||||
"autofocus": "autofocus",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
|
||||
def clean_code(self):
|
||||
"""Validate code against all confirmed devices"""
|
||||
code = self.cleaned_data.get("code")
|
||||
device = match_token(self.user, code)
|
||||
if not device:
|
||||
raise forms.ValidationError(_("Invalid Token"))
|
||||
return code
|
||||
|
||||
|
||||
class AuthenticatorValidateStageForm(forms.ModelForm):
|
||||
"""OTP Validate stage forms"""
|
||||
|
||||
|
|
|
@ -378,8 +378,8 @@ stages:
|
|||
python ./scripts/az_do_set_branch.py
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'GHCR'
|
||||
repository: 'beryju/authentik'
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/server'
|
||||
command: 'buildAndPush'
|
||||
Dockerfile: 'Dockerfile'
|
||||
tags: "gh-$(branchName)"
|
||||
|
|
|
@ -98,8 +98,8 @@ stages:
|
|||
python ./scripts/az_do_set_branch.py
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'GHCR'
|
||||
repository: 'beryju/authentik-proxy'
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/proxy'
|
||||
command: 'buildAndPush'
|
||||
Dockerfile: 'outpost/proxy.Dockerfile'
|
||||
buildContext: 'outpost/'
|
||||
|
|
2364
swagger.yaml
|
@ -78,8 +78,8 @@ stages:
|
|||
python ./scripts/az_do_set_branch.py
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'GHCR'
|
||||
repository: 'beryju/authentik-static'
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/static'
|
||||
command: 'buildAndPush'
|
||||
Dockerfile: 'web/Dockerfile'
|
||||
tags: "gh-$(branchName)"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { gettext } from "django";
|
||||
import { showMessage } from "../elements/messages/MessageContainer";
|
||||
import { getCookie } from "../utils";
|
||||
import { NotFoundError, RequestError } from "./Error";
|
||||
|
||||
|
@ -47,6 +49,13 @@ export class Client {
|
|||
}
|
||||
return r;
|
||||
})
|
||||
.catch((e) => {
|
||||
showMessage({
|
||||
level_tag: "error",
|
||||
message: gettext(`Unexpected error while fetching: ${e.toString()}`),
|
||||
});
|
||||
return e;
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((r) => <T>r);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import Chart from "chart.js";
|
||||
import { showMessage } from "./messages/MessageContainer";
|
||||
import { DefaultClient } from "../api/Client";
|
||||
|
||||
interface TickValue {
|
||||
value: number;
|
||||
major: boolean;
|
||||
}
|
||||
|
||||
export interface LoginMetrics {
|
||||
logins_failed_per_1h: { x: number, y: number }[];
|
||||
logins_per_1h: { x: number, y: number }[];
|
||||
}
|
||||
|
||||
@customElement("ak-admin-logins-chart")
|
||||
export class AdminLoginsChart extends LitElement {
|
||||
@property()
|
||||
url = "";
|
||||
@property({type: Array})
|
||||
url: string[] = [];
|
||||
|
||||
chart?: Chart;
|
||||
|
||||
|
@ -40,15 +45,7 @@ export class AdminLoginsChart extends LitElement {
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
fetch(this.url)
|
||||
.then((r) => r.json())
|
||||
.catch((e) => {
|
||||
showMessage({
|
||||
level_tag: "error",
|
||||
message: "Unexpected error"
|
||||
});
|
||||
console.error(e);
|
||||
})
|
||||
DefaultClient.fetch<LoginMetrics>(this.url)
|
||||
.then((r) => {
|
||||
const canvas = <HTMLCanvasElement>this.shadowRoot?.querySelector("canvas");
|
||||
if (!canvas) {
|
||||
|
|
|
@ -12,10 +12,17 @@ export class Tabs extends LitElement {
|
|||
@property()
|
||||
currentPage?: string;
|
||||
|
||||
@property({type: Boolean})
|
||||
vertical = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [GlobalsStyle, TabsStyle, css`
|
||||
::slotted(*) {
|
||||
height: 100%;
|
||||
flex-grow: 2;
|
||||
}
|
||||
:host([vertical]) {
|
||||
display: flex;
|
||||
}
|
||||
`];
|
||||
}
|
||||
|
@ -39,7 +46,7 @@ export class Tabs extends LitElement {
|
|||
}
|
||||
this.currentPage = pages[0].attributes.getNamedItem("slot")?.value;
|
||||
}
|
||||
return html`<div class="pf-c-tabs">
|
||||
return html`<div class="pf-c-tabs ${this.vertical ? "pf-m-vertical pf-m-box" : ""}">
|
||||
<ul class="pf-c-tabs__list">
|
||||
${pages.map((page) => this.renderTab(page))}
|
||||
</ul>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { gettext } from "django";
|
||||
import { LitElement, html, customElement, TemplateResult, property } from "lit-element";
|
||||
import { DefaultClient } from "../../api/Client";
|
||||
import "./Message";
|
||||
import { APIMessage } from "./Message";
|
||||
|
||||
|
@ -15,7 +14,6 @@ export function showMessage(message: APIMessage): void {
|
|||
|
||||
@customElement("ak-message-container")
|
||||
export class MessageContainer extends LitElement {
|
||||
url = DefaultClient.makeUrl(["root", "messages"]);
|
||||
|
||||
@property({attribute: false})
|
||||
messages: APIMessage[] = [];
|
||||
|
@ -36,10 +34,6 @@ export class MessageContainer extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
this.fetchMessages();
|
||||
}
|
||||
|
||||
connect(): void {
|
||||
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
|
||||
window.location.host
|
||||
|
@ -74,21 +68,6 @@ export class MessageContainer extends LitElement {
|
|||
});
|
||||
}
|
||||
|
||||
/* Fetch messages which were stored in the session.
|
||||
* This mostly gets messages which were created when the user arrives/leaves the site
|
||||
* and especially the login flow */
|
||||
fetchMessages(): Promise<void> {
|
||||
console.debug("authentik/messages: fetching messages over direct api");
|
||||
return fetch(this.url)
|
||||
.then((r) => r.json())
|
||||
.then((r: APIMessage[]) => {
|
||||
r.forEach((m: APIMessage) => {
|
||||
this.messages.push(m);
|
||||
this.requestUpdate();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<ul class="pf-c-alert-group pf-m-toast">
|
||||
${this.messages.map((m) => {
|
||||
|
|
|
@ -119,13 +119,15 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
|
|||
return html`<ak-stage-authenticator-validate-code
|
||||
.host=${this}
|
||||
.challenge=${this.challenge}
|
||||
.deviceChallenge=${this.selectedDeviceChallenge}>
|
||||
.deviceChallenge=${this.selectedDeviceChallenge}
|
||||
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
|
||||
</ak-stage-authenticator-validate-code>`;
|
||||
case DeviceClasses.WEBAUTHN:
|
||||
return html`<ak-stage-authenticator-validate-webauthn
|
||||
.host=${this}
|
||||
.challenge=${this.challenge}
|
||||
.deviceChallenge=${this.selectedDeviceChallenge}>
|
||||
.deviceChallenge=${this.selectedDeviceChallenge}
|
||||
.showBackButton=${(this.challenge?.device_challenges.length || []) > 1}>
|
||||
</ak-stage-authenticator-validate-webauthn>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
|
|||
@property({ attribute: false })
|
||||
deviceChallenge?: DeviceChallenge;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showBackButton = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
@ -61,14 +64,16 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
|
|||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links">
|
||||
<li class="pf-c-login__main-footer-links-item">
|
||||
${this.showBackButton ?
|
||||
html`<li class="pf-c-login__main-footer-links-item">
|
||||
<button class="pf-c-button pf-m-secondary pf-m-block" @click=${() => {
|
||||
if (!this.host) return;
|
||||
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
|
||||
}}>
|
||||
${gettext("Return to device picker")}
|
||||
</button>
|
||||
</li>
|
||||
</li>`:
|
||||
html``}
|
||||
</ul>
|
||||
</footer>`;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
|
|||
@property()
|
||||
authenticateMessage = "";
|
||||
|
||||
@property({type: Boolean})
|
||||
showBackButton = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
@ -98,14 +101,16 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage {
|
|||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links">
|
||||
<li class="pf-c-login__main-footer-links-item">
|
||||
${this.showBackButton ?
|
||||
html`<li class="pf-c-login__main-footer-links-item">
|
||||
<button class="pf-c-button pf-m-secondary pf-m-block" @click=${() => {
|
||||
if (!this.host) return;
|
||||
(this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
|
||||
}}>
|
||||
${gettext("Return to device picker")}
|
||||
</button>
|
||||
</li>
|
||||
</li>`:
|
||||
html``}
|
||||
</ul>
|
||||
</footer>`;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
import { DefaultClient } from "../../api/Client";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
import "../../elements/AdminLoginsChart";
|
||||
|
@ -31,7 +30,7 @@ export class AdminOverviewPage extends LitElement {
|
|||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-gallery pf-m-gutter">
|
||||
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
|
||||
<ak-admin-logins-chart url="${DefaultClient.makeUrl(["admin", "metrics"])}"></ak-admin-logins-chart>
|
||||
<ak-admin-logins-chart .url="${["admin", "metrics"]}"></ak-admin-logins-chart>
|
||||
</ak-aggregate-card>
|
||||
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
|
||||
<ak-top-applications-table></ak-top-applications-table>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { Application } from "../../api/Applications";
|
||||
import { DefaultClient } from "../../api/Client";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
|
@ -71,7 +70,7 @@ export class ApplicationViewPage extends LitElement {
|
|||
<div class="pf-c-card__body">
|
||||
${this.application ? html`
|
||||
<ak-admin-logins-chart
|
||||
url="${DefaultClient.makeUrl(["core", "applications", this.application?.slug, "metrics"])}">
|
||||
.url="${["core", "applications", this.application?.slug, "metrics"]}">
|
||||
</ak-admin-logins-chart>`: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Static Authenticator stage
|
||||
title: Static Authentication Setup stage
|
||||
---
|
||||
|
||||
This stage configures static OTP Tokens, which can be used as a backup method to time-based OTP tokens.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: TOTP stage
|
||||
title: TOTP Authentication Setup stage
|
||||
---
|
||||
|
||||
This stage configures a time-based OTP Device, such as Google Authenticator or Authy.
|
||||
|
||||
You can configure how many digest should be used for the OTP Token.
|
||||
You can configure how many digits should be used for the OTP Token.
|
||||
|
|
|
@ -2,7 +2,16 @@
|
|||
title: Authenticator Validation Stage
|
||||
---
|
||||
|
||||
This stage validates an already configured OTP Device. This device has to be configured using any of the other authenticator stages:
|
||||
This stage validates an already configured Authenticator Device. This device has to be configured using any of the other authenticator stages:
|
||||
|
||||
- [TOTP authenticator stage](../authenticator_totp/index.md)
|
||||
- [Static authenticator stage](../authenticator_static/index.md).
|
||||
- [WebAuth authenticator stage](../authenticator_webauthn/index.md).
|
||||
|
||||
You can select which type of device classes are allowed.
|
||||
|
||||
Using the `Not configured action`, you can choose what happens when a user does not have any matching devices.
|
||||
|
||||
- Skip: Validation is skipped and the flow continues
|
||||
- Deny: Access is denied, the flow execution ends
|
||||
- Configure: This option requires a *Configuration stage* to be set. The validation stage will be marked as successful, and the configuration stage will be injected into the flow.
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: WebAuthn Authentication Setup stage
|
||||
---
|
||||
|
||||
This stage configures a WebAuthn-based Authenticator. This can either be a browser, biometrics or a Security stick like a YubiKey.
|
||||
|
||||
There are no stage-specific settings.
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 115 KiB |
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Deny stage
|
||||
---
|
||||
|
||||
This stage stops the execution of a flow. This can be used to conditionally deny users access to a flow,
|
||||
even if they are not signed in (and permissions can't be checked via groups).
|
||||
|
||||
:::caution
|
||||
To effectively use this stage, make sure to **disable** *Evaluate on plan* on the Stage binding.
|
||||
:::
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
title: Apache Guacamole™
|
||||
---
|
||||
|
||||
## What is Apache Guacamole™
|
||||
|
||||
From https://guacamole.apache.org/
|
||||
|
||||
:::note
|
||||
Apache Guacamole is a clientless remote desktop gateway. It supports standard protocols like VNC, RDP, and SSH.
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `guacamole.company` is the FQDN of the Guacamole install.
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
|
||||
Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Redirect URIs: `https://guacamole.company/` (depending on your Tomcat setup, you might have to add `/guacamole/` if the application runs in a subfolder)
|
||||
- Scopes: OpenID, Email and Profile
|
||||
|
||||
Note the Client ID value. Create an application, using the provider you've created above.
|
||||
|
||||
## Guacamole
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker"
|
||||
values={[
|
||||
{label: 'Docker', value: 'docker'},
|
||||
{label: 'Standalone', value: 'standalone'},
|
||||
]}>
|
||||
<TabItem value="docker">
|
||||
The docker containers are configured via environment variables. The following variables are required:
|
||||
|
||||
```yaml
|
||||
OPENID_AUTHORIZATION_ENDPOINT: https://authentik.company/application/o/authorize/
|
||||
OPENID_CLIENT_ID: # client ID from above
|
||||
OPENID_ISSUER: https://authentik.company/application/o/apache-guacamole/
|
||||
OPENID_JWKS_ENDPOINT: https://authentik.company/application/o/apache-guacamole/jwks/
|
||||
OPENID_REDIRECT_URI: https://guacamole.company/ # This must match the redirect URI above
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="standalone">
|
||||
Standalone Guacamole is configured using the `guacamole.properties` file. Add the following settings:
|
||||
|
||||
```
|
||||
openid-authorization-endpoint=https://authentik.company/application/o/authorize/
|
||||
openid-client-id=# client ID from above
|
||||
openid-issuer=https://authentik.company/application/o/apache-guacamole/
|
||||
openid-jwks-endpoint=https://authentik.company/application/o/apache-guacamole/jwks/
|
||||
openid-redirect-uri=https://guacamole.company/ # This must match the redirect URI above
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
|
@ -29,6 +29,16 @@ Note the Client ID and Client Secret values. Create an application, using the pr
|
|||
|
||||
## Grafana
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
<Tabs
|
||||
defaultValue="docker"
|
||||
values={[
|
||||
{label: 'Docker', value: 'docker'},
|
||||
{label: 'Standalone', value: 'standalone'},
|
||||
]}>
|
||||
<TabItem value="docker">
|
||||
If your Grafana is running in docker, set the following environment variables:
|
||||
|
||||
```yaml
|
||||
|
@ -45,7 +55,8 @@ environment:
|
|||
# Optionally enable auto-login
|
||||
GF_AUTH_OAUTH_AUTO_LOGIN: "true"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="standalone">
|
||||
If you are using a config-file instead, you have to set these options:
|
||||
|
||||
```ini
|
||||
|
@ -64,3 +75,5 @@ auth_url = https://authentik.company/application/o/authorize/
|
|||
token_url = https://authentik.company/application/o/token/
|
||||
api_url = https://authentik.company/application/o/userinfo/
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 188 KiB |
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
title: Wiki.js
|
||||
---
|
||||
|
||||
## What is Wiki.js
|
||||
|
||||
From https://en.wikipedia.org/wiki/Wiki.js
|
||||
|
||||
:::note
|
||||
Wiki.js is a wiki engine running on Node.js and written in JavaScript. It is free software released under the Affero GNU General Public License. It is available as a self-hosted solution or using "single-click" install on the DigitalOcean and AWS marketplace.
|
||||
:::
|
||||
|
||||
:::note
|
||||
This is based on authentik 2021.3 and Wiki.js 2.5. Instructions may differ between versions.
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `wiki.company` is the FQDN of Wiki.js.
|
||||
- `authentik.company` is the FQDN of authentik.
|
||||
|
||||
### Step 1
|
||||
|
||||
In Wiki.js, navigate to the _Authentication_ section in the _Administration_ interface.
|
||||
|
||||
Add a _Generic OpenID Connect / OAuth2_ strategy and note the _Callback URL / Redirect URI_ in the _Configuration Reference_ section at the bottom.
|
||||
|
||||
### Step 2
|
||||
|
||||
In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings:
|
||||
|
||||
- Client Type: Confidential
|
||||
- JWT Algorithm: RS256
|
||||
- Redirect URI: The _Callback URL / Redirect URI_ you noted from the previous step.
|
||||
- Scopes: Default OAUth mappings for: OpenID, email, profile.
|
||||
- RSA Key: Choose a certificate.
|
||||
- Sub Mode: Based on username.
|
||||
|
||||
Note the _client ID_ and _client secret_, then save the provider. If you need to retrieve these values, you can do so by editing the provider.
|
||||
|
||||
![](./authentik_provider.png)
|
||||
|
||||
### Step 3
|
||||
|
||||
In Wiki.js, configure the authentication strategy with these settings:
|
||||
|
||||
- Client ID: Client ID from the authentik provider.
|
||||
- Client Secret: Client Secret from the authentik provider.
|
||||
- Authorization Endpoint URL: https://authentik.company/application/o/authorize/
|
||||
- Token Endpoint URL: https://authentik.company/application/o/token/
|
||||
- User Info Endpont URL: https://authentik.company/application/o/userinfo/
|
||||
- Issuer: https://authentik.company/application/o/wikijs/
|
||||
- Logout URL: https://authentik.company/application/o/wikijs/end-session/
|
||||
- Allow self-registration: Enabled
|
||||
- Assign to group: The group to which new users logging in from authentik should be assigned.
|
||||
|
||||
![](./wiki-js_strategy.png)
|
||||
|
||||
:::note
|
||||
You do not have to enable "Allow self-registration" and select a group to which new users should be assigned, but if you don't you will have to manually provision users in Wiki.js and ensure that their usernames match the username they have in authentik.
|
||||
:::
|
||||
|
||||
### Step 5
|
||||
|
||||
In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings.
|
||||
|
||||
Set the Launch URL to the _Callback URL / Redirect URI_ without the `/callback` at the end, as shown below. This will skip Wiki.js' login prompt and log you in directly.
|
||||
|
||||
![](./authentik_application.png)
|
||||
|
After Width: | Height: | Size: 217 KiB |
|
@ -12,7 +12,6 @@ Scopes can be configured using Scope Mappings, a type of [Property Mappings](../
|
|||
| Token | `/application/o/token/` |
|
||||
| User Info | `/application/o/userinfo/` |
|
||||
| End Session | `/application/o/end-session/` |
|
||||
| Introspect | `/application/o/end-session/` |
|
||||
| JWKS | `/application/o/<application slug>/jwks/` |
|
||||
| OpenID Configuration | `/application/o/<application slug>/.well-known/openid-configuration` |
|
||||
|
||||
|
|
|
@ -49,7 +49,9 @@ module.exports = {
|
|||
"flow/stages/authenticator_static/index",
|
||||
"flow/stages/authenticator_totp/index",
|
||||
"flow/stages/authenticator_validate/index",
|
||||
"flow/stages/authenticator_webauthn/index",
|
||||
"flow/stages/captcha/index",
|
||||
"flow/stages/deny",
|
||||
"flow/stages/email/index",
|
||||
"flow/stages/identification/index",
|
||||
"flow/stages/invitation/index",
|
||||
|
@ -106,6 +108,7 @@ module.exports = {
|
|||
type: "category",
|
||||
label: "as Provider",
|
||||
items: [
|
||||
"integrations/services/apache-guacamole/index",
|
||||
"integrations/services/aws/index",
|
||||
"integrations/services/awx-tower/index",
|
||||
"integrations/services/gitlab/index",
|
||||
|
@ -120,6 +123,7 @@ module.exports = {
|
|||
"integrations/services/ubuntu-landscape/index",
|
||||
"integrations/services/veeam-enterprise-manager/index",
|
||||
"integrations/services/vmware-vcenter/index",
|
||||
"integrations/services/wiki-js/index",
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -141,6 +145,7 @@ module.exports = {
|
|||
"releases/0.14",
|
||||
"releases/2021.1",
|
||||
"releases/2021.2",
|
||||
"releases/2021.3",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|