Merge branch 'master' into version-2021.3

# Conflicts:
#	web/src/constants.ts
This commit is contained in:
Jens Langhammer 2021-03-02 21:39:30 +01:00
commit 0018fbacd3
59 changed files with 310 additions and 62 deletions

View File

@ -1,4 +1,6 @@
<img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="250" alt="authentik logo"> <p align="center">
<img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo">
</p>
--- ---
@ -22,8 +24,10 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum
## Screenshots ## Screenshots
![](https://goauthentik.io/img/screen_apps.png) Light | Dark
![](https://goauthentik.io/img/screen_admin.png) --- | ---
![](https://goauthentik.io/img/screen_apps_light.png) | ![](https://goauthentik.io/img/screen_apps_dark.png)
![](https://goauthentik.io/img/screen_admin_light.png) | ![](https://goauthentik.io/img/screen_admin_dark.png)
## Development ## Development

View File

@ -4,9 +4,9 @@
| Version | Supported | | Version | Supported |
| ---------- | ------------------ | | ---------- | ------------------ |
| 0.13.x | :white_check_mark: |
| 0.14.x | :white_check_mark: |
| 2021.1.x | :white_check_mark: | | 2021.1.x | :white_check_mark: |
| 2021.2.x | :white_check_mark: |
| 2021.3.x | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View File

@ -27,7 +27,9 @@
</div> </div>
</section> </section>
<footer class="pf-c-modal-box__footer"> <footer class="pf-c-modal-box__footer">
<input class="pf-c-button pf-m-primary" type="submit" form="main-form" value="{% block action %}{% endblock %}" /> <ak-spinner-button form="main-form">
{% block action %}{% endblock %}
</ak-spinner-button>&nbsp;
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a> <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
</footer> </footer>
{% endblock %} {% endblock %}

View File

@ -6,6 +6,7 @@ from rest_framework.response import Response
class Pagination(pagination.PageNumberPagination): class Pagination(pagination.PageNumberPagination):
"""Pagination which includes total pages and current page""" """Pagination which includes total pages and current page"""
page_query_param = "page"
page_size_query_param = "page_size" page_size_query_param = "page_size"
def get_paginated_response(self, data): def get_paginated_response(self, data):

View File

@ -1,12 +0,0 @@
{% extends "base/skeleton.html" %}
{% load i18n %}
{% block body %}
<ak-message-container></ak-message-container>
<div class="pf-c-page">
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
{% block page_content %}
{% endblock %}
</div>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'base/page.html' %} {% extends 'base/skeleton.html' %}
{% load i18n %} {% load i18n %}
{% load authentik_utils %} {% load authentik_utils %}

View File

@ -7,7 +7,6 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
@ -35,7 +34,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
form_class = UserDetailForm form_class = UserDetailForm
success_message = _("Successfully updated user.") success_message = _("Successfully updated user.")
success_url = reverse_lazy("authentik_core:user-details") success_url = "/"
def get_object(self): def get_object(self):
return self.request.user return self.request.user
@ -62,7 +61,7 @@ class TokenCreateView(
permission_required = "authentik_core.add_token" permission_required = "authentik_core.add_token"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_core:user-tokens") success_url = "/"
success_message = _("Successfully created Token") success_message = _("Successfully created Token")
def form_valid(self, form: UserTokenForm) -> HttpResponse: def form_valid(self, form: UserTokenForm) -> HttpResponse:
@ -80,7 +79,7 @@ class TokenUpdateView(
form_class = UserTokenForm form_class = UserTokenForm
permission_required = "authentik_core.change_token" permission_required = "authentik_core.change_token"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_core:user-tokens") success_url = "/"
success_message = _("Successfully updated Token") success_message = _("Successfully updated Token")
def get_object(self) -> Token: def get_object(self) -> Token:
@ -100,7 +99,7 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
model = Token model = Token
permission_required = "authentik_core.delete_token" permission_required = "authentik_core.delete_token"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_core:user-tokens") success_url = "/"
success_message = _("Successfully deleted Token") success_message = _("Successfully deleted Token")
def get_object(self) -> Token: def get_object(self) -> Token:

View File

@ -2,8 +2,8 @@
from urllib.parse import urlparse from urllib.parse import urlparse
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect, reverse from django.shortcuts import redirect
from django.urls import NoReverseMatch from django.urls import NoReverseMatch, reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from structlog.stdlib import get_logger from structlog.stdlib import get_logger

View File

@ -0,0 +1,87 @@
# Generated by Django 3.1.7 on 2021-03-02 08:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0010_auto_20210222_1821"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.events", "authentik Events"),
("authentik.crypto", "authentik Crypto"),
("authentik.flows", "authentik Flows"),
("authentik.outposts", "authentik Outpost"),
("authentik.lib", "authentik lib"),
("authentik.policies", "authentik Policies"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
(
"authentik.policies.event_matcher",
"authentik Policies.Event Matcher",
),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
(
"authentik.policies.group_membership",
"authentik Policies.Group Membership",
),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.saml", "authentik Sources.SAML"),
(
"authentik.stages.authenticator_static",
"authentik Stages.Authenticator.Static",
),
(
"authentik.stages.authenticator_totp",
"authentik Stages.Authenticator.TOTP",
),
(
"authentik.stages.authenticator_validate",
"authentik Stages.Authenticator.Validate",
),
(
"authentik.stages.authenticator_webauthn",
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.deny", "authentik Stages.Deny"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
(
"authentik.stages.identification",
"authentik Stages.Identification",
),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.managed", "authentik Managed"),
("authentik.core", "authentik Core"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View File

@ -62,11 +62,15 @@ class PolicyAccessView(AccessMixin, View):
return self.handle_no_permission() return self.handle_no_permission()
try: try:
self.resolve_provider_application() self.resolve_provider_application()
except (Application.DoesNotExist, Provider.DoesNotExist): except (Application.DoesNotExist, Provider.DoesNotExist) as exc:
return self.handle_no_permission_authenticated() LOGGER.warning("failed to resolve application", exc=exc)
return self.handle_no_permission_authenticated(
PolicyResult(False, _("Failed to resolve application"))
)
# Check if user is unauthenticated, so we pass the application # Check if user is unauthenticated, so we pass the application
# for the identification stage # for the identification stage
if not request.user.is_authenticated: if not request.user.is_authenticated:
LOGGER.warning("user not authenticated")
return self.handle_no_permission() return self.handle_no_permission()
# Check permissions # Check permissions
result = self.user_has_access() result = self.user_has_access()

View File

@ -45,6 +45,7 @@ class OAuth2ProviderSetupURLs(Serializer):
token = ReadOnlyField() token = ReadOnlyField()
user_info = ReadOnlyField() user_info = ReadOnlyField()
provider_info = ReadOnlyField() provider_info = ReadOnlyField()
logout = ReadOnlyField()
def create(self, request: Request) -> Response: def create(self, request: Request) -> Response:
raise NotImplementedError raise NotImplementedError
@ -83,6 +84,7 @@ class OAuth2ProviderViewSet(ModelViewSet):
) )
), ),
"provider_info": None, "provider_info": None,
"logout": None,
} }
try: try:
data["provider_info"] = request.build_absolute_uri( data["provider_info"] = request.build_absolute_uri(
@ -91,6 +93,12 @@ class OAuth2ProviderViewSet(ModelViewSet):
kwargs={"application_slug": provider.application.slug}, kwargs={"application_slug": provider.application.slug},
) )
) )
data["logout"] = request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:end-session",
kwargs={"application_slug": provider.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
pass pass
return Response(data) return Response(data)

View File

@ -1,7 +1,10 @@
"""Sync LDAP Users into authentik""" """Sync LDAP Users into authentik"""
from datetime import datetime
import ldap3 import ldap3
import ldap3.core.exceptions import ldap3.core.exceptions
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from pytz import UTC
from authentik.core.models import User from authentik.core.models import User
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
@ -53,11 +56,21 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
) )
) )
else: else:
if created:
ak_user.set_unusable_password()
ak_user.save()
self._logger.debug( self._logger.debug(
"Synced User", user=ak_user.username, created=created "Synced User", user=ak_user.username, created=created
) )
user_count += 1 user_count += 1
# pylint: disable=no-value-for-parameter
pwd_last_set = UTC.localize(
attributes.get("pwdLastSet", datetime.now())
)
if created or pwd_last_set >= ak_user.password_change_date:
self._logger.debug(
"Reset user's password",
user=ak_user.username,
created=created,
pwd_last_set=pwd_last_set,
)
ak_user.set_unusable_password()
ak_user.save()
return user_count return user_count

