stages/identification: auto-redirect to source when no user fields are selected (#5583)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
7265a56f05
commit
61434c807d
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -4,7 +4,7 @@
|
|||
Please check the [Contributing guidelines](https://github.com/goauthentik/authentik/blob/main/CONTRIBUTING.md#how-can-i-contribute).
|
||||
-->
|
||||
|
||||
# Details
|
||||
## Details
|
||||
|
||||
- **Does this resolve an issue?**
|
||||
Resolves #
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Identification Stage API Views"""
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
|
@ -9,6 +11,16 @@ from authentik.stages.identification.models import IdentificationStage
|
|||
class IdentificationStageSerializer(StageSerializer):
|
||||
"""IdentificationStage Serializer"""
|
||||
|
||||
def validate(self, attrs: dict) -> dict:
|
||||
# Check that at least 1 source is selected when no user fields are selected.
|
||||
sources = attrs.get("sources", [])
|
||||
user_fields = attrs.get("user_fields", [])
|
||||
if len(user_fields) < 1 and len(sources) < 1:
|
||||
raise ValidationError(
|
||||
_("When no user fields are selected, at least one source must be selected")
|
||||
)
|
||||
return super().validate(attrs)
|
||||
|
||||
class Meta:
|
||||
model = IdentificationStage
|
||||
fields = StageSerializer.Meta.fields + [
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
"""identification tests"""
|
||||
from django.urls import reverse
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.flows.challenge import ChallengeTypes
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
||||
from authentik.flows.tests import FlowTestCase
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.stages.identification.api import IdentificationStageSerializer
|
||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||
from authentik.stages.password import BACKEND_INBUILT
|
||||
from authentik.stages.password.models import PasswordStage
|
||||
|
@ -222,3 +225,22 @@ class TestIdentificationStage(FlowTestCase):
|
|||
}
|
||||
],
|
||||
)
|
||||
|
||||
def test_api_validate(self):
|
||||
"""Test API validation"""
|
||||
self.assertTrue(
|
||||
IdentificationStageSerializer(
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"user_fields": [UserFields.E_MAIL, UserFields.USERNAME],
|
||||
}
|
||||
).is_valid(raise_exception=True)
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
IdentificationStageSerializer(
|
||||
data={
|
||||
"name": generate_id(),
|
||||
"user_fields": [],
|
||||
"sources": [],
|
||||
}
|
||||
).is_valid(raise_exception=True)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SeverityToLabel } from "@goauthentik/admin/events/RuleListPage";
|
||||
import { SeverityToLabel } from "@goauthentik/admin/events/utils";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "@goauthentik/admin/events/RuleForm";
|
||||
import { SeverityToLabel } from "@goauthentik/admin/events/utils";
|
||||
import "@goauthentik/admin/policies/BoundPoliciesList";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
|
@ -14,20 +15,7 @@ import { t } from "@lingui/macro";
|
|||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { EventsApi, NotificationRule, SeverityEnum } from "@goauthentik/api";
|
||||
|
||||
export function SeverityToLabel(severity: SeverityEnum | null | undefined): string {
|
||||
if (!severity) return t`Unknown severity`;
|
||||
switch (severity) {
|
||||
case SeverityEnum.Alert:
|
||||
return t`Alert`;
|
||||
case SeverityEnum.Notice:
|
||||
return t`Notice`;
|
||||
case SeverityEnum.Warning:
|
||||
return t`Warning`;
|
||||
}
|
||||
return t`Unknown severity`;
|
||||
}
|
||||
import { EventsApi, NotificationRule } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-event-rule-list")
|
||||
export class RuleListPage extends TablePage<NotificationRule> {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { t } from "@lingui/macro";
|
|||
|
||||
import { TemplateResult, html } from "lit";
|
||||
|
||||
import { EventActions } from "@goauthentik/api";
|
||||
import { EventActions, SeverityEnum } from "@goauthentik/api";
|
||||
|
||||
export function EventGeo(event: EventWithContext): TemplateResult {
|
||||
let geo: KeyUnknown | undefined = undefined;
|
||||
|
@ -78,3 +78,16 @@ export function ActionToLabel(action?: EventActions): string {
|
|||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
export function SeverityToLabel(severity: SeverityEnum | null | undefined): string {
|
||||
if (!severity) return t`Unknown severity`;
|
||||
switch (severity) {
|
||||
case SeverityEnum.Alert:
|
||||
return t`Alert`;
|
||||
case SeverityEnum.Notice:
|
||||
return t`Notice`;
|
||||
case SeverityEnum.Warning:
|
||||
return t`Warning`;
|
||||
}
|
||||
return t`Unknown severity`;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,23 @@ export class IdentificationStage extends BaseStage<
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
this.autoRedirect();
|
||||
this.createHelperForm();
|
||||
}
|
||||
|
||||
autoRedirect(): void {
|
||||
if (!this.challenge) return;
|
||||
// we only want to auto-redirect to a source if there's only one source
|
||||
if (this.challenge.sources?.length !== 1) return;
|
||||
// and we also only do an auto-redirect if no user fields are select
|
||||
// meaning that without the auto-redirect the user would only have the option
|
||||
// to manually click on the source button
|
||||
if ((this.challenge.userFields || []).length !== 0) return;
|
||||
const source = this.challenge.sources[0];
|
||||
this.host.challenge = source.challenge;
|
||||
}
|
||||
|
||||
createHelperForm(): void {
|
||||
this.form = document.createElement("form");
|
||||
document.documentElement.appendChild(this.form);
|
||||
// Only add the additional username input if we're in a shadow dom
|
||||
|
@ -206,7 +223,6 @@ export class IdentificationStage extends BaseStage<
|
|||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge.responseErrors || {})["uid_field"]}
|
||||
>
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
type=${type}
|
||||
name="uidField"
|
||||
|
|
|
@ -4,24 +4,24 @@ title: Identification stage
|
|||
|
||||
This stage provides a ready-to-go form for users to identify themselves.
|
||||
|
||||
## Options
|
||||
## User Fields
|
||||
|
||||
### User Fields
|
||||
Select which fields the user can use to identify themselves. Multiple fields can be selected. If no fields are selected, only sources will be shown.
|
||||
|
||||
Select which fields the user can use to identify themselves. Multiple fields can be specified and separated with a comma.
|
||||
Valid choices:
|
||||
- Username
|
||||
- Email
|
||||
- UPN
|
||||
|
||||
- email
|
||||
- username
|
||||
UPN will attempt to identify the user based on the `upn` attribute, which can be imported with an [LDAP Source](/integrations/sources/ldap/index)
|
||||
|
||||
### Template
|
||||
:::info
|
||||
Starting with authentik 2023.5, when no user fields are selected and only one source is selected, authentik will automatically redirect the user to that source.
|
||||
:::
|
||||
|
||||
This specifies which template is rendered. Currently there are two templates:
|
||||
## Password stage
|
||||
|
||||
The `Login` template shows configured Sources below the login form, as well as linking to the defined Enrollment and Recovery flows.
|
||||
To prompt users for their password on the same step as identifying themselves, a password stage can be selected here. If a password stage is selected in the Identification stage, the password stage should not be bound to the flow.
|
||||
|
||||
The `Recovery` template shows only the form.
|
||||
|
||||
### Enrollment/Recovery Flow
|
||||
## Enrollment/Recovery Flow
|
||||
|
||||
These fields specify if and which flows are linked on the form. The enrollment flow is linked as `Need an account? Sign up.`, and the recovery flow is linked as `Forgot username or password?`.
|
||||
|
|
Reference in a new issue