Merge pull request #4289 from FinnStutzenstein/resetPassword

Rework the reset password component (fixes #4079)
This commit is contained in:
Emanuel Schütze 2019-02-10 11:54:52 +01:00 committed by GitHub
commit b5294cc5fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 181 deletions

View File

@ -132,11 +132,15 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
*
* @param user The user to update
* @param password The password to set
* @param updateDefaultPassword Control, if the default password should be updated.
*/
public async resetPassword(user: ViewUser, password: string): Promise<void> {
public async resetPassword(
user: ViewUser,
password: string,
updateDefaultPassword: boolean = false
): Promise<void> {
const path = `/rest/users/user/${user.id}/reset_password/`;
await this.httpService.post(path, { password: password });
await this.update({ default_password: password }, user);
await this.httpService.post(path, { password: password, update_default_password: updateDefaultPassword });
}
/**

View File

@ -11,24 +11,37 @@
<!-- can Manage, but not own Page (a.k.a. Admin) -->
<div *ngIf="user">
<h1><span translate>Change password for</span> {{ user.full_name }}</h1>
<mat-icon>warning</mat-icon> <span translate>You override the personally set password!</span>
<mat-icon>warning</mat-icon>
&nbsp;<span translate>You override the personally set password!</span>
</div>
<br>
<form [formGroup]="adminPasswordForm" (keydown)="onKeyDown($event)">
<mat-form-field>
<input
[type]="hide_admin_newPassword ? 'password' : 'text'"
[type]="hidePassword ? 'password' : 'text'"
matInput
formControlName="admin_newPassword"
formControlName="newPassword"
placeholder="{{ 'New password' | translate }}"
required
/>
<mat-icon
class="pointer"
matSuffix
mat-icon-button
(click)="admin_generatePassword()">
sync_problem
matTooltip="{{ hidePassword ? ('Show password' | translate) : ('Hide password' | translate) }}"
(click)="hidePassword = !hidePassword">
{{ hidePassword ? 'visibility' : 'visibility_off' }}
</mat-icon>
<mat-icon
class="pointer spacer-left-10"
matSuffix
mat-icon-button
matTooltip="{{ 'Generate password' | translate }}"
(click)="generatePassword()">
settings
</mat-icon>
</mat-form-field>
</form>
<br>
@ -36,62 +49,45 @@
<span translate>Initial password</span>: {{ user.default_password }}<br>
<span translate>Username</span>: {{ user.username }}
</div>
<br>
<button
mat-raised-button
color="primary"
(click)="hide_admin_newPassword = !hide_admin_newPassword">
<mat-icon
matSuffix
mat-icon-button>
{{hide_admin_newPassword ? 'visibility_off' : 'visibility'}}
</mat-icon>
&nbsp;<span translate>Show password</span>
</button>
</div>
<div *ngIf="this.ownPage">
<!-- can not Manage, but own Page (a.k.a. User) -->
<form [formGroup]="userPasswordForm" (keydown)="onKeyDown($event)">
<mat-form-field>
<input
[type]="hide_user_password ? 'password' : 'text'"
matInput
formControlName="user_oldPassword"
formControlName="oldPassword"
placeholder="{{ 'Old password' | translate }}"
required
/>
</mat-form-field><br>
<mat-form-field>
<input
[type]="hide_user_password ? 'password' : 'text'"
[type]="hidePassword ? 'password' : 'text'"
matInput
formControlName="user_newPassword1"
formControlName="newPassword1"
placeholder="{{ 'New password' | translate }}"
required
/>
<mat-icon
mat-button matSuffix mat-icon-button
(click)="user_generatePassword()">
sync_problem
(click)="hidePassword = !hidePassword">
{{ hidePassword ? 'visibility' : 'visibility_off' }}
</mat-icon>
</mat-form-field><br>
<mat-form-field>
<input
[type]="hide_user_password ? 'password' : 'text'"
[type]="hidePassword ? 'password' : 'text'"
matInput
formControlName="user_newPassword2"
formControlName="newPassword2"
placeholder="{{ 'Confirm new password' | translate }}"
required
/>
</mat-form-field>
</form>
<button
mat-raised-button
color="primary"
(click)="hide_user_password = !hide_user_password">
<mat-icon
matSuffix
mat-icon-button>
{{ hide_user_password ? 'visibility_off' : 'visibility' }}
</mat-icon>
&nbsp;<span translate>Show password</span>
</button>
</div>
</mat-card>

View File

@ -1,10 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { MatSnackBar } from '@angular/material';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { ViewUser } from '../../models/view-user';
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
import { OperatorService } from 'app/core/core-services/operator.service';
@ -29,11 +30,6 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
*/
public ownPage: boolean;
/**
* user id from url parameter
*/
public userId: number;
/**
* if current user has the "can_manage" permission
*/
@ -50,14 +46,11 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
public userPasswordForm: FormGroup;
/**
* if the new password in userform is hidden
* if all password inputs is hidden
*/
public hide_user_password = true;
public hidePassword = true;
/**
* If the new Password in the adminform is hidden
*/
public hide_admin_newPassword = true;
private urlUserId: number | null;
/**
* Constructor
@ -82,39 +75,48 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
private formBuilder: FormBuilder
) {
super(title, translate, matSnackBar);
this.route.params.subscribe(params => {
if (params.id) {
this.userId = params.id;
}
});
if (this.userId === undefined) {
this.operator.getUserObservable().subscribe(user => {
if (user) {
this.userId = user.id;
this.router.navigate([`./users/password/${this.userId}`]);
}
});
}
}
/**
* Initializes the forms and some of the frontend options
*/
public ngOnInit(): void {
this.setViewUser(this.userId);
this.setOpOwnsPage(this.userId);
this.route.params.subscribe(params => {
if (params.id) {
this.urlUserId = +params.id;
this.repo.getViewModelObservable(this.urlUserId).subscribe(() => {
this.updateUser();
});
}
this.updateUser();
});
this.operator.getUserObservable().subscribe(() => {
this.updateUser();
});
this.adminPasswordForm = this.formBuilder.group({
admin_newPassword: ['', Validators.required]
newPassword: ['', Validators.required]
});
this.userPasswordForm = this.formBuilder.group({
user_newPassword1: ['', Validators.required],
user_newPassword2: ['', Validators.required],
user_oldPassword: ['', Validators.required]
newPassword1: ['', Validators.required],
newPassword2: ['', Validators.required],
oldPassword: ['', Validators.required]
});
}
private updateUser(): void {
const operator = this.operator.user;
this.ownPage = this.urlUserId ? operator.id === this.urlUserId : true;
if (this.ownPage) {
this.user = this.operator.viewUser;
} else {
this.user = this.repo.getViewModel(this.urlUserId);
}
this.canManage = this.operator.hasPerms('users.can_manage');
}
/**
* Triggered by the "x" Button of the Form
*/
@ -126,34 +128,6 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
}
}
/**
* sets the current user that should be worked on
*
* @param userId user id from the route
*/
private setViewUser(userId: number): void {
this.repo.getViewModelObservable(userId).subscribe(newViewUser => {
if (newViewUser) {
this.user = newViewUser;
}
});
}
/**
* sets the parameters if the pw-page is our own and if the current
* user has the can_manage permission
*
* @param userId user id from the route
*/
private setOpOwnsPage(userId: number): void {
this.operator.getUserObservable().subscribe(user => {
if (user) {
this.ownPage = +userId === +user.id;
this.canManage = this.operator.hasPerms('users.can_manage');
}
});
}
/**
* Handles the whole save routine for every possible event
*/
@ -161,46 +135,32 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
// can Manage, but not own Page (a.k.a. Admin)
try {
if (this.canManage && !this.ownPage) {
const pw = this.adminPasswordForm.get('admin_newPassword').value;
this.adminNewPassword(pw);
this.router.navigate([`./users/${this.user.id}`]);
if (!this.adminPasswordForm.valid) {
return;
}
// can not Manage, but own Page (a.k.a. User)
if (this.ownPage) {
const oldPw = this.userPasswordForm.get('user_oldPassword').value;
const newPw1 = this.userPasswordForm.get('user_newPassword1').value;
const newPw2 = this.userPasswordForm.get('user_newPassword2').value;
await this.userNewPassword(newPw1, newPw2, oldPw);
const password = this.adminPasswordForm.value.newPassword;
await this.repo.resetPassword(this.user, password);
this.router.navigate([`./users/${this.user.id}`]);
} else if (this.ownPage) {
if (!this.userPasswordForm.valid) {
return;
}
const oldPassword = this.userPasswordForm.value.oldPassword;
const newPassword1 = this.userPasswordForm.value.newPassword1;
const newPassword2 = this.userPasswordForm.value.newPassword2;
if (newPassword1 !== newPassword2) {
this.raiseError(this.translate.instant('New passwords do not match'));
} else {
await this.repo.setNewPassword(oldPassword, newPassword1);
this.router.navigate(['./']);
}
}
} catch (e) {
this.raiseError(e);
}
}
/**
* Sends new Password entered in the new password field to server
*
* @param password the password that should be set
*/
private adminNewPassword(password: string): void {
this.repo.resetPassword(this.user, password).catch(this.raiseError);
}
/**
* sets the new password for a user and sends it to the server
*
* @param newPassword1 the new password
* @param newPassword2 confirmation of the new password
* @param oldPassword the old password
*/
private userNewPassword(newPassword1: string, newPassword2: string, oldPassword: string): void {
if (newPassword1 !== newPassword2) {
this.raiseError(this.translate.instant('Passwords do not match'));
}
this.repo.setNewPassword(oldPassword, newPassword1).catch(this.raiseError);
}
/**
* clicking Shift and Enter will save automatically
*
@ -216,49 +176,15 @@ export class PasswordComponent extends BaseViewComponent implements OnInit {
* Takes generated password and puts it into admin PW field
* and displays it
*/
public admin_generatePassword(): void {
public generatePassword(): void {
const randomPassword = this.repo.getRandomPassword();
this.adminPasswordForm.patchValue({
admin_newPassword: this.repo.getRandomPassword()
newPassword: randomPassword
});
this.admin_hidePassword(false);
}
/**
* Takes generated password and puts it into user PW fields
* and displays them
*/
public user_generatePassword(): void {
const newPW = this.repo.getRandomPassword();
this.userPasswordForm.patchValue({
user_newPassword1: newPW,
user_newPassword2: newPW
newPassword1: randomPassword,
newPassword2: randomPassword
});
this.user_hidePassword(false);
}
/**
* Helper function to hide or display the pw in cleartext for admin form
*
* @param hide optional - states if it should be shown or not
*/
public admin_hidePassword(hide?: boolean): void {
if (hide !== null) {
this.hide_admin_newPassword = hide;
} else {
this.hide_admin_newPassword = !this.hide_admin_newPassword;
}
}
/**
* Helper function to hide or display new pw in clearext for user form
*
* @param hide optional - states if it should be shown or not
*/
public user_hidePassword(hide?: boolean): void {
if (hide !== null) {
this.hide_user_password = hide;
} else {
this.hide_user_password = !this.hide_user_password;
}
this.hidePassword = false;
}
}

