flows: add default challenge response
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
fb4e0723ee
commit
c6bb6709fd
|
@ -83,7 +83,7 @@ class ChallengeResponse(PassiveSerializer):
|
|||
"""Base class for all challenge responses"""
|
||||
|
||||
stage: Optional["StageView"]
|
||||
component = CharField(default="")
|
||||
component = CharField(default="xak-flow-response-default")
|
||||
|
||||
def __init__(self, instance=None, data=None, **kwargs):
|
||||
self.stage = kwargs.pop("stage", None)
|
||||
|
|
|
@ -215,7 +215,7 @@ class FlowExecutorView(APIView):
|
|||
),
|
||||
},
|
||||
request=PolymorphicProxySerializer(
|
||||
component_name="ChallengeResponse",
|
||||
component_name="FlowChallengeResponse",
|
||||
serializers=challenge_response_types(),
|
||||
resource_type_field_name="component",
|
||||
),
|
||||
|
|
|
@ -37,12 +37,20 @@ class AutosubmitChallenge(Challenge):
|
|||
component = CharField(default="ak-stage-autosubmit")
|
||||
|
||||
|
||||
class AutoSubmitChallengeResponse(ChallengeResponse):
|
||||
"""Pseudo class for autosubmit response"""
|
||||
|
||||
component = CharField(default="ak-stage-autosubmit")
|
||||
|
||||
|
||||
# This View doesn't have a URL on purpose, as its called by the FlowExecutor
|
||||
class SAMLFlowFinalView(ChallengeStageView):
|
||||
"""View used by FlowExecutor after all stages have passed. Logs the authorization,
|
||||
and redirects to the SP (if REDIRECT is configured) or shows an auto-submit element
|
||||
(if POST is configured)."""
|
||||
|
||||
response_class = AutoSubmitChallengeResponse
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
application: Application = self.executor.plan.context[PLAN_CONTEXT_APPLICATION]
|
||||
provider: SAMLProvider = get_object_or_404(
|
||||
|
|
|
@ -8,7 +8,7 @@ from rest_framework.serializers import BaseSerializer
|
|||
|
||||
from authentik.core.models import Source, UserSourceConnection
|
||||
from authentik.core.types import UILoginButton
|
||||
from authentik.flows.challenge import Challenge, ChallengeTypes
|
||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
||||
from authentik.providers.oauth2.generators import generate_client_id
|
||||
|
||||
|
||||
|
@ -20,6 +20,12 @@ class PlexAuthenticationChallenge(Challenge):
|
|||
component = CharField(default="ak-flow-sources-plex")
|
||||
|
||||
|
||||
class PlexAuthenticationChallengeResponse(ChallengeResponse):
|
||||
"""Pseudo class for plex response"""
|
||||
|
||||
component = CharField(default="ak-flow-sources-plex")
|
||||
|
||||
|
||||
class PlexSource(Source):
|
||||
"""Authenticate against plex.tv"""
|
||||
|
||||
|
|
|
@ -28,9 +28,17 @@ class AuthenticatorDuoChallenge(WithUserInfoChallenge):
|
|||
component = CharField(default="ak-stage-authenticator-duo")
|
||||
|
||||
|
||||
class AuthenticatorDuoChallengeResponse(ChallengeResponse):
|
||||
"""Pseudo class for duo response"""
|
||||
|
||||
component = CharField(default="ak-stage-authenticator-duo")
|
||||
|
||||
|
||||
class AuthenticatorDuoStageView(ChallengeStageView):
|
||||
"""Duo stage"""
|
||||
|
||||
response_class = AuthenticatorDuoChallengeResponse
|
||||
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
user = self.get_pending_user()
|
||||
stage: AuthenticatorDuoStage = self.executor.current_stage
|
||||
|
|
|
@ -25,9 +25,17 @@ class AuthenticatorStaticChallenge(WithUserInfoChallenge):
|
|||
component = CharField(default="ak-stage-authenticator-static")
|
||||
|
||||
|
||||
class AuthenticatorStaticChallengeResponse(ChallengeResponse):
|
||||
"""Pseudo class for static response"""
|
||||
|
||||
component = CharField(default="ak-stage-authenticator-static")
|
||||
|
||||
|
||||
class AuthenticatorStaticStageView(ChallengeStageView):
|
||||
"""Static OTP Setup stage"""
|
||||
|
||||
response_class = AuthenticatorStaticChallengeResponse
|
||||
|
||||
def get_challenge(self, *args, **kwargs) -> AuthenticatorStaticChallenge:
|
||||
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
||||
return AuthenticatorStaticChallenge(
|
||||
|
|
92
schema.yml
92
schema.yml
|
@ -3550,13 +3550,13 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChallengeResponseRequest'
|
||||
$ref: '#/components/schemas/FlowChallengeResponseRequest'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChallengeResponseRequest'
|
||||
$ref: '#/components/schemas/FlowChallengeResponseRequest'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChallengeResponseRequest'
|
||||
$ref: '#/components/schemas/FlowChallengeResponseRequest'
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
|
@ -15194,6 +15194,13 @@ components:
|
|||
- pending_user_avatar
|
||||
- stage_uuid
|
||||
- type
|
||||
AuthenticatorDuoChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for duo response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-duo
|
||||
AuthenticatorDuoStage:
|
||||
type: object
|
||||
description: AuthenticatorDuoStage Serializer
|
||||
|
@ -15296,6 +15303,13 @@ components:
|
|||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AuthenticatorStaticChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for static response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-authenticator-static
|
||||
AuthenticatorStaticStage:
|
||||
type: object
|
||||
description: AuthenticatorStaticStage Serializer
|
||||
|
@ -15624,6 +15638,13 @@ components:
|
|||
additionalProperties: {}
|
||||
required:
|
||||
- response
|
||||
AutoSubmitChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for autosubmit response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
default: ak-stage-autosubmit
|
||||
AutosubmitChallenge:
|
||||
type: object
|
||||
description: Autosubmit challenge used to send and navigate a POST request
|
||||
|
@ -15889,31 +15910,6 @@ components:
|
|||
- shell
|
||||
- redirect
|
||||
type: string
|
||||
ChallengeResponseRequest:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/CaptchaChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/ConsentChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PromptResponseChallengeRequest'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
|
||||
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
|
||||
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
|
||||
ak-stage-captcha: '#/components/schemas/CaptchaChallengeResponseRequest'
|
||||
ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest'
|
||||
ak-stage-dummy: '#/components/schemas/DummyChallengeResponseRequest'
|
||||
ak-stage-email: '#/components/schemas/EmailChallengeResponseRequest'
|
||||
ak-stage-identification: '#/components/schemas/IdentificationChallengeResponseRequest'
|
||||
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
ak-stage-prompt: '#/components/schemas/PromptResponseChallengeRequest'
|
||||
ClientTypeEnum:
|
||||
enum:
|
||||
- confidential
|
||||
|
@ -16796,6 +16792,39 @@ components:
|
|||
- slug
|
||||
- stages
|
||||
- title
|
||||
FlowChallengeResponseRequest:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AutoSubmitChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/CaptchaChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/ConsentChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/DummyChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/EmailChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/IdentificationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PromptResponseChallengeRequest'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
|
||||
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
|
||||
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
|
||||
ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallengeResponseRequest'
|
||||
ak-stage-autosubmit: '#/components/schemas/AutoSubmitChallengeResponseRequest'
|
||||
ak-stage-captcha: '#/components/schemas/CaptchaChallengeResponseRequest'
|
||||
ak-stage-consent: '#/components/schemas/ConsentChallengeResponseRequest'
|
||||
ak-stage-dummy: '#/components/schemas/DummyChallengeResponseRequest'
|
||||
ak-stage-email: '#/components/schemas/EmailChallengeResponseRequest'
|
||||
ak-stage-identification: '#/components/schemas/IdentificationChallengeResponseRequest'
|
||||
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
|
||||
ak-stage-prompt: '#/components/schemas/PromptResponseChallengeRequest'
|
||||
FlowDesignationEnum:
|
||||
enum:
|
||||
- authentication
|
||||
|
@ -22612,6 +22641,13 @@ components:
|
|||
- client_id
|
||||
- slug
|
||||
- type
|
||||
PlexAuthenticationChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for plex response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
default: ak-flow-sources-plex
|
||||
PlexSource:
|
||||
type: object
|
||||
description: Plex Source Serializer
|
||||
|
|
|
@ -136,13 +136,13 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
|||
)
|
||||
|
||||
identification_stage.find_element(
|
||||
By.CSS_SELECTOR, "input[name=uid_field]"
|
||||
By.CSS_SELECTOR, "input[name=uidField]"
|
||||
).click()
|
||||
identification_stage.find_element(
|
||||
By.CSS_SELECTOR, "input[name=uid_field]"
|
||||
By.CSS_SELECTOR, "input[name=uidField]"
|
||||
).send_keys(USER().username)
|
||||
identification_stage.find_element(
|
||||
By.CSS_SELECTOR, "input[name=uid_field]"
|
||||
By.CSS_SELECTOR, "input[name=uidField]"
|
||||
).send_keys(Keys.ENTER)
|
||||
|
||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
|
|
|
@ -40,7 +40,7 @@ export class AutosubmitStage extends BaseStage {
|
|||
<div class="pf-c-login__main-body">
|
||||
<form class="pf-c-form" action="${this.challenge.url}" method="POST">
|
||||
${Object.entries(this.challenge.attrs).map(([ key, value ]) => {
|
||||
return html`<input type="hidden" .name="${key}" .value="${value}">`;
|
||||
return html`<input type="hidden" name="${key as string}" value="${value as string}">`;
|
||||
})}
|
||||
<ak-empty-state
|
||||
?loading="${true}">
|
||||
|
|
|
@ -10,19 +10,16 @@ export interface StageHost {
|
|||
export class BaseStage extends LitElement {
|
||||
|
||||
host?: StageHost;
|
||||
challenge?: Challenge;
|
||||
challenge!: Challenge;
|
||||
|
||||
submitForm(e: Event): void {
|
||||
e.preventDefault();
|
||||
const object: {
|
||||
component: string;
|
||||
[key: string]: unknown;
|
||||
} = {
|
||||
component: this.challenge.component,
|
||||
};
|
||||
} = {};
|
||||
const form = new FormData(this.shadowRoot?.querySelector("form") || undefined);
|
||||
form.forEach((value, key) => object[key] = value);
|
||||
this.host?.submit(object);
|
||||
this.host?.submit(object as unknown as ChallengeResponseRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ export class IdentificationStage extends BaseStage {
|
|||
username.setAttribute("autocomplete", "username");
|
||||
username.onkeyup = (ev: Event) => {
|
||||
const el = ev.target as HTMLInputElement;
|
||||
(this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uid_field]").forEach(input => {
|
||||
(this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uidField]").forEach(input => {
|
||||
input.value = el.value;
|
||||
// Because we assume only one input field exists that matches this
|
||||
// call focus so the user can press enter
|
||||
|
@ -80,7 +80,7 @@ export class IdentificationStage extends BaseStage {
|
|||
PasswordManagerPrefill.password = el.value;
|
||||
// Because password managers fill username, then password,
|
||||
// we need to re-focus the uid_field here too
|
||||
(this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uid_field]").forEach(input => {
|
||||
(this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uidField]").forEach(input => {
|
||||
// Because we assume only one input field exists that matches this
|
||||
// call focus so the user can press enter
|
||||
input.focus();
|
||||
|
@ -102,7 +102,7 @@ export class IdentificationStage extends BaseStage {
|
|||
PasswordManagerPrefill.totp = el.value;
|
||||
// Because totp managers fill username, then password, then optionally,
|
||||
// we need to re-focus the uid_field here too
|
||||
(this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uid_field]").forEach(input => {
|
||||
(this.shadowRoot || this).querySelectorAll<HTMLInputElement>("input[name=uidField]").forEach(input => {
|
||||
// Because we assume only one input field exists that matches this
|
||||
// call focus so the user can press enter
|
||||
input.focus();
|
||||
|
|
|
@ -14,7 +14,7 @@ import "../../../elements/forms/FormElement";
|
|||
import "../../../elements/EmptyState";
|
||||
import "../../../elements/Divider";
|
||||
import { Error } from "../../../api/Flows";
|
||||
import { Prompt, PromptChallenge } from "authentik-api";
|
||||
import { Prompt, PromptChallenge, StagePrompt } from "authentik-api";
|
||||
|
||||
|
||||
@customElement("ak-stage-prompt")
|
||||
|
@ -27,7 +27,7 @@ export class PromptStage extends BaseStage {
|
|||
return [PFBase, PFLogin, PFAlert, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal];
|
||||
}
|
||||
|
||||
renderPromptInner(prompt: Prompt): string {
|
||||
renderPromptInner(prompt: StagePrompt): string {
|
||||
switch (prompt.type) {
|
||||
case "text":
|
||||
return `<input
|
||||
|
|
Reference in a new issue