stages/authenticator_duo: improved import (#3601)

* prepare for duo admin integration

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

* make duo import params required

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

* add UI to import devices

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

* rework form, automatic import

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

* limit amount of concurrent tasks on worker

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

* load tasks

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

* fix API codes

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

* fix tests and such

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

* add tests

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

* sigh

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

* make stage better

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

* basic stage test

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

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L
2022-09-17 12:10:47 +02:00
committed by GitHub
parent 02e2c117ac
commit be64296494
28 changed files with 1219 additions and 106 deletions

View File

@ -141,8 +141,10 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Context`} name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}
>>
<ak-codemirror
mode="yaml"
value=${YAML.stringify(first(this.request?.context, {}))}
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Set custom attributes using YAML or JSON.`}

View File

@ -33,6 +33,7 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { Stage, StagesApi } from "@goauthentik/api";
@ -99,6 +100,22 @@ export class StageListPage extends TablePage<Stage> {
</ak-forms-delete-bulk>`;
}
async renderStageActions(stage: Stage): Promise<TemplateResult> {
if (stage.component === "ak-stage-authenticator-duo-form") {
await import("@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm");
return html`<ak-forms-modal>
<span slot="submit">${t`Import`}</span>
<span slot="header">${t`Import Duo device`}</span>
<ak-stage-authenticator-duo-device-import-form slot="form" .instancePk=${stage.pk}>
</ak-stage-authenticator-duo-device-import-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-file-import"></i>
</button>
</ak-forms-modal>`;
}
return html``;
}
row(item: Stage): TemplateResult[] {
return [
html`<div>
@ -114,21 +131,22 @@ export class StageListPage extends TablePage<Stage> {
</li>`;
})}
</ul>`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-edit"></i>
</button>
</ak-forms-modal>`,
html`<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-edit"></i>
</button>
</ak-forms-modal>
${until(this.renderStageActions(item))}`,
];
}

View File

@ -61,8 +61,20 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage,
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`API Hostname`}
?required=${true}
name="apiHostname"
>
<input
type="text"
value="${first(this.instance?.apiHostname, "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Stage-specific settings`} </span>
<span slot="header"> ${t`Duo Auth API`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Integration key`}
@ -84,18 +96,37 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage,
>
<input type="text" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${t`Duo Admin API (optional)`}</span>
<span slot="description">
${t`When using a Duo MFA, Access or Beyond plan, an Admin API application can be created.
This will allow authentik to import devices automatically.`}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`API Hostname`}
?required=${true}
name="apiHostname"
label=${t`Integration key`}
name="adminIntegrationKey"
>
<input
type="text"
value="${first(this.instance?.apiHostname, "")}"
value="${first(this.instance?.adminIntegrationKey, "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Secret key`}
?writeOnly=${this.instance !== undefined}
name="adminSecretKey"
>
<input type="text" value="" class="pf-c-form-control" />
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control">
<option

View File

@ -0,0 +1,113 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { MessageLevel } from "@goauthentik/common/messages";
import "@goauthentik/elements/Divider";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModalForm } from "@goauthentik/elements/forms/ModalForm";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { UserOption } from "@goauthentik/elements/user/utils";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import {
AuthenticatorDuoStage,
CoreApi,
StagesApi,
StagesAuthenticatorDuoImportDeviceManualCreateRequest,
} from "@goauthentik/api";
@customElement("ak-stage-authenticator-duo-device-import-form")
export class DuoDeviceImportForm extends ModelForm<AuthenticatorDuoStage, string> {
loadInstance(pk: string): Promise<AuthenticatorDuoStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoRetrieve({
stageUuid: pk,
});
}
getSuccessMessage(): string {
return t`Successfully imported device.`;
}
send = (data: AuthenticatorDuoStage): Promise<void> => {
const importData = data as unknown as StagesAuthenticatorDuoImportDeviceManualCreateRequest;
importData.stageUuid = this.instancePk;
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoImportDeviceManualCreate(
importData,
);
};
renderForm(): TemplateResult {
return html`${this.instance?.adminIntegrationKey !== ""
? this.renderFormAutomatic()
: html``}
${this.renderFormManual()}`;
}
renderFormManual(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="username">
<select class="pf-c-form-control">
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option value=${user.username}>
${UserOption(user)}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`The user in authentik this device will be assigned to.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Duo User ID`} ?required=${true} name="duoUserId">
<input type="text" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">
${t`The user ID in Duo.`}
${t`Can be either the username (found in the Users list) or the ID (can be found in the URL after clicking on a user).`}
</p>
</ak-form-element-horizontal>
</form>`;
}
renderFormAutomatic(): TemplateResult {
return html`
<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Automatic import`}>
<ak-action-button
class="pf-m-primary"
.apiRequest=${() => {
return new StagesApi(DEFAULT_CONFIG)
.stagesAuthenticatorDuoImportDevicesAutomaticCreate({
stageUuid: this.instance?.pk || "",
})
.then((res) => {
showMessage({
level: MessageLevel.info,
message: t`Successfully imported ${res.count} devices.`,
});
const modal = this.parentElement as ModalForm;
modal.open = false;
});
}}
>
${t`Start automatic import`}
</ak-action-button>
</ak-form-element-horizontal>
</form>
<ak-divider>${t`Or manually import`}</ak-divider>
<br />
`;
}
}