core: add API endpoint to directly set user's password

closes #2040

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-01-03 13:30:39 +01:00
parent d18a691f63
commit 3e22740eac
11 changed files with 234 additions and 35 deletions

View File

@ -3,6 +3,7 @@ from datetime import timedelta
from json import loads
from typing import Optional
from django.contrib.auth import update_session_auth_hash
from django.db.models.query import QuerySet
from django.db.transaction import atomic
from django.db.utils import IntegrityError
@ -359,6 +360,35 @@ class UserViewSet(UsedByMixin, ModelViewSet):
).data
return Response(serializer.initial_data)
@permission_required("authentik_core.reset_user_password")
@extend_schema(
request=inline_serializer(
"UserPasswordSetSerializer",
{
"password": CharField(required=True),
},
),
responses={
204: "",
400: "",
},
)
@action(detail=True, methods=["POST"])
# pylint: disable=invalid-name, unused-argument
def set_password(self, request: Request, pk: int) -> Response:
"""Set password for user"""
user: User = self.get_object()
try:
user.set_password(request.data.get("password"))
user.save()
except (ValidationError, IntegrityError) as exc:
LOGGER.debug("Failed to set password", exc=exc)
return Response(status=400)
if user.pk == request.user.pk and SESSION_IMPERSONATE_USER not in self.request.session:
LOGGER.debug("Updating session hash after password change")
update_session_auth_hash(self.request, user)
return Response(status=204)
@extend_schema(request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)})
@action(
methods=["PUT"],

View File

@ -5,6 +5,7 @@ from rest_framework.test import APITestCase
from authentik.core.models import USER_ATTRIBUTE_CHANGE_EMAIL, USER_ATTRIBUTE_CHANGE_USERNAME, User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
from authentik.flows.models import FlowDesignation
from authentik.lib.generators import generate_key
from authentik.stages.email.models import EmailStage
from authentik.tenants.models import Tenant
@ -68,6 +69,18 @@ class TestUsersAPI(APITestCase):
)
self.assertEqual(response.status_code, 404)
def test_set_password(self):
"""Test Direct password set"""
self.client.force_login(self.admin)
new_pw = generate_key()
response = self.client.post(
reverse("authentik_api:user-set-password", kwargs={"pk": self.admin.pk}),
data={"password": new_pw},
)
self.assertEqual(response.status_code, 204)
self.admin.refresh_from_db()
self.assertTrue(self.admin.check_password(new_pw))
def test_recovery(self):
"""Test user recovery link (no recovery flow set)"""
flow = create_test_flow(FlowDesignation.RECOVERY)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-01 14:02+0000\n"
"POT-Creation-Date: 2022-01-03 12:29+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -39,99 +39,99 @@ msgstr ""
msgid "Create a SAML Provider by importing its Metadata."
msgstr ""
#: authentik/core/models.py:68
#: authentik/core/models.py:69
msgid "name"
msgstr ""
#: authentik/core/models.py:70
#: authentik/core/models.py:71
msgid "Users added to this group will be superusers."
msgstr ""
#: authentik/core/models.py:128
#: authentik/core/models.py:129
msgid "User's display name."
msgstr ""
#: authentik/core/models.py:195 authentik/providers/oauth2/models.py:299
#: authentik/core/models.py:212 authentik/providers/oauth2/models.py:299
msgid "User"
msgstr ""
#: authentik/core/models.py:196
#: authentik/core/models.py:213
msgid "Users"
msgstr ""
#: authentik/core/models.py:207
#: authentik/core/models.py:224
msgid "Flow used when authorizing this provider."
msgstr ""
#: authentik/core/models.py:240
#: authentik/core/models.py:257
msgid "Application's display Name."
msgstr ""
#: authentik/core/models.py:241
#: authentik/core/models.py:258
msgid "Internal application name, used in URLs."
msgstr ""
#: authentik/core/models.py:294
#: authentik/core/models.py:311
msgid "Application"
msgstr ""
#: authentik/core/models.py:295
#: authentik/core/models.py:312
msgid "Applications"
msgstr ""
#: authentik/core/models.py:301
#: authentik/core/models.py:318
msgid "Use the source-specific identifier"
msgstr ""
#: authentik/core/models.py:309
#: authentik/core/models.py:326
msgid ""
"Use the user's email address, but deny enrollment when the email address "
"already exists."
msgstr ""
#: authentik/core/models.py:318
#: authentik/core/models.py:335
msgid ""
"Use the user's username, but deny enrollment when the username already "
"exists."
msgstr ""
#: authentik/core/models.py:325
#: authentik/core/models.py:342
msgid "Source's display Name."
msgstr ""
#: authentik/core/models.py:326
#: authentik/core/models.py:343
msgid "Internal source name, used in URLs."
msgstr ""
#: authentik/core/models.py:337
#: authentik/core/models.py:354
msgid "Flow to use when authenticating existing users."
msgstr ""
#: authentik/core/models.py:346
#: authentik/core/models.py:363
msgid "Flow to use when enrolling new users."
msgstr ""
#: authentik/core/models.py:484
#: authentik/core/models.py:501
msgid "Token"
msgstr ""
#: authentik/core/models.py:485
#: authentik/core/models.py:502
msgid "Tokens"
msgstr ""
#: authentik/core/models.py:528
#: authentik/core/models.py:545
msgid "Property Mapping"
msgstr ""
#: authentik/core/models.py:529
#: authentik/core/models.py:546
msgid "Property Mappings"
msgstr ""
#: authentik/core/models.py:565
#: authentik/core/models.py:582
msgid "Authenticated Session"
msgstr ""
#: authentik/core/models.py:566
#: authentik/core/models.py:583
msgid "Authenticated Sessions"
msgstr ""

