core: prevent self-impersonation (#6885)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
44ac944706
commit
3e81824388
|
@ -616,8 +616,10 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
|||
if not request.user.has_perm("impersonate"):
|
||||
LOGGER.debug("User attempted to impersonate without permissions", user=request.user)
|
||||
return Response(status=401)
|
||||
|
||||
user_to_be = self.get_object()
|
||||
if user_to_be.pk == self.request.user.pk:
|
||||
LOGGER.debug("User attempted to impersonate themselves", user=request.user)
|
||||
return Response(status=401)
|
||||
|
||||
request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER] = request.user
|
||||
request.session[SESSION_KEY_IMPERSONATE_USER] = user_to_be
|
||||
|
|
|
@ -6,6 +6,7 @@ from rest_framework.test import APITestCase
|
|||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
class TestImpersonation(APITestCase):
|
||||
|
@ -46,12 +47,42 @@ class TestImpersonation(APITestCase):
|
|||
"""test impersonation without permissions"""
|
||||
self.client.force_login(self.other_user)
|
||||
|
||||
self.client.get(reverse("authentik_api:user-impersonate", kwargs={"pk": self.user.pk}))
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:user-impersonate", kwargs={"pk": self.user.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
self.assertEqual(response_body["user"]["username"], self.other_user.username)
|
||||
|
||||
@CONFIG.patch("impersonation", False)
|
||||
def test_impersonate_disabled(self):
|
||||
"""test impersonation that is disabled"""
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:user-impersonate", kwargs={"pk": self.other_user.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
self.assertEqual(response_body["user"]["username"], self.user.username)
|
||||
|
||||
def test_impersonate_self(self):
|
||||
"""test impersonation that user can't impersonate themselves"""
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:user-impersonate", kwargs={"pk": self.user.pk})
|
||||
)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
self.assertEqual(response_body["user"]["username"], self.user.username)
|
||||
|
||||
def test_un_impersonate_empty(self):
|
||||
"""test un-impersonation without impersonating first"""
|
||||
self.client.force_login(self.other_user)
|
||||
|
|
|
@ -3,6 +3,7 @@ import "@goauthentik/admin/users/UserActiveForm";
|
|||
import "@goauthentik/admin/users/UserForm";
|
||||
import "@goauthentik/admin/users/UserPasswordForm";
|
||||
import "@goauthentik/admin/users/UserResetEmailForm";
|
||||
import { me } from "@goauthentik/app/common/users";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
|
@ -37,6 +38,7 @@ import {
|
|||
CoreUsersListTypeEnum,
|
||||
Group,
|
||||
ResponseError,
|
||||
SessionUser,
|
||||
User,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
|
@ -123,12 +125,15 @@ export class RelatedUserList extends Table<User> {
|
|||
@property({ type: Boolean })
|
||||
hideServiceAccounts = getURLParam<boolean>("hideServiceAccounts", true);
|
||||
|
||||
@state()
|
||||
me?: SessionUser;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFDescriptionList, PFAlert, PFBanner);
|
||||
}
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<User>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
||||
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
|
@ -138,6 +143,8 @@ export class RelatedUserList extends Table<User> {
|
|||
? [CoreUsersListTypeEnum.External, CoreUsersListTypeEnum.Internal]
|
||||
: undefined,
|
||||
});
|
||||
this.me = await me();
|
||||
return users;
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
|
@ -181,6 +188,9 @@ export class RelatedUserList extends Table<User> {
|
|||
}
|
||||
|
||||
row(item: User): TemplateResult[] {
|
||||
const canImpersonate =
|
||||
rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) &&
|
||||
item.pk !== this.me?.user.pk;
|
||||
return [
|
||||
html`<a href="#/identity/users/${item.pk}">
|
||||
<div>${item.username}</div>
|
||||
|
@ -200,7 +210,7 @@ export class RelatedUserList extends Table<User> {
|
|||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate)
|
||||
${canImpersonate
|
||||
? html`
|
||||
<ak-action-button
|
||||
class="pf-m-tertiary"
|
||||
|
|
|
@ -4,6 +4,7 @@ import "@goauthentik/admin/users/UserActiveForm";
|
|||
import "@goauthentik/admin/users/UserForm";
|
||||
import "@goauthentik/admin/users/UserPasswordForm";
|
||||
import "@goauthentik/admin/users/UserResetEmailForm";
|
||||
import { me } from "@goauthentik/app/common/users";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { DefaultUIConfig, uiConfig } from "@goauthentik/common/ui/config";
|
||||
|
@ -30,7 +31,14 @@ import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
|||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
import { CapabilitiesEnum, CoreApi, ResponseError, User, UserPath } from "@goauthentik/api";
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
CoreApi,
|
||||
ResponseError,
|
||||
SessionUser,
|
||||
User,
|
||||
UserPath,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-user-list")
|
||||
export class UserListPage extends TablePage<User> {
|
||||
|
@ -62,6 +70,9 @@ export class UserListPage extends TablePage<User> {
|
|||
@state()
|
||||
userPaths?: UserPath;
|
||||
|
||||
@state()
|
||||
me?: SessionUser;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFDescriptionList, PFCard, PFAlert);
|
||||
}
|
||||
|
@ -88,6 +99,7 @@ export class UserListPage extends TablePage<User> {
|
|||
this.userPaths = await new CoreApi(DEFAULT_CONFIG).coreUsersPathsRetrieve({
|
||||
search: this.search,
|
||||
});
|
||||
this.me = await me();
|
||||
return users;
|
||||
}
|
||||
|
||||
|
@ -179,6 +191,9 @@ export class UserListPage extends TablePage<User> {
|
|||
}
|
||||
|
||||
row(item: User): TemplateResult[] {
|
||||
const canImpersonate =
|
||||
rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) &&
|
||||
item.pk !== this.me?.user.pk;
|
||||
return [
|
||||
html`<a href="#/identity/users/${item.pk}">
|
||||
<div>${item.username}</div>
|
||||
|
@ -198,7 +213,7 @@ export class UserListPage extends TablePage<User> {
|
|||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate)
|
||||
${canImpersonate
|
||||
? html`
|
||||
<ak-action-button
|
||||
class="pf-m-tertiary"
|
||||
|
|
|
@ -3,6 +3,7 @@ import "@goauthentik/admin/users/UserActiveForm";
|
|||
import "@goauthentik/admin/users/UserChart";
|
||||
import "@goauthentik/admin/users/UserForm";
|
||||
import "@goauthentik/admin/users/UserPasswordForm";
|
||||
import { me } from "@goauthentik/app/common/users";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
|
@ -24,7 +25,7 @@ import "@goauthentik/elements/user/UserConsentList";
|
|||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
|
@ -37,7 +38,7 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
|
|||
import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
|
||||
import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
|
||||
|
||||
import { CapabilitiesEnum, CoreApi, User } from "@goauthentik/api";
|
||||
import { CapabilitiesEnum, CoreApi, SessionUser, User } from "@goauthentik/api";
|
||||
|
||||
import "./UserDevicesList";
|
||||
|
||||
|
@ -45,18 +46,24 @@ import "./UserDevicesList";
|
|||
export class UserViewPage extends AKElement {
|
||||
@property({ type: Number })
|
||||
set userId(id: number) {
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersRetrieve({
|
||||
id: id,
|
||||
})
|
||||
.then((user) => {
|
||||
this.user = user;
|
||||
});
|
||||
me().then((me) => {
|
||||
this.me = me;
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersRetrieve({
|
||||
id: id,
|
||||
})
|
||||
.then((user) => {
|
||||
this.user = user;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
user?: User;
|
||||
|
||||
@state()
|
||||
me?: SessionUser;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
|
@ -103,6 +110,9 @@ export class UserViewPage extends AKElement {
|
|||
if (!this.user) {
|
||||
return html``;
|
||||
}
|
||||
const canImpersonate =
|
||||
rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) &&
|
||||
this.user.pk !== this.me?.user.pk;
|
||||
return html`
|
||||
<div class="pf-c-card__title">${msg("User Info")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
|
@ -213,9 +223,7 @@ export class UserViewPage extends AKElement {
|
|||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-user-active-form>
|
||||
${rootInterface()?.config?.capabilities.includes(
|
||||
CapabilitiesEnum.CanImpersonate,
|
||||
)
|
||||
${canImpersonate
|
||||
? html`
|
||||
<ak-action-button
|
||||
class="pf-m-secondary pf-m-block"
|
||||
|
|
Reference in New Issue