stages/identification: fix challenges not being annotated correctly and API client not loading data correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
ec4c3f44cb
commit
53100a72fe
|
@ -2,7 +2,7 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from rest_framework.fields import CharField, DictField
|
from rest_framework.fields import CharField
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.flows.challenge import Challenge
|
from authentik.flows.challenge import Challenge
|
||||||
|
@ -22,14 +22,6 @@ class UILoginButton:
|
||||||
icon_url: Optional[str] = None
|
icon_url: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class UILoginButtonSerializer(PassiveSerializer):
|
|
||||||
"""Serializer for Login buttons of sources"""
|
|
||||||
|
|
||||||
name = CharField()
|
|
||||||
challenge = DictField()
|
|
||||||
icon_url = CharField(required=False, allow_null=True)
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettingSerializer(PassiveSerializer):
|
class UserSettingSerializer(PassiveSerializer):
|
||||||
"""Serializer for User settings for stages and sources"""
|
"""Serializer for User settings for stages and sources"""
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ class FlowExecutorView(APIView):
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
200: PolymorphicProxySerializer(
|
200: PolymorphicProxySerializer(
|
||||||
component_name="FlowChallengeRequest",
|
component_name="ChallengeTypes",
|
||||||
serializers=challenge_types(),
|
serializers=challenge_types(),
|
||||||
resource_type_field_name="component",
|
resource_type_field_name="component",
|
||||||
),
|
),
|
||||||
|
@ -214,7 +214,7 @@ class FlowExecutorView(APIView):
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
200: PolymorphicProxySerializer(
|
200: PolymorphicProxySerializer(
|
||||||
component_name="FlowChallengeRequest",
|
component_name="ChallengeTypes",
|
||||||
serializers=challenge_types(),
|
serializers=challenge_types(),
|
||||||
resource_type_field_name="component",
|
resource_type_field_name="component",
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,19 +8,25 @@ from django.db.models import Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework.fields import BooleanField, CharField, ListField
|
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
|
||||||
|
from rest_framework.fields import (
|
||||||
|
BooleanField,
|
||||||
|
CharField,
|
||||||
|
DictField,
|
||||||
|
ListField,
|
||||||
|
)
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.core.models import Application, Source, User
|
from authentik.core.models import Application, Source, User
|
||||||
from authentik.core.types import UILoginButtonSerializer
|
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from authentik.flows.stage import (
|
from authentik.flows.stage import (
|
||||||
PLAN_CONTEXT_PENDING_USER_IDENTIFIER,
|
PLAN_CONTEXT_PENDING_USER_IDENTIFIER,
|
||||||
ChallengeStageView,
|
ChallengeStageView,
|
||||||
)
|
)
|
||||||
from authentik.flows.views import SESSION_KEY_APPLICATION_PRE
|
from authentik.flows.views import SESSION_KEY_APPLICATION_PRE, challenge_types
|
||||||
from authentik.stages.identification.models import IdentificationStage
|
from authentik.stages.identification.models import IdentificationStage
|
||||||
from authentik.stages.identification.signals import identification_failed
|
from authentik.stages.identification.signals import identification_failed
|
||||||
from authentik.stages.password.stage import authenticate
|
from authentik.stages.password.stage import authenticate
|
||||||
|
@ -28,6 +34,26 @@ from authentik.stages.password.stage import authenticate
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_field(
|
||||||
|
PolymorphicProxySerializer(
|
||||||
|
component_name="ChallengeTypes",
|
||||||
|
serializers=challenge_types(),
|
||||||
|
resource_type_field_name="component",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
class ChallengeDictWrapper(DictField):
|
||||||
|
"""Wrapper around DictField that annotates itself as challenge proxy"""
|
||||||
|
|
||||||
|
|
||||||
|
class LoginSourceSerializer(PassiveSerializer):
|
||||||
|
"""Serializer for Login buttons of sources"""
|
||||||
|
|
||||||
|
name = CharField()
|
||||||
|
icon_url = CharField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
challenge = ChallengeDictWrapper()
|
||||||
|
|
||||||
|
|
||||||
class IdentificationChallenge(Challenge):
|
class IdentificationChallenge(Challenge):
|
||||||
"""Identification challenges with all UI elements"""
|
"""Identification challenges with all UI elements"""
|
||||||
|
|
||||||
|
@ -38,7 +64,7 @@ class IdentificationChallenge(Challenge):
|
||||||
enroll_url = CharField(required=False)
|
enroll_url = CharField(required=False)
|
||||||
recovery_url = CharField(required=False)
|
recovery_url = CharField(required=False)
|
||||||
primary_action = CharField()
|
primary_action = CharField()
|
||||||
sources = UILoginButtonSerializer(many=True, required=False)
|
sources = LoginSourceSerializer(many=True, required=False)
|
||||||
|
|
||||||
component = CharField(default="ak-stage-identification")
|
component = CharField(default="ak-stage-identification")
|
||||||
|
|
||||||
|
@ -154,6 +180,7 @@ class IdentificationStageView(ChallengeStageView):
|
||||||
button = asdict(ui_login_button)
|
button = asdict(ui_login_button)
|
||||||
button["challenge"] = ui_login_button.challenge.data
|
button["challenge"] = ui_login_button.challenge.data
|
||||||
ui_sources.append(button)
|
ui_sources.append(button)
|
||||||
|
print(ui_sources)
|
||||||
challenge.initial_data["sources"] = ui_sources
|
challenge.initial_data["sources"] = ui_sources
|
||||||
return challenge
|
return challenge
|
||||||
|
|
||||||
|
|
137
schema.yml
137
schema.yml
|
@ -4608,7 +4608,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/FlowChallengeRequest'
|
$ref: '#/components/schemas/ChallengeTypes'
|
||||||
description: ''
|
description: ''
|
||||||
'404':
|
'404':
|
||||||
description: No Token found
|
description: No Token found
|
||||||
|
@ -4654,7 +4654,7 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/FlowChallengeRequest'
|
$ref: '#/components/schemas/ChallengeTypes'
|
||||||
description: ''
|
description: ''
|
||||||
'400':
|
'400':
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
@ -18412,12 +18412,75 @@ components:
|
||||||
required:
|
required:
|
||||||
- certificate_data
|
- certificate_data
|
||||||
- name
|
- name
|
||||||
|
ChallT:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
Challenge that gets sent to the client based on which stage
|
||||||
|
is currently active
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/ChallengeChoices'
|
||||||
|
flow_info:
|
||||||
|
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||||
|
component:
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
response_errors:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ErrorDetail'
|
||||||
|
required:
|
||||||
|
- type
|
||||||
ChallengeChoices:
|
ChallengeChoices:
|
||||||
enum:
|
enum:
|
||||||
- native
|
- native
|
||||||
- shell
|
- shell
|
||||||
- redirect
|
- redirect
|
||||||
type: string
|
type: string
|
||||||
|
ChallengeTypes:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/AccessDeniedChallenge'
|
||||||
|
- $ref: '#/components/schemas/AuthenticatorDuoChallenge'
|
||||||
|
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
|
||||||
|
- $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
|
||||||
|
- $ref: '#/components/schemas/AuthenticatorValidationChallenge'
|
||||||
|
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge'
|
||||||
|
- $ref: '#/components/schemas/AutosubmitChallenge'
|
||||||
|
- $ref: '#/components/schemas/CaptchaChallenge'
|
||||||
|
- $ref: '#/components/schemas/ChallT'
|
||||||
|
- $ref: '#/components/schemas/ConsentChallenge'
|
||||||
|
- $ref: '#/components/schemas/DummyChallenge'
|
||||||
|
- $ref: '#/components/schemas/EmailChallenge'
|
||||||
|
- $ref: '#/components/schemas/IdentificationChallenge'
|
||||||
|
- $ref: '#/components/schemas/PasswordChallenge'
|
||||||
|
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
||||||
|
- $ref: '#/components/schemas/PromptChallenge'
|
||||||
|
- $ref: '#/components/schemas/RedirectChallenge'
|
||||||
|
- $ref: '#/components/schemas/ShellChallenge'
|
||||||
|
discriminator:
|
||||||
|
propertyName: component
|
||||||
|
mapping:
|
||||||
|
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
|
||||||
|
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
|
||||||
|
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
|
||||||
|
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge'
|
||||||
|
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge'
|
||||||
|
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge'
|
||||||
|
ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge'
|
||||||
|
ak-stage-captcha: '#/components/schemas/CaptchaChallenge'
|
||||||
|
? ''
|
||||||
|
: '#/components/schemas/ChallT'
|
||||||
|
ak-stage-consent: '#/components/schemas/ConsentChallenge'
|
||||||
|
ak-stage-dummy: '#/components/schemas/DummyChallenge'
|
||||||
|
ak-stage-email: '#/components/schemas/EmailChallenge'
|
||||||
|
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
|
||||||
|
ak-stage-password: '#/components/schemas/PasswordChallenge'
|
||||||
|
ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
||||||
|
ak-stage-prompt: '#/components/schemas/PromptChallenge'
|
||||||
|
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
||||||
|
xak-flow-shell: '#/components/schemas/ShellChallenge'
|
||||||
ClientTypeEnum:
|
ClientTypeEnum:
|
||||||
enum:
|
enum:
|
||||||
- confidential
|
- confidential
|
||||||
|
@ -19388,45 +19451,6 @@ components:
|
||||||
- slug
|
- slug
|
||||||
- stages
|
- stages
|
||||||
- title
|
- title
|
||||||
FlowChallengeRequest:
|
|
||||||
oneOf:
|
|
||||||
- $ref: '#/components/schemas/AccessDeniedChallenge'
|
|
||||||
- $ref: '#/components/schemas/AuthenticatorDuoChallenge'
|
|
||||||
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
|
|
||||||
- $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
|
|
||||||
- $ref: '#/components/schemas/AuthenticatorValidationChallenge'
|
|
||||||
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge'
|
|
||||||
- $ref: '#/components/schemas/AutosubmitChallenge'
|
|
||||||
- $ref: '#/components/schemas/CaptchaChallenge'
|
|
||||||
- $ref: '#/components/schemas/ConsentChallenge'
|
|
||||||
- $ref: '#/components/schemas/DummyChallenge'
|
|
||||||
- $ref: '#/components/schemas/EmailChallenge'
|
|
||||||
- $ref: '#/components/schemas/IdentificationChallenge'
|
|
||||||
- $ref: '#/components/schemas/PasswordChallenge'
|
|
||||||
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
|
||||||
- $ref: '#/components/schemas/PromptChallenge'
|
|
||||||
- $ref: '#/components/schemas/RedirectChallenge'
|
|
||||||
- $ref: '#/components/schemas/ShellChallenge'
|
|
||||||
discriminator:
|
|
||||||
propertyName: component
|
|
||||||
mapping:
|
|
||||||
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
|
|
||||||
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
|
|
||||||
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
|
|
||||||
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge'
|
|
||||||
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge'
|
|
||||||
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge'
|
|
||||||
ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge'
|
|
||||||
ak-stage-captcha: '#/components/schemas/CaptchaChallenge'
|
|
||||||
ak-stage-consent: '#/components/schemas/ConsentChallenge'
|
|
||||||
ak-stage-dummy: '#/components/schemas/DummyChallenge'
|
|
||||||
ak-stage-email: '#/components/schemas/EmailChallenge'
|
|
||||||
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
|
|
||||||
ak-stage-password: '#/components/schemas/PasswordChallenge'
|
|
||||||
ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
|
||||||
ak-stage-prompt: '#/components/schemas/PromptChallenge'
|
|
||||||
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
|
||||||
xak-flow-shell: '#/components/schemas/ShellChallenge'
|
|
||||||
FlowChallengeResponseRequest:
|
FlowChallengeResponseRequest:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||||
|
@ -19776,7 +19800,7 @@ components:
|
||||||
sources:
|
sources:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/UILoginButton'
|
$ref: '#/components/schemas/LoginSource'
|
||||||
required:
|
required:
|
||||||
- password_fields
|
- password_fields
|
||||||
- primary_action
|
- primary_action
|
||||||
|
@ -20472,6 +20496,20 @@ components:
|
||||||
required:
|
required:
|
||||||
- logins_failed_per_1h
|
- logins_failed_per_1h
|
||||||
- logins_per_1h
|
- logins_per_1h
|
||||||
|
LoginSource:
|
||||||
|
type: object
|
||||||
|
description: Serializer for Login buttons of sources
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
icon_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
challenge:
|
||||||
|
$ref: '#/components/schemas/ChallengeTypes'
|
||||||
|
required:
|
||||||
|
- challenge
|
||||||
|
- name
|
||||||
NameIdPolicyEnum:
|
NameIdPolicyEnum:
|
||||||
enum:
|
enum:
|
||||||
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
||||||
|
@ -27526,21 +27564,6 @@ components:
|
||||||
- description
|
- description
|
||||||
- model_name
|
- model_name
|
||||||
- name
|
- name
|
||||||
UILoginButton:
|
|
||||||
type: object
|
|
||||||
description: Serializer for Login buttons of sources
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
challenge:
|
|
||||||
type: object
|
|
||||||
additionalProperties: {}
|
|
||||||
icon_url:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
required:
|
|
||||||
- challenge
|
|
||||||
- name
|
|
||||||
UsedBy:
|
UsedBy:
|
||||||
type: object
|
type: object
|
||||||
description: A list of all objects referencing the queried object
|
description: A list of all objects referencing the queried object
|
||||||
|
|
|
@ -26,7 +26,7 @@ import "./stages/password/PasswordStage";
|
||||||
import "./stages/prompt/PromptStage";
|
import "./stages/prompt/PromptStage";
|
||||||
import "./sources/plex/PlexLoginInit";
|
import "./sources/plex/PlexLoginInit";
|
||||||
import { StageHost } from "./stages/base";
|
import { StageHost } from "./stages/base";
|
||||||
import { ChallengeChoices, CurrentTenant, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api";
|
import { ChallengeChoices, CurrentTenant, ChallengeTypes, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api";
|
||||||
import { DEFAULT_CONFIG, tenant } from "../api/Config";
|
import { DEFAULT_CONFIG, tenant } from "../api/Config";
|
||||||
import { ifDefined } from "lit-html/directives/if-defined";
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
import { until } from "lit-html/directives/until";
|
import { until } from "lit-html/directives/until";
|
||||||
|
@ -40,7 +40,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
flowSlug: string;
|
flowSlug: string;
|
||||||
|
|
||||||
@property({attribute: false})
|
@property({attribute: false})
|
||||||
challenge?: FlowChallengeRequest;
|
challenge?: ChallengeTypes;
|
||||||
|
|
||||||
@property({type: Boolean})
|
@property({type: Boolean})
|
||||||
loading = false;
|
loading = false;
|
||||||
|
@ -163,7 +163,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</footer>`
|
</footer>`
|
||||||
} as FlowChallengeRequest;
|
} as ChallengeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLoading(): TemplateResult {
|
renderLoading(): TemplateResult {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||||
import AKGlobal from "../../../authentik.css";
|
import AKGlobal from "../../../authentik.css";
|
||||||
import "../../../elements/forms/FormElement";
|
import "../../../elements/forms/FormElement";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import { FlowChallengeRequest, IdentificationChallenge, IdentificationChallengeResponseRequest, UILoginButton, UserFieldsEnum } from "authentik-api";
|
import { IdentificationChallenge, IdentificationChallengeResponseRequest, LoginSource, UserFieldsEnum } from "authentik-api";
|
||||||
|
|
||||||
export const PasswordManagerPrefill: {
|
export const PasswordManagerPrefill: {
|
||||||
password: string | undefined;
|
password: string | undefined;
|
||||||
|
@ -110,7 +110,7 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden
|
||||||
wrapperForm.appendChild(totp);
|
wrapperForm.appendChild(totp);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSource(source: UILoginButton): TemplateResult {
|
renderSource(source: LoginSource): TemplateResult {
|
||||||
let icon = html`<i class="fas fas fa-share-square" title="${source.name}"></i>`;
|
let icon = html`<i class="fas fas fa-share-square" title="${source.name}"></i>`;
|
||||||
if (source.iconUrl) {
|
if (source.iconUrl) {
|
||||||
icon = html`<img src="${source.iconUrl}" alt="${source.name}">`;
|
icon = html`<img src="${source.iconUrl}" alt="${source.name}">`;
|
||||||
|
@ -118,7 +118,7 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden
|
||||||
return html`<li class="pf-c-login__main-footer-links-item">
|
return html`<li class="pf-c-login__main-footer-links-item">
|
||||||
<button type="button" @click=${() => {
|
<button type="button" @click=${() => {
|
||||||
if (!this.host) return;
|
if (!this.host) return;
|
||||||
this.host.challenge = source.challenge as FlowChallengeRequest;
|
this.host.challenge = source.challenge;
|
||||||
}}>
|
}}>
|
||||||
${icon}
|
${icon}
|
||||||
</button>
|
</button>
|
||||||
|
|
Reference in a new issue