View File

@ -3269,6 +3269,34 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/core/users/{id}/set_password/:
post:
operationId: core_users_set_password_create
description: Set password for user
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this User.
required: true
tags:
- core
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserPasswordSetRequest'
required: true
security:
- authentik: []
responses:
'204':
description: No response body
'400':
description: No response body
'403':
$ref: '#/components/schemas/GenericError'
/core/users/{id}/used_by/:
get:
operationId: core_users_used_by_list
@ -31091,6 +31119,14 @@ components:
- identifier
- source
- user
UserPasswordSetRequest:
type: object
properties:
password:
type: string
minLength: 1
required:
- password
UserReputation:
type: object
description: UserReputation Serializer

View File

@ -3441,6 +3441,7 @@ msgstr "Passing"
#: src/flows/stages/identification/IdentificationStage.ts
#: src/flows/stages/password/PasswordStage.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserPasswordForm.ts
msgid "Password"
msgstr "Password"
@ -4259,6 +4260,10 @@ msgstr "Set a custom HTTP-Basic Authentication header based on values from authe
msgid "Set custom attributes using YAML or JSON."
msgstr "Set custom attributes using YAML or JSON."
#: src/pages/users/UserListPage.ts
msgid "Set password"
msgstr "Set password"
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
msgstr "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
@ -4765,6 +4770,10 @@ msgstr "Successfully updated mapping."
msgid "Successfully updated outpost."
msgstr "Successfully updated outpost."
#: src/pages/users/UserPasswordForm.ts
msgid "Successfully updated password."
msgstr "Successfully updated password."
#: src/pages/policies/dummy/DummyPolicyForm.ts
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts
#: src/pages/policies/expiry/ExpiryPolicyForm.ts
@ -5155,8 +5164,12 @@ msgid "To create a recovery link, the current tenant needs to have a recovery fl
msgstr "To create a recovery link, the current tenant needs to have a recovery flow configured."
#: src/pages/users/UserListPage.ts
msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
msgstr "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#~ msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#~ msgstr "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#: src/pages/users/UserListPage.ts
msgid "To let a user directly reset a their password, configure a recovery flow on the currently active tenant."
msgstr "To let a user directly reset a their password, configure a recovery flow on the currently active tenant."
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "To use SSL instead, use 'ldaps://' and disable this option."
@ -5520,6 +5533,11 @@ msgstr "Update available"
msgid "Update details"
msgstr "Update details"
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Update password"
msgstr "Update password"
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
#: src/pages/policies/BoundPoliciesList.ts

View File

