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:
		| @ -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.`} | ||||
|  | ||||
| @ -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))}`, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										113
									
								
								web/src/admin/stages/authenticator_duo/DuoDeviceImportForm.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								web/src/admin/stages/authenticator_duo/DuoDeviceImportForm.ts
									
									
									
									
									
										Normal 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 /> | ||||
|         `; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L