View File

@ -1,7 +1,10 @@
"""Webauthn stage forms""" """Webauthn stage forms"""
from django import forms from django import forms
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage from authentik.stages.authenticator_webauthn.models import (
AuthenticateWebAuthnStage,
WebAuthnDevice,
)
class AuthenticateWebAuthnStageForm(forms.ModelForm): class AuthenticateWebAuthnStageForm(forms.ModelForm):
@ -15,3 +18,16 @@ class AuthenticateWebAuthnStageForm(forms.ModelForm):
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
} }
class DeviceEditForm(forms.ModelForm):
"""Form to edit webauthn device"""
class Meta:
model = WebAuthnDevice
fields = ["name"]
widgets = {
"name": forms.TextInput(),
}

View File

@ -79,3 +79,8 @@ class WebAuthnDevice(Device):
def __str__(self): def __str__(self):
return self.name or str(self.user) return self.name or str(self.user)
class Meta:
verbose_name = _("WebAuthn Device")
verbose_name_plural = _("WebAuthn Devices")

View File

@ -17,6 +17,20 @@
Created {{ created_on }} Created {{ created_on }}
{% endblocktrans %} {% endblocktrans %}
</div> </div>
<div class="pf-c-data-list__cell">
<ak-modal-button href="{% url 'authentik_stages_authenticator_webauthn:device-update' pk=device.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Update' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_stages_authenticator_webauthn:device-delete' pk=device.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div> </div>
</div> </div>
</li> </li>