@ -3414,6 +3414,7 @@ msgstr "Réussite"
#: src/flows/stages/identification/IdentificationStage.ts
#: src/flows/stages/password/PasswordStage.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserPasswordForm.ts
msgid "Password"
msgstr "Mot de passe"
@ -4223,6 +4224,10 @@ msgstr "Définir un en-tête d'authentification HTTP-Basic personnalisé basé s
msgid "Set custom attributes using YAML or JSON."
msgstr "Définissez des attributs personnalisés via YAML ou JSON."
#: src/pages/users/UserListPage.ts
msgid "Set password"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
msgstr ""
@ -4724,6 +4729,10 @@ msgstr "Mapping mis à jour avec succès"
msgid "Successfully updated outpost."
msgstr "Avant-poste mis à jour avec succès"
#: src/pages/users/UserPasswordForm.ts
msgid "Successfully updated password."
msgstr ""
#: src/pages/policies/dummy/DummyPolicyForm.ts
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts
#: src/pages/policies/expiry/ExpiryPolicyForm.ts
@ -5099,8 +5108,12 @@ msgid "To create a recovery link, the current tenant needs to have a recovery fl
msgstr "Pour créer un lien de récupération, le locataire actuel doit avoir un flux de récupération configuré."
#: src/pages/users/UserListPage.ts
msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
msgstr "Pour réinitialiser directement le mot de passe d'un utilisateur, configurez un flux de récupération sur le locataire actuel."
#~ msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#~ msgstr "Pour réinitialiser directement le mot de passe d'un utilisateur, configurez un flux de récupération sur le locataire actuel."
#: src/pages/users/UserListPage.ts
msgid "To let a user directly reset a their password, configure a recovery flow on the currently active tenant."
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "To use SSL instead, use 'ldaps://' and disable this option."
@ -5461,6 +5474,11 @@ msgstr "Mise à jour disponibl"
msgid "Update details"
msgstr "Détails de la mise à jour"
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Update password"
msgstr ""
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
#: src/pages/policies/BoundPoliciesList.ts

View File

@ -3431,6 +3431,7 @@ msgstr ""
#: src/flows/stages/identification/IdentificationStage.ts
#: src/flows/stages/password/PasswordStage.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserPasswordForm.ts
msgid "Password"
msgstr ""
@ -4249,6 +4250,10 @@ msgstr ""
msgid "Set custom attributes using YAML or JSON."
msgstr ""
#: src/pages/users/UserListPage.ts
msgid "Set password"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
msgstr ""
@ -4755,6 +4760,10 @@ msgstr ""
msgid "Successfully updated outpost."
msgstr ""
#: src/pages/users/UserPasswordForm.ts
msgid "Successfully updated password."
msgstr ""
#: src/pages/policies/dummy/DummyPolicyForm.ts
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts
#: src/pages/policies/expiry/ExpiryPolicyForm.ts
@ -5135,7 +5144,11 @@ msgid "To create a recovery link, the current tenant needs to have a recovery fl
msgstr ""
#: src/pages/users/UserListPage.ts
msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#~ msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#~ msgstr ""
#: src/pages/users/UserListPage.ts
msgid "To let a user directly reset a their password, configure a recovery flow on the currently active tenant."
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
@ -5500,6 +5513,11 @@ msgstr ""
msgid "Update details"
msgstr ""
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Update password"
msgstr ""
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
#: src/pages/policies/BoundPoliciesList.ts

View File

