Merge pull request #4048 from jsaalfeld/user_profile_enhancement

add change password field for users/admins
This commit is contained in:
Jochen Saalfeld 2018-12-12 14:23:39 +01:00 committed by GitHub
commit 656753c2a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 438 additions and 6 deletions

View File

@ -17,6 +17,7 @@ import { MainMenuService } from '../../../core/services/main-menu.service';
* ```html
* <os-head-bar
* [nav]="false"
* [goBack]="true"
* [mainButton]="opCanEdit()"
* [mainButtonIcon]="edit"
* [editMode]="editMotion"

View File

@ -142,8 +142,12 @@ export class SiteComponent extends BaseComponent implements OnInit {
}
}
// TODO: Implement this
public changePassword(): void {}
/**
* Redirects to the change password component
*/
public changePassword(): void {
this.router.navigate([`./users/password`]);
}
/**
* Function to log out the current user

View File

@ -0,0 +1,94 @@
<os-head-bar (mainEvent)="goBack()" [mainButton]="true" [nav]="false" [editMode]="true" (saveEvent)="save()">a
<!-- Title -->
<div class="title-slot"><h2 translate>Change Password</h2></div>
</os-head-bar>
<mat-card class="os-card">
<div *ngIf="!this.canManage && !this.ownPage">
<!-- no Admin, cannot Manage (a.k.a Attack Prevention) -->
<span translate>You are not supposed to be here please leave...</span>
</div>
<div *ngIf="this.canManage && !this.ownPage">
<!-- can Manage, but not own Page (a.k.a. Admin) -->
<div *ngIf="user">
<mat-icon>warning</mat-icon> <span translate>You are changing the password for the user:</span> {{user.full_name}}
</div>
<br>
<form [formGroup]="adminPasswordForm" (keydown)="onKeyDown($event)">
<mat-form-field>
<input
[type]="hide_admin_newPassword ? 'password' : 'text'"
matInput
formControlName="admin_newPassword"
placeholder="{{ 'New Password' | translate }}"
/>
<mat-icon
matSuffix
mat-icon-button
(click)="admin_generatePassword()">
sync_problem
</mat-icon>
</mat-form-field>
</form>
<br>
<div *ngIf="user">
<span translate>Initial Password:</span> {{user.default_password}}
</div>
<br>
<button
mat-raised-button
color="primary"
(click)="hide_admin_newPassword = !hide_admin_newPassword">
<span translate>Show Password</span>&nbsp;
<mat-icon
matSuffix
mat-icon-button>
{{hide_admin_newPassword ? 'visibility_off' : 'visibility'}}
</mat-icon>
</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"
placeholder="{{ 'Old Password' | translate }}"
/>
</mat-form-field><br>
<mat-form-field>
<input
[type]="hide_user_password ? 'password' : 'text'"
matInput
formControlName="user_newPassword1"
placeholder="{{ 'New Password' | translate }}"
/>
<mat-icon
mat-button matSuffix mat-icon-button
(click)="user_generatePassword()">
sync_problem
</mat-icon>
</mat-form-field><br>
<mat-form-field>
<input
[type]="hide_user_password ? 'password' : 'text'"
matInput
formControlName="user_newPassword2"
placeholder="{{ 'Confirm New Password' | translate }}"
/>
</mat-form-field>
</form>
<button
mat-raised-button
color="primary"
(click)="hide_user_password = !hide_user_password">
<span translate>Show Password</span>&nbsp;
<mat-icon
matSuffix
mat-icon-button>
{{ hide_user_password ? 'visibility_off' : 'visibility' }}
</mat-icon>
</button>
</div>
</mat-card>

View File

@ -0,0 +1,3 @@
mat-form-field {
width: 100%;
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordComponent } from './password.component';
import { E2EImportsModule } from 'e2e-imports.module';
describe('PasswordComponent', () => {
let component: PasswordComponent;
let fixture: ComponentFixture<PasswordComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [PasswordComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,265 @@
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 { ViewUser } from '../../models/view-user';
import { UserRepositoryService } from '../../services/user-repository.service';
import { OperatorService } from '../../../../core/services/operator.service';
import { BaseViewComponent } from '../../../../site/base/base-view';
/**
* Component for the Password-Reset Handling
*/
@Component({
selector: 'os-password',
templateUrl: './password.component.html',
styleUrls: ['./password.component.scss']
})
export class PasswordComponent extends BaseViewComponent implements OnInit {
/**
* the user that is currently worked own
*/
public user: ViewUser;
/**
* if this pw-page is for your own user
*/
public ownPage: boolean;
/**
* user id from url parameter
*/
public userId: number;
/**
* if current user has the "can_manage" permission
*/
public canManage: boolean;
/**
* formGroup for the admin user
*/
public adminPasswordForm: FormGroup;
/**
* formGroup for the normal user
*/
public userPasswordForm: FormGroup;
/**
* if the new password in userform is hidden
*/
public hide_user_password = true;
/**
* If the new Password in the adminform is hidden
*/
public hide_admin_newPassword = true;
/**
* Constructor
*
* @param title the title
* @param translate translation server
* @param matSnackBar snack bar for errors
* @param route current route
* @param router router service
* @param repo user repository
* @param operator the operatorservice
* @param formBuilder formbuilder for the two forms
*/
public constructor(
title: Title,
translate: TranslateService,
matSnackBar: MatSnackBar,
private route: ActivatedRoute,
private router: Router,
private repo: UserRepositoryService,
private operator: OperatorService,
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.getObservable().subscribe(users => {
if (users) {
this.userId = users.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.adminPasswordForm = this.formBuilder.group({
admin_newPassword: ['', Validators.required]
});
this.userPasswordForm = this.formBuilder.group({
user_newPassword1: ['', Validators.required],
user_newPassword2: ['', Validators.required],
user_oldPassword: ['', Validators.required]
});
}
/**
* Triggered by the "x" Button of the Form
*/
public goBack(): void {
if (!this.ownPage) {
this.router.navigate([`./users/${this.user.id}`]);
} else {
this.router.navigate(['./']);
}
}
/**
* 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.getObservable().subscribe(users => {
if (users) {
this.ownPage = +userId === +users.id;
this.canManage = this.operator.hasPerms('users.can_manage');
}
});
}
/**
* Handles the whole save routine for every possible event
*/
public async save(): Promise<void> {
// 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}`]);
}
// 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);
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
*
* @param event has the code
*/
public onKeyDown(event: KeyboardEvent): void {
if (event.key === 'Enter' && event.shiftKey) {
this.save();
}
}
/**
* Takes generated password and puts it into admin PW field
* and displays it
*/
public admin_generatePassword(): void {
this.adminPasswordForm.patchValue({
admin_newPassword: this.repo.getRandomPassword()
});
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
});
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;
}
}
}

View File

@ -25,6 +25,10 @@
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
<button mat-menu-item (click)="changePassword()">
<mat-icon>security</mat-icon>
<span translate>Change password</span>
</button>
</mat-menu>
</os-head-bar>

View File

@ -37,6 +37,11 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
*/
public editUser = false;
/**
* Set new Password
*/
public newPassword = false;
/**
* True if a new user is created
*/
@ -140,7 +145,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
case 'seePersonal':
return this.operator.hasPerms('users.can_see_extra_data', 'users.can_manage') || this.ownPage;
case 'changePersonal':
return this.operator.hasPerms('user.cans_manage') || this.ownPage;
return this.operator.hasPerms('users.can_manage') || this.ownPage;
default:
return false;
}
@ -322,6 +327,13 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
}
}
/**
* navigate to the change Password site
*/
public changePassword(): void {
this.router.navigate([`./users/password/${this.user.id}`]);
}
/**
* Init function.
*/

View File

@ -10,6 +10,7 @@ import { CollectionStringModelMapperService } from '../../../core/services/colle
import { ConfigService } from 'app/core/services/config.service';
import { HttpService } from 'app/core/services/http.service';
import { TranslateService } from '@ngx-translate/core';
import { environment } from '../../../../environments/environment';
/**
* Repository service for users
@ -110,17 +111,29 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
}
/**
* Updates the default password and sets the real password.
* Updates the password and sets the password without checking for the old one
*
* @param user The user to update
* @param password The password to set
*/
public async resetPassword(user: ViewUser, password: string): Promise<void> {
await this.update({ default_password: password }, user);
const path = `/rest/users/user/${user.id}/reset_password/`;
await this.httpService.post(path, { password: password });
}
/**
* Updates the password and sets a new one, if the old one was correct.
*
* @param oldPassword the old password
* @param newPassword the new password
*/
public async setNewPassword(oldPassword: string, newPassword: string): Promise<void> {
await this.httpService.post(`${environment.urlPrefix}/users/setpassword/`, {
old_password: oldPassword,
new_password: newPassword
});
}
/**
* Sends invitation emails to all given users. Returns a prepared string to show the user.
* This string should always be shown, becuase even in success cases, some users may not get

View File

@ -3,12 +3,21 @@ import { Routes, RouterModule } from '@angular/router';
import { UserListComponent } from './components/user-list/user-list.component';
import { UserDetailComponent } from './components/user-detail/user-detail.component';
import { GroupListComponent } from './components/group-list/group-list.component';
import { PasswordComponent } from './components/password/password.component';
const routes: Routes = [
{
path: '',
component: UserListComponent
},
{
path: 'password',
component: PasswordComponent
},
{
path: 'password/:id',
component: PasswordComponent
},
{
path: 'new',
component: UserDetailComponent

View File

@ -6,9 +6,10 @@ import { SharedModule } from '../../shared/shared.module';
import { UserListComponent } from './components/user-list/user-list.component';
import { UserDetailComponent } from './components/user-detail/user-detail.component';
import { GroupListComponent } from './components/group-list/group-list.component';
import { PasswordComponent } from './components/password/password.component';
@NgModule({
imports: [CommonModule, UsersRoutingModule, SharedModule],
declarations: [UserListComponent, UserDetailComponent, GroupListComponent]
declarations: [UserListComponent, UserDetailComponent, GroupListComponent, PasswordComponent]
})
export class UsersModule {}