Skip to content

Commit cf100c9

Browse files
committed
core: add ability to provide reason for impersonation
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
1 parent 556eca2 commit cf100c9

9 files changed

Lines changed: 143 additions & 37 deletions

File tree

authentik/core/api/users.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,12 @@ def recovery_email(self, request: Request, pk: int) -> Response:
666666

667667
@permission_required("authentik_core.impersonate")
668668
@extend_schema(
669-
request=OpenApiTypes.NONE,
669+
request=inline_serializer(
670+
"ImpersonationSerializer",
671+
{
672+
"reason": CharField(required=True),
673+
},
674+
),
670675
responses={
671676
"204": OpenApiResponse(description="Successfully started impersonation"),
672677
"401": OpenApiResponse(description="Access denied"),
@@ -679,6 +684,7 @@ def impersonate(self, request: Request, pk: int) -> Response:
679684
LOGGER.debug("User attempted to impersonate", user=request.user)
680685
return Response(status=401)
681686
user_to_be = self.get_object()
687+
reason = request.data.get("reason", "")
682688
# Check both object-level perms and global perms
683689
if not request.user.has_perm(
684690
"authentik_core.impersonate", user_to_be
@@ -688,11 +694,14 @@ def impersonate(self, request: Request, pk: int) -> Response:
688694
if user_to_be.pk == self.request.user.pk:
689695
LOGGER.debug("User attempted to impersonate themselves", user=request.user)
690696
return Response(status=401)
697+
if not reason and request.tenant.impersonation_require_reason:
698+
LOGGER.debug("User attempted to impersonate without providing a reason", user=request.user)
699+
return Response(status=401)
691700

692701
request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER] = request.user
693702
request.session[SESSION_KEY_IMPERSONATE_USER] = user_to_be
694703

695-
Event.new(EventAction.IMPERSONATION_STARTED).from_http(request, user_to_be)
704+
Event.new(EventAction.IMPERSONATION_STARTED, reason=reason).from_http(request, user_to_be)
696705

697706
return Response(status=201)
698707

authentik/events/migrations/0001_squashed_0019_alter_notificationtransport_webhook_url.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.conf import settings
99
from django.db import migrations, models
1010
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
11+
from django.utils.timezone import now
1112

1213
import authentik.events.models
1314
import authentik.lib.models
@@ -292,7 +293,7 @@ class Migration(migrations.Migration):
292293
migrations.AddField(
293294
model_name="event",
294295
name="expires",
295-
field=models.DateTimeField(default=authentik.events.models.default_event_duration),
296+
field=models.DateTimeField(default=now),
296297
),
297298
migrations.AddField(
298299
model_name="event",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.0.9 on 2024-11-07 15:12
2+
3+
import authentik.events.models
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("authentik_events", "0007_event_authentik_e_action_9a9dd9_idx_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="event",
16+
name="expires",
17+
field=models.DateTimeField(default=authentik.events.models.default_event_duration),
18+
),
19+
]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 5.0.9 on 2024-11-07 15:08
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("authentik_tenants", "0003_alter_tenant_default_token_duration"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="tenant",
15+
name="impersonation_require_reason",
16+
field=models.BooleanField(
17+
default=True,
18+
help_text="Require administrators to provide a reason for impersonating a user.",
19+
),
20+
),
21+
]

authentik/tenants/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class Tenant(TenantMixin, SerializerModel):
8585
impersonation = models.BooleanField(
8686
help_text=_("Globally enable/disable impersonation."), default=True
8787
)
88+
impersonation_require_reason = models.BooleanField(
89+
help_text=_("Require administrators to provide a reason for impersonating a user."), default=True
90+
)
8891
default_token_duration = models.TextField(
8992
help_text=_("Default token duration"),
9093
default=DEFAULT_TOKEN_DURATION,

schema.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5295,6 +5295,12 @@ paths:
52955295
required: true
52965296
tags:
52975297
- core
5298+
requestBody:
5299+
content:
5300+
application/json:
5301+
schema:
5302+
$ref: '#/components/schemas/ImpersonationRequest'
5303+
required: true
52985304
security:
52995305
- authentik: []
53005306
responses:
@@ -42713,6 +42719,14 @@ components:
4271342719
incorrect user info is entered.
4271442720
required:
4271542721
- name
42722+
ImpersonationRequest:
42723+
type: object
42724+
properties:
42725+
reason:
42726+
type: string
42727+
minLength: 1
42728+
required:
42729+
- reason
4271642730
InstallID:
4271742731
type: object
4271842732
properties:
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
2+
import "@goauthentik/components/ak-text-input";
3+
import { Form } from "@goauthentik/elements/forms/Form";
4+
5+
import { msg } from "@lit/localize";
6+
import { TemplateResult, html } from "lit";
7+
import { customElement, property } from "lit/decorators.js";
8+
9+
import { CoreApi, ImpersonationRequest } from "@goauthentik/api";
10+
11+
@customElement("ak-user-impersonate-form")
12+
export class UserImpersonateForm extends Form<ImpersonationRequest> {
13+
@property({ type: Number })
14+
instancePk?: number;
15+
16+
async send(data: ImpersonationRequest): Promise<void> {
17+
return new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateCreate({
18+
id: this.instancePk || 0,
19+
impersonationRequest: data,
20+
});
21+
// .then(() => {
22+
// window.location.href = "/";
23+
// });
24+
}
25+
26+
renderForm(): TemplateResult {
27+
return html`<ak-text-input
28+
name="reason"
29+
label=${msg("Reason")}
30+
help=${msg("Reason for impersonating the user")}
31+
></ak-text-input>`;
32+
}
33+
}
34+
35+
declare global {
36+
interface HTMLElementTagNameMap {
37+
"ak-user-impersonate-form": UserImpersonateForm;
38+
}
39+
}

web/src/admin/users/UserListPage.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AdminInterface } from "@goauthentik/admin/AdminInterface";
22
import "@goauthentik/admin/users/ServiceAccountForm";
33
import "@goauthentik/admin/users/UserActiveForm";
44
import "@goauthentik/admin/users/UserForm";
5+
import "@goauthentik/admin/users/UserImpersonateForm";
56
import "@goauthentik/admin/users/UserPasswordForm";
67
import "@goauthentik/admin/users/UserResetEmailForm";
78
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
@@ -266,20 +267,22 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
266267
</ak-forms-modal>
267268
${canImpersonate
268269
? html`
269-
<ak-action-button
270-
class="pf-m-tertiary"
271-
.apiRequest=${() => {
272-
return new CoreApi(DEFAULT_CONFIG)
273-
.coreUsersImpersonateCreate({
274-
id: item.pk,
275-
})
276-
.then(() => {
277-
window.location.href = "/";
278-
});
279-
}}
280-
>
281-
${msg("Impersonate")}
282-
</ak-action-button>
270+
<ak-forms-modal size=${PFSize.Medium} id="impersonate-request">
271+
<span slot="submit">${msg("Impersonate")}</span>
272+
<span slot="header">${msg("Impersonate")} ${item.username}</span>
273+
<ak-user-impersonate-form
274+
slot="form"
275+
.instancePk=${item.pk}
276+
></ak-user-impersonate-form>
277+
<button slot="trigger" class="pf-c-button pf-m-tertiary">
278+
<pf-tooltip
279+
position="top"
280+
content=${msg("Temporarily assume the identity of this user")}
281+
>
282+
${msg("Impersonate")}
283+
</pf-tooltip>
284+
</button>
285+
</ak-forms-modal>
283286
`
284287
: html``}`,
285288
];

web/src/admin/users/UserViewPage.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "@goauthentik/admin/users/UserActiveForm";
55
import "@goauthentik/admin/users/UserApplicationTable";
66
import "@goauthentik/admin/users/UserChart";
77
import "@goauthentik/admin/users/UserForm";
8+
import "@goauthentik/admin/users/UserImpersonateForm";
89
import {
910
renderRecoveryEmailRequest,
1011
requestRecoveryLink,
@@ -208,26 +209,22 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) {
208209
</ak-user-active-form>
209210
${canImpersonate
210211
? html`
211-
<ak-action-button
212-
class="pf-m-secondary pf-m-block"
213-
id="impersonate-user-button"
214-
.apiRequest=${() => {
215-
return new CoreApi(DEFAULT_CONFIG)
216-
.coreUsersImpersonateCreate({
217-
id: user.pk,
218-
})
219-
.then(() => {
220-
window.location.href = "/";
221-
});
222-
}}
223-
>
224-
<pf-tooltip
225-
position="top"
226-
content=${msg("Temporarily assume the identity of this user")}
227-
>
228-
${msg("Impersonate")}
229-
</pf-tooltip>
230-
</ak-action-button>
212+
<ak-forms-modal size=${PFSize.Medium} id="impersonate-request">
213+
<span slot="submit">${msg("Impersonate")}</span>
214+
<span slot="header">${msg("Impersonate")} ${user.username}</span>
215+
<ak-user-impersonate-form
216+
slot="form"
217+
.instancePk=${user.pk}
218+
></ak-user-impersonate-form>
219+
<button slot="trigger" class="pf-c-button pf-m-secondary pf-m-block">
220+
<pf-tooltip
221+
position="top"
222+
content=${msg("Temporarily assume the identity of this user")}
223+
>
224+
${msg("Impersonate")}
225+
</pf-tooltip>
226+
</button>
227+
</ak-forms-modal>
231228
`
232229
: nothing}
233230
</div> `;

0 commit comments

Comments
 (0)