@ -3383,6 +3383,7 @@ msgstr "Geçiyor"
#: src/flows/stages/identification/IdentificationStage.ts
#: src/flows/stages/password/PasswordStage.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserPasswordForm.ts
msgid "Password"
msgstr "Parola"
@ -4182,6 +4183,10 @@ msgstr "authentik değerlerine göre özel bir HTTP-Basic Kimlik Doğrulama baş
msgid "Set custom attributes using YAML or JSON."
msgstr "YAML veya JSON kullanarak özel nitelikleri ayarlayın."
#: src/pages/users/UserListPage.ts
msgid "Set password"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
msgstr "Bunu kimlik doğrulamasının geçerli olmasını istediğiniz etki alanına ayarlayın. Yukarıdaki URL'nin bir üst etki alanı olmalıdır. Uygulamaları app1.domain.tld, app2.domain.tld olarak çalıştırıyorsanız, bunu 'domain.tld' olarak ayarlayın."
@ -4671,6 +4676,10 @@ msgstr "Eşleme başarıyla güncellendi."
msgid "Successfully updated outpost."
msgstr "İleri üssü başarıyla güncelledi."
#: src/pages/users/UserPasswordForm.ts
msgid "Successfully updated password."
msgstr ""
#: src/pages/policies/dummy/DummyPolicyForm.ts
#: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts
#: src/pages/policies/expiry/ExpiryPolicyForm.ts
@ -5050,8 +5059,12 @@ msgid "To create a recovery link, the current tenant needs to have a recovery fl
msgstr "Kurtarma bağlantısı oluşturmak için geçerli sakinin yapılandırılmış bir kurtarma akışı olması gerekir."
#: src/pages/users/UserListPage.ts
msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
msgstr "Bir kullanıcının parolasını doğrudan sıfırlamak için, etkin olan sakin üzerinde bir kurtarma akışı yapılandırın."
#~ msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
#~ msgstr "Bir kullanıcının parolasını doğrudan sıfırlamak için, etkin olan sakin üzerinde bir kurtarma akışı yapılandırın."
#: src/pages/users/UserListPage.ts
msgid "To let a user directly reset a their password, configure a recovery flow on the currently active tenant."
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "To use SSL instead, use 'ldaps://' and disable this option."
@ -5412,6 +5425,11 @@ msgstr "Güncelleme mevcut"
msgid "Update details"
msgstr "Ayrıntıları güncelle"
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Update password"
msgstr ""
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
#: src/pages/policies/BoundPoliciesList.ts

View File

@ -404,7 +404,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
<ak-form-element-horizontal
label="${this.mode === ProxyMode.ForwardDomain
? t`Unauthenticated URLs`
: t`Unauthenticated Paths`}${t``}"
: t`Unauthenticated Paths`}"
name="skipPathRegex"
>
<textarea class="pf-c-form-control">

View File

@ -26,6 +26,7 @@ import { first } from "../../utils";
import "./ServiceAccountForm";
import "./UserActiveForm";
import "./UserForm";
import "./UserPasswordForm";
import "./UserResetEmailForm";
@customElement("ak-user-list")
@ -136,11 +137,11 @@ export class UserListPage extends TablePage<User> {
<div>${item.username}</div>
<small>${item.name}</small>
</a>`,
html` <ak-label color=${item.isActive ? PFColor.Green : PFColor.Red}>
html`<ak-label color=${item.isActive ? PFColor.Green : PFColor.Red}>
${item.isActive ? t`Yes` : t`No`}
</ak-label>`,
html`${first(item.lastLogin?.toLocaleString(), t`-`)}`,
html` <ak-forms-modal>
html`<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update User`} </span>
<ak-user-form slot="form" .instancePk=${item.pk}> </ak-user-form>
@ -204,12 +205,23 @@ export class UserListPage extends TablePage<User> {
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-forms-modal>
<span slot="submit">${t`Update password`}</span>
<span slot="header">${t`Update password`}</span>
<ak-user-password-form
slot="form"
.instancePk=${item.pk}
></ak-user-password-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Set password`}
</button>
</ak-forms-modal>
${until(
tenant().then((tenant) => {
if (!tenant.flowRecovery) {
return html`
<p>
${t`To directly reset a user's password, configure a recovery flow on the currently active tenant.`}
${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`}
</p>
`;
}

View File

@ -0,0 +1,36 @@
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { CoreApi, UserPasswordSetRequest } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../api/Config";
import "../../elements/buttons/SpinnerButton";
import { Form } from "../../elements/forms/Form";
import "../../elements/forms/HorizontalFormElement";
@customElement("ak-user-password-form")
export class UserPasswordForm extends Form<UserPasswordSetRequest> {
@property({ type: Number })
instancePk?: number;
getSuccessMessage(): string {
return t`Successfully updated password.`;
}
send = (data: UserPasswordSetRequest): Promise<void> => {
return new CoreApi(DEFAULT_CONFIG).coreUsersSetPasswordCreate({
id: this.instancePk || 0,
userPasswordSetRequest: data,
});
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Password`} ?required=${true} name="password">
<input type="password" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>
</form>`;
}
}