Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS will be completely phased out by mid-2025. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 00c625d7 authored by Sebastian-Ion TINCU's avatar Sebastian-Ion TINCU
Browse files

EDELIVERY-4054 Existing password is being accepted in change password

Add an async validator to enforce the password to be different than the
previous one when updating one user's own password (not applying to
System Administrator users managing other users' passwords).
parent 82e5ce7c
No related branches found
No related tags found
No related merge requests found
# Service Metadata Publishing
## Continous Integration
## Continuous Integration
[https://webgate.ec.europa.eu/CITnet/bamboo/browse/EDELIVERY-SMPDEV]
......
......@@ -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,
......
......@@ -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">
......
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 {
......
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);
}
}
......@@ -29,9 +29,4 @@ public class SMPAuthorizationService {
return false;
}
private DBUser getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SMPAuthenticationToken authToken = (SMPAuthenticationToken) authentication;
return authToken.getUser();
}
}
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();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment