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.component.html b/smp-angular/src/app/app.component.html index 09fffd200c508e1c7182b5263bab6510564e30ae..283cf208016111a2a3f210921b215866c5b760f2 100644 --- a/smp-angular/src/app/app.component.html +++ b/smp-angular/src/app/app.component.html @@ -10,32 +10,31 @@ </div> <button mat-raised-button class="sideNavButton" [routerLink]="['/']" id="sidebar_search_id"> - <mat-icon matTooltip="Search" matTooltipDisabled="{{fullMenu}}" matTooltipDisabled="right">search</mat-icon> + <mat-icon matTooltip="Search" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">search</mat-icon> <span>Search</span> </button> - <button mat-raised-button class="sideNavButton" *ngIf="isCurrentUserSMPAdmin() || isCurrentUserServiceGroupAdmin()" [routerLink]="['/edit']" id="sidebar_edit_id"> - <mat-icon matTooltip="Edit" matTooltipDisabled="{{fullMenu}}" matTooltipDisabled="right">edit</mat-icon> + <button mat-raised-button class="sideNavButton" *ngIf="isCurrentUserSMPAdmin() || isCurrentUserServiceGroupAdmin()" [routerLink]="['/edit']" id="sidebar_edit_id"> + <mat-icon matTooltip="Edit" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">edit</mat-icon> <span>Edit</span> </button> - <button mat-raised-button class="sideNavButton" [routerLink]="['/domain']" *ngIf="isCurrentUserSystemAdmin()" id="sidebar_domain_id"> - <mat-icon matTooltip="Domain" matTooltipDisabled="{{fullMenu}}" matTooltipDisabled="right">domain</mat-icon> + <button mat-raised-button class="sideNavButton" [routerLink]="['/domain']" *ngIf="isCurrentUserSystemAdmin()" id="sidebar_domain_id"> + <mat-icon matTooltip="Domain" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">domain</mat-icon> <span>Domain</span> </button> <!-- button mat-raised-button class="sideNavButton" [routerLink]="['/user']" *ngIf="hasAdmin()" id="user_id" --> - <button mat-raised-button class="sideNavButton" [routerLink]="['/user']" *ngIf="isCurrentUserSystemAdmin()" id="sidebar_user_id"> - <mat-icon matTooltip="Users" matTooltipDisabled="{{fullMenu}}" matTooltipDisabled="right">people</mat-icon> + <button mat-raised-button class="sideNavButton" [routerLink]="['/user']" *ngIf="isCurrentUserSystemAdmin()" id="sidebar_user_id"> + <mat-icon matTooltip="Users" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">people</mat-icon> <span>Users</span> </button> - <div class="collapse-button"> <button *ngIf="fullMenu" mat-raised-button id="expand_id" (click)="toggleMenu()"> - <mat-icon matTooltip="Collapse" matTooltipDisabled="right">chevron_left</mat-icon> + <mat-icon matTooltip="Collapse" [matTooltipPosition]="'right'">chevron_left</mat-icon> </button> <button *ngIf="!fullMenu" mat-raised-button id="collapse_id" (click)="toggleMenu()"> - <mat-icon matTooltip="Esxpand" matTooltipDisabled="right">chevron_right</mat-icon> + <mat-icon matTooltip="Expand" [matTooltipPosition]="'right'">chevron_right</mat-icon> </button> </div> @@ -61,8 +60,6 @@ <alert style=" left:220px; top:0;right:0;z-index: 500"></alert> <div id="sandwichMenuHolder" style="z-index: 500"> - - <div id="sandwichMenu"> <a *ngIf="!currentUser" [routerLink]="['/login']" > Login </a> @@ -73,10 +70,9 @@ </button> <mat-menu x-position="before" #settingsMenu="matMenu"> - <div *ngIf="currentUser"> - <button mat-menu-item disabled="true" id="currentuser_id"> + <button mat-menu-item id="currentuser_id" (click)="editCurrentUser()"> <mat-icon>person</mat-icon> <span>{{currentUser}}</span> </button> @@ -96,7 +92,6 @@ <span>Not logged in</span> </button> </div> - </mat-menu> </div> </div> @@ -104,7 +99,5 @@ <div fxFill="100" id="routerHolder" style="min-height: 100%" > <router-outlet></router-outlet> </div> - </div> - </mat-sidenav-container> diff --git a/smp-angular/src/app/app.component.ts b/smp-angular/src/app/app.component.ts index 7e0df476180964a7009032f2c46a78a7ac63f5d7..a1857739995029e5ece4b7e9e854a961cc304c0c 100644 --- a/smp-angular/src/app/app.component.ts +++ b/smp-angular/src/app/app.component.ts @@ -1,35 +1,37 @@ -import {Component, OnInit, ViewChild} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {SecurityService} from './security/security.service'; -import {Router, RouterOutlet} from '@angular/router'; -import {SecurityEventService} from './security/security-event.service'; -import {Title} from '@angular/platform-browser'; -import {HttpClient, HttpResponse} from '@angular/common/http'; +import {Router} from '@angular/router'; import {Authority} from "./security/authority.model"; - +import {AlertService} from "./alert/alert.service"; +import {MatDialog, MatDialogRef} from "@angular/material"; +import {GlobalLookups} from "./common/global-lookups"; +import {UserController} from "./user/user-controller"; +import {HttpClient} from "@angular/common/http"; +import {SearchTableEntityStatus} from "./common/search-table/search-table-entity-status.model"; +import {SmpConstants} from "./smp.constants"; +import {UserService} from "./user/user.service"; +import {UserDetailsDialogMode} from "./user/user-details-dialog/user-details-dialog.component"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent implements OnInit { +export class AppComponent { - _currentUser: string; fullMenu: boolean = true; menuClass: string = this.fullMenu ? "menu-expanded" : "menu-collapsed"; - - @ViewChild(RouterOutlet) - outlet: RouterOutlet; - - constructor(private securityService: SecurityService, - private router: Router, - private securityEventService: SecurityEventService, - private http: HttpClient, - private titleService: Title) { - - } - - ngOnInit() { + userController: UserController; + + constructor( + private securityService: SecurityService, + private router: Router, + private http: HttpClient, + private dialog: MatDialog, + private lookups: GlobalLookups, + private userService: UserService, + ) { + this.userController = new UserController(this.http, this.lookups, this.dialog); } isCurrentUserSystemAdmin(): boolean { @@ -39,44 +41,55 @@ export class AppComponent implements OnInit { isCurrentUserSMPAdmin(): boolean { return this.securityService.isCurrentUserInRole([ Authority.SMP_ADMIN]); } + isCurrentUserServiceGroupAdmin(): boolean { return this.securityService.isCurrentUserInRole([ Authority.SERVICE_GROUP_ADMIN]); } + editCurrentUser() { + const formRef: MatDialogRef<any> = this.userController.newDialog({ + data: {mode: UserDetailsDialogMode.PREFERENCES_MODE, row: this.securityService.getCurrentUser()} + }); + formRef.afterClosed().subscribe(result => { + if (result) { + const user = {...formRef.componentInstance.getCurrent(), status: SearchTableEntityStatus.UPDATED}; + this.userService.updateUser(user); + } + }); + } + get currentUser(): string { let user = this.securityService.getCurrentUser(); return user ? user.username : ""; } - get currentUserRoleDescription(): string { if (this.securityService.isCurrentUserSystemAdmin()){ return "System administrator"; } else if (this.securityService.isCurrentUserSMPAdmin()){ return "SMP administrator"; } else if (this.securityService.isCurrentUserServiceGroupAdmin()){ - return "Service group administrator" + return "Service group administrator"; } return ""; } - logout(event: Event): void { event.preventDefault(); this.router.navigate(['/search']).then((ok) => { if (ok) { this.securityService.logout(); } - }) + }); } toggleMenu() { - this.fullMenu = !this.fullMenu - this.menuClass = this.fullMenu ? "menu-expanded" : "menu-collapsed" + this.fullMenu = !this.fullMenu; + this.menuClass = this.fullMenu ? "menu-expanded" : "menu-collapsed"; setTimeout(() => { - var evt = document.createEvent("HTMLEvents") - evt.initEvent('resize', true, false) - window.dispatchEvent(evt) + var evt = document.createEvent("HTMLEvents"); + evt.initEvent('resize', true, false); + window.dispatchEvent(evt); }, 500) //ugly hack but otherwise the ng-datatable doesn't resize when collapsing the menu //alternatively this can be tried (https://github.com/swimlane/ngx-datatable/issues/193) but one has to implement it on every page diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index 0caea69b181fd646e93e658b01fcb635c036e450..f127a35eeae2357dd1b2fcabfd511058cc7e0b57 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -75,6 +75,8 @@ import {ServiceGroupExtensionWizardDialogComponent} from "./service-group-edit/s import {ServiceMetadataWizardDialogComponent} from "./service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component"; 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: [ @@ -164,6 +166,8 @@ import {SpinnerComponent} from "./common/spinner/spinner.component"; CertificateService, GlobalLookups, DatePipe, + UserService, + UserDetailsService, { provide: ExtendedHttpClient, useFactory: extendedHttpClientCreator, diff --git a/smp-angular/src/app/security/security.service.ts b/smp-angular/src/app/security/security.service.ts index 3b28efdd209be643fe77ed6dd5e0f7cd25caffb7..3a80585ce52b1d9d7e8c23d2ff7f5e22689cf147 100644 --- a/smp-angular/src/app/security/security.service.ts +++ b/smp-angular/src/app/security/security.service.ts @@ -23,8 +23,7 @@ export class SecurityService { }), { headers }) .subscribe((response: string) => { - this.populateLocalStorage(JSON.stringify(response)); - this.securityEventService.notifyLoginSuccessEvent(response); + this.updateUserDetails(response); }, (error: any) => { this.securityEventService.notifyLoginErrorEvent(error); @@ -32,8 +31,6 @@ export class SecurityService { } logout() { - console.log('Logging out'); - // this.domainService.resetDomain(); this.http.delete(SmpConstants.REST_SECURITY_AUTHENTICATION).subscribe((res: Response) => { this.clearLocalStorage(); this.securityEventService.notifyLogoutSuccessEvent(res); @@ -116,6 +113,11 @@ export class SecurityService { return subject.asObservable(); } + updateUserDetails(userDetails) { + this.populateLocalStorage(JSON.stringify(userDetails)); + this.securityEventService.notifyLoginSuccessEvent(userDetails); + } + private populateLocalStorage(userDetails: string) { localStorage.setItem(this.LOCAL_STORAGE_KEY_CURRENT_USER, userDetails); } diff --git a/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts b/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts index c52d10d668754a98aac2b7cf7c870293ed20c2c4..ff3fe7f49a9c6c38aa0176a224dcff472cd9200d 100644 --- a/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts +++ b/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts @@ -4,7 +4,7 @@ import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {HttpClient} from "@angular/common/http"; import {SmpConstants} from "../../smp.constants"; import {ServiceMetadataEditRo} from "../service-metadata-edit-ro.model"; - +import {CertificateService} from "../../user/certificate.service"; @Component({ selector: 'service-metadata-wizard-dialog', @@ -17,22 +17,22 @@ export class ServiceMetadataWizardDialogComponent { static readonly EDIT_MODE = 'Edit ServiceMetadata XML'; editMode: boolean; - formTitle: string; current: ServiceMetadataEditRo & { confirmation?: string }; dialogForm: FormGroup; certificateValidationMessage: string; isCertificateValid: string; selectedFile: File; - dummyXML: string = "<!-- Custom element is mandatory by OASIS SMP schema.\n Replace following element with your XML structure. -->\n<ext:example xmlns:ext=\"http://my.namespace.eu\">my mandatory content</ext:example>" - + // dummyXML: string = "<!-- Custom element is mandatory by OASIS SMP schema.\n Replace following element with your XML structure. -->\n<ext:example xmlns:ext=\"http://my.namespace.eu\">my mandatory content</ext:example>" - constructor( protected http: HttpClient, - public dialogRef: MatDialogRef<ServiceMetadataWizardDialogComponent>, - private dialogFormBuilder: FormBuilder) { + constructor( + private http: HttpClient, + private dialogRef: MatDialogRef<ServiceMetadataWizardDialogComponent>, + private dialogFormBuilder: FormBuilder, + private certificateService: CertificateService, + ) { this.dialogForm = dialogFormBuilder.group({ - 'documentIdentifier': new FormControl({value: ''}, [Validators.required]), 'documentIdentifierScheme': new FormControl({value: ''}, null), 'processScheme': new FormControl({value: ''}, [Validators.required]), @@ -49,14 +49,12 @@ export class ServiceMetadataWizardDialogComponent { onUpload() { // this.http is the injected HttpClient - this.http.post(SmpConstants.REST_CERTIFICATE, this.selectedFile) + this.certificateService.uploadCertificate$(this.selectedFile) .subscribe(event => { - console.log(event); // handle event here }); } - getExtensionXML() { /* var xmlString = '<Extension xmlns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05">' @@ -79,6 +77,4 @@ export class ServiceMetadataWizardDialogComponent { .replace(/>/g, ">") .replace(/"/g, """); } - - } diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts index cdded6c53ba93102b76dec3f6ca96984696a77c9..ad7858ded07b5d6992669106f4d4cb785aa28acd 100644 --- a/smp-angular/src/app/smp.constants.ts +++ b/smp-angular/src/app/smp.constants.ts @@ -9,8 +9,6 @@ export class SmpConstants { public static readonly REST_SECURITY_USER = 'rest/security/user'; public static readonly REST_APPLICATION = 'rest/application/info'; - - public static readonly REST_CERTIFICATE = `${SmpConstants.REST_USER}/certdata`; public static readonly REST_USER_VALIDATE_DELETE = `${SmpConstants.REST_USER}/validateDelete`; public static readonly REST_DOMAIN_VALIDATE_DELETE = `${SmpConstants.REST_DOMAIN}/validateDelete`; public static readonly REST_SERVICE_GROUP_EXTENSION = `${SmpConstants.REST_EDIT}/extension`; diff --git a/smp-angular/src/app/user/certificate.service.ts b/smp-angular/src/app/user/certificate.service.ts index 7c969c1800b73f8c0ae1ce43fce1d1e62d322f30..30508bf7b4d4ddb1af24ca42fef359410136f86e 100644 --- a/smp-angular/src/app/user/certificate.service.ts +++ b/smp-angular/src/app/user/certificate.service.ts @@ -3,13 +3,22 @@ import {Observable} from 'rxjs'; import {CertificateRo} from './certificate-ro.model'; import {HttpClient} from '@angular/common/http'; import {SmpConstants} from "../smp.constants"; +import {SecurityService} from "../security/security.service"; +import {User} from "../security/user.model"; @Injectable() export class CertificateService { - constructor(private http: HttpClient) {} + constructor( + private http: HttpClient, + private securityService: SecurityService, + ) { } uploadCertificate$(payload): Observable<CertificateRo> { - return this.http.post<CertificateRo>(SmpConstants.REST_CERTIFICATE, payload); + // The user identifier below belongs to the currently logged in user and it may or may not be the same as the + // identifier of the user being modified (e.g. a normal user editing his own details vs. a system administrator + // adding or editing another user) + const currentUser: User = this.securityService.getCurrentUser(); + return this.http.post<CertificateRo>(`${SmpConstants.REST_USER}/${currentUser.id}/certdata`, payload); } } diff --git a/smp-angular/src/app/user/user-controller.ts b/smp-angular/src/app/user/user-controller.ts index 856d2173b9b321680cc7420fc26ab7961aaedb70..76c18573f835437a2e5536fe662d6501f60be278 100644 --- a/smp-angular/src/app/user/user-controller.ts +++ b/smp-angular/src/app/user/user-controller.ts @@ -1,6 +1,6 @@ import {SearchTableController} from '../common/search-table/search-table-controller'; import {MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material'; -import {UserDetailsDialogComponent} from './user-details-dialog/user-details-dialog.component'; +import {UserDetailsDialogComponent, UserDetailsDialogMode} from './user-details-dialog/user-details-dialog.component'; import {UserRo} from './user-ro.model'; import {SearchTableEntityStatus} from '../common/search-table/search-table-entity-status.model'; import {GlobalLookups} from "../common/global-lookups"; @@ -10,7 +10,6 @@ import {SmpConstants} from "../smp.constants"; import {HttpClient} from "@angular/common/http"; export class UserController implements SearchTableController { - constructor(protected http: HttpClient, protected lookups: GlobalLookups, public dialog: MatDialog) { } public showDetails(row: any) { @@ -22,12 +21,20 @@ export class UserController implements SearchTableController { public edit(row: any) { } - public delete(row: any) { + public delete(row: any) { } + public newDialog(config?: MatDialogConfig): MatDialogRef<UserDetailsDialogComponent> { + return this.dialog.open(UserDetailsDialogComponent, this.convertWithMode(config)); } - public newDialog(config?: MatDialogConfig): MatDialogRef<UserDetailsDialogComponent> { - return this.dialog.open(UserDetailsDialogComponent, config); + private convertWithMode(config) { + return (config && config.data) + ? {...config, + data: {...config.data, + mode: config.data.mode || (config.data.edit ? UserDetailsDialogMode.EDIT_MODE : UserDetailsDialogMode.NEW_MODE) + } + } + : config; } public newRow(): UserRo { @@ -43,7 +50,6 @@ export class UserController implements SearchTableController { } } - public dataSaved() { this.lookups.refreshUserLookup(); } 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 b031850b0d0d9a2182866df4611c041f0373e7ae..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 @@ -1,11 +1,10 @@ <mat-dialog-content fxFlex="column"> - <h2 mat-dialog-title>{{formTitle}}</h2> + <h2 mat-dialog-title>{{mode}}</h2> <mat-card> <mat-card-content > - <mat-slide-toggle class="user-toggle" - mat-no-ink class="mat-primary" [formControl]="userForm.controls['active']" - (change)="onUserToggleChanged($event)" id="active_id"> + <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> @@ -76,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 64d0f45837c733fe84b134642fd60a3009566b52..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', @@ -28,16 +31,14 @@ export class UserDetailsDialogComponent { @ViewChild('fileInput') private fileInput; - static readonly NEW_MODE = 'New User'; - static readonly EDIT_MODE = 'User Edit'; - readonly emailPattern = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'; readonly passwordPattern = '^(?=.*[A-Z])(?=.*[ !#$%&\'()*+,-./:;<=>?@\\[^_`{|}~\\\]"])(?=.*[0-9])(?=.*[a-z]).{8,32}$'; readonly dateFormat: string = 'yyyy-MM-dd HH:mm:ssZ'; readonly usernamePattern='^[a-zA-Z0-9]{4,32}$'; + mode: UserDetailsDialogMode; editMode: boolean; - formTitle: string; + userId: number; userRoles = []; existingRoles = []; userForm: FormGroup; @@ -79,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; } } @@ -91,12 +112,14 @@ 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.editMode = data.edit; - this.formTitle = this.editMode ? UserDetailsDialogComponent.EDIT_MODE: UserDetailsDialogComponent.NEW_MODE; + this.mode = data.mode; + this.userId = data.row && data.row.id; + this.editMode = this.mode !== UserDetailsDialogMode.NEW_MODE; this.current = this.editMode ? { @@ -117,7 +140,7 @@ export class UserDetailsDialogComponent { }; // The password authentication is if username exists - // if is off on clear than clear the username! + // if it's off on clear then clear the username! const bUserPasswordAuthentication: boolean = !!this.current.username; const bSetPassword: boolean = false; @@ -129,14 +152,15 @@ export class UserDetailsDialogComponent { // common values 'active': new FormControl({ value: ''},[]), 'emailAddress': new FormControl({ value:'' },[ Validators.pattern(this.emailPattern), Validators.maxLength(255)]), - 'role': new FormControl({ value: '' }, Validators.required), + 'role': new FormControl({ value: '', disabled: this.mode === UserDetailsDialogMode.PREFERENCES_MODE }, Validators.required), // username/password authentication 'userToggle': new FormControl(bUserPasswordAuthentication), - 'passwordToggle': new FormControl({value: bSetPassword, disabled:!bUserPasswordAuthentication}), + 'passwordToggle': new FormControl({value: bSetPassword, disabled: !bUserPasswordAuthentication}), 'username': new FormControl({ value: '', disabled: this.editMode || !bUserPasswordAuthentication }, - !this.editMode || !this.current.username ? [Validators.nullValidator, Validators.pattern(this.usernamePattern), - this.notInList(this.lookups.cachedServiceGroupOwnerList.map(a => a.username?a.username.toLowerCase():null))] : null), - // // improve notInList validator + !this.editMode || !this.current.username + ? [Validators.nullValidator, Validators.pattern(this.usernamePattern), this.notInList(this.lookups.cachedServiceGroupOwnerList.map(a => a.username ? a.username.toLowerCase() : null))] + : null), + // improve notInList validator 'password': new FormControl({ value: '', disabled: !bUserPasswordAuthentication || !bSetPassword}, [Validators.required, Validators.pattern(this.passwordPattern)]), 'confirmation': new FormControl({ value: '', disabled: !bUserPasswordAuthentication || !bSetPassword}, @@ -155,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); @@ -172,9 +197,9 @@ export class UserDetailsDialogComponent { this.userForm.controls['serialNumber'].setValue(this.current.certificate.serialNumber); this.userForm.controls['certificateId'].setValue(this.current.certificate.certificateId); - // if edit mode and user is given - toggle is dissabled - // username should not be changed.! - if (this.editMode && !!this.current.username){ + // if edit mode and user is given - toggle is disabled + // username should not be changed.! + if (this.editMode && bUserPasswordAuthentication){ this.userForm.controls['userToggle'].disable(); } } @@ -215,7 +240,6 @@ export class UserDetailsDialogComponent { } onCertificateToggleChanged({checked}: MatSlideToggleChange) { - if (checked) { // fill from temp this.userForm.controls['certificateId'].setValue( this.tempStoreForCertificate.certificateId); @@ -241,7 +265,6 @@ export class UserDetailsDialogComponent { this.userForm.controls['validFrom'].setValue(""); this.userForm.controls['validTo'].setValue(""); } - } onUserToggleChanged({checked}: MatSlideToggleChange) { @@ -258,7 +281,6 @@ export class UserDetailsDialogComponent { this.tempStoreForUser.username = this.userForm.controls['username'].value; this.tempStoreForUser.password = this.userForm.controls['password'].value; - this.userForm.controls['username'].setValue(""); this.userForm.controls['password'].setValue(""); } @@ -275,6 +297,10 @@ export class UserDetailsDialogComponent { } } + isPreferencesMode() { + return this.mode === UserDetailsDialogMode.PREFERENCES_MODE; + } + public getCurrent(): UserRo { this.current.active =this.userForm.get('active').value; this.current.emailAddress =this.userForm.get('emailAddress').value; @@ -346,3 +372,9 @@ export class UserDetailsDialogComponent { } } } + +export enum UserDetailsDialogMode { + NEW_MODE = 'New User', + EDIT_MODE = 'User Edit', + PREFERENCES_MODE = 'Edit', +} 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-angular/src/app/user/user.service.ts b/smp-angular/src/app/user/user.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a9a31236d374d2d5e0b33b173babb24191b0604 --- /dev/null +++ b/smp-angular/src/app/user/user.service.ts @@ -0,0 +1,27 @@ +import {Injectable} from '@angular/core'; +import {Observable, of, Subject} from 'rxjs'; +import {HttpClient} from '@angular/common/http'; +import {Role} from '../security/role.model'; +import {SmpConstants} from "../smp.constants"; +import {User} from "../security/user.model"; +import {AlertService} from "../alert/alert.service"; +import {SecurityService} from "../security/security.service"; + +@Injectable() +export class UserService { + + constructor( + private http: HttpClient, + private securityService: SecurityService, + private alertService: AlertService, + ) { } + + updateUser(user: User) { + this.http.put<string>(`${SmpConstants.REST_USER}/${user.id}`, user).subscribe(response => { + this.securityService.updateUserDetails(response); + this.alertService.success('The operation \'update user\' completed successfully.'); + }, err => { + this.alertService.exception('The operation \'update user\' not completed successfully.', err); + }); + } +} diff --git a/smp-server-library/pom.xml b/smp-server-library/pom.xml index cdc609be8ae8c0dbac413eeea5a226b25a3b5bec..7034665ad29c15f199f514d2f9a90cad33804368 100644 --- a/smp-server-library/pom.xml +++ b/smp-server-library/pom.xml @@ -218,6 +218,7 @@ <filtering>true</filtering> <excludes> <exclude>**/*.jks</exclude> + <exclude>**/*.crt</exclude> </excludes> </testResource> <testResource> @@ -226,6 +227,7 @@ <!-- With filtering=true Maven was introducing changes in keystore binary files [sic!] --> <includes> <include>**/*.jks</include> + <include>**/*.crt</include> </includes> </testResource> </testResources> diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/CertificateROToDBCertificateConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/CertificateROToDBCertificateConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..7b1c4113e7bed99fda369e0228ee12c5e266d182 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/CertificateROToDBCertificateConverter.java @@ -0,0 +1,32 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.DBCertificate; +import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * @author Sebastian-Ion TINCU + */ +@Component +public class CertificateROToDBCertificateConverter implements Converter<CertificateRO, DBCertificate> { + + @Override + public DBCertificate convert(CertificateRO source) { + DBCertificate target = new DBCertificate(); + if (source.getValidTo() != null) { + target.setValidTo(LocalDateTime.ofInstant(source.getValidTo().toInstant(), ZoneId.systemDefault())); + } + if (source.getValidFrom() != null) { + target.setValidFrom(LocalDateTime.ofInstant(source.getValidFrom().toInstant(), ZoneId.systemDefault())); + } + target.setCertificateId(source.getCertificateId()); + target.setSerialNumber(source.getSerialNumber()); + target.setIssuer(source.getIssuer()); + target.setSubject(source.getSubject()); + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/ConvertersRegistrar.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/ConvertersRegistrar.java new file mode 100644 index 0000000000000000000000000000000000000000..8669adc2844029c7814dcbeb1cfbd9f8060b23ac --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/ConvertersRegistrar.java @@ -0,0 +1,35 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * Registrar allowing registration of custom {@link org.springframework.core.convert.converter.Converter converters} for + * the back-end domain model and autowiring them with the {@link org.springframework.core.convert.ConversionService} and + * possibly other Spring beans (by first autowiring them into this registrar). + * + * @author Sebastian-Ion TINCU + */ +@Component +public class ConvertersRegistrar { + + private final Logger logger = LoggerFactory.getLogger(ConvertersRegistrar.class); + + @Autowired + private ConfigurableConversionService conversionRegistry; + + @Autowired + public void registerCustomConverters(List<Converter<?,?>> converters) { + for (Converter<?, ?> converter : converters) { + conversionRegistry.addConverter(converter); + } + + logger.info("Finished registering custom converters: {}", converters); + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBCertificateToCertificateROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBCertificateToCertificateROConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..a7e55effe436db6532412916a374553731465ba6 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBCertificateToCertificateROConverter.java @@ -0,0 +1,32 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.DBCertificate; +import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.sql.Date; +import java.time.ZoneOffset; + +/** + * @author Sebastian-Ion TINCU + */ +@Component +public class DBCertificateToCertificateROConverter implements Converter<DBCertificate, CertificateRO> { + + @Override + public CertificateRO convert(DBCertificate source) { + CertificateRO target = new CertificateRO(); + if (source.getValidTo() != null) { + target.setValidTo(Date.from(source.getValidTo().toInstant(ZoneOffset.UTC))); + } + if (source.getValidFrom() != null) { + target.setValidFrom(Date.from(source.getValidFrom().toInstant(ZoneOffset.UTC))); + } + target.setCertificateId(source.getCertificateId()); + target.setSerialNumber(source.getSerialNumber()); + target.setIssuer(source.getIssuer()); + target.setSubject(source.getSubject()); + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..e3d69649b0edfae28847e8fd0bae4eb4c2782d06 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java @@ -0,0 +1,36 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; +import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +/** + * @author Sebastian-Ion TINCU + */ +@Component +public class DBUserToUserROConverter implements Converter<DBUser, UserRO> { + + @Autowired + private ConversionService conversionService; + + @Override + public UserRO convert(DBUser source) { + UserRO target = new UserRO(); + target.setEmailAddress(source.getEmailAddress()); + target.setUsername(source.getUsername()); + target.setRole(source.getRole()); + target.setPassword(source.getPassword()); + target.setPasswordChanged(source.getPasswordChanged()); + target.setActive(source.isActive()); + target.setId(source.getId()); + if (source.getCertificate() != null) { + CertificateRO certificateRO = conversionService.convert(source.getCertificate(), CertificateRO.class); + target.setCertificate(certificateRO); + } + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..7dc34f62a75a8b363ccb30a5389e0183fb64e1bf --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java @@ -0,0 +1,36 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.DBCertificate; +import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +/** + * @author Sebastian-Ion TINCU + */ +@Component +public class UserROToDBUserConverter implements Converter<UserRO, DBUser> { + + @Autowired + private ConversionService conversionService; + + @Override + public DBUser convert(UserRO source) { + DBUser dro = new DBUser(); + dro.setEmailAddress(source.getEmailAddress()); + dro.setUsername(source.getUsername()); + dro.setRole(source.getRole()); + dro.setPassword(source.getPassword()); + dro.setActive(source.isActive()); + dro.setId(source.getId()); + dro.setPasswordChanged(source.getPasswordChanged()); + if (source.getCertificate() != null) { + DBCertificate certData = conversionService.convert(source.getCertificate(), DBCertificate.class); + dro.setCertificate(certData); + } + return dro; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java index e4d8e98ba64c1cd7f69990a87e8ad2a2fd6e7900..49e0b2860da5cd7f19da8a99b9dfcf31e4e8a027 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java @@ -36,10 +36,8 @@ import static eu.europa.ec.edelivery.smp.exceptions.ErrorCode.ILLEGAL_STATE_USER @Repository public class UserDao extends BaseDao<DBUser> { - - /** - * Persis user to database. Before that test if user has identifiers. Usernames are saved to database in lower caps + * Persists the user to the database. Before that test if user has identifiers. Usernames are saved to database in lower caps * @param user */ @Override @@ -57,7 +55,18 @@ public class UserDao extends BaseDao<DBUser> { } /** - * Method finds user by identifier. User identifier is username or certificateId. First it tries to find user by username + * Searches for a user entity by its primary key and returns it if found. Returns an empty {@code Optional} if missing. + * + * @param userId The primary key of the user entity to find + * @return an optional user entity + */ + public Optional<DBUser> findUser(Long userId) { + DBUser dbUser = memEManager.find(DBUser.class, userId); + return Optional.ofNullable(dbUser); + } + + /** + * Finds a user by identifier. User identifier is username or certificateId. First it tries to find user by username * and than by certificate id. If user does not exists Optional with isPresent - false is returned. * @param identifier * @return resturns Optional DBUser for identifier diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java index 4b52c4f3a0e8e51ea40b9203ddd2c455f814907f..d47773cb2914d5d5639cdb0ef32d10d369e47774 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java @@ -12,14 +12,15 @@ import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import sun.java2d.pipe.SpanShapeRenderer; import java.io.*; import java.math.BigInteger; @@ -27,11 +28,9 @@ import java.net.URLEncoder; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.sql.Date; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; -import java.time.ZoneOffset; import java.util.Arrays; import java.util.Base64; import java.util.List; @@ -42,12 +41,16 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UIUserService.class); private static final byte[] S_PEM_START_TAG = "-----BEGIN CERTIFICATE-----\n".getBytes(); + private static final byte[] S_PEM_END_TAG = "\n-----END CERTIFICATE-----".getBytes(); private static final String S_BLUECOAT_DATEFORMAT ="MMM dd HH:mm:ss yyyy"; @Autowired - UserDao userDao; + private UserDao userDao; + + @Autowired + private ConversionService conversionService; @Override protected BaseDao<DBUser> getDatabaseDao() { @@ -62,13 +65,10 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { * @param sortField * @param sortOrder * @param filter - * @return ServiceResult wiht list + * @return ServiceResult with list */ @Transactional - public ServiceResult<UserRO> getTableList(int page, int pageSize, - String sortField, - String sortOrder, Object filter) { - + public ServiceResult<UserRO> getTableList(int page, int pageSize, String sortField, String sortOrder, Object filter) { ServiceResult<UserRO> resUsers = super.getTableList(page, pageSize, sortField, sortOrder, filter); resUsers.getServiceEntities().forEach(usr -> usr.setPassword(null)); return resUsers; @@ -78,7 +78,6 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { public void updateUserList(List<UserRO> lst) { boolean suc = false; for (UserRO userRO : lst) { - if (userRO.getStatus() == EntityROStatus.NEW.getStatusNumber()) { DBUser dbUser = convertFromRo(userRO); if (!StringUtils.isBlank(userRO.getPassword())) { @@ -125,8 +124,19 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { } } - public CertificateRO getCertificateData(byte[] buff) throws CertificateException, IOException { + /** + * Returns the user entity by its primary key or throws a {@code SMPRuntimeException} if such entity does not exist. + * + * @param userId The primary key of the user entity + * @return the user entity + * @throws SMPRuntimeException if a user entity having the provided primary key does not exist. + */ + @Transactional(readOnly = true) + public DBUser findUser(Long userId) { + return userDao.findUser(userId).orElseThrow(() -> new SMPRuntimeException(ErrorCode.USER_NOT_EXISTS)); + } + public CertificateRO getCertificateData(byte[] buff) throws CertificateException, IOException { // get pem encoding - InputStream isCert = createPEMFormat(buff); @@ -227,9 +237,6 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { return null; } return Arrays.copyOfRange(buff, iStart, iEnd + S_PEM_END_TAG.length); - - - } /** @@ -258,32 +265,7 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { @Override public UserRO convertToRo(DBUser d) { - - UserRO dro = new UserRO(); - dro.setEmailAddress(d.getEmailAddress()); - dro.setUsername(d.getUsername()); - dro.setRole(d.getRole()); - dro.setPassword(d.getPassword()); - dro.setPasswordChanged(d.getPasswordChanged()); - dro.setActive(d.isActive()); - dro.setId(d.getId()); - - if (d.getCertificate() != null) { - CertificateRO certData = new CertificateRO(); - if (d.getCertificate().getValidTo() != null) { - - certData.setValidTo(Date.from(d.getCertificate().getValidTo().toInstant(ZoneOffset.UTC))); - } - if (d.getCertificate().getValidFrom() != null) { - certData.setValidFrom(Date.from(d.getCertificate().getValidFrom().toInstant(ZoneOffset.UTC))); - } - certData.setCertificateId(d.getCertificate().getCertificateId()); - certData.setSerialNumber(d.getCertificate().getSerialNumber()); - certData.setIssuer(d.getCertificate().getIssuer()); - certData.setSubject(d.getCertificate().getSubject()); - dro.setCertificate(certData); - } - return dro; + return conversionService.convert(d, UserRO.class); } public DeleteEntityValidation validateDeleteRequest(DeleteEntityValidation dev){ @@ -306,31 +288,6 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { @Override public DBUser convertFromRo(UserRO d) { - DBUser dro = new DBUser(); - dro.setEmailAddress(d.getEmailAddress()); - dro.setUsername(d.getUsername()); - dro.setRole(d.getRole()); - dro.setPassword(d.getPassword()); - dro.setActive(d.isActive()); - dro.setId(d.getId()); - dro.setPasswordChanged(d.getPasswordChanged()); - if (d.getCertificate() != null) { - DBCertificate certData = new DBCertificate(); - if (d.getCertificate().getValidTo() != null) { - certData.setValidTo(LocalDateTime.ofInstant(d.getCertificate().getValidTo().toInstant(), ZoneId.systemDefault())); - } - if (d.getCertificate().getValidFrom() != null) { - certData.setValidFrom(LocalDateTime.ofInstant(d.getCertificate().getValidFrom().toInstant(), ZoneId.systemDefault())); - } - certData.setCertificateId(d.getCertificate().getCertificateId()); - certData.setSerialNumber(d.getCertificate().getSerialNumber()); - certData.setIssuer(d.getCertificate().getIssuer()); - certData.setSubject(d.getCertificate().getSubject()); - - dro.setCertificate(certData); - } - return dro; - + return conversionService.convert(d, DBUser.class); } - } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/ConversionTestConfig.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/ConversionTestConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..d523cc6dba4c3e496050035196fbb81937236a68 --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/ConversionTestConfig.java @@ -0,0 +1,19 @@ +package eu.europa.ec.edelivery.smp.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ConversionServiceFactoryBean; + +/** + * @author Sebastian-Ion TINCU + */ +@Configuration +@ComponentScan("eu.europa.ec.edelivery.smp.conversion") +public class ConversionTestConfig { + + @Bean + public ConversionServiceFactoryBean conversionService() { + return new ConversionServiceFactoryBean(); + } +} diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/H2JPATestConfiguration.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/H2JPATestConfig.java similarity index 98% rename from smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/H2JPATestConfiguration.java rename to smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/H2JPATestConfig.java index 62492632775a4a64da5abbf2c68cfdf6fd20a69d..bf1c76de708183de560f798218e8b27422b923bd 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/H2JPATestConfiguration.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/H2JPATestConfig.java @@ -1,6 +1,5 @@ package eu.europa.ec.edelivery.smp.config; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -22,7 +21,8 @@ import java.sql.SQLException; @Configuration @PropertySource("./persistence-test-h2.properties") @EnableTransactionManagement -public class H2JPATestConfiguration { +public class H2JPATestConfig { + @Autowired private Environment env; diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractBaseDao.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractBaseDao.java index 538f03dcbcf1157250f9182a299c266f70a615a4..a5e3e4c09a6cd5103ecb63c83058d0af77c870aa 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractBaseDao.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractBaseDao.java @@ -1,16 +1,14 @@ package eu.europa.ec.edelivery.smp.data.dao; - -import eu.europa.ec.edelivery.smp.config.H2JPATestConfiguration; +import eu.europa.ec.edelivery.smp.config.H2JPATestConfig; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {H2JPATestConfiguration.class, +@ContextConfiguration(classes = {H2JPATestConfig.class, ServiceGroupDao.class, ServiceMetadataDao.class, DomainDao.class, UserDao.class}) @Sql(scripts = "classpath:cleanup-database.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, config = @SqlConfig @@ -19,5 +17,4 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; dataSource = "h2DataSource")) public abstract class AbstractBaseDao { - } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AuditIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AuditIntegrationTest.java index a6778b6f1ae02b4f97fe8db2632761b28f4e88b1..653dd60bfe9f0129b583fb45b122760f93df5cab 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AuditIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AuditIntegrationTest.java @@ -10,10 +10,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. */ - package eu.europa.ec.edelivery.smp.data.dao; -import eu.europa.ec.edelivery.smp.config.H2JPATestConfiguration; +import eu.europa.ec.edelivery.smp.config.H2JPATestConfig; import eu.europa.ec.edelivery.smp.data.model.*; import org.hibernate.envers.AuditReader; import org.hibernate.envers.AuditReaderFactory; @@ -36,7 +35,6 @@ import java.util.UUID; import static eu.europa.ec.edelivery.smp.testutil.TestDBUtils.createDBDomain; import static org.junit.Assert.assertTrue; - /** * Purpose of class is to test all Audit classes and methods with database. * @@ -44,7 +42,7 @@ import static org.junit.Assert.assertTrue; * @since 4.1 */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {H2JPATestConfiguration.class}) +@ContextConfiguration(classes = {H2JPATestConfig.class}) @Sql(scripts = "classpath:cleanup-database.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, config = @SqlConfig (transactionMode = SqlConfig.TransactionMode.ISOLATED, transactionManager = "transactionManager", @@ -56,8 +54,6 @@ public class AuditIntegrationTest { @PersistenceUnit EntityManagerFactory emf; - - @Test public void testClassesForAudit() { AuditReader ar = AuditReaderFactory.get(emf.createEntityManager()); @@ -69,7 +65,6 @@ public class AuditIntegrationTest { assertTrue(ar.isEntityClassAudited(DBServiceGroupExtension.class)); } - @Test public void testAuditDBDomain() { diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoIntegrationTest.java index ce7f681c4c083b12124eacd18839161f5567df2a..a58e342bd42b1ca574b2682275db9a09f7a9afba 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoIntegrationTest.java @@ -1,6 +1,5 @@ package eu.europa.ec.edelivery.smp.data.dao; -import eu.europa.ec.edelivery.smp.config.H2JPATestConfiguration; import eu.europa.ec.edelivery.smp.data.model.*; import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; import eu.europa.ec.edelivery.smp.testutil.TestConstants; @@ -8,12 +7,7 @@ import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.Collections; import java.util.List; @@ -38,7 +32,6 @@ public class DomainDaoIntegrationTest extends AbstractBaseDao { @Rule public ExpectedException expectedEx = ExpectedException.none(); - @Test public void persistDomain() { // set @@ -93,7 +86,6 @@ public class DomainDaoIntegrationTest extends AbstractBaseDao { assertTrue(!res.isPresent()); } - @Test public void getDomainByCodeExists() { // set @@ -162,7 +154,6 @@ public class DomainDaoIntegrationTest extends AbstractBaseDao { assertFalse(optDmn.isPresent()); } - @Test public void testValidateUsersForDeleteOKScenario() { // set @@ -185,7 +176,6 @@ public class DomainDaoIntegrationTest extends AbstractBaseDao { serviceGroupDao.persistFlushDetach(sg); - // execute List<DBDomainDeleteValidation> lst = testInstance.validateDomainsForDelete(Collections.singletonList(d.getId())); assertEquals(1, lst.size()); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceGroupDaoIntegrationBase.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceGroupDaoIntegrationBase.java index f6da7769f20d8b1d7bedc07b44808334bc835c38..3dfa1a99a79a83abf22afcf3a9b3ecbdb6b1b7a1 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceGroupDaoIntegrationBase.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceGroupDaoIntegrationBase.java @@ -1,6 +1,5 @@ package eu.europa.ec.edelivery.smp.data.dao; -import eu.europa.ec.edelivery.smp.config.H2JPATestConfiguration; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup; import eu.europa.ec.edelivery.smp.data.model.DBServiceMetadata; @@ -11,15 +10,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; import javax.transaction.Transactional; import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*; - /** * Purpose of class is to test all resource methods with database. * @@ -55,7 +50,6 @@ public abstract class ServiceGroupDaoIntegrationBase extends AbstractBaseDao{ userDao.persistFlushDetach(u3); } - public DBServiceGroup createAndSaveNewServiceGroup(){ return createAndSaveNewServiceGroup(TEST_DOMAIN_CODE_1, TestConstants.TEST_SG_ID_1, TestConstants.TEST_SG_SCHEMA_1); } @@ -75,7 +69,6 @@ public abstract class ServiceGroupDaoIntegrationBase extends AbstractBaseDao{ return sg; } - public void createAndSaveNewServiceGroups(int iCount, String domain, String participant){ createAndSaveNewServiceGroups(iCount, domain, participant, null); } @@ -119,5 +112,4 @@ public abstract class ServiceGroupDaoIntegrationBase extends AbstractBaseDao{ testInstance.update(sg); } - } \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceMetadataDaoIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceMetadataDaoIntegrationTest.java index 8ef4e1198fb7f5a880e51d50f8b2333f18e9e1c1..1a81d1cba1cd0cfcceaffc3e5edeb559891e31ac 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceMetadataDaoIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/ServiceMetadataDaoIntegrationTest.java @@ -1,6 +1,5 @@ package eu.europa.ec.edelivery.smp.data.dao; -import eu.europa.ec.edelivery.smp.config.H2JPATestConfiguration; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup; import eu.europa.ec.edelivery.smp.data.model.DBServiceMetadata; @@ -11,12 +10,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.context.jdbc.SqlConfig; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.transaction.Transactional; import java.util.List; @@ -116,5 +110,4 @@ public class ServiceMetadataDaoIntegrationTest extends AbstractBaseDao { assertEquals(2, lst2.size()); } - } \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java index 1aacf70e34257531d47268887fa9906ce6c24b54..8ad02e5c2a8f7b4b6140fd4a9723c74f13781cbc 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java @@ -1,7 +1,7 @@ package eu.europa.ec.edelivery.smp.services; -import eu.europa.ec.edelivery.smp.config.H2JPATestConfiguration; +import eu.europa.ec.edelivery.smp.config.H2JPATestConfig; import eu.europa.ec.edelivery.smp.config.PropertiesSingleDomainTestConfig; import eu.europa.ec.edelivery.smp.conversion.CaseSensitivityNormalizer; import eu.europa.ec.edelivery.smp.data.dao.DomainDao; @@ -33,7 +33,7 @@ import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*; */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {H2JPATestConfiguration.class,PropertiesSingleDomainTestConfig.class, +@ContextConfiguration(classes = {H2JPATestConfig.class,PropertiesSingleDomainTestConfig.class, CaseSensitivityNormalizer.class,SmlConnector.class,ServiceMetadataSigner.class, ServiceGroupService.class, DomainService.class, ServiceMetadataService.class, ServiceGroupDao.class,ServiceMetadataDao.class, DomainDao.class, UserDao.class,DBAssertion.class}) @@ -175,5 +175,4 @@ public abstract class AbstractServiceIntegrationTest { sg3.getServiceGroupDomains().get(1).addServiceMetadata(sg3md2); serviceGroupDao.update(sg3); } - } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java index a261fdc55c9af6f6ac8031f2ca2bfa91b82806f1..6791adeb22bcc134d46bfec931d5e7198ea80d60 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java @@ -1,6 +1,7 @@ package eu.europa.ec.edelivery.smp.services.ui; +import eu.europa.ec.edelivery.smp.config.ConversionTestConfig; import eu.europa.ec.edelivery.smp.data.model.DBCertificate; import eu.europa.ec.edelivery.smp.data.model.DBUser; import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; @@ -17,14 +18,21 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.test.context.ContextConfiguration; -import java.io.FileInputStream; import java.io.IOException; import java.security.cert.CertificateException; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Optional; +import java.util.UUID; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** @@ -33,7 +41,7 @@ import static org.junit.Assert.*; * @author Joze Rihtarsic * @since 4.1 */ -@ContextConfiguration(classes= UIUserService.class) +@ContextConfiguration(classes = {UIUserService.class, ConversionTestConfig.class}) public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest { @Rule public ExpectedException expectedExeption = ExpectedException.none(); @@ -51,11 +59,11 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest @Test public void testGetTableListEmpty() { - // given //when ServiceResult<UserRO> res = testInstance.getTableList(-1, -1, null, null, null); + // then assertNotNull(res); assertEquals(0, res.getCount().intValue()); @@ -67,9 +75,9 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest @Test public void testGetTableList15() { - // given insertDataObjects(15); + //when ServiceResult<UserRO> res = testInstance.getTableList(-1, -1, null, null, null); @@ -127,6 +135,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest //when testInstance.updateUserList(Collections.singletonList(user)); + // then long iCntNew = userDao.getDataListCount(null); assertEquals(iCnt+1, iCntNew); @@ -166,9 +175,9 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest user.setStatus(EntityROStatus.NEW.getStatusNumber()); - //when testInstance.updateUserList(Collections.singletonList(user)); + // then long iCntNew = userDao.getDataListCount(null); assertEquals(iCnt+1, iCntNew); @@ -212,9 +221,9 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest user.setStatus(EntityROStatus.NEW.getStatusNumber()); - //when testInstance.updateUserList(Collections.singletonList(user)); + // then long iCntNew = userDao.getDataListCount(null); assertEquals(iCnt+1, iCntNew); @@ -262,6 +271,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest userRO.setStatus(EntityROStatus.UPDATED.getStatusNumber()); testInstance.updateUserList(Collections.singletonList(userRO)); + // then ServiceResult<UserRO> res = testInstance.getTableList(-1,-1,null, null, null); assertEquals(1, urTest.getServiceEntities().size()); @@ -296,8 +306,10 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest public void testGetCertificateDataPEM() throws IOException, CertificateException { // given byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/SMPtest.crt")); + // when CertificateRO cer = testInstance.getCertificateData(buff); + //then assertEquals("CN=SMP test,O=DIGIT,C=BE:0000000000000003", cer.getCertificateId()); assertEquals("CN=Intermediate CA, O=DIGIT, C=BE", cer.getIssuer()); @@ -312,8 +324,10 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest public void testGetCertificateDataPEMWithHeader() throws IOException, CertificateException { // given byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/pem-with-header.crt")); + // when CertificateRO cer = testInstance.getCertificateData(buff); + //then assertEquals("CN=alice,O=www.freelan.org,C=FR:0000000000000001", cer.getCertificateId()); assertEquals("EMAILADDRESS=contact@freelan.org, CN=Freelan Sample Certificate Authority, OU=freelan, O=www.freelan.org, L=Strasbourg, ST=Alsace, C=FR", cer.getIssuer()); @@ -324,15 +338,14 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest assertTrue(cer.getValidFrom().before(cer.getValidTo())); } - - @Test public void testGetCertificateDataDER() throws IOException, CertificateException { // given - byte[] buff = IOUtils.toByteArray(new FileInputStream("src/test/resources/truststore/NewPeppolAP.crt")); + byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/NewPeppolAP.crt")); // when CertificateRO cer = testInstance.getCertificateData(buff); + //then assertEquals("CN=POP000004,O=European Commission,C=BE:474980c51478cf62761667461aef5e8e", cer.getCertificateId()); assertEquals("CN=PEPPOL ACCESS POINT TEST CA - G2, OU=FOR TEST ONLY, O=OpenPEPPOL AISBL, C=BE", cer.getIssuer()); @@ -342,5 +355,4 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest assertNotNull(cer.getValidTo()); assertTrue(cer.getValidFrom().before(cer.getValidTo())); } - } 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 new file mode 100644 index 0000000000000000000000000000000000000000..923c635c570c34e90b2265b9c70c7eada37ffedf --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java @@ -0,0 +1,52 @@ +package eu.europa.ec.edelivery.smp.auth; + +import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import static eu.europa.ec.edelivery.smp.auth.SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN; +import static java.util.stream.Collectors.toList; + +/** + * @author Sebastian-Ion TINCU + */ +@Service("smpAuthorizationService") +public class SMPAuthorizationService { + + public boolean isSystemAdministrator() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication instanceof SMPAuthenticationToken + && authentication.getAuthorities().stream().anyMatch(grantedAuthority -> S_AUTHORITY_TOKEN_SYSTEM_ADMIN.equals(grantedAuthority.getAuthority())); + } + + public boolean isCurrentlyLoggedIn(Long userId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if(authentication instanceof SMPAuthenticationToken) { + Long loggedInUserId = ((SMPAuthenticationToken) authentication).getUser().getId(); + return loggedInUserId.equals(userId); + } + + return false; + } + /** + * Returns a user resource with password credentials removed and authorities populated for use in the front-end. + * + * @param userRO The user resource to sanitize for use in the front-end. + * @return the sanitized user resource + */ + public UserRO sanitize(UserRO userRO) { + userRO.setPassword(""); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if(authentication instanceof SMPAuthenticationToken) { + userRO.setAuthorities( + authentication.getAuthorities() + .stream() + .map(authority -> authority.getAuthority()) + .collect(toList())); + } + + return userRO; + } +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java index abe9526b17b6d1efb8d0f2135573a3707c71af03..cac805896d084a49c82bf4c970ec23095aa04335 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SmpWebAppConfig.java @@ -42,6 +42,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; @ComponentScan(basePackages = { "eu.europa.ec.edelivery.smp.controllers", "eu.europa.ec.edelivery.smp.validation", + "eu.europa.ec.edelivery.smp.conversion", "eu.europa.ec.edelivery.smp.monitor", "eu.europa.ec.edelivery.smp.ui"}) @Import({GlobalMethodSecurityConfig.class, ErrorMappingControllerAdvice.class}) diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java index 3a80d1972eb96f6d379d7f1875bdea647d10aa0b..2ae1ded9b8f3c5de61e71199635b8a0f58412433 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResource.java @@ -1,14 +1,17 @@ package eu.europa.ec.edelivery.smp.ui; +import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationService; +import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; import eu.europa.ec.edelivery.smp.auth.SMPAuthority; +import eu.europa.ec.edelivery.smp.auth.SMPAuthorizationService; import eu.europa.ec.edelivery.smp.data.ui.ErrorRO; import eu.europa.ec.edelivery.smp.data.ui.LoginRO; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; -import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; import org.springframework.security.access.annotation.Secured; import org.springframework.security.authentication.BadCredentialsException; @@ -23,8 +26,6 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import static java.util.stream.Collectors.toList; - /** * @author Sebastian-Ion TINCU * @since 4.0 @@ -38,6 +39,12 @@ public class AuthenticationResource { @Autowired protected SMPAuthenticationService authenticationService; + @Autowired + protected SMPAuthorizationService authorizationService; + + @Autowired + private ConversionService conversionService; + @ResponseStatus(value = HttpStatus.FORBIDDEN) @ExceptionHandler({AuthenticationException.class}) public ErrorRO handleException(Exception ex) { @@ -49,16 +56,9 @@ public class AuthenticationResource { @Transactional(noRollbackFor = BadCredentialsException.class) public UserRO authenticate(@RequestBody LoginRO loginRO, HttpServletResponse response) { LOG.debug("Authenticating user [{}]", loginRO.getUsername()); - final Authentication principal = authenticationService.authenticate(loginRO.getUsername(), loginRO.getPassword()); - - UserRO userRO = new UserRO(); - userRO.setUsername(loginRO.getUsername()); - userRO.setAuthorities( - principal.getAuthorities() - .stream() - .map(authority -> authority.getAuthority()) - .collect(toList())); - return userRO; + SMPAuthenticationToken authentication = (SMPAuthenticationToken) authenticationService.authenticate(loginRO.getUsername(), loginRO.getPassword()); + UserRO userRO = conversionService.convert(authentication.getUser(), UserRO.class); + return authorizationService.sanitize(userRO); } @RequestMapping(value = "authentication", method = RequestMethod.DELETE) @@ -76,18 +76,16 @@ public class AuthenticationResource { LOG.info("Logged out"); } - // @PutMapping(produces = {"text/plain"}) @RequestMapping(value = "user", method = RequestMethod.GET) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) public UserRO getUser() { - // User securityUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - //return securityUser.getUsername(); - String username = (String)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - System.out.println("get user: " + username); - UserRO ur =new UserRO(); - ur.setUsername(username); - return ur; + UserRO user = new UserRO(); + + String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + LOG.debug("get user: {}", username); + user.setUsername(username); + return user; } } \ No newline at end of file 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 affa1fbd042b4c456316d036f4d51a259aedc6c0..8fd5eeac096722b846c17f6fc81441318d7532f1 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,9 +1,8 @@ package eu.europa.ec.edelivery.smp.ui; - import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; import eu.europa.ec.edelivery.smp.auth.SMPAuthority; -import eu.europa.ec.edelivery.smp.auth.SMPRole; +import eu.europa.ec.edelivery.smp.auth.SMPAuthorizationService; import eu.europa.ec.edelivery.smp.data.model.DBUser; import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; @@ -15,11 +14,12 @@ import eu.europa.ec.edelivery.smp.services.ui.UIUserService; import eu.europa.ec.edelivery.smp.services.ui.filters.UserFilter; import org.springframework.beans.factory.annotation.Autowired; 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 javax.annotation.PostConstruct; import java.io.IOException; import java.security.cert.CertificateException; import java.util.Arrays; @@ -29,7 +29,6 @@ import java.util.List; * @author Joze Rihtarsic * @since 4.1 */ - @RestController @RequestMapping(value = "/ui/rest/user") public class UserResource { @@ -39,15 +38,11 @@ public class UserResource { @Autowired private UIUserService uiUserService; - @PostConstruct - protected void init() { - - } + @Autowired + protected SMPAuthorizationService authorizationService; @PutMapping(produces = {"application/json"}) - @ResponseBody @RequestMapping(method = RequestMethod.GET) - //update gui to call this when somebody is logged in. @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN}) public ServiceResult<UserRO> getUsers( @RequestParam(value = "page", defaultValue = "0") int page, @@ -56,8 +51,8 @@ public class UserResource { @RequestParam(value = "orderType", defaultValue = "asc", required = false) String orderType, @RequestParam(value = "roles", required = false) String roleList ) { - UserFilter filter =null; - if (roleList!=null){ + UserFilter filter = null; + if (roleList != null) { filter = new UserFilter(); filter.setRoleList(Arrays.asList(roleList.split(","))); } @@ -65,17 +60,37 @@ public class UserResource { return uiUserService.getTableList(page,pageSize, orderBy, orderType, filter); } + /** + * Update the details of the currently logged in user (e.g. update the role, the credentials or add certificate details). + * + * @param id the identifier of the user being updated; it must match the currently logged in user's identifier + * @param user the updated details + * + * @throws org.springframework.security.access.AccessDeniedException when trying to update the details of another user, different than the one being currently logged in + */ + @PutMapping(path = "/{id}") + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#id)") + public UserRO updateCurrentUser(@PathVariable("id") Long id, @RequestBody UserRO user) { + LOG.info("Update current user: {}", user); + + uiUserService.updateUserList(Arrays.asList(user)); + + DBUser updatedUser = uiUserService.findUser(id); + UserRO userRO = uiUserService.convertToRo(updatedUser); + + return authorizationService.sanitize(userRO); + } + @PutMapping(produces = {"application/json"}) - @RequestMapping(method = RequestMethod.PUT) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) - public void updateUserList(@RequestBody(required = true) UserRO[] updateEntities ){ - LOG.info("Update user list, count: {}" + updateEntities.length); + public void updateUserList(@RequestBody UserRO[] updateEntities ){ + LOG.info("Update user list, count: {}", updateEntities.length); uiUserService.updateUserList(Arrays.asList(updateEntities)); } - @RequestMapping(path = "certdata", method = RequestMethod.POST) - @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) - public CertificateRO uploadFile(@RequestBody byte[] data) { + @PostMapping("/{id}/certdata") + @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") + public CertificateRO uploadFile(@PathVariable("id") Long id, @RequestBody byte[] data) { LOG.info("Got certificate data: " + data.length); try { return uiUserService.getCertificateData(data); @@ -83,17 +98,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 user = uiUserService.findUser(getCurrentUser().getId()); + return BCrypt.checkpw(password, user.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); @@ -103,4 +122,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(); + } }