flows: inspector (#1469)

* flows: add initial inspector

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: change naming a bit

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flow: add inspector frame

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: don't use shadydom when inspecting

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: add current stage to api

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* stages/*: fix imports

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: deep-copy plan instead of just adding

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: ui

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: restrict inspector to admin

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/admin: add buttons to launch flow with inspector

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: don't automatically follow redirects when inspector is open

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: make current_plan optional, only require historry

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: handle error messages in inspector

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web/flows: improve UI when flow is done

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: add is_completed flag to inspector

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: fix monkeypatches for tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* flows: add inspector tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* ci: re-enable cache

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L
2021-09-28 09:36:48 +02:00
committed by GitHub
parent ea4b920264
commit f9ad102915
50 changed files with 1009 additions and 170 deletions

View File

@ -14,6 +14,7 @@ export const EVENT_API_DRAWER_TOGGLE = "ak-api-drawer-toggle";
export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle";
export const EVENT_API_DRAWER_REFRESH = "ak-api-drawer-refresh";
export const EVENT_WS_MESSAGE = "ak-ws-message";
export const EVENT_FLOW_ADVANCE = "ak-flow-advance";
export const WS_MSG_TYPE_MESSAGE = "message";
export const WS_MSG_TYPE_REFRESH = "refresh";

View File

@ -11,10 +11,10 @@ export class Expand extends LitElement {
expanded = false;
@property()
textOpen = "Show less";
textOpen = t`Show less`;
@property()
textClosed = "Show more";
textClosed = t`Show more`;
static get styles(): CSSResult[] {
return [PFExpandableSection];

View File

@ -8,6 +8,7 @@ import { until } from "lit/directives/until";
import AKGlobal from "../authentik.css";
import PFBackgroundImage from "@patternfly/patternfly/components/BackgroundImage/background-image.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
import PFList from "@patternfly/patternfly/components/List/list.css";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
@ -26,12 +27,14 @@ import {
import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { configureSentry } from "../api/Sentry";
import { WebsocketClient } from "../common/ws";
import { TITLE_DEFAULT } from "../constants";
import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "../constants";
import "../elements/LoadingOverlay";
import { DefaultTenant } from "../elements/sidebar/SidebarBrand";
import { first } from "../utils";
import "./FlowInspector";
import "./access_denied/FlowAccessDenied";
import "./sources/plex/PlexLoginInit";
import "./stages/RedirectStage";
import "./stages/authenticator_duo/AuthenticatorDuoStage";
import "./stages/authenticator_static/AuthenticatorStaticStage";
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
@ -59,7 +62,9 @@ export class FlowExecutor extends LitElement implements StageHost {
// Assign the location as soon as we get the challenge and *not* in the render function
// as the render function might be called multiple times, which will navigate multiple
// times and can invalidate oauth codes
if (value?.type === ChallengeChoices.Redirect) {
// Also only auto-redirect when the inspector is open, so that a user can inspect the
// redirect in the inspector
if (value?.type === ChallengeChoices.Redirect && !this.inspectorOpen) {
console.debug(
"authentik/flows: redirecting to url from server",
(value as RedirectChallenge).to,
@ -86,10 +91,14 @@ export class FlowExecutor extends LitElement implements StageHost {
@property({ attribute: false })
tenant?: CurrentTenant;
@property({ attribute: false })
inspectorOpen: boolean;
ws: WebsocketClient;
static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal].concat(css`
return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal]
.concat(css`
.ak-hidden {
display: none;
}
@ -100,6 +109,9 @@ export class FlowExecutor extends LitElement implements StageHost {
font-family: monospace;
overflow-x: scroll;
}
.pf-c-drawer__content {
background-color: transparent;
}
`);
}
@ -107,6 +119,7 @@ export class FlowExecutor extends LitElement implements StageHost {
super();
this.ws = new WebsocketClient();
this.flowSlug = window.location.pathname.split("/")[3];
this.inspectorOpen = window.location.search.includes("inspector");
}
setBackground(url: string): void {
@ -130,6 +143,14 @@ export class FlowExecutor extends LitElement implements StageHost {
flowChallengeResponseRequest: payload,
})
.then((data) => {
if (this.inspectorOpen) {
window.dispatchEvent(
new CustomEvent(EVENT_FLOW_ADVANCE, {
bubbles: true,
composed: true,
}),
);
}
this.challenge = data;
})
.catch((e: Error | Response) => {
@ -150,6 +171,14 @@ export class FlowExecutor extends LitElement implements StageHost {
query: window.location.search.substring(1),
})
.then((challenge) => {
if (this.inspectorOpen) {
window.dispatchEvent(
new CustomEvent(EVENT_FLOW_ADVANCE, {
bubbles: true,
composed: true,
}),
);
}
this.challenge = challenge;
// Only set background on first update, flow won't change throughout execution
if (this.challenge?.flowInfo?.background) {
@ -199,6 +228,13 @@ export class FlowExecutor extends LitElement implements StageHost {
}
switch (this.challenge.type) {
case ChallengeChoices.Redirect:
if (this.inspectorOpen) {
return html`<ak-stage-redirect
.host=${this as StageHost}
.challenge=${this.challenge}
>
</ak-stage-redirect>`;
}
return html`<ak-empty-state ?loading=${true} header=${t`Loading`}>
</ak-empty-state>`;
case ChallengeChoices.Shell:
@ -333,50 +369,74 @@ export class FlowExecutor extends LitElement implements StageHost {
</filter>
</svg>
</div>
<div class="pf-c-login">
<div class="ak-login-container">
<header class="pf-c-login__header">
<div class="pf-c-brand ak-brand">
<img
src="${first(
this.tenant?.brandingLogo,
DefaultTenant.brandingLogo,
)}"
alt="authentik icon"
/>
<div class="pf-c-page__drawer">
<div class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}">
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<div class="pf-c-login">
<div class="ak-login-container">
<header class="pf-c-login__header">
<div class="pf-c-brand ak-brand">
<img
src="${first(
this.tenant?.brandingLogo,
DefaultTenant.brandingLogo,
)}"
alt="authentik icon"
/>
</div>
</header>
<div class="pf-c-login__main">
${this.renderChallengeWrapper()}
</div>
<footer class="pf-c-login__footer">
<p></p>
<ul class="pf-c-list pf-m-inline">
${until(
this.tenant?.uiFooterLinks?.map((link) => {
return html`<li>
<a href="${link.href || ""}"
>${link.name}</a
>
</li>`;
}),
)}
${this.tenant?.brandingTitle != "authentik"
? html`
<li>
<a href="https://goauthentik.io"
>${t`Powered by authentik`}</a
>
</li>
`
: html``}
${this.challenge?.flowInfo?.background?.startsWith(
"/static",
)
? html`
<li>
<a
href="https://unsplash.com/@introspectivedsgn"
>${t`Background image`}</a
>
</li>
`
: html``}
</ul>
</footer>
</div>
</div>
</div>
</div>
</header>
<div class="pf-c-login__main">${this.renderChallengeWrapper()}</div>
<footer class="pf-c-login__footer">
<p></p>
<ul class="pf-c-list pf-m-inline">
${until(
this.tenant?.uiFooterLinks?.map((link) => {
return html`<li>
<a href="${link.href || ""}">${link.name}</a>
</li>`;
}),
)}
${this.tenant?.brandingTitle != "authentik"
? html`
<li>
<a href="https://goauthentik.io"
>${t`Powered by authentik`}</a
>
</li>
`
: html``}
${this.challenge?.flowInfo?.background?.startsWith("/static")
? html`
<li>
<a href="https://unsplash.com/@introspectivedsgn"
>${t`Background image`}</a
>
</li>
`
: html``}
</ul>
</footer>
<ak-flow-inspector
class="pf-c-drawer__panel pf-m-width-33 ${this.inspectorOpen
? ""
: "display-none"}"
?hidden=${!this.inspectorOpen}
></ak-flow-inspector>
</div>
</div>
</div>`;
}

View File

@ -0,0 +1,297 @@
import { t } from "@lingui/macro";
import { css, CSSResult, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import AKGlobal from "../authentik.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFNotificationDrawer from "@patternfly/patternfly/components/NotificationDrawer/notification-drawer.css";
import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";
import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { FlowInspection, FlowsApi, Stage } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../api/Config";
import { EVENT_FLOW_ADVANCE } from "../constants";
import "../elements/Expand";
@customElement("ak-flow-inspector")
export class FlowInspector extends LitElement {
flowSlug: string;
@property({ attribute: false })
state?: FlowInspection;
@property({ attribute: false })
error?: Response;
static get styles(): CSSResult[] {
return [
PFBase,
PFStack,
PFCard,
PFNotificationDrawer,
PFDescriptionList,
PFProgressStepper,
AKGlobal,
css`
code.break {
word-break: break-all;
}
`,
];
}
constructor() {
super();
this.flowSlug = window.location.pathname.split("/")[3];
window.addEventListener(EVENT_FLOW_ADVANCE, this.advanceHandler as EventListener);
}
disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener(EVENT_FLOW_ADVANCE, this.advanceHandler as EventListener);
}
advanceHandler = (): void => {
new FlowsApi(DEFAULT_CONFIG)
.flowsInspectorGet({
flowSlug: this.flowSlug,
})
.then((state) => {
this.state = state;
})
.catch((exc) => {
this.error = exc;
});
};
// getStage return a stage without flowSet, for brevity
getStage(stage?: Stage): unknown {
if (!stage) {
return stage;
}
delete stage.flowSet;
return stage;
}
renderAccessDenied(): TemplateResult {
return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer">
<div class="pf-c-notification-drawer__header">
<div class="text">
<h1 class="pf-c-notification-drawer__header-title">${t`Flow inspector`}</h1>
</div>
</div>
<div class="pf-c-notification-drawer__body">
<div class="pf-l-stack pf-m-gutter">
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__body">${this.error?.statusText}</div>
</div>
</div>
</div>
</div>
</div>
</div>`;
}
render(): TemplateResult {
if (this.error) {
return this.renderAccessDenied();
}
if (!this.state) {
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
}
return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer">
<div class="pf-c-notification-drawer__header">
<div class="text">
<h1 class="pf-c-notification-drawer__header-title">${t`Flow inspector`}</h1>
</div>
</div>
<div class="pf-c-notification-drawer__body">
<div class="pf-l-stack pf-m-gutter">
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${t`Next stage`}</div>
</div>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Stage name`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.state.currentPlan?.nextPlannedStage
?.stageObj?.name || "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Stage kind`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.state.currentPlan?.nextPlannedStage
?.stageObj?.verboseName || "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`Stage object`}</span
>
</dt>
<dd class="pf-c-description-list__description">
${this.state.isCompleted
? html` <div
class="pf-c-description-list__text"
>
${t`This flow is completed.`}
</div>`
: html`<ak-expand>
<pre class="pf-c-description-list__text">
${JSON.stringify(this.getStage(this.state.currentPlan?.nextPlannedStage?.stageObj), null, 4)}</pre
>
</ak-expand>`}
</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${t`Plan history`}</div>
</div>
<div class="pf-c-card__body">
<ol class="pf-c-progress-stepper pf-m-vertical">
${this.state.plans.map((plan) => {
return html`<li
class="pf-c-progress-stepper__step pf-m-success"
>
<div class="pf-c-progress-stepper__step-connector">
<span class="pf-c-progress-stepper__step-icon">
<i
class="fas fa-check-circle"
aria-hidden="true"
></i>
</span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div class="pf-c-progress-stepper__step-title">
${plan.currentStage.stageObj?.name}
</div>
<div
class="pf-c-progress-stepper__step-description"
>
${plan.currentStage.stageObj?.verboseName}
</div>
</div>
</li> `;
})}
${this.state.currentPlan?.currentStage &&
!this.state.isCompleted
? html` <li
class="pf-c-progress-stepper__step pf-m-current pf-m-info"
>
<div
class="pf-c-progress-stepper__step-connector"
>
<span
class="pf-c-progress-stepper__step-icon"
>
<i
class="pficon pf-icon-resources-full"
aria-hidden="true"
></i>
</span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div
class="pf-c-progress-stepper__step-title"
>
${this.state.currentPlan?.currentStage
?.stageObj?.name}
</div>
<div
class="pf-c-progress-stepper__step-description"
>
${this.state.currentPlan?.currentStage
?.stageObj?.verboseName}
</div>
</div>
</li>`
: html``}
${this.state.currentPlan?.nextPlannedStage &&
!this.state.isCompleted
? html`<li
class="pf-c-progress-stepper__step pf-m-pending"
>
<div
class="pf-c-progress-stepper__step-connector"
>
<span
class="pf-c-progress-stepper__step-icon"
></span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div
class="pf-c-progress-stepper__step-title"
>
${this.state.currentPlan.nextPlannedStage
.stageObj?.name}
</div>
<div
class="pf-c-progress-stepper__step-description"
>
${this.state.currentPlan?.nextPlannedStage
?.stageObj?.verboseName}
</div>
</div>
</li>`
: html``}
</ol>
</div>
</div>
</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${t`Current plan cntext`}</div>
</div>
<div class="pf-c-card__body">
<pre>
${JSON.stringify(this.state.currentPlan?.planContext, null, 4)}</pre
>
</div>
</div>
</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${t`Session ID`}</div>
</div>
<div class="pf-c-card__body">
<code class="break">${this.state.currentPlan?.sessionId}</code>
</div>
</div>
</div>
</div>
</div>
</div>
</div>`;
}
}

View File

@ -0,0 +1,56 @@
import { t } from "@lingui/macro";
import { CSSResult, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import AKGlobal from "../../authentik.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { FlowChallengeResponseRequest, RedirectChallenge } from "@goauthentik/api";
import { BaseStage } from "./base";
@customElement("ak-stage-redirect")
export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeResponseRequest> {
static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFForm, PFButton, PFFormControl, PFTitle, AKGlobal];
}
renderURL(): string {
if (!this.challenge.to.includes("://")) {
return window.location.origin + this.challenge.to;
}
return this.challenge.to;
}
render(): TemplateResult {
return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${t`Redirect`}</h1>
</header>
<div class="pf-c-login__main-body">
<form method="POST" class="pf-c-form">
<div class="pf-c-form__group">
<p>${t`You're about to be redirect to the following URL.`}</p>
<pre>${this.renderURL()}</pre>
</div>
<div class="pf-c-form__group pf-m-action">
<a
type="submit"
class="pf-c-button pf-m-primary pf-m-block"
href=${this.challenge.to}
>
${t`Follow redirect`}
</a>
</div>
</form>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer> `;
}
}

View File

@ -1151,6 +1151,10 @@ msgstr "Created {0}"
msgid "Creation Date"
msgstr "Creation Date"
#: src/flows/FlowInspector.ts
msgid "Current plan cntext"
msgstr "Current plan cntext"
#: src/pages/applications/ApplicationForm.ts
#: src/pages/flows/FlowForm.ts
msgid "Currently set to:"
@ -1626,6 +1630,10 @@ msgstr "Execute"
msgid "Execute flow"
msgstr "Execute flow"
#: src/pages/flows/FlowViewPage.ts
msgid "Execute with inspector"
msgstr "Execute with inspector"
#: src/pages/policies/expression/ExpressionPolicyForm.ts
msgid "Executes the python snippet to determine whether to allow or deny a request."
msgstr "Executes the python snippet to determine whether to allow or deny a request."
@ -1793,6 +1801,11 @@ msgstr "Flow"
msgid "Flow Overview"
msgstr "Flow Overview"
#: src/flows/FlowInspector.ts
#: src/flows/FlowInspector.ts
msgid "Flow inspector"
msgstr "Flow inspector"
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts
@ -1860,6 +1873,10 @@ msgstr "Flows"
msgid "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them."
msgstr "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them."
#: src/flows/stages/RedirectStage.ts
msgid "Follow redirect"
msgstr "Follow redirect"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Force the user to configure an authenticator"
msgstr "Force the user to configure an authenticator"
@ -2350,6 +2367,7 @@ msgstr "Load servers"
#: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts
#: src/flows/access_denied/FlowAccessDenied.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
@ -2714,6 +2732,10 @@ msgstr "New version available!"
msgid "Newly created users are added to this group, if a group is selected."
msgstr "Newly created users are added to this group, if a group is selected."
#: src/flows/FlowInspector.ts
msgid "Next stage"
msgstr "Next stage"
#: src/elements/oauth/UserRefreshList.ts
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/crypto/CertificateKeyPairListPage.ts
@ -3079,6 +3101,10 @@ msgstr "Persistent"
msgid "Placeholder"
msgstr "Placeholder"
#: src/flows/FlowInspector.ts
msgid "Plan history"
msgstr "Plan history"
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Please enter your TOTP Code"
@ -3381,6 +3407,7 @@ msgstr "Recovery keys"
msgid "Recovery link cannot be emailed, user has no email address saved."
msgstr "Recovery link cannot be emailed, user has no email address saved."
#: src/flows/stages/RedirectStage.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Redirect"
msgstr "Redirect"
@ -3748,6 +3775,10 @@ msgstr "Service Provider Binding"
#~ msgid "Session"
#~ msgstr "Session"
#: src/flows/FlowInspector.ts
msgid "Session ID"
msgstr "Session ID"
#: src/pages/stages/user_login/UserLoginStageForm.ts
msgid "Session duration"
msgstr "Session duration"
@ -3794,10 +3825,18 @@ msgstr "Severity"
msgid "Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable."
msgstr "Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable."
#: src/elements/Expand.ts
msgid "Show less"
msgstr "Show less"
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "Show matched user"
msgstr "Show matched user"
#: src/elements/Expand.ts
msgid "Show more"
msgstr "Show more"
#: src/pages/flows/FlowForm.ts
msgid "Shown as the Title in Flow pages."
msgstr "Shown as the Title in Flow pages."
@ -3897,6 +3936,18 @@ msgstr "Stage Configuration"
msgid "Stage binding(s)"
msgstr "Stage binding(s)"
#: src/flows/FlowInspector.ts
msgid "Stage kind"
msgstr "Stage kind"
#: src/flows/FlowInspector.ts
msgid "Stage name"
msgstr "Stage name"
#: src/flows/FlowInspector.ts
msgid "Stage object"
msgstr "Stage object"
#: src/pages/flows/BoundStagesList.ts
msgid "Stage type"
msgstr "Stage type"
@ -4505,6 +4556,10 @@ msgstr ""
msgid "These policies control which users can access this application."
msgstr "These policies control which users can access this application."
#: src/flows/FlowInspector.ts
msgid "This flow is completed."
msgstr "This flow is completed."
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well."
msgstr "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well."
@ -5276,6 +5331,10 @@ msgstr "Yes"
msgid "You can only select providers that match the type of the outpost."
msgstr "You can only select providers that match the type of the outpost."
#: src/flows/stages/RedirectStage.ts
msgid "You're about to be redirect to the following URL."
msgstr "You're about to be redirect to the following URL."
#: src/interfaces/AdminInterface.ts
msgid "You're currently impersonating {0}. Click to stop."
msgstr "You're currently impersonating {0}. Click to stop."

View File

@ -1145,6 +1145,10 @@ msgstr ""
msgid "Creation Date"
msgstr ""
#: src/flows/FlowInspector.ts
msgid "Current plan cntext"
msgstr ""
#: src/pages/applications/ApplicationForm.ts
#: src/pages/flows/FlowForm.ts
msgid "Currently set to:"
@ -1618,6 +1622,10 @@ msgstr ""
msgid "Execute flow"
msgstr ""
#: src/pages/flows/FlowViewPage.ts
msgid "Execute with inspector"
msgstr ""
#: src/pages/policies/expression/ExpressionPolicyForm.ts
msgid "Executes the python snippet to determine whether to allow or deny a request."
msgstr ""
@ -1785,6 +1793,11 @@ msgstr ""
msgid "Flow Overview"
msgstr ""
#: src/flows/FlowInspector.ts
#: src/flows/FlowInspector.ts
msgid "Flow inspector"
msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts
@ -1852,6 +1865,10 @@ msgstr ""
msgid "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them."
msgstr ""
#: src/flows/stages/RedirectStage.ts
msgid "Follow redirect"
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Force the user to configure an authenticator"
msgstr ""
@ -2342,6 +2359,7 @@ msgstr ""
#: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts
#: src/flows/access_denied/FlowAccessDenied.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
@ -2706,6 +2724,10 @@ msgstr ""
msgid "Newly created users are added to this group, if a group is selected."
msgstr ""
#: src/flows/FlowInspector.ts
msgid "Next stage"
msgstr ""
#: src/elements/oauth/UserRefreshList.ts
#: src/pages/applications/ApplicationCheckAccessForm.ts
#: src/pages/crypto/CertificateKeyPairListPage.ts
@ -3071,6 +3093,10 @@ msgstr ""
msgid "Placeholder"
msgstr ""
#: src/flows/FlowInspector.ts
msgid "Plan history"
msgstr ""
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Please enter your TOTP Code"
@ -3373,6 +3399,7 @@ msgstr ""
msgid "Recovery link cannot be emailed, user has no email address saved."
msgstr ""
#: src/flows/stages/RedirectStage.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Redirect"
msgstr ""
@ -3740,6 +3767,10 @@ msgstr ""
#~ msgid "Session"
#~ msgstr ""
#: src/flows/FlowInspector.ts
msgid "Session ID"
msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts
msgid "Session duration"
msgstr ""
@ -3786,10 +3817,18 @@ msgstr ""
msgid "Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable."
msgstr ""
#: src/elements/Expand.ts
msgid "Show less"
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "Show matched user"
msgstr ""
#: src/elements/Expand.ts
msgid "Show more"
msgstr ""
#: src/pages/flows/FlowForm.ts
msgid "Shown as the Title in Flow pages."
msgstr ""
@ -3889,6 +3928,18 @@ msgstr ""
msgid "Stage binding(s)"
msgstr ""
#: src/flows/FlowInspector.ts
msgid "Stage kind"
msgstr ""
#: src/flows/FlowInspector.ts
msgid "Stage name"
msgstr ""
#: src/flows/FlowInspector.ts
msgid "Stage object"
msgstr ""
#: src/pages/flows/BoundStagesList.ts
msgid "Stage type"
msgstr ""
@ -4490,6 +4541,10 @@ msgstr ""
msgid "These policies control which users can access this application."
msgstr ""
#: src/flows/FlowInspector.ts
msgid "This flow is completed."
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well."
msgstr ""
@ -5259,6 +5314,10 @@ msgstr ""
msgid "You can only select providers that match the type of the outpost."
msgstr ""
#: src/flows/stages/RedirectStage.ts
msgid "You're about to be redirect to the following URL."
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "You're currently impersonating {0}. Click to stop."
msgstr ""

View File

@ -104,7 +104,9 @@ export class FlowListPage extends TablePage<Flow> {
slug: item.slug,
})
.then((link) => {
window.open(`${link.link}?next=/%23${window.location.href}`);
window.open(
`${link.link}?inspector&next=/%23${window.location.href}`,
);
});
}}
>

View File

@ -107,6 +107,21 @@ export class FlowViewPage extends LitElement {
>
${t`Execute`}
</button>
<button
class="pf-c-button pf-m-secondary"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesExecuteRetrieve({
slug: this.flow.slug,
})
.then((link) => {
const finalURL = `${link.link}?inspector&next=/%23${window.location.hash}`;
window.open(finalURL, "_blank");
});
}}
>
${t`Execute with inspector`}
</button>
</div>
</dd>
<dt class="pf-c-description-list__term">