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:
Jens Langhammer 2021-06-14 22:24:34 +02:00
parent ec4c3f44cb
commit 53100a72fe
6 changed files with 120 additions and 78 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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