core: prevent self-impersonation (#6885)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		@ -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,6 +46,8 @@ import "./UserDevicesList";
 | 
			
		||||
export class UserViewPage extends AKElement {
 | 
			
		||||
    @property({ type: Number })
 | 
			
		||||
    set userId(id: number) {
 | 
			
		||||
        me().then((me) => {
 | 
			
		||||
            this.me = me;
 | 
			
		||||
            new CoreApi(DEFAULT_CONFIG)
 | 
			
		||||
                .coreUsersRetrieve({
 | 
			
		||||
                    id: id,
 | 
			
		||||
@ -52,11 +55,15 @@ export class UserViewPage extends AKElement {
 | 
			
		||||
                .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
	
	Block a user