View File

@ -1,10 +1,16 @@
"""WebAuthn urls""" """WebAuthn urls"""
from django.urls import path from django.urls import path
from authentik.stages.authenticator_webauthn.views import UserSettingsView from authentik.stages.authenticator_webauthn.views import (
DeviceDeleteView,
DeviceUpdateView,
UserSettingsView,
)
urlpatterns = [ urlpatterns = [
path( path(
"<uuid:stage_uuid>/settings/", UserSettingsView.as_view(), name="user-settings" "<uuid:stage_uuid>/settings/", UserSettingsView.as_view(), name="user-settings"
), ),
path("devices/<int:pk>/delete/", DeviceDeleteView.as_view(), name="device-delete"),
path("devices/<int:pk>/update/", DeviceUpdateView.as_view(), name="device-update"),
] ]

View File

@ -1,8 +1,13 @@
"""webauthn views""" """webauthn views"""
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.http.response import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView from django.utils.translation import gettext as _
from django.views.generic import TemplateView, UpdateView
from authentik.admin.views.utils import DeleteMessageView
from authentik.stages.authenticator_webauthn.forms import DeviceEditForm
from authentik.stages.authenticator_webauthn.models import ( from authentik.stages.authenticator_webauthn.models import (
AuthenticateWebAuthnStage, AuthenticateWebAuthnStage,
WebAuthnDevice, WebAuthnDevice,
@ -22,3 +27,34 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
) )
kwargs["stage"] = stage kwargs["stage"] = stage
return kwargs return kwargs
class DeviceUpdateView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
"""Update device"""
model = WebAuthnDevice
form_class = DeviceEditForm
template_name = "generic/update.html"
success_url = "/"
success_message = _("Successfully updated Device")
def get_object(self) -> WebAuthnDevice:
device: WebAuthnDevice = super().get_object()
if device.user != self.request.user:
raise Http404
return device
class DeviceDeleteView(LoginRequiredMixin, DeleteMessageView):
"""Delete device"""
model = WebAuthnDevice
template_name = "generic/delete.html"
success_url = "/"
success_message = _("Successfully deleted Device")
def get_object(self) -> WebAuthnDevice:
device: WebAuthnDevice = super().get_object()
if device.user != self.request.user:
raise Http404
return device