View File

@ -273,7 +273,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
public async resetPasswordsSelected(): Promise<void> {
for (const user of this.selectedRows) {
const password = this.repo.getRandomPassword();
this.repo.resetPassword(user, password);
this.repo.resetPassword(user, password, true);
}
}

View File

@ -398,6 +398,11 @@ button.mat-menu-item.selected {
.spacer-bottom-20 {
margin-bottom: 20px !important;
}
.spacer-left-10 {
margin-left: 10px;
}
.button24 {
background-color: white;
width: 24px !important;

View File

@ -141,19 +141,30 @@ class UserViewSet(ModelViewSet):
def reset_password(self, request, pk=None):
"""
View to reset the password using the requested password.
If update_defualt_password=True is given, the new password will also be set
as the default_password.
"""
user = self.get_object()
if isinstance(request.data.get("password"), str):
password = request.data.get("password")
if not isinstance(password, str):
raise ValidationError({"detail": "Password has to be a string."})
update_default_password = request.data.get("update_default_password", False)
if not isinstance(update_default_password, bool):
raise ValidationError(
{"detail": "update_default_password has to be a boolean."}
)
try:
validate_password(request.data.get("password"), user=request.user)
validate_password(password, user=request.user)
except DjangoValidationError as errors:
raise ValidationError({"detail": " ".join(errors)})
user.set_password(request.data.get("password"))
user.set_password(password)
if update_default_password:
user.default_password = password
user.save()
return Response({"detail": "Password successfully reset."})
raise ValidationError({"detail": "Password has to be a string."})
@list_route(methods=["post"])
@transaction.atomic
def mass_import(self, request):