diff --git a/README.md b/README.md index 39fdc0490667f2f9dee0a5e4f19c998d1adcfb6b..7ede578edbf2f4223689ced780dab4dea7cc8f29 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Service Metadata Publishing -## Continous Integration +## Continuous Integration [https://webgate.ec.europa.eu/CITnet/bamboo/browse/EDELIVERY-SMPDEV] diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index 69479d37459d4e86e1ca24e15baf6ccf3423064d..f127a35eeae2357dd1b2fcabfd511058cc7e0b57 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -76,6 +76,7 @@ import {ServiceMetadataWizardDialogComponent} from "./service-group-edit/service import {ConfirmationDialogComponent} from "./common/confirmation-dialog/confirmation-dialog.component"; import {SpinnerComponent} from "./common/spinner/spinner.component"; import {UserService} from "./user/user.service"; +import {UserDetailsService} from "./user/user-details-dialog/user-details.service"; @NgModule({ declarations: [ @@ -166,6 +167,7 @@ import {UserService} from "./user/user.service"; GlobalLookups, DatePipe, UserService, + UserDetailsService, { provide: ExtendedHttpClient, useFactory: extendedHttpClientCreator, diff --git a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html index fd6e95df8c0c1f8094d6ca9f5f53a1eacaeb1222..b22730bc83245c6f299c2d0103e7ec7743d46bd4 100644 --- a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html +++ b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html @@ -3,7 +3,7 @@ <mat-card> <mat-card-content > - <mat-slide-toggle *ngIf="isNotPreferencesMode()" class="user-toggle" + <mat-slide-toggle *ngIf="!isPreferencesMode()" class="user-toggle" mat-no-ink class="mat-primary" [formControl]="userForm.controls['active']" id="active_id"> Active </mat-slide-toggle> @@ -75,6 +75,9 @@ - At least one digit<br> - At least one special character </div> + <div *ngIf="userForm.errors?.previousPasswordUsed" class="has-error"> + Password should be different than the previous chosen one + </div> </mat-form-field> <mat-form-field class="password-confirmation"> diff --git a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts index 40af749c6da8387af75d7a4e77c3e72c4c9dfe51..ed852adbda5ada84b5ca0d832e2b852271f561d7 100644 --- a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts +++ b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.ts @@ -1,7 +1,7 @@ import {Component, Inject, TemplateRef, ViewChild} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef, MatSlideToggleChange} from '@angular/material'; import { - AbstractControl, + AbstractControl, AsyncValidatorFn, FormBuilder, FormControl, FormGroup, @@ -18,6 +18,9 @@ import {CertificateRo} from "../certificate-ro.model"; import {DatePipe} from "../../custom-date/date.pipe"; import {UserController} from "../user-controller"; import {GlobalLookups} from "../../common/global-lookups"; +import {Observable, of} from "rxjs"; +import {catchError, map} from "rxjs/operators"; +import {UserDetailsService} from "./user-details.service"; @Component({ selector: 'user-details-dialog', @@ -35,6 +38,7 @@ export class UserDetailsDialogComponent { mode: UserDetailsDialogMode; editMode: boolean; + userId: number; userRoles = []; existingRoles = []; userForm: FormGroup; @@ -76,11 +80,31 @@ export class UserDetailsDialogComponent { && listIds.includes(certificateId.value) && this.current.certificate && certificateId.value !== this.current.certificate.certificateId ? { certificateIdExists: true} : null; }; + private asyncPasswordValidator: AsyncValidatorFn = (control:Â AbstractControl):Â Promise<ValidationErrors | null>Â |Â Observable<ValidationErrors | null> => { + if(this.isPreferencesMode()) { + const userToggle = control.get('userToggle'); + const passwordToggle = control.get('passwordToggle'); + const password = control.get('password'); + const confirmation = control.get('confirmation'); + + if(userToggle && passwordToggle && password + && this.userId && userToggle.value && passwordToggle.value && password.value) { + return this.userDetailsService.isSamePreviousPasswordUsed$(this.userId, password.value).pipe( + map(previousPasswordUsed => previousPasswordUsed ? { previousPasswordUsed: true } : null), + catchError(() => { + this.alertService.error("Error occurred while validating the password against the previously chosen one!"); + return of(null); + })); + } + } + return of(null); + }; + notInList(list: string[]) { return (c: AbstractControl): { [key: string]: any } => { - if (c.value && list.includes(c.value.toString().toLowerCase())) + if (c.value && list.includes(c.value.toString().toLowerCase())) { return {'notInList': {valid: false}}; - + } return null; } } @@ -88,11 +112,13 @@ export class UserDetailsDialogComponent { constructor(private dialogRef: MatDialogRef<UserDetailsDialogComponent>, private lookups: GlobalLookups, private certificateService: CertificateService, + private userDetailsService: UserDetailsService, private alertService: AlertService, private datePipe: DatePipe, @Inject(MAT_DIALOG_DATA) public data: any, private fb: FormBuilder) { this.mode = data.mode; + this.userId = data.row && data.row.id; this.editMode = this.mode !== UserDetailsDialogMode.NEW_MODE; this.current = this.editMode @@ -153,7 +179,8 @@ export class UserDetailsDialogComponent { this.atLeastOneToggleCheckedValidator, this.certificateValidator, this.certificateExistValidator, - ] + ], + asyncValidator: this.asyncPasswordValidator, }); // bind values to form! not property this.userForm.controls['active'].setValue(this.current.active); @@ -270,8 +297,8 @@ export class UserDetailsDialogComponent { } } - isNotPreferencesMode() { - return this.mode !== UserDetailsDialogMode.PREFERENCES_MODE; + isPreferencesMode() { + return this.mode === UserDetailsDialogMode.PREFERENCES_MODE; } public getCurrent(): UserRo { diff --git a/smp-angular/src/app/user/user-details-dialog/user-details.service.ts b/smp-angular/src/app/user/user-details-dialog/user-details.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..221b7011ac05a5191105617802e82657559b7600 --- /dev/null +++ b/smp-angular/src/app/user/user-details-dialog/user-details.service.ts @@ -0,0 +1,17 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {catchError, map} from "rxjs/operators"; +import {SmpConstants} from "../../smp.constants"; +import {Observable} from "rxjs"; + +@Injectable() +export class UserDetailsService { + + constructor( + private http: HttpClient, + ) { } + + isSamePreviousPasswordUsed$(userId: number, password: string): Observable<boolean> { + return this.http.post<boolean>(`${SmpConstants.REST_USER}/${userId}/samePreviousPasswordUsed`, password); + } +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java index ed090465fc0f1beeeb6c890f89d824d24e50e1df..347356b56be1dc813f83fff30fef051fce0e01d8 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java @@ -29,9 +29,4 @@ public class SMPAuthorizationService { return false; } - private DBUser getCurrentUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - SMPAuthenticationToken authToken = (SMPAuthenticationToken) authentication; - return authToken.getUser(); - } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java index f2f9832f9ac5e185551d26a0ffef1623fbc80d11..add88a8f11cad205ed9a93dd49274f3367761346 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java @@ -1,5 +1,6 @@ package eu.europa.ec.edelivery.smp.ui; +import eu.europa.ec.edelivery.smp.BCryptPasswordHash; import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; import eu.europa.ec.edelivery.smp.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.model.DBUser; @@ -16,6 +17,7 @@ import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -87,17 +89,21 @@ public class UserResource { LOG.error("Error occurred while parsing certificate.", e); } return null; + } + @PostMapping(path = "/{id}/samePreviousPasswordUsed", produces = {"application/json"}) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#id)") + public boolean samePreviousPasswordUsed(@PathVariable("id") Long id, @RequestBody String password) { + LOG.info("Validating the password of the currently logged in user: {} ", id); + DBUser currentUser = getCurrentUser(); + return BCrypt.checkpw(password, currentUser.getPassword()); } @PutMapping(produces = {"application/json"}) @RequestMapping(path = "validateDelete", method = RequestMethod.POST) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) public DeleteEntityValidation validateDeleteUsers(@RequestBody List<Long> query) { - // test if looged user - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - SMPAuthenticationToken authToken = (SMPAuthenticationToken) authentication; - DBUser user = authToken.getUser(); + DBUser user = getCurrentUser(); DeleteEntityValidation dres = new DeleteEntityValidation(); if (query.contains(user.getId())){ dres.setValidOperation(false); @@ -107,4 +113,10 @@ public class UserResource { dres.getListIds().addAll(query); return uiUserService.validateDeleteRequest(dres); } + + private DBUser getCurrentUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + SMPAuthenticationToken authToken = (SMPAuthenticationToken) authentication; + return authToken.getUser(); + } }