View File

@ -1,9 +1,6 @@
{% extends "base/page.html" %}
{% load i18n %} {% load i18n %}
{% load authentik_utils %} {% load authentik_utils %}
{% block body %}
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md"> <div class="pf-c-card__header pf-c-title pf-m-md">
{% trans 'Reset your password' %} {% trans 'Reset your password' %}
@ -14,4 +11,3 @@
</a> </a>
</div> </div>
</div> </div>
{% endblock %}

View File

@ -10684,6 +10684,10 @@ definitions:
title: Provider info title: Provider info
type: string type: string
readOnly: true readOnly: true
logout:
title: Logout
type: string
readOnly: true
ProxyProvider: ProxyProvider:
description: ProxyProvider Serializer description: ProxyProvider Serializer
required: required:

View File

@ -4,7 +4,9 @@ import { NotFoundError, RequestError } from "./Error";
export const VERSION = "v2beta"; export const VERSION = "v2beta";
export interface QueryArguments { export interface QueryArguments {
[key: string]: number | string | boolean | null; page?: number;
page_size?: number;
[key: string]: number | string | boolean | undefined | null;
} }
export interface BaseInheritanceModel { export interface BaseInheritanceModel {

View File

@ -8,6 +8,7 @@ export interface OAuth2SetupURLs {
token: string; token: string;
user_info: string; user_info: string;
provider_info?: string; provider_info?: string;
logout?: string;
} }

View File

@ -61,11 +61,6 @@ select[multiple] {
font-family: monospace; font-family: monospace;
} }
/* Fix pre elements within alerts */
.pf-c-alert pre {
white-space: pre-wrap;
}
.pf-c-content h1 { .pf-c-content h1 {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@ -85,6 +80,12 @@ select[multiple] {
z-index: auto !important; z-index: auto !important;
} }
/* ensure background on non-flow pages match */
.pf-c-background-image::before {
background-image: url("dist/assets/images/flow_background.jpg");
background-position: center;
}
/* Fix spacing between messages */ /* Fix spacing between messages */
ak-message { ak-message {
display: block; display: block;

View File

@ -4,3 +4,4 @@ export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress"; export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current"; export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2021.3.1-rc1"; export const VERSION = "2021.3.1-rc1";
export const PAGE_SIZE = 20;

View File

@ -59,9 +59,9 @@ export class SpinnerButton extends LitElement {
return; return;
} }
if (this.form) { if (this.form) {
// Because safari we can't just extend HTMLButtonElement, hence I have to implement // Since the form= attribute is only used within a modal button,
// these attributes by myself here, sigh... // we can assume the form is always two levels up
document.querySelector<HTMLFormElement>(`#${this.form}`)?.submit(); this.parentElement?.parentElement?.querySelector < HTMLFormElement>(`#${this.form}`)?.dispatchEvent(new Event("submit"));
} }
this.setLoading(); this.setLoading();
} }

View File

@ -40,9 +40,7 @@ export class NotificationDrawer extends LitElement {
} }
renderItem(item: Notification): TemplateResult { renderItem(item: Notification): TemplateResult {
const delta = Date.now() - (parseInt(item.created, 10) * 1000); const created = new Date(parseInt(item.created, 10) * 1000);
// TODO: more flexible display, minutes and seconds
const age = `${Math.round(delta / 1000 / 3600)} Hours ago`;
let level = ""; let level = "";
switch (item.severity) { switch (item.severity) {
case "notice": case "notice":
@ -76,7 +74,7 @@ export class NotificationDrawer extends LitElement {
</button> </button>
</div> </div>
<p class="pf-c-notification-drawer__list-item-description">${item.body}</p> <p class="pf-c-notification-drawer__list-item-description">${item.body}</p>
<small class="pf-c-notification-drawer__list-item-timestamp">${age}</small> <small class="pf-c-notification-drawer__list-item-timestamp">${created.toLocaleString()}</small>
</li>`; </li>`;
} }

View File

@ -11,6 +11,7 @@ import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
import { Policy } from "../../api/Policies"; import { Policy } from "../../api/Policies";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-bound-policies-list") @customElement("ak-bound-policies-list")
export class BoundPoliciesList extends Table<PolicyBinding> { export class BoundPoliciesList extends Table<PolicyBinding> {
@ -22,6 +23,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
target: this.target || "", target: this.target || "",
ordering: "order", ordering: "order",
page: page, page: page,
page_size: PAGE_SIZE,
}); });
} }

View File

@ -7,6 +7,7 @@ import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-application-list") @customElement("ak-application-list")
export class ApplicationListPage extends TablePage<Application> { export class ApplicationListPage extends TablePage<Application> {
@ -30,6 +31,7 @@ export class ApplicationListPage extends TablePage<Application> {
return Application.list({ return Application.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { CertificateKeyPair } from "../../api/CertificateKeyPair"; import { CertificateKeyPair } from "../../api/CertificateKeyPair";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-crypto-certificatekeypair-list") @customElement("ak-crypto-certificatekeypair-list")
export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> { export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
@ -32,6 +33,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
return CertificateKeyPair.list({ return CertificateKeyPair.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -2,6 +2,7 @@ import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element"; import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client"; import { AKResponse } from "../../api/Client";
import { Event } from "../../api/Events"; import { Event } from "../../api/Events";
import { PAGE_SIZE } from "../../constants";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage"; import { TablePage } from "../../elements/table/TablePage";
import { time } from "../../utils"; import { time } from "../../utils";
@ -31,6 +32,7 @@ export class EventListPage extends TablePage<Event> {
return Event.list({ return Event.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE * 3,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -8,6 +8,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Rule } from "../../api/EventRules"; import { Rule } from "../../api/EventRules";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-event-rule-list") @customElement("ak-event-rule-list")
export class RuleListPage extends TablePage<Rule> { export class RuleListPage extends TablePage<Rule> {
@ -33,6 +34,7 @@ export class RuleListPage extends TablePage<Rule> {
return Rule.list({ return Rule.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -8,6 +8,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Transport } from "../../api/EventTransports"; import { Transport } from "../../api/EventTransports";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-event-transport-list") @customElement("ak-event-transport-list")
export class TransportListPage extends TablePage<Transport> { export class TransportListPage extends TablePage<Transport> {
@ -31,6 +32,7 @@ export class TransportListPage extends TablePage<Transport> {
return Transport.list({ return Transport.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -11,6 +11,7 @@ import "../../elements/buttons/Dropdown";
import "../../elements/policies/BoundPoliciesList"; import "../../elements/policies/BoundPoliciesList";
import { FlowStageBinding, Stage } from "../../api/Flows"; import { FlowStageBinding, Stage } from "../../api/Flows";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-bound-stages-list") @customElement("ak-bound-stages-list")
export class BoundStagesList extends Table<FlowStageBinding> { export class BoundStagesList extends Table<FlowStageBinding> {
@ -24,6 +25,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
target: this.target || "", target: this.target || "",
ordering: "order", ordering: "order",
page: page, page: page,
page_size: PAGE_SIZE,
}); });
} }

View File

@ -7,6 +7,7 @@ import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-flow-list") @customElement("ak-flow-list")
export class FlowListPage extends TablePage<Flow> { export class FlowListPage extends TablePage<Flow> {
@ -30,6 +31,7 @@ export class FlowListPage extends TablePage<Flow> {
return Flow.list({ return Flow.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Group } from "../../api/Groups"; import { Group } from "../../api/Groups";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-group-list") @customElement("ak-group-list")
export class GroupListPage extends TablePage<Group> { export class GroupListPage extends TablePage<Group> {
@ -30,6 +31,7 @@ export class GroupListPage extends TablePage<Group> {
return Group.list({ return Group.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -10,6 +10,7 @@ import "./OutpostHealth";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/TokenCopyButton"; import "../../elements/buttons/TokenCopyButton";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-outpost-list") @customElement("ak-outpost-list")
export class OutpostListPage extends TablePage<Outpost> { export class OutpostListPage extends TablePage<Outpost> {
@ -29,6 +30,7 @@ export class OutpostListPage extends TablePage<Outpost> {
return Outpost.list({ return Outpost.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -11,6 +11,7 @@ import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-outpost-service-connection-list") @customElement("ak-outpost-service-connection-list")
export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceConnection> { export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceConnection> {
@ -31,6 +32,7 @@ export class OutpostServiceConnectionListPage extends TablePage<OutpostServiceCo
return OutpostServiceConnection.list({ return OutpostServiceConnection.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -9,6 +9,7 @@ import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Policy } from "../../api/Policies"; import { Policy } from "../../api/Policies";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-policy-list") @customElement("ak-policy-list")
export class PolicyListPage extends TablePage<Policy> { export class PolicyListPage extends TablePage<Policy> {
@ -32,6 +33,7 @@ export class PolicyListPage extends TablePage<Policy> {
return Policy.list({ return Policy.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -9,6 +9,7 @@ import "../../elements/buttons/Dropdown";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-property-mapping-list") @customElement("ak-property-mapping-list")
export class PropertyMappingListPage extends TablePage<PropertyMapping> { export class PropertyMappingListPage extends TablePage<PropertyMapping> {
@ -35,6 +36,7 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
return PropertyMapping.list({ return PropertyMapping.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
managed__isnull: this.hideManaged, managed__isnull: this.hideManaged,
}); });

View File

@ -148,10 +148,16 @@ export class OAuth2ProviderViewPage extends Page {
</div> </div>
<div class="pf-c-form__group"> <div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name"> <label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">${gettext("Userinfo Endpoint")}</span> <span class="pf-c-form__label-text">${gettext("Userinfo URL")}</span>
</label> </label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.user_info || "-"}" /> <input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.user_info || "-"}" />
</div> </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">${gettext("Logout URL")}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="${this.providerUrls?.logout || "-"}" />
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@ -9,6 +9,7 @@ import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-provider-list") @customElement("ak-provider-list")
export class ProviderListPage extends TablePage<Provider> { export class ProviderListPage extends TablePage<Provider> {
@ -32,6 +33,7 @@ export class ProviderListPage extends TablePage<Provider> {
return Provider.list({ return Provider.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -9,6 +9,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-source-list") @customElement("ak-source-list")
export class SourceListPage extends TablePage<Source> { export class SourceListPage extends TablePage<Source> {
@ -32,6 +33,7 @@ export class SourceListPage extends TablePage<Source> {
return Source.list({ return Source.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Invitation } from "../../api/Invitations"; import { Invitation } from "../../api/Invitations";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-stage-invitation-list") @customElement("ak-stage-invitation-list")
export class InvitationListPage extends TablePage<Invitation> { export class InvitationListPage extends TablePage<Invitation> {
@ -30,6 +31,7 @@ export class InvitationListPage extends TablePage<Invitation> {
return Invitation.list({ return Invitation.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -7,6 +7,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Prompt } from "../../api/Prompts"; import { Prompt } from "../../api/Prompts";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-stage-prompt-list") @customElement("ak-stage-prompt-list")
export class PromptListPage extends TablePage<Prompt> { export class PromptListPage extends TablePage<Prompt> {
@ -30,6 +31,7 @@ export class PromptListPage extends TablePage<Prompt> {
return Prompt.list({ return Prompt.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -9,6 +9,7 @@ import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { Stage } from "../../api/Flows"; import { Stage } from "../../api/Flows";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-stage-list") @customElement("ak-stage-list")
export class StageListPage extends TablePage<Stage> { export class StageListPage extends TablePage<Stage> {
@ -32,6 +33,7 @@ export class StageListPage extends TablePage<Stage> {
return Stage.list({ return Stage.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -8,6 +8,7 @@ import "../../elements/buttons/Dropdown";
import "../../elements/buttons/TokenCopyButton"; import "../../elements/buttons/TokenCopyButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { Token } from "../../api/Tokens"; import { Token } from "../../api/Tokens";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-token-list") @customElement("ak-token-list")
export class TokenListPage extends TablePage<Token> { export class TokenListPage extends TablePage<Token> {
@ -31,6 +32,7 @@ export class TokenListPage extends TablePage<Token> {
return Token.list({ return Token.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -7,6 +7,7 @@ import "../../elements/buttons/Dropdown";
import "../../elements/buttons/TokenCopyButton"; import "../../elements/buttons/TokenCopyButton";
import { Table, TableColumn } from "../../elements/table/Table"; import { Table, TableColumn } from "../../elements/table/Table";
import { Token } from "../../api/Tokens"; import { Token } from "../../api/Tokens";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-token-user-list") @customElement("ak-token-user-list")
export class UserTokenList extends Table<Token> { export class UserTokenList extends Table<Token> {
@ -21,6 +22,7 @@ export class UserTokenList extends Table<Token> {
return Token.list({ return Token.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }
@ -35,6 +37,18 @@ export class UserTokenList extends Table<Token> {
]; ];
} }
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href="-/user/tokens/create/">
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
${super.renderToolbar()}
`;
}
row(item: Token): TemplateResult[] { row(item: Token): TemplateResult[] {
return [ return [
html`${item.identifier}`, html`${item.identifier}`,

View File

@ -8,6 +8,7 @@ import "../../elements/buttons/Dropdown";
import "../../elements/buttons/ActionButton"; import "../../elements/buttons/ActionButton";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { User } from "../../api/Users"; import { User } from "../../api/Users";
import { PAGE_SIZE } from "../../constants";
@customElement("ak-user-list") @customElement("ak-user-list")
export class UserListPage extends TablePage<User> { export class UserListPage extends TablePage<User> {
@ -31,6 +32,7 @@ export class UserListPage extends TablePage<User> {
return User.list({ return User.list({
ordering: this.order, ordering: this.order,
page: page, page: page,
page_size: PAGE_SIZE,
search: this.search || "", search: this.search || "",
}); });
} }

View File

@ -13,5 +13,7 @@ See [Docker-compose](installation/docker-compose) or [Kubernetes](installation/k
## Screenshots ## Screenshots
![](/img/screen_apps.png) Light | Dark
![](/img/screen_admin.png) --- | ---
![](/img/screen_apps_light.png) | ![](/img/screen_apps_dark.png)
![](/img/screen_admin_light.png) | ![](/img/screen_admin_dark.png)

View File

@ -123,7 +123,7 @@ The integrations affected are:
### docker-compose ### docker-compose
Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.1/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`. Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.2/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`.
### Kubernetes ### Kubernetes

View File

@ -45,7 +45,7 @@ This release does not introduce any new requirements.
### docker-compose ### docker-compose
Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.1/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`. Download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/authentik/version-2021.3/docker-compose.yml). Afterwards, simply run `docker-compose up -d` and then the standard upgrade command of `docker-compose run --rm server migrate`.
### Kubernetes ### Kubernetes

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -100,7 +100,7 @@ function Home() {
<div className="row"> <div className="row">
<div className="col col--5"> <div className="col col--5">
<div> <div>
<img className={styles.featureImage} src={useBaseUrl('img/screen_apps.png')} alt="library screenshot"/> <img className={styles.featureImage} src={useBaseUrl('img/screen_apps_light.png')} alt="library screenshot"/>
</div> </div>
</div> </div>
<div className="col col--5 col--offset-2 padding-vert--xl"> <div className="col col--5 col--offset-2 padding-vert--xl">
@ -131,7 +131,7 @@ function Home() {
</div> </div>
<div className="col col--5"> <div className="col col--5">
<div> <div>
<img className={styles.featureImage} src={useBaseUrl('img/screen_admin.png')} alt="library screenshot" /> <img className={styles.featureImage} src={useBaseUrl('img/screen_admin_light.png')} alt="library screenshot" />
</div> </div>
</div> </div>
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 KiB