core: add ability to provide reason for impersonation (#11951)

* core: add ability to provide reason for impersonation

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* tenants api things

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add missing implem

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* A tooltip needs a DOM object to determine the coordinates where it should render.  A solitary string is not enough; a  is needed here.

* web: user impersonation reason

To determine where to render the Tooltip content, the object associated with the Tooltip must be a DOM object with an HTML tag.  A naked string is not enough; a `<span>` will do nicely here.

Also, fixed a build failure: PFSize was not defined in RelatedUserList.

* add and fix tests

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* avoid migration change

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* small fixes

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Ken Sternberg <ken@goauthentik.io>
This commit is contained in:
Marc 'risson' Schmitt
2024-11-12 14:42:53 +01:00
committed by GitHub
parent 6d5a61187e
commit 0cffe0c953
13 changed files with 195 additions and 59 deletions

View File

@ -193,6 +193,13 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
help=${msg("Globally enable/disable impersonation.")}
>
</ak-switch-input>
<ak-switch-input
name="impersonationRequireReason"
label=${msg("Require reason for impersonation")}
?checked="${this._settings?.impersonationRequireReason}"
help=${msg("Require administrators to provide a reason for impersonating a user.")}
>
</ak-switch-input>
<ak-text-input
name="defaultTokenDuration"
label=${msg("Default token duration")}

View File

@ -1,9 +1,11 @@
import "@goauthentik/admin/users/ServiceAccountForm";
import "@goauthentik/admin/users/UserActiveForm";
import "@goauthentik/admin/users/UserForm";
import "@goauthentik/admin/users/UserImpersonateForm";
import "@goauthentik/admin/users/UserPasswordForm";
import "@goauthentik/admin/users/UserResetEmailForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { PFSize } from "@goauthentik/common/enums.js";
import { MessageLevel } from "@goauthentik/common/messages";
import { me } from "@goauthentik/common/users";
import { getRelativeTime } from "@goauthentik/common/utils";
@ -213,20 +215,22 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl
</ak-forms-modal>
${canImpersonate
? html`
<ak-action-button
class="pf-m-tertiary"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: item.pk,
})
.then(() => {
window.location.href = "/";
});
}}
>
${msg("Impersonate")}
</ak-action-button>
<ak-forms-modal size=${PFSize.Medium} id="impersonate-request">
<span slot="submit">${msg("Impersonate")}</span>
<span slot="header">${msg("Impersonate")} ${item.username}</span>
<ak-user-impersonate-form
slot="form"
.instancePk=${item.pk}
></ak-user-impersonate-form>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
<pf-tooltip
position="top"
content=${msg("Temporarily assume the identity of this user")}
>
<span>${msg("Impersonate")}</span>
</pf-tooltip>
</button>
</ak-forms-modal>
`
: html``}`,
];

View File

@ -0,0 +1,40 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-text-input";
import { Form } from "@goauthentik/elements/forms/Form";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { CoreApi, ImpersonationRequest } from "@goauthentik/api";
@customElement("ak-user-impersonate-form")
export class UserImpersonateForm extends Form<ImpersonationRequest> {
@property({ type: Number })
instancePk?: number;
async send(data: ImpersonationRequest): Promise<void> {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: this.instancePk || 0,
impersonationRequest: data,
})
.then(() => {
window.location.href = "/";
});
}
renderForm(): TemplateResult {
return html`<ak-text-input
name="reason"
label=${msg("Reason")}
help=${msg("Reason for impersonating the user")}
></ak-text-input>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-user-impersonate-form": UserImpersonateForm;
}
}

View File

@ -2,6 +2,7 @@ import { AdminInterface } from "@goauthentik/admin/AdminInterface";
import "@goauthentik/admin/users/ServiceAccountForm";
import "@goauthentik/admin/users/UserActiveForm";
import "@goauthentik/admin/users/UserForm";
import "@goauthentik/admin/users/UserImpersonateForm";
import "@goauthentik/admin/users/UserPasswordForm";
import "@goauthentik/admin/users/UserResetEmailForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
@ -266,20 +267,22 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
</ak-forms-modal>
${canImpersonate
? html`
<ak-action-button
class="pf-m-tertiary"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: item.pk,
})
.then(() => {
window.location.href = "/";
});
}}
>
${msg("Impersonate")}
</ak-action-button>
<ak-forms-modal size=${PFSize.Medium} id="impersonate-request">
<span slot="submit">${msg("Impersonate")}</span>
<span slot="header">${msg("Impersonate")} ${item.username}</span>
<ak-user-impersonate-form
slot="form"
.instancePk=${item.pk}
></ak-user-impersonate-form>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
<pf-tooltip
position="top"
content=${msg("Temporarily assume the identity of this user")}
>
<span>${msg("Impersonate")}</span>
</pf-tooltip>
</button>
</ak-forms-modal>
`
: html``}`,
];

View File

@ -5,6 +5,7 @@ import "@goauthentik/admin/users/UserActiveForm";
import "@goauthentik/admin/users/UserApplicationTable";
import "@goauthentik/admin/users/UserChart";
import "@goauthentik/admin/users/UserForm";
import "@goauthentik/admin/users/UserImpersonateForm";
import {
renderRecoveryEmailRequest,
requestRecoveryLink,
@ -208,26 +209,22 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) {
</ak-user-active-form>
${canImpersonate
? html`
<ak-action-button
class="pf-m-secondary pf-m-block"
id="impersonate-user-button"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: user.pk,
})
.then(() => {
window.location.href = "/";
});
}}
>
<pf-tooltip
position="top"
content=${msg("Temporarily assume the identity of this user")}
>
${msg("Impersonate")}
</pf-tooltip>
</ak-action-button>
<ak-forms-modal size=${PFSize.Medium} id="impersonate-request">
<span slot="submit">${msg("Impersonate")}</span>
<span slot="header">${msg("Impersonate")} ${user.username}</span>
<ak-user-impersonate-form
slot="form"
.instancePk=${user.pk}
></ak-user-impersonate-form>
<button slot="trigger" class="pf-c-button pf-m-secondary pf-m-block">
<pf-tooltip
position="top"
content=${msg("Temporarily assume the identity of this user")}
>
<span>${msg("Impersonate")}</span>
</pf-tooltip>
</button>
</ak-forms-modal>
`
: nothing}
</div> `;