diff --git a/changelog.txt b/changelog.txt index 0f6c13938319850d7317e8f9f352c3eadd3915ee..ab2dec575b9b2f2f1b8e09fbe017892b6c24a076 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,7 @@ eDelivery SMP 4.2 - added new properties: + smp.passwordPolicy.validationRegex: Regular expression do define password minimum complexity rules! + smp.passwordPolicy.validationMessage: The error message shown to the user in case the password does not follow the regex put in the domibus.passwordPolicy.pattern property" smp.ui.authentication.types: Set list of '|' separated UI authentication types. Currently supported PASSWORD, SSO: ex. PASSWORD|SSO smp.automation.authentication.types: Set list of '|' separated automation authentication types (Web-Service integration). Currently supported PASSWORD, CERT: ex. PASSWORD|CERT smp.http.forwarded.headers.enabled to control usage of Forwarded parameters RP/LoadBalancer. diff --git a/pom.xml b/pom.xml index 5e08d1af1528909e9cbba9ced8db828d8c630a0d..750159a5f86c9bb3804a88baf3fabc759fb03e40 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,6 @@ <cxf.version>3.4.5</cxf.version> <dnsjava.version>2.1.7</dnsjava.version> <ehcache.version>2.10.6</ehcache.version> - <guava.version>24.1.1-jre</guava.version> <h2.version>1.4.187</h2.version> <hamcrest.version>2.0.0.0</hamcrest.version> <hibernate-jpa.version>1.0.2.Final</hibernate-jpa.version> @@ -351,11 +350,6 @@ <artifactId>orika-core</artifactId> <version>${orika.version}</version> </dependency> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <version>${guava.version}</version> - </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> diff --git a/smp-angular/proxy-config.json b/smp-angular/proxy-config.json index dc458d6f4ac03189d41aa649490f4f60bebc112a..b885c7fec301933136d38ed42c291b3ce9aae0b9 100644 --- a/smp-angular/proxy-config.json +++ b/smp-angular/proxy-config.json @@ -11,5 +11,31 @@ "/smp": "/" }, "cookieDomainRewrite": "localhost" + }, + "/public/**": { + "target" : "http://localhost:8080/", + "secure" : "false", + "pathRewrite": { + "^/public": "/smp/ui/public" + }, + "logLevel": "debug", + "changeOrigin": true, + "cookiePathRewrite": { + "/smp": "/" + }, + "cookieDomainRewrite": "localhost" + }, + "/internal/**": { + "target" : "http://localhost:8080/", + "secure" : "false", + "pathRewrite": { + "^/internal": "/smp/ui/internal" + }, + "logLevel": "debug", + "changeOrigin": true, + "cookiePathRewrite": { + "/smp": "/" + }, + "cookieDomainRewrite": "localhost" } } diff --git a/smp-angular/src/app/app-config/smp-config.model.ts b/smp-angular/src/app/app-config/smp-config.model.ts index 5d6942161083e64e171dddd1ae9316b0329b6416..c1469f866513fcfc980052dd86f81bdf0f90c6af 100644 --- a/smp-angular/src/app/app-config/smp-config.model.ts +++ b/smp-angular/src/app/app-config/smp-config.model.ts @@ -3,4 +3,6 @@ export interface SmpConfig { smlParticipantMultiDomainOn?: boolean; participantSchemaRegExp?: string; participantSchemaRegExpMessage?: string; + passwordValidationRegExp?: string; + passwordValidationRegExpMessage?: string; } diff --git a/smp-angular/src/app/app-config/smp-config.service.ts b/smp-angular/src/app/app-config/smp-config.service.ts index 07d3a590918b755564a31c06636ebce58fbbce0a..56aeeee659d613683f0efe6e1c9e9f66016e5fe9 100644 --- a/smp-angular/src/app/app-config/smp-config.service.ts +++ b/smp-angular/src/app/app-config/smp-config.service.ts @@ -12,7 +12,7 @@ export class SmpConfigService { getSmpInfo(): Observable<SmpConfig> { let subject = new ReplaySubject<SmpConfig>(); - this.http.get<SmpConfig>(SmpConstants.REST_CONFIG) + this.http.get<SmpConfig>(SmpConstants.REST_INTERNAL_APPLICATION_CONFIG) .subscribe((res: SmpConfig) => { subject.next(res); }, error => { diff --git a/smp-angular/src/app/app-info/smp-info.service.ts b/smp-angular/src/app/app-info/smp-info.service.ts index a336538b8bacc21d1b9fe3e83029c8c32aa89b01..c8e898a8e166c49e92d5e32eefe0581184fe9f26 100644 --- a/smp-angular/src/app/app-info/smp-info.service.ts +++ b/smp-angular/src/app/app-info/smp-info.service.ts @@ -12,7 +12,7 @@ export class SmpInfoService { getSmpInfo(): Observable<SmpInfo> { let subject = new ReplaySubject<SmpInfo>(); - this.http.get<SmpInfo>(SmpConstants.REST_APPLICATION) + this.http.get<SmpInfo>(SmpConstants.REST_PUBLIC_APPLICATION_INFO) .subscribe((res: SmpInfo) => { subject.next(res); }, error => { diff --git a/smp-angular/src/app/app.component.html b/smp-angular/src/app/app.component.html index d33059101c681ca80d210edcb0a66ffe97d424f9..aa95545668e3ff89763ee693766cb39ae7cf7e11 100644 --- a/smp-angular/src/app/app.component.html +++ b/smp-angular/src/app/app.component.html @@ -77,10 +77,17 @@ <mat-menu x-position="before" #settingsMenu="matMenu"> <div *ngIf="currentUser"> + <button mat-menu-item id="currentuser_id" (click)="editCurrentUser()"> <mat-icon>person</mat-icon> <span>{{currentUser}}</span> </button> + <button mat-menu-item id="changePassword_id" (click)="changeCurrentUserPassword()"> + <span>Change password</span> + </button> + <button mat-menu-item id="getAccessToken_id" (click)="regenerateAccesesToken()"> + <span>Generated access token</span> + </button> <hr/> diff --git a/smp-angular/src/app/app.component.ts b/smp-angular/src/app/app.component.ts index c0995f8643125b510ed7faa6a849d717b47ec7c0..d1155654c11b0d9d3ed2e157ff06b0004342737a 100644 --- a/smp-angular/src/app/app.component.ts +++ b/smp-angular/src/app/app.component.ts @@ -59,6 +59,24 @@ export class AppComponent { }); } + changeCurrentUserPassword() { + const formRef: MatDialogRef<any> = this.userController.changePasswordDialog({ + data: this.securityService.getCurrentUser() + }); + } + + regenerateAccesesToken() { + const formRef: MatDialogRef<any> = this.userController.generateAccessTokenDialog({ + data: 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 : ""; diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index 8ef283709c79c69de34d0ea48e88f1de142cb5ca..9eac5577842c0dd626828dd56511c82825b7f4cb 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -71,8 +71,8 @@ import {ConfirmationDialogComponent} from './common/confirmation-dialog/confirma import {SpinnerComponent} from './common/spinner/spinner.component'; import {UserService} from './user/user.service'; import {UserDetailsService} from './user/user-details-dialog/user-details.service'; -import { ExpiredPasswordDialogComponent } from './common/expired-password-dialog/expired-password-dialog.component'; -import { DialogComponent } from './common/dialog/dialog.component'; +import {ExpiredPasswordDialogComponent} from './common/expired-password-dialog/expired-password-dialog.component'; +import {DialogComponent} from './common/dialog/dialog.component'; import {KeystoreImportDialogComponent} from "./domain/keystore-import-dialog/keystore-import-dialog.component"; import {KeystoreEditDialogComponent} from "./domain/keystore-edit-dialog/keystore-edit-dialog.component"; import {CertificateDialogComponent} from "./common/certificate-dialog/certificate-dialog.component"; @@ -81,106 +81,110 @@ import {InformationDialogComponent} from "./common/information-dialog/informatio import {KeystoreService} from "./domain/keystore.service"; import {TruststoreService} from "./user/truststore.service"; import {SmlIntegrationService} from "./domain/sml-integration.service"; +import {PasswordChangeDialogComponent} from "./common/password-change-dialog/password-change-dialog.component"; +import {AccessTokenGenerationDialogComponent} from "./common/access-token-generation-dialog/access-token-generation-dialog.component"; @NgModule({ - declarations: [ - AppComponent, - LoginComponent, - HomeComponent, - ServiceGroupEditComponent, - ServiceGroupSearchComponent, - DomainComponent, - DomainDetailsDialogComponent, - UserComponent, - AlertComponent, - FooterComponent, - SpinnerComponent, - IsAuthorized, - SaveDialogComponent, - ServiceGroupMetadataDialogComponent, - CancelDialogComponent, - ConfirmationDialogComponent, - InformationDialogComponent, - RowLimiterComponent, - DatePipe, - CapitalizeFirstPipe, - DefaultPasswordDialogComponent, - ServiceGroupDetailsDialogComponent, - ServiceGroupExtensionWizardDialogComponent, - ServiceMetadataWizardDialogComponent, - ColumnPickerComponent, - PageHelperComponent, - ClearInvalidDirective, - PageHeaderComponent, - DomainSelectorComponent, - SearchTableComponent, - UserDetailsDialogComponent, - ExpiredPasswordDialogComponent, - DialogComponent, - KeystoreImportDialogComponent, - KeystoreEditDialogComponent, - CertificateDialogComponent, - TruststoreEditDialogComponent, - ], - imports: [ - BrowserModule, - FlexLayoutModule, - HttpClientModule, - HttpClientXsrfModule.withOptions({ - cookieName: 'XSRF-TOKEN', - headerName: 'X-XSRF-TOKEN' - }), - BrowserAnimationsModule, - FormsModule, - NgxDatatableModule, - MatButtonModule, - MatCardModule, - MatDatepickerModule, - MatDialogModule, - MatTooltipModule, - MatToolbarModule, - MatMenuModule, - MatInputModule, - MatIconModule, - MatListModule, - MatSidenavModule, - MatSelectModule, - MatTabsModule, - MatSlideToggleModule, - MatProgressSpinnerModule, - routing, - ReactiveFormsModule, - SharedModule, - MatExpansionModule, - ], - providers: [ - AuthenticatedGuard, - AuthorizedGuard, - AuthorizedAdminGuard, - DirtyGuard, - HttpEventService, - SecurityService, - SecurityEventService, - DomainService, - SmpInfoService, - AlertService, - DownloadService, - CertificateService, - KeystoreService, - TruststoreService, - SmlIntegrationService, - GlobalLookups, - DatePipe, - UserService, - UserDetailsService, - { - provide: ExtendedHttpClient, - useFactory: extendedHttpClientCreator, - deps: [HttpClient, HttpEventService, SecurityService] - } - ], - bootstrap: [AppComponent] + declarations: [ + AppComponent, + LoginComponent, + HomeComponent, + ServiceGroupEditComponent, + ServiceGroupSearchComponent, + DomainComponent, + DomainDetailsDialogComponent, + UserComponent, + AlertComponent, + FooterComponent, + SpinnerComponent, + IsAuthorized, + SaveDialogComponent, + ServiceGroupMetadataDialogComponent, + CancelDialogComponent, + ConfirmationDialogComponent, + InformationDialogComponent, + RowLimiterComponent, + DatePipe, + CapitalizeFirstPipe, + DefaultPasswordDialogComponent, + ServiceGroupDetailsDialogComponent, + ServiceGroupExtensionWizardDialogComponent, + ServiceMetadataWizardDialogComponent, + ColumnPickerComponent, + PageHelperComponent, + ClearInvalidDirective, + PageHeaderComponent, + DomainSelectorComponent, + SearchTableComponent, + UserDetailsDialogComponent, + ExpiredPasswordDialogComponent, + PasswordChangeDialogComponent, + AccessTokenGenerationDialogComponent, + DialogComponent, + KeystoreImportDialogComponent, + KeystoreEditDialogComponent, + CertificateDialogComponent, + TruststoreEditDialogComponent, + ], + imports: [ + BrowserModule, + FlexLayoutModule, + HttpClientModule, + HttpClientXsrfModule.withOptions({ + cookieName: 'XSRF-TOKEN', + headerName: 'X-XSRF-TOKEN' + }), + BrowserAnimationsModule, + FormsModule, + NgxDatatableModule, + MatButtonModule, + MatCardModule, + MatDatepickerModule, + MatDialogModule, + MatTooltipModule, + MatToolbarModule, + MatMenuModule, + MatInputModule, + MatIconModule, + MatListModule, + MatSidenavModule, + MatSelectModule, + MatTabsModule, + MatSlideToggleModule, + MatProgressSpinnerModule, + routing, + ReactiveFormsModule, + SharedModule, + MatExpansionModule, + ], + providers: [ + AuthenticatedGuard, + AuthorizedGuard, + AuthorizedAdminGuard, + DirtyGuard, + HttpEventService, + SecurityService, + SecurityEventService, + DomainService, + SmpInfoService, + AlertService, + DownloadService, + CertificateService, + KeystoreService, + TruststoreService, + SmlIntegrationService, + GlobalLookups, + DatePipe, + UserService, + UserDetailsService, + { + provide: ExtendedHttpClient, + useFactory: extendedHttpClientCreator, + deps: [HttpClient, HttpEventService, SecurityService] + } + ], + bootstrap: [AppComponent] }) export class AppModule { } diff --git a/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.css b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e3fad2cbffc9ce3386e5695a983b544037216b08 --- /dev/null +++ b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.css @@ -0,0 +1,4 @@ +.password-panel .mat-form-field { + margin-bottom: 1.5em; + padding: 2px; +} diff --git a/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.html b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.html new file mode 100644 index 0000000000000000000000000000000000000000..3c48461f16f2267dbecd072b07c60ca59741f30f --- /dev/null +++ b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.html @@ -0,0 +1,60 @@ +<h2 mat-dialog-title>{{formTitle}}</h2> +<mat-dialog-content style="width:700px"> + + <div *ngIf="message" [ngClass]="{ 'alert-message': message, 'alert-message-success': messageType === 'success', 'alert-message-error':messageType === 'error' }" id="alertmessage_id"> + <span class="alert-message-close-button" (click)="clearAlert()">×</span> + {{message}} + </div> + <form [formGroup]="dialogForm"> + <mat-card> + <mat-card-content fxLayout="column"> + <mat-form-field style="width:100%"> + <input matInput placeholder="User email" formControlName="email" id="em_id" readonly="true"> + </mat-form-field> + <mat-form-field style="width:100%"> + <input matInput placeholder="Username" formControlName="username" id="un_id" readonly="true"> + </mat-form-field> + </mat-card-content> + </mat-card> + <mat-card> + <mat-card-content> + <mat-card-actions > + <button mat-raised-button color="primary" (click)="regenerateAccessToken()" + [disabled]="!dialogForm.valid" > + <mat-icon>check_circle</mat-icon> + <span>Regenerate access token</span> + </button> + </mat-card-actions> + <mat-form-field style="width:100%"> + <input matInput placeholder="Current Password" [type]="hideCurrPwdFiled ? 'password' : 'text'" + formControlName="current-password" required id="cp_id"> + <mat-icon matSuffix + (click)="hideCurrPwdFiled = !hideCurrPwdFiled">{{hideCurrPwdFiled ? 'visibility_off' : 'visibility'}}</mat-icon> + <mat-error *ngIf="passwordError('current-password', 'required')">Password is required</mat-error> + </mat-form-field> + + <mat-form-field style="width:100%"> + <input matInput placeholder="Access token id" formControlName="accessTokenId" id="at_id" readonly="true"> + </mat-form-field> + + <mat-form-field style="width:100%"> + <input matInput placeholder="Valid until" formControlName="accessTokenExpireOn" id="expireOn_id" + readonly="true"> + </mat-form-field> + + </mat-card-content> + </mat-card> + </form> + + <table class="buttonsRow"> + <tr> + <td> + <button mat-raised-button color="primary" mat-dialog-close> + <mat-icon>cancel</mat-icon> + <span>Close</span> + </button> + </td> + </tr> + </table> + <div class="required-fields">* required fields</div> +</mat-dialog-content> diff --git a/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.spec.ts b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d71a0fdc74e1e58fc014cb70720ef59668b23ced --- /dev/null +++ b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PasswordChangeDialogComponent } from './password-change-dialog.component'; + +describe('PasswordChangeDialogComponent', () => { + let component: PasswordChangeDialogComponent; + let fixture: ComponentFixture<PasswordChangeDialogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PasswordChangeDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordChangeDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.ts b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0dfb7e5198fbc4717870c69721647de654453cc --- /dev/null +++ b/smp-angular/src/app/common/access-token-generation-dialog/access-token-generation-dialog.component.ts @@ -0,0 +1,101 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, NgForm, + ValidatorFn, + Validators +} from "@angular/forms"; +import {User} from "../../security/user.model"; +import {GlobalLookups} from "../global-lookups"; +import {UserDetailsService} from "../../user/user-details-dialog/user-details.service"; +import {AccessTokenRo} from "./access-token-ro.model"; +import {SearchTableEntityStatus} from "../search-table/search-table-entity-status.model"; +import {SecurityService} from "../../security/security.service"; + +@Component({ + selector: 'smp-access-token-generation-dialog', + templateUrl: './access-token-generation-dialog.component.html', + styleUrls: ['./access-token-generation-dialog.component.css'] +}) +export class AccessTokenGenerationDialogComponent { + + formTitle = "Access token generation dialog!"; + dialogForm: FormGroup; + hideCurrPwdFiled: boolean = true; + hideNewPwdFiled: boolean = true; + hideConfPwdFiled: boolean = true; + current: User; + message: string; + messageType: string = "alert-error"; + + constructor( + public dialogRef: MatDialogRef<AccessTokenGenerationDialogComponent>, + @Inject(MAT_DIALOG_DATA) public data: User, + private lookups: GlobalLookups, + private userDetailsService: UserDetailsService, + private securityService: SecurityService, + private fb: FormBuilder + ) { + this.current = {...data} + + + this.dialogForm = fb.group({ + 'email': new FormControl({value: null, readonly: true}, null), + 'username': new FormControl({value: null, readonly: true}, null), + 'accessTokenId': new FormControl({value: null, readonly: true}, null), + 'accessTokenExpireOn': new FormControl({value: null, readonly: true}, null), + 'current-password': new FormControl({value: null, readonly: false}, [Validators.required]), + }); + + this.dialogForm.controls['email'].setValue(this.current.emailAddress); + this.dialogForm.controls['username'].setValue(this.current.username); + this.dialogForm.controls['accessTokenId'].setValue(this.current.accessTokenId); + this.dialogForm.controls['accessTokenExpireOn'].setValue(this.current.accessTokenExpireOn); + this.dialogForm.controls['current-password'].setValue(''); + } + + public passwordError = (controlName: string, errorName: string) => { + return this.dialogForm.controls[controlName].hasError(errorName); + } + + regenerateAccessToken() { + this.clearAlert(); + + // update password + this.userDetailsService.regenerateAccessToken(this.current.userId, + this.dialogForm.controls['current-password'].value).subscribe((response: AccessTokenRo) => { + this.showSuccessMessage("Token with id: " + response.identifier + " and value: " + response.value + " was generated!") + this.current.accessTokenId = response.identifier; + this.current.accessTokenExpireOn = response.expireOn; + // set to current form + this.dialogForm.controls['accessTokenId'].setValue(this.current.accessTokenId); + this.dialogForm.controls['accessTokenExpireOn'].setValue(this.current.accessTokenExpireOn); + // save new values + const user = {...this.current, status: SearchTableEntityStatus.UPDATED}; + this.securityService.updateUserDetails(user); + }, + (err) => { + this.showErrorMessage(err.error.errorDescription); + } + ); + } + + showSuccessMessage(value: string) { + this.message = value; + this.messageType = "success"; + } + + showErrorMessage(value: string) { + this.message = value; + this.messageType = "error"; + } + + clearAlert() { + this.message = null; + this.messageType = null; + } +} diff --git a/smp-angular/src/app/user/access-token-ro.model.ts b/smp-angular/src/app/common/access-token-generation-dialog/access-token-ro.model.ts similarity index 83% rename from smp-angular/src/app/user/access-token-ro.model.ts rename to smp-angular/src/app/common/access-token-generation-dialog/access-token-ro.model.ts index c399755ffb79a973254633205c18f5d1f02feb2e..0dd11abcb4093355df42427ed4773c4c950e9c88 100644 --- a/smp-angular/src/app/user/access-token-ro.model.ts +++ b/smp-angular/src/app/common/access-token-generation-dialog/access-token-ro.model.ts @@ -2,4 +2,5 @@ export interface AccessTokenRo { identifier: string; value: string; generatedOn?: Date; + expireOn?: Date; } diff --git a/smp-angular/src/app/common/error/error-model.ts b/smp-angular/src/app/common/error/error-model.ts new file mode 100644 index 0000000000000000000000000000000000000000..53cd1b8dfdffe5b21e227742f980eac9c9f24354 --- /dev/null +++ b/smp-angular/src/app/common/error/error-model.ts @@ -0,0 +1,5 @@ +export interface ErrorResponseRO { + businessCode?: string; + errorDescription?: string; + errorUniqueId?: string; +} diff --git a/smp-angular/src/app/common/global-lookups.ts b/smp-angular/src/app/common/global-lookups.ts index 3d693cb222ca59fb51f562924c89494428a51419..d80706a4bc85b7a21619ee3b2afe9ca51c9c3070 100644 --- a/smp-angular/src/app/common/global-lookups.ts +++ b/smp-angular/src/app/common/global-lookups.ts @@ -9,6 +9,8 @@ import {AlertService} from "../alert/alert.service"; import {Subscription} from "rxjs/internal/Subscription"; import {SmpInfo} from "../app-info/smp-info.model"; import {SmpConfig} from "../app-config/smp-config.model"; +import {SecurityEventService} from "../security/security-event.service"; +import {ExpiredPasswordDialogComponent} from "./expired-password-dialog/expired-password-dialog.component"; /** * Purpose of object is to fetch lookups as domains and users @@ -30,12 +32,16 @@ export class GlobalLookups implements OnInit { cachedApplicationConfig: SmpConfig; cachedTrustedCertificateList: Array<any> = []; + loginSubscription: Subscription; + logoutSubscription: Subscription; - constructor(protected alertService: AlertService, protected securityService: SecurityService, protected http: HttpClient) { + constructor(protected alertService: AlertService, + protected securityService: SecurityService, + protected http: HttpClient, + private securityEventService: SecurityEventService) { securityService.refreshLoggedUserFromServer(); this.refreshDomainLookup(); - this.refreshUserLookup(); this.refreshCertificateLookup(); this.refreshApplicationInfo(); this.refreshApplicationConfiguration(); @@ -43,28 +49,32 @@ export class GlobalLookups implements OnInit { } ngOnInit() { - } public refreshDomainLookup() { + let domainUrl = SmpConstants.REST_PUBLIC_DOMAIN_SEARCH; + // for authenticated admin use internal url which returns more data! + if (this.securityService.isCurrentUserSMPAdmin() || this.securityService.isCurrentUserSystemAdmin()) { + domainUrl = SmpConstants.REST_INTERNAL_DOMAIN_MANAGE; + } let params: HttpParams = new HttpParams() .set('page', '-1') .set('pageSize', '-1'); // init domains - this.domainObserver = this.http.get<SearchTableResult>(SmpConstants.REST_DOMAIN, {params}); + this.domainObserver = this.http.get<SearchTableResult>(domainUrl, {params}); this.domainObserver.subscribe((domains: SearchTableResult) => { this.cachedDomainList = domains.serviceEntities.map(serviceEntity => { - return {...serviceEntity} - }, - (error:any) => { + return {...serviceEntity} + }, + (error: any) => { this.alertService.error("Error occurred while loading domain lookup [" + error + "].") - }); + }); }); } public refreshApplicationInfo() { - this.http.get<SmpInfo>(SmpConstants.REST_APPLICATION) + this.http.get<SmpInfo>(SmpConstants.REST_PUBLIC_APPLICATION_INFO) .subscribe((res: SmpInfo) => { this.cachedApplicationInfo = res; }, error => { @@ -73,11 +83,12 @@ export class GlobalLookups implements OnInit { ); } + public refreshApplicationConfiguration() { - // check if authenticated + // check if authenticated this.securityService.isAuthenticated(false).subscribe((isAuthenticated: boolean) => { - if(isAuthenticated) { - this.http.get<SmpConfig>(SmpConstants.REST_CONFIG) + if (isAuthenticated) { + this.http.get<SmpConfig>(SmpConstants.REST_INTERNAL_APPLICATION_CONFIG) .subscribe((res: SmpConfig) => { this.cachedApplicationConfig = res; }, error => { @@ -90,47 +101,47 @@ export class GlobalLookups implements OnInit { public refreshUserLookup() { // call only for authenticated users. - if (this.securityService.isCurrentUserSMPAdmin() || this.securityService.isCurrentUserSystemAdmin() ) { + if (this.securityService.isCurrentUserSMPAdmin() || this.securityService.isCurrentUserSystemAdmin()) { let params: HttpParams = new HttpParams() .set('page', '-1') .set('pageSize', '-1'); // return only smp and service group admins.. - if (this.securityService.isCurrentUserSMPAdmin() ) { - params = params .set('roles', Role.SMP_ADMIN +","+Role.SERVICE_GROUP_ADMIN); + if (this.securityService.isCurrentUserSMPAdmin()) { + params = params.set('roles', Role.SMP_ADMIN + "," + Role.SERVICE_GROUP_ADMIN); } - // init users - this.userObserver = this.http.get<SearchTableResult>(SmpConstants.REST_USER, {params}); + // retrieve user list + this.userObserver = this.http.get<SearchTableResult>(SmpConstants.REST_INTERNAL_USER_MANAGE, {params}); let sub: Subscription = this.userObserver.subscribe((users: SearchTableResult) => { this.cachedServiceGroupOwnerList = users.serviceEntities.map(serviceEntity => { return {...serviceEntity} }); sub.unsubscribe(); - },(error:any) => { + }, (error: any) => { // check if unauthorized // just console try latter sub.unsubscribe(); - console.log("Error occurred while loading user owners lookup [" + error + "]"); - }); + console.log("Error occurred while loading user owners lookup [" + error + "]"); + }); } } public refreshCertificateLookup() { // call only for authenticated users. - if ( this.securityService.isCurrentUserSystemAdmin() ) { + if (this.securityService.isCurrentUserSystemAdmin()) { // init users - this.certificateObserver = this.http.get<SearchTableResult>(SmpConstants.REST_KEYSTORE ); + this.certificateObserver = this.http.get<SearchTableResult>(SmpConstants.REST_INTERNAL_KEYSTORE); this.certificateObserver.subscribe((certs: SearchTableResult) => { this.cachedCertificateList = certs.serviceEntities.map(serviceEntity => { return {...serviceEntity} }); //update alias list - this.cachedCertificateAliasList =this.cachedCertificateList.map(cert => cert.alias); - },(error:any) => { + this.cachedCertificateAliasList = this.cachedCertificateList.map(cert => cert.alias); + }, (error: any) => { // check if unauthorized // just console try latter console.log("Error occurred while loading user owners lookup [" + error + "]"); @@ -141,16 +152,16 @@ export class GlobalLookups implements OnInit { public refreshTrustedCertificateLookup() { // call only for authenticated users. - if ( this.securityService.isCurrentUserSystemAdmin() ) { + if (this.securityService.isCurrentUserSystemAdmin()) { // init users - this.trustedCertificateObserver = this.http.get<SearchTableResult>(SmpConstants.REST_TRUSTSTORE ); + this.trustedCertificateObserver = this.http.get<SearchTableResult>(SmpConstants.REST_INTERNAL_TRUSTSTORE); this.trustedCertificateObserver.subscribe((certs: SearchTableResult) => { this.cachedTrustedCertificateList = certs.serviceEntities.map(serviceEntity => { return {...serviceEntity} }); - },(error:any) => { + }, (error: any) => { // check if unauthorized // just console try latter console.log("Error occurred while loading trusted certifcates lookup [" + error + "]"); diff --git a/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.css b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.css new file mode 100644 index 0000000000000000000000000000000000000000..6336193ff3f2c5a889890b25a7e4407b4f4382d0 --- /dev/null +++ b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.css @@ -0,0 +1,16 @@ +.password-panel .mat-form-field { + margin-bottom: 1.5em; + padding: 2px; +} + + +.alert { + padding: 20px; + color: white; + opacity: 1; + transition: opacity 0.6s; + margin-bottom: 15px; + z-index: 1000; + background-color: #f44336; +} + diff --git a/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.html b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.html new file mode 100644 index 0000000000000000000000000000000000000000..1078ed25caaa65ab889584603fb8f3c16ea7339d --- /dev/null +++ b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.html @@ -0,0 +1,72 @@ +<h2 mat-dialog-title>{{formTitle}}</h2> +<mat-dialog-content style="width:500px"> + + <div *ngIf="message" [ngClass]="{ 'alert-message': message, 'alert-message-success': messageType === 'success', 'alert-message-error':messageType === 'error' }" id="alertmessage_id"> + <span class="alert-message-close-button" (click)="clearAlert()">×</span> + {{message}} + </div> + <form [formGroup]="dialogForm"> + <mat-card> + <mat-card-content fxLayout="column"> + <mat-form-field style="width:100%"> + <input matInput placeholder="User email" formControlName="email" id="em_id" readonly="true"> + </mat-form-field> + <mat-form-field style="width:100%"> + <input matInput placeholder="Username" formControlName="username" id="un_id" readonly="true"> + </mat-form-field> + </mat-card-content> + </mat-card> + <mat-card class="password-panel"> + <mat-card-content> + <mat-card-actions > + <button mat-raised-button color="primary" (click)="changeCurrentUserPassword()" + [disabled]="!dialogForm.valid "> + <mat-icon>check_circle</mat-icon> + <span>Change password</span> + </button> + </mat-card-actions> + <mat-form-field style="width:100%"> + <input matInput placeholder="Current Password" [type]="hideCurrPwdFiled ? 'password' : 'text'" + formControlName="current-password" required id="cp_id"> + <mat-icon matSuffix + (click)="hideCurrPwdFiled = !hideCurrPwdFiled">{{hideCurrPwdFiled ? 'visibility_off' : 'visibility'}}</mat-icon> + </mat-form-field> + + <mat-form-field style="width:100%"> + <input matInput placeholder="New Password" [type]="hideNewPwdFiled ? 'password' : 'text'" + formControlName="new-password" required id="np_id"> + <mat-icon matSuffix + (click)="hideNewPwdFiled = !hideNewPwdFiled">{{hideNewPwdFiled ? 'visibility_off' : 'visibility'}}</mat-icon> + <mat-error *ngIf="passwordError('new-password', 'required')">New password is required</mat-error> + <mat-error *ngIf="passwordError('new-password', 'equal')">New password must not be equal than old current + password! + </mat-error> + <mat-error *ngIf="passwordError('new-password', 'pattern')">{{passwordValidationMessage}}</mat-error> + </mat-form-field> + + <mat-form-field style="width:100%"> + <input matInput placeholder="Confirm New Password" [type]="hideConfPwdFiled ? 'password' : 'text'" + formControlName="confirm-new-password" required id="cnp_id"> + <mat-icon matSuffix + (click)="hideConfPwdFiled = !hideConfPwdFiled">{{hideConfPwdFiled ? 'visibility_off' : 'visibility'}}</mat-icon> + <mat-error *ngIf="passwordError('confirm-new-password', 'equal')">Confirm valued does not match new password! + </mat-error> + <mat-error *ngIf="passwordError('confirm-new-password', 'required')">Confirm New password is required + </mat-error> + </mat-form-field> + </mat-card-content> + </mat-card> + </form> + + <table class="buttonsRow" > + <tr> + <td> + <button mat-raised-button color="primary" mat-dialog-close> + <mat-icon>cancel</mat-icon> + <span>Close</span> + </button> + </td> + </tr> + </table> + <div class="required-fields">* required fields</div> +</mat-dialog-content> diff --git a/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.spec.ts b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d71a0fdc74e1e58fc014cb70720ef59668b23ced --- /dev/null +++ b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PasswordChangeDialogComponent } from './password-change-dialog.component'; + +describe('PasswordChangeDialogComponent', () => { + let component: PasswordChangeDialogComponent; + let fixture: ComponentFixture<PasswordChangeDialogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PasswordChangeDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordChangeDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.ts b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a3782954b4f39003e1ffc2cf98223e276c582bf --- /dev/null +++ b/smp-angular/src/app/common/password-change-dialog/password-change-dialog.component.ts @@ -0,0 +1,113 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, NgForm, + ValidatorFn, + Validators +} from "@angular/forms"; +import {User} from "../../security/user.model"; +import {GlobalLookups} from "../global-lookups"; +import {ErrorStateMatcher} from "@angular/material/core"; +import {UserDetailsService} from "../../user/user-details-dialog/user-details.service"; +import {CertificateRo} from "../../user/certificate-ro.model"; +import {AlertService} from "../../alert/alert.service"; +import {ErrorResponseRO} from "../error/error-model"; + +@Component({ + selector: 'smp-password-change-dialog', + templateUrl: './password-change-dialog.component.html', + styleUrls: ['./password-change-dialog.component.css'] +}) +export class PasswordChangeDialogComponent { + + formTitle = "Change password dialog!"; + dialogForm: FormGroup; + hideCurrPwdFiled: boolean = true; + hideNewPwdFiled: boolean = true; + hideConfPwdFiled: boolean = true; + current: User; + message: string; + messageType: string = "alert-error"; + + constructor( + public dialogRef: MatDialogRef<PasswordChangeDialogComponent>, + @Inject(MAT_DIALOG_DATA) public data: User, + private lookups: GlobalLookups, + private userDetailsService: UserDetailsService, + private alertService: AlertService, + private fb: FormBuilder + ) { + this.current = {...data} + + let currentPasswdFormControl: FormControl = new FormControl({value: null, readonly: false}, [Validators.required]); + let newPasswdFormControl: FormControl = new FormControl({value: null, readonly: false}, + [Validators.required, Validators.pattern(this.passwordValidationRegExp), equal(currentPasswdFormControl, false)]); + let confirmNewPasswdFormControl: FormControl = new FormControl({value: null, readonly: false}, + [Validators.required, equal(newPasswdFormControl, true)]); + + this.dialogForm = fb.group({ + 'email': new FormControl({value: null, readonly: true}, null), + 'username': new FormControl({value: null, readonly: true}, null), + 'current-password': currentPasswdFormControl, + 'new-password': newPasswdFormControl, + 'confirm-new-password': confirmNewPasswdFormControl + }); + + this.dialogForm.controls['email'].setValue(this.current.emailAddress); + this.dialogForm.controls['username'].setValue(this.current.username); + this.dialogForm.controls['current-password'].setValue(''); + this.dialogForm.controls['new-password'].setValue(''); + this.dialogForm.controls['confirm-new-password'].setValue(''); + } + + public passwordError = (controlName: string, errorName: string) => { + return this.dialogForm.controls[controlName].hasError(errorName); + } + + get passwordValidationMessage() { + return this.lookups.cachedApplicationConfig?.passwordValidationRegExpMessage; + } + + get passwordValidationRegExp() { + return this.lookups.cachedApplicationConfig?.passwordValidationRegExp; + } + + changeCurrentUserPassword() { + this.clearAlert(); + + // update password + this.userDetailsService.changePassword(this.current.userId, + this.dialogForm.controls['new-password'].value, + this.dialogForm.controls['current-password'].value).subscribe((res: boolean) => { + this.showSuccessMessage("Password has been changed!") + }, + (err) => { + this.showErrorMessage(err.error.errorDescription); + } + ); + } + showSuccessMessage(value: string) { + this.message = value; + this.messageType = "success"; + } + + showErrorMessage(value: string) { + this.message = value; + this.messageType = "error"; + } + + clearAlert() { + this.message = null; + this.messageType = null; + } +} + +export function equal(currentPasswdFormControl: FormControl, matchEqual: boolean): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => + (matchEqual ? control.value === currentPasswdFormControl.value : control.value !== currentPasswdFormControl.value) + ? null : {error: control.value}; +} diff --git a/smp-angular/src/app/common/search-table/search-table.component.ts b/smp-angular/src/app/common/search-table/search-table.component.ts index cb045136f5352bea4b0a039e5e0249a01a7ed3f2..0f71a62d377aa2164d0b8a2cd6676383a0d56caf 100644 --- a/smp-angular/src/app/common/search-table/search-table.component.ts +++ b/smp-angular/src/app/common/search-table/search-table.component.ts @@ -38,7 +38,8 @@ export class SearchTableComponent implements OnInit { @Input() id: String = ""; @Input() title: String = ""; @Input() columnPicker: ColumnPicker; - @Input() url: string = ''; + @Input() url: string = ''; // URL for query (and if manageUrl is null also for "managing") + @Input() manageUrl: string = ''; // (for "managing" the entities (add, update, remove) ) @Input() searchTableController: SearchTableController; @Input() filter: any = {}; @Input() showActionButtons: boolean = true; @@ -294,7 +295,7 @@ export class SearchTableComponent implements OnInit { const modifiedRowEntities = this.rows.filter(el => el.status !== SearchTableEntityStatus.PERSISTED); // this.isBusy = true; this.showSpinner = true; - this.http.put(this.url, modifiedRowEntities).toPromise().then(res => { + this.http.put(this.managementUrl, modifiedRowEntities).toPromise().then(res => { this.showSpinner = false; // this.isBusy = false; // this.getUsers(); @@ -352,6 +353,10 @@ export class SearchTableComponent implements OnInit { return this.selected && this.selected.length == 1 && !this.selected[0].deleted; } + get managementUrl(): string { + return (this.manageUrl == null || this.manageUrl.length === 0)? this.url:this.manageUrl; + } + get deleteButtonEnabled(): boolean { return this.selected && this.selected.length > 0 && !this.selected.every(el => el.deleted); } diff --git a/smp-angular/src/app/domain/domain-controller.ts b/smp-angular/src/app/domain/domain-controller.ts index af77488ee4a75e544144e349f605fe86d3e786f0..a01842f226040e83a17814deeef7d1f20594d9ef 100644 --- a/smp-angular/src/app/domain/domain-controller.ts +++ b/smp-angular/src/app/domain/domain-controller.ts @@ -54,7 +54,7 @@ export class DomainController implements SearchTableController { validateDeleteOperation(rows: Array<SearchTableEntity>){ var deleteRowIds = rows.map(rows => rows.id); - return this.http.post<SearchTableValidationResult>(SmpConstants.REST_DOMAIN_VALIDATE_DELETE, deleteRowIds); + return this.http.post<SearchTableValidationResult>(SmpConstants.REST_INTERNAL_DOMAIN_VALIDATE_DELETE, deleteRowIds); } public newValidationResult(result: boolean, message: string): SearchTableValidationResult { diff --git a/smp-angular/src/app/domain/domain.component.html b/smp-angular/src/app/domain/domain.component.html index 040bbf05b8b4f6ba4936d986d1245a5be99cc5ea..fb41969834cd0ca0907838e05115b86769bb15e9 100644 --- a/smp-angular/src/app/domain/domain.component.html +++ b/smp-angular/src/app/domain/domain.component.html @@ -10,24 +10,16 @@ [filter]="filter" [allowNewItems]="securityService.isCurrentUserSystemAdmin()" [allowDeleteItems]="securityService.isCurrentUserSystemAdmin()" - > - <ng-template #domainCodeColumnTemplate let-row="row" ngx-datatable-cell-template> - <span [class]='aliasCssForDomainCodeClass(row)' [matTooltip]='getDomainConfigurationWarning(row)' >{{row.domainCode}}</span> - </ng-template> - - <ng-template #signKeyColumnTemplate let-row="row" ngx-datatable-cell-template> - <span [class]='aliasCssClass(row.signatureKeyAlias, row)'>{{row.signatureKeyAlias}}</span> + <ng-template #domainCodeColumnTemplate let-row="row" let-value="value" ngx-datatable-cell-template> + <span [class]='aliasCssForDomainCodeClass(row)' [matTooltip]='getDomainConfigurationWarning(row)' >{{value}}</span> </ng-template> - <ng-template #smlKeyColumnTemplate let-row="row" ngx-datatable-cell-template> - <span [class]='aliasCssClass(row.smlClientKeyAlias, row)'>{{row.smlClientKeyAlias}}</span> + <ng-template #certificateAliasTemplate let-row="row" let-value="value" ngx-datatable-cell-template> + <span [class]='aliasCssClass(value, row)'>{{value}}</span> </ng-template> - - - <ng-template #additionalToolButtons > <span style="width: 2px;background-color: deepskyblue;"> </span> diff --git a/smp-angular/src/app/domain/domain.component.ts b/smp-angular/src/app/domain/domain.component.ts index c38eae3540e43a335e232d487c55878ea9d2e20f..c675b32e801bce74583141a46295f38d205c745e 100644 --- a/smp-angular/src/app/domain/domain.component.ts +++ b/smp-angular/src/app/domain/domain.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import {AfterViewInit, Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog, MatDialogRef} from '@angular/material/dialog'; @@ -23,17 +23,16 @@ import {SMLResult} from "./sml-result.model"; templateUrl: './domain.component.html', styleUrls: ['./domain.component.css'] }) -export class DomainComponent implements OnInit { +export class DomainComponent implements AfterViewInit { @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; - @ViewChild('signKeyColumnTemplate') signKeyColumnTemplate: TemplateRef<any>; - @ViewChild('smlKeyColumnTemplate') smlKeyColumnTemplate: TemplateRef<any>; + @ViewChild('certificateAliasTemplate') certificateAliasColumn: TemplateRef<any>; @ViewChild('domainCodeColumnTemplate') domainCodeColumnTemplate: TemplateRef<any>; @ViewChild('rowActions') rowActions: TemplateRef<any>; @ViewChild('searchTable') searchTable: SearchTableComponent; - baseUrl = SmpConstants.REST_DOMAIN; + baseUrl = SmpConstants.REST_INTERNAL_DOMAIN_MANAGE; columnPicker: ColumnPicker = new ColumnPicker(); domainController: DomainController; filter: any = {}; @@ -60,13 +59,14 @@ export class DomainComponent implements OnInit { } } - ngOnInit() { + ngAfterViewInit() { this.domainController = new DomainController(this.http, this.lookups, this.dialog); this.columnPicker.allColumns = [ { name: 'Domain code', title: "Unique domain code.", + prop: 'domainCode', cellTemplate: this.domainCodeColumnTemplate, width: 250 @@ -80,7 +80,8 @@ export class DomainComponent implements OnInit { { name: 'Signature CertAlias', title: "Certificate for signing REST responses", - cellTemplate: this.signKeyColumnTemplate, + prop: 'signatureKeyAlias', + cellTemplate: this.certificateAliasColumn, width: 150 }, @@ -92,7 +93,8 @@ export class DomainComponent implements OnInit { }, { name: 'SML ClientCert Alias', - cellTemplate: this.smlKeyColumnTemplate, + prop: 'smlClientKeyAlias', + cellTemplate: this.certificateAliasColumn, width: 150 }, { @@ -116,7 +118,6 @@ export class DomainComponent implements OnInit { if (alias) { return this.lookups.cachedCertificateAliasList.includes(alias); } else { - return false; } } diff --git a/smp-angular/src/app/domain/keystore.service.ts b/smp-angular/src/app/domain/keystore.service.ts index 9c38ac6f00a4a4d26760cd85c813e53d355a0070..e0f9b41f4f23e3efa3c2f7da05cf00128e923100 100644 --- a/smp-angular/src/app/domain/keystore.service.ts +++ b/smp-angular/src/app/domain/keystore.service.ts @@ -25,7 +25,7 @@ export class KeystoreService { let passwordEncoded = encodeURIComponent(password); const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<KeystoreResult>(`${SmpConstants.REST_KEYSTORE}/${currentUser.id}/upload/${keystoreType}/${passwordEncoded}`, selectedFile, { + return this.http.post<KeystoreResult>(`${SmpConstants.REST_INTERNAL_KEYSTORE}/${currentUser.userId}/upload/${keystoreType}/${passwordEncoded}`, selectedFile, { headers }); } @@ -36,6 +36,6 @@ export class KeystoreService { let certificateAliasEncoded = encodeURIComponent(certificateAlias); const currentUser: User = this.securityService.getCurrentUser(); - return this.http.delete<KeystoreResult>(`${SmpConstants.REST_KEYSTORE}/${currentUser.id}/delete/${certificateAliasEncoded}`); + return this.http.delete<KeystoreResult>(`${SmpConstants.REST_INTERNAL_KEYSTORE}/${currentUser.userId}/delete/${certificateAliasEncoded}`); } } diff --git a/smp-angular/src/app/domain/sml-integration.service.ts b/smp-angular/src/app/domain/sml-integration.service.ts index 932ef1344b71d9d38235fff4a2940779181056ee..9aabf0071c684114a3a3186888f350761aa64dd7 100644 --- a/smp-angular/src/app/domain/sml-integration.service.ts +++ b/smp-angular/src/app/domain/sml-integration.service.ts @@ -18,11 +18,11 @@ export class SmlIntegrationService { registerDomainToSML$(domainCode): Observable<SMLResult> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<SMLResult>(`${SmpConstants.REST_DOMAIN}/${currentUser.id}/smlregister/${domainCode}`, {}); + return this.http.put<SMLResult>(`${SmpConstants.REST_INTERNAL_DOMAIN_MANAGE}/${currentUser.userId}/sml-register/${domainCode}`, {}); } unregisterDomainToSML$(domainCode): Observable<SMLResult> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<SMLResult>(`${SmpConstants.REST_DOMAIN}/${currentUser.id}/smlunregister/${domainCode}`, {}); + return this.http.put<SMLResult>(`${SmpConstants.REST_INTERNAL_DOMAIN_MANAGE}/${currentUser.userId}/sml-unregister/${domainCode}`, {}); } } diff --git a/smp-angular/src/app/login/login.component.ts b/smp-angular/src/app/login/login.component.ts index ddcfc7da64ef95100c6e32b666a596ff91d8f250..1fc24581a74e5bef5162b4fce29cefcf9b71ca73 100644 --- a/smp-angular/src/app/login/login.component.ts +++ b/smp-angular/src/app/login/login.component.ts @@ -43,6 +43,7 @@ export class LoginComponent implements OnInit, OnDestroy { } else { this.router.navigate([this.returnUrl]); } + this.lookups.refreshApplicationConfiguration(); }); this.securityEventService.onLoginErrorEvent().subscribe( diff --git a/smp-angular/src/app/security/security.service.ts b/smp-angular/src/app/security/security.service.ts index ee767814760ff55bc4da9fc95b0bde97c1726e1d..6bfe4e5e66cc76ed3e563882e258d4eddd776500 100644 --- a/smp-angular/src/app/security/security.service.ts +++ b/smp-angular/src/app/security/security.service.ts @@ -24,7 +24,7 @@ export class SecurityService { login(username: string, password: string) { let headers: HttpHeaders = new HttpHeaders({'Content-Type': 'application/json'}); - return this.http.post<string>(SmpConstants.REST_SECURITY_AUTHENTICATION, + return this.http.post<string>(SmpConstants.REST_PUBLIC_SECURITY_AUTHENTICATION, JSON.stringify({ username: username, password: password @@ -50,7 +50,7 @@ export class SecurityService { } logout() { - this.http.delete(SmpConstants.REST_SECURITY_AUTHENTICATION).subscribe((res: Response) => { + this.http.delete(SmpConstants.REST_PUBLIC_SECURITY_AUTHENTICATION).subscribe((res: Response) => { this.clearLocalStorage(); this.securityEventService.notifyLogoutSuccessEvent(res); }, @@ -65,7 +65,7 @@ export class SecurityService { private getCurrentUsernameFromServer(): Observable<string> { let subject = new ReplaySubject<string>(); - this.http.get<string>(SmpConstants.REST_SECURITY_USER) + this.http.get<string>(SmpConstants.REST_PUBLIC_SECURITY_USER) .subscribe((res: string) => { subject.next(res); }, (error: any) => { diff --git a/smp-angular/src/app/security/user.model.ts b/smp-angular/src/app/security/user.model.ts index 929b76b6d4dfef350222ebd9a0030d61ab9bfce3..40fe572ea159c59dea05c1b3223bec2b59a76c43 100644 --- a/smp-angular/src/app/security/user.model.ts +++ b/smp-angular/src/app/security/user.model.ts @@ -1,9 +1,11 @@ import {Authority} from "./authority.model"; export interface User { - id: number; + userId: string; + emailAddress: string; username: string; accessTokenId?: string; + accessTokenExpireOn?: Date; authorities: Array<Authority>; defaultPasswordUsed: boolean; } diff --git a/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.css b/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.css index 5e47b4070b9aa525e16025657e4f85929f57ab0d..0545fd5b2564bd76936b476fca4c36dbf1a50542 100644 --- a/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.css +++ b/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.css @@ -12,3 +12,11 @@ font-weight: bold; color:#c6c639; } + + +.list-form-item .mat-form-field-wrapper { + margin-bottom: -1.25em; + padding: 0; + margin: 0; +} + diff --git a/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.html b/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.html index e508111f3e6e154b1a4f7a1dae0ee6c4eef19bd4..e94bdf35d04587be74db60faaf75b1f6e0011ba1 100644 --- a/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.html +++ b/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.html @@ -106,10 +106,8 @@ style="height: 200px; overflow-y: scroll; overflow-x: auto;"> <mat-list-option *ngFor="let domain of lookups.cachedDomainList" [value]='domain' style="max-width: 450px !important; word-wrap: break-word !important; height: auto; min-height: 30px !important;" - [disabled]="!isDomainProperlyConfigured(domain)" - [matTooltip]="getDomainConfigurationWarning(domain)" > - <span [class]="getDomainCodeClass(domain)"> - {{domain.domainCode}} ({{domain.smlSubdomain}})</span> + > + <label [class]="getDomainCodeClass(domain)" [title]="getDomainConfigurationWarning(domain)">{{domain.domainCode}} ({{domain.smlSubdomain}})</label> </mat-list-option> </mat-selection-list> </mat-expansion-panel> @@ -207,3 +205,8 @@ </div> </mat-dialog-actions> <div style="text-align: right; font-size: 70%">* required fields</div> + +<ng-template mat-tab-label> + <label class="labelHeading" matTooltip="See Pictures"matTooltipClass="example-tooltip-red1">Bounced Users + </label> +</ng-template> diff --git a/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.ts b/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.ts index 52300ece25c14c7d7c346eceb999888a026c6d84..da55d19e1a11ca74aa5b9da7f66acef9f61fa431 100644 --- a/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.ts +++ b/smp-angular/src/app/service-group-edit/service-group-details-dialog/service-group-details-dialog.component.ts @@ -169,6 +169,7 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { } return msg; } + isDomainProperlyConfigured(domain: DomainRo){ return !this.getDomainConfigurationWarning(domain); } diff --git a/smp-angular/src/app/service-group-edit/service-group-edit.component.ts b/smp-angular/src/app/service-group-edit/service-group-edit.component.ts index 5de7d4012ced51abfa456d47c412df6ffad2af21..62cd9aaa0b525e2da16f5255430db1862321f876 100644 --- a/smp-angular/src/app/service-group-edit/service-group-edit.component.ts +++ b/smp-angular/src/app/service-group-edit/service-group-edit.component.ts @@ -25,7 +25,7 @@ export class ServiceGroupEditComponent implements OnInit { columnPicker: ColumnPicker = new ColumnPicker(); serviceGroupEditController: ServiceGroupEditController; filter: any = {}; - baseUrl: string = SmpConstants.REST_EDIT; + baseUrl: string = SmpConstants.REST_PUBLIC_SERVICE_GROUP; contextPath: string = location.pathname.substring(0, location.pathname.length - 3); // remove /ui s constructor(public securityService: SecurityService, diff --git a/smp-angular/src/app/service-group-search/service-group-search.component.html b/smp-angular/src/app/service-group-search/service-group-search.component.html index b8f070230b26a2520407fcdc7904c74b64198032..ffbb6dfbd6dcddc2d912076b1abd8dd635690f7a 100644 --- a/smp-angular/src/app/service-group-search/service-group-search.component.html +++ b/smp-angular/src/app/service-group-search/service-group-search.component.html @@ -58,7 +58,7 @@ <div *ngIf="row.serviceMetadata.length !== 0"> <ngx-datatable class='inner-table material striped' - [loadingIndicator]="loading" + [loadingIndicator]="'Loading ...'" [rows]='row.serviceMetadata' [columnMode]='"force"' [headerHeight]='50' diff --git a/smp-angular/src/app/service-group-search/service-group-search.component.ts b/smp-angular/src/app/service-group-search/service-group-search.component.ts index 031900db4a695f928de0b40b60feec459265e146..a64932caac98c3656e57e6672cf28ca1a719b238 100644 --- a/smp-angular/src/app/service-group-search/service-group-search.component.ts +++ b/smp-angular/src/app/service-group-search/service-group-search.component.ts @@ -23,7 +23,7 @@ export class ServiceGroupSearchComponent implements OnInit { serviceGroupSearchController: ServiceGroupSearchController; filter: any = {}; contextPath: string = location.pathname.substring(0, location.pathname.length - 3); // remove /ui s - baseUrl: string = SmpConstants.REST_SEARCH; + baseUrl: string = SmpConstants.REST_PUBLIC_SEARCH_SERVICE_GROUP; constructor(protected lookups: GlobalLookups, protected http: HttpClient, protected alertService: AlertService, public dialog: MatDialog) { diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts index 0be8f1cb327450b114bb67b9a0ecd57f5fc07bd1..0323792a8a815cd796f74fe374a3d4a87ef32f45 100644 --- a/smp-angular/src/app/smp.constants.ts +++ b/smp-angular/src/app/smp.constants.ts @@ -1,25 +1,45 @@ export class SmpConstants { + //------------------------------ + // public endpoints + public static readonly REST_PUBLIC = 'public/rest/'; + public static readonly REST_INTERNAL = 'internal/rest/'; + public static readonly REST_PUBLIC_SEARCH_SERVICE_GROUP = SmpConstants.REST_PUBLIC + 'search'; + public static readonly REST_PUBLIC_DOMAIN_SEARCH = SmpConstants.REST_PUBLIC + 'domain'; + public static readonly REST_PUBLIC_APPLICATION_INFO = SmpConstants.REST_PUBLIC + 'application/info'; + // user public services + public static readonly REST_PUBLIC_USER = SmpConstants.REST_PUBLIC + 'user'; + public static readonly REST_PUBLIC_USER_UPDATE = SmpConstants.REST_PUBLIC_USER + '/{user-id}/'; + public static readonly REST_PUBLIC_USER_CERT_VALIDATE = SmpConstants.REST_PUBLIC_USER_UPDATE + 'validate-certificate'; + public static readonly REST_PUBLIC_USER_GENERATE_ACCESS_TOKEN = SmpConstants.REST_PUBLIC_USER_UPDATE + 'generate-access-token'; + public static readonly REST_PUBLIC_USER_CHANGE_PASSWORD = SmpConstants.REST_PUBLIC_USER_UPDATE + 'change-password'; + public static readonly REST_PUBLIC_TRUSTSTORE = SmpConstants.REST_PUBLIC + 'truststore/{user-id}/'; + public static readonly REST_PUBLIC_TRUSTSTORE_VALIDATE_CERT = SmpConstants.REST_PUBLIC_TRUSTSTORE + 'validate-certificate'; + // public authentication services + public static readonly REST_PUBLIC_SECURITY_AUTHENTICATION = SmpConstants.REST_PUBLIC + 'security/authentication'; + public static readonly REST_PUBLIC_SECURITY_USER = SmpConstants.REST_PUBLIC + 'security/user'; - public static readonly REST_DOMAIN = 'rest/domain'; - public static readonly REST_USER = 'rest/user'; - public static readonly REST_SEARCH = 'rest/search'; - public static readonly REST_EDIT = 'rest/servicegroup'; - public static readonly REST_METADATA = 'rest/servicemetadata'; - public static readonly REST_SECURITY_AUTHENTICATION = 'rest/security/authentication'; - public static readonly REST_SECURITY_CAS_AUTHENTICATION = 'rest/security/cas'; - - public static readonly REST_SECURITY_USER = 'rest/security/user'; - public static readonly REST_APPLICATION = 'rest/application/info'; - public static readonly REST_CONFIG = 'rest/application/config'; - public static readonly REST_KEYSTORE = 'rest/keystore'; - public static readonly REST_TRUSTSTORE = 'rest/truststore'; - - 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`; + public static readonly REST_PUBLIC_SERVICE_GROUP = SmpConstants.REST_PUBLIC + 'service-group'; + public static readonly REST_SERVICE_GROUP_EXTENSION = `${SmpConstants.REST_PUBLIC_SERVICE_GROUP}/extension`; public static readonly REST_SERVICE_GROUP_EXTENSION_VALIDATE = `${SmpConstants.REST_SERVICE_GROUP_EXTENSION}/validate`; public static readonly REST_SERVICE_GROUP_EXTENSION_FORMAT = `${SmpConstants.REST_SERVICE_GROUP_EXTENSION}/format`; + + public static readonly REST_METADATA = SmpConstants.REST_PUBLIC +'service-metadata'; public static readonly REST_METADATA_VALIDATE = `${SmpConstants.REST_METADATA}/validate`; + //------------------------------ + // internal endpoints + public static readonly REST_INTERNAL_DOMAIN_MANAGE = SmpConstants.REST_INTERNAL + 'domain'; + public static readonly REST_INTERNAL_DOMAIN_VALIDATE_DELETE = SmpConstants.REST_INTERNAL_DOMAIN_MANAGE + '/validate-delete'; + public static readonly REST_INTERNAL_USER_MANAGE = SmpConstants.REST_INTERNAL + 'user'; + public static readonly REST_INTERNAL_USER_VALIDATE_DELETE = `${SmpConstants.REST_INTERNAL_USER_MANAGE}/validate-delete`; + public static readonly REST_INTERNAL_APPLICATION_CONFIG = SmpConstants.REST_INTERNAL + 'application/config'; + public static readonly REST_INTERNAL_KEYSTORE = SmpConstants.REST_INTERNAL + 'keystore'; + public static readonly REST_INTERNAL_TRUSTSTORE = SmpConstants.REST_INTERNAL + 'truststore'; + + + + + + } diff --git a/smp-angular/src/app/user/certificate.service.ts b/smp-angular/src/app/user/certificate.service.ts index 41fa7ecca837839fb1d34971eab2813654b9fb6d..f1bf5be8dbf3d8e3a0366718553881b8bf669892 100644 --- a/smp-angular/src/app/user/certificate.service.ts +++ b/smp-angular/src/app/user/certificate.service.ts @@ -24,6 +24,19 @@ export class CertificateService { .set("Content-Type", "application/octet-stream"); const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<CertificateRo>(`${SmpConstants.REST_USER}/${currentUser.id}/certdata`, payload, {headers}); + return this.http.post<CertificateRo>(SmpConstants.REST_PUBLIC_TRUSTSTORE_VALIDATE_CERT.replace('{user-id}', currentUser.userId+""), payload, {headers}); + } + + validateCertificate(payload): Observable<CertificateRo> { + // 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) + + // upload file as binary file + const headers = new HttpHeaders() + .set("Content-Type", "application/octet-stream"); + + const currentUser: User = this.securityService.getCurrentUser(); + return this.http.post<CertificateRo>(SmpConstants.REST_PUBLIC_TRUSTSTORE_VALIDATE_CERT.replace('{user-id}', currentUser.userId+""), payload, {headers}); } } diff --git a/smp-angular/src/app/user/truststore.service.ts b/smp-angular/src/app/user/truststore.service.ts index 3a76d31b318eca1c9a9fd79a7465e378843aca9b..cd43eec0115b735cca749caa94b061d621ab14ca 100644 --- a/smp-angular/src/app/user/truststore.service.ts +++ b/smp-angular/src/app/user/truststore.service.ts @@ -26,7 +26,7 @@ export class TruststoreService { .set("Content-Type", "application/octet-stream"); const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<CertificateRo>(`${SmpConstants.REST_TRUSTSTORE}/${currentUser.id}/certdata`, payload, {headers}); + return this.http.post<CertificateRo>(`${SmpConstants.REST_INTERNAL_TRUSTSTORE}/${currentUser.userId}/validate-certificate`, payload, {headers}); } deleteCertificateFromKeystore$(certificateAlias): Observable<TruststoreResult> { @@ -35,6 +35,6 @@ export class TruststoreService { let certificateAliasEncoded = encodeURIComponent(certificateAlias); const currentUser: User = this.securityService.getCurrentUser(); - return this.http.delete<TruststoreResult>(`${SmpConstants.REST_TRUSTSTORE}/${currentUser.id}/delete/${certificateAliasEncoded}`); + return this.http.delete<TruststoreResult>(`${SmpConstants.REST_INTERNAL_TRUSTSTORE}/${currentUser.userId}/delete/${certificateAliasEncoded}`); } } diff --git a/smp-angular/src/app/user/user-controller.ts b/smp-angular/src/app/user/user-controller.ts index 0d0002a18dbc7c703775124c78bf170ed13d6474..91e693ea17980ccec877abd0aa52ad0c9fade01a 100644 --- a/smp-angular/src/app/user/user-controller.ts +++ b/smp-angular/src/app/user/user-controller.ts @@ -9,6 +9,8 @@ import {SearchTableValidationResult} from "../common/search-table/search-table-v import {SmpConstants} from "../smp.constants"; import {HttpClient} from "@angular/common/http"; import {CertificateRo} from "./certificate-ro.model"; +import {PasswordChangeDialogComponent} from "../common/password-change-dialog/password-change-dialog.component"; +import {AccessTokenGenerationDialogComponent} from "../common/access-token-generation-dialog/access-token-generation-dialog.component"; export class UserController implements SearchTableController { @@ -40,6 +42,14 @@ export class UserController implements SearchTableController { return this.dialog.open(UserDetailsDialogComponent, this.convertWithMode(config)); } + public changePasswordDialog(config?: MatDialogConfig): MatDialogRef<PasswordChangeDialogComponent> { + return this.dialog.open(PasswordChangeDialogComponent, this.convertWithMode(config)); + } + + public generateAccessTokenDialog(config?: MatDialogConfig): MatDialogRef<AccessTokenGenerationDialogComponent> { + return this.dialog.open(AccessTokenGenerationDialogComponent, this.convertWithMode(config)); + } + private convertWithMode(config) { return (config && config.data) ? { @@ -71,7 +81,7 @@ export class UserController implements SearchTableController { validateDeleteOperation(rows: Array<SearchTableEntity>) { var deleteRowIds = rows.map(rows => rows.id); - return this.http.post<SearchTableValidationResult>(SmpConstants.REST_USER_VALIDATE_DELETE, deleteRowIds); + return this.http.post<SearchTableValidationResult>(SmpConstants.REST_INTERNAL_USER_VALIDATE_DELETE, deleteRowIds); } public newValidationResult(lst: Array<number>): SearchTableValidationResult { 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 6deed874c2f6c0b0824d0742ddcd611b95f6e899..cee2655804740b3bb1c9fd0805cc08b33a105995 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 @@ -23,7 +23,7 @@ import {Observable, of} from "rxjs"; import {catchError, map} from "rxjs/operators"; import {UserDetailsService} from "./user-details.service"; import {MatSlideToggleChange} from "@angular/material/slide-toggle"; -import {AccessTokenRo} from "../access-token-ro.model"; +import {AccessTokenRo} from "../../common/access-token-generation-dialog/access-token-ro.model"; @Component({ selector: 'user-details-dialog', @@ -41,7 +41,7 @@ export class UserDetailsDialogComponent { mode: UserDetailsDialogMode; editMode: boolean; - userId: number; + userId: string; userRoles = []; certificateValidationMessage: string = null; isCertificateInvalid: boolean = true; @@ -50,6 +50,7 @@ export class UserDetailsDialogComponent { current: UserRo; tempStoreForCertificate: CertificateRo = this.newCertificateRo(); tempStoreForUser: UserRo = this.newUserRo(); + newCertFile:File=null; private passwordConfirmationValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => { const userToggle = control.get('userToggle'); @@ -86,25 +87,7 @@ 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 } => { @@ -125,7 +108,7 @@ export class UserDetailsDialogComponent { @Inject(MAT_DIALOG_DATA) public data: any, private fb: FormBuilder) { this.mode = data.mode; - this.userId = data.row && data.row.id; + this.userId = data.row && data.row.userId; this.editMode = this.mode !== UserDetailsDialogMode.NEW_MODE; this.current = this.editMode @@ -195,8 +178,7 @@ 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); @@ -233,6 +215,7 @@ export class UserDetailsDialogComponent { } regenerateAccessToken() { + /* let accessTokenPromise: Promise<AccessTokenRo> = this.userDetailsService.regenerateAccessToken(this.userId, "AccessPassword"); accessTokenPromise.then(response => { this.alertService.success("Token with\n id: " + response.identifier + " and\nvalue: " + response.value + " was generated!") @@ -240,12 +223,13 @@ export class UserDetailsDialogComponent { 'accessTokenId': response.identifier}) }, err => { this.alertService.error("Failed to generated access token. Please try again. If this happens again please contact Administrator!") - }); + });*/ } uploadCertificate(event) { + this.newCertFile=null; const file = event.target.files[0]; - this.certificateService.uploadCertificate$(file).subscribe((res: CertificateRo) => { + this.certificateService.validateCertificate(file).subscribe((res: CertificateRo) => { if (res && res.certificateId) { this.userForm.patchValue({ 'subject': res.subject, @@ -260,7 +244,7 @@ export class UserDetailsDialogComponent { }); this.certificateValidationMessage = res.invalidReason; this.isCertificateInvalid = res.invalid; - + this.newCertFile=file; } else { this.alertService.exception("Error occurred while reading certificate.", "Check if uploaded file has valid certificate type.", false); } 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 index edc1db69c0339ddbbf34343bf45614fa29d637e7..9de63ebdf78fea685a6b5cd5fbca1e827b4ff63b 100644 --- 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 @@ -1,9 +1,8 @@ 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"; -import {AccessTokenRo} from "../access-token-ro.model"; +import {AccessTokenRo} from "../../common/access-token-generation-dialog/access-token-ro.model"; import {AlertService} from "../../alert/alert.service"; @Injectable() @@ -12,14 +11,28 @@ export class UserDetailsService { constructor( private http: HttpClient, private alertService: AlertService, - ) { } - - isSamePreviousPasswordUsed$(userId: number, password: string): Observable<boolean> { - return this.http.post<boolean>(`${SmpConstants.REST_USER}/${userId}/samePreviousPasswordUsed`, password); + ) { } - regenerateAccessToken(userId: number, password: string):Promise<AccessTokenRo> { - return this.http.post<AccessTokenRo>(`${SmpConstants.REST_USER}/${userId}/generate-access-token`,password).toPromise() + /** + * Submits password to validate password + * @param userId + * @param password + */ + changePassword(userId: string, newPassword: string, currentPassword: string): Observable<boolean> { + return this.http.put<boolean>(SmpConstants.REST_PUBLIC_USER_CHANGE_PASSWORD.replace('{user-id}', userId), + { + currentPassword:currentPassword, + newPassword:newPassword + }); + } + /** + * Submit request to regenerated request token! + * @param userId + * @param password - password to authenticate user before regenerating the access token. + */ + regenerateAccessToken(userId: string, password: string): Observable<AccessTokenRo> { + return this.http.post<AccessTokenRo>(SmpConstants.REST_PUBLIC_USER_GENERATE_ACCESS_TOKEN.replace('{user-id}', userId), password) } } diff --git a/smp-angular/src/app/user/user.component.html b/smp-angular/src/app/user/user.component.html index 98b091f2de366567ffee1b76b10b8844f9de7a2d..b44ede3e20d21b8a58a023e624533e8c8d8e5e89 100644 --- a/smp-angular/src/app/user/user.component.html +++ b/smp-angular/src/app/user/user.component.html @@ -3,7 +3,7 @@ page_id="user_id" [title]="'Users'" [columnPicker]="columnPicker" - [url]="'rest/user'" + [url]="baseUrl" [additionalToolButtons]="additionalToolButtons" [searchTableController]="userController" [showSearchPanel]="false" @@ -14,10 +14,8 @@ <ng-template #roleCellTemplate let-value="value" ngx-datatable-cell-template>{{getRoleLabel(value)}}</ng-template> - <ng-template #certificateTemplate let-row="row" ngx-datatable-cell-template> - <span [class]='certCssClass(row)' - matTooltip="{{row.certificate?.invalidReason}}" - >{{row.certificate?.certificateId}}</span> + <ng-template #certificateTemplate let-row="row" let-value="value" ngx-datatable-cell-template> + <span [class]='certCssClass(row)' matTooltip="{{value?.invalidReason}}">{{value?.certificateId}}</span> </ng-template> <ng-template #additionalToolButtons > diff --git a/smp-angular/src/app/user/user.component.ts b/smp-angular/src/app/user/user.component.ts index 7a70e024b08d95d4aa574357cae703205530d9f2..fed30e362ace126b99c5a0495d7244d704d8c1a7 100644 --- a/smp-angular/src/app/user/user.component.ts +++ b/smp-angular/src/app/user/user.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import {AfterViewInit, Component, TemplateRef, ViewChild} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {AlertService} from '../alert/alert.service'; @@ -9,12 +9,13 @@ import {SecurityService} from "../security/security.service"; import {GlobalLookups} from "../common/global-lookups"; import {TruststoreEditDialogComponent} from "./truststore-edit-dialog/truststore-edit-dialog.component"; import {SearchTableEntityStatus} from "../common/search-table/search-table-entity-status.model"; +import {SmpConstants} from "../smp.constants"; @Component({ templateUrl:'./user.component.html', styleUrls: ['./user.component.css'] }) -export class UserComponent implements OnInit { +export class UserComponent implements AfterViewInit { @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; @ViewChild('rowExtensionAction') rowExtensionAction: TemplateRef<any>; @@ -25,6 +26,7 @@ export class UserComponent implements OnInit { columnPicker: ColumnPicker = new ColumnPicker(); userController: UserController; filter: any = {}; + baseUrl:string=SmpConstants.REST_INTERNAL_USER_MANAGE; constructor(private lookups: GlobalLookups, public securityService: SecurityService, @@ -33,7 +35,7 @@ export class UserComponent implements OnInit { public dialog: MatDialog) { } - ngOnInit() { + ngAfterViewInit() { this.userController = new UserController(this.http, this.lookups, this.dialog); this.columnPicker.allColumns = [ @@ -44,6 +46,7 @@ export class UserComponent implements OnInit { }, { name: 'Certificate', + prop: 'certificate', cellTemplate: this.certificateTemplate, canAutoResize: true }, diff --git a/smp-angular/src/app/user/user.service.ts b/smp-angular/src/app/user/user.service.ts index 5e06900c18f995cfca807f8c9cfd2e91c4b3a0e5..8ddd4d0743ac75cd8d6b84741ce5c3b14acedcff 100644 --- a/smp-angular/src/app/user/user.service.ts +++ b/smp-angular/src/app/user/user.service.ts @@ -6,7 +6,7 @@ import {SmpConstants} from "../smp.constants"; import {User} from "../security/user.model"; import {AlertService} from "../alert/alert.service"; import {SecurityService} from "../security/security.service"; -import {AccessTokenRo} from "./access-token-ro.model"; +import {AccessTokenRo} from "../common/access-token-generation-dialog/access-token-ro.model"; @Injectable() export class UserService { @@ -18,7 +18,7 @@ export class UserService { ) { } updateUser(user: User) { - this.http.put<string>(`${SmpConstants.REST_USER}/${user.id}`, user).subscribe(response => { + this.http.put<string>(SmpConstants.REST_PUBLIC_USER_UPDATE.replace('{user-id}', user.userId), user).subscribe(response => { this.securityService.updateUserDetails(response); this.alertService.success('The operation \'update user\' completed successfully.'); }, err => { diff --git a/smp-angular/src/styles.css b/smp-angular/src/styles.css index 769cbaba2f19a919e5f0834bad01bf281b887797..a8c0d75e9bc5dd41172cd2850a45840b1cebe619 100644 --- a/smp-angular/src/styles.css +++ b/smp-angular/src/styles.css @@ -74,6 +74,8 @@ ngx-datatable span:before { word-wrap: break-word; } .buttonsRow { + display: flex; + justify-content: flex-end; width: 100%; margin: 2px; padding: 5px; @@ -325,3 +327,29 @@ mat-card { border: 1px solid rgba(0, 0, 0, 0.12); } +.alert-message { + padding: 20px; + color: white; + opacity: 1; + transition: opacity 0.6s; + margin-bottom: 15px; + z-index: 1000; +} +.alert-message-error {background-color: #f44336;} +.alert-message-success {background-color: #4CAF50;} + +.alert-message-close-button { + margin-left: 15px; + padding: 4px; + color: white; + font-weight: bold; + float: right; + font-size: 24px; + line-height: 24px; + cursor: pointer; + transition: 0.3s; +} + +.alert-message-close-button:hover { + color: black; +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationToken.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationToken.java similarity index 67% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationToken.java rename to smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationToken.java index f7b7bc016a500ce16237bd38b97b500804772592..f34588bdbe368e8b0d81d259439494165bbc16cd 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationToken.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationToken.java @@ -1,6 +1,9 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.utils.SecurityUtils; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -9,8 +12,11 @@ import java.util.Collection; import java.util.Objects; public class SMPAuthenticationToken extends UsernamePasswordAuthenticationToken { - + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPAuthenticationToken.class); DBUser user; + // session encryption key to encrypt sensitive data + // at the moment used for UI sessions + SecurityUtils.Secret secret=null; public SMPAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(principal,credentials, authorities ); @@ -25,6 +31,15 @@ public class SMPAuthenticationToken extends UsernamePasswordAuthenticationToken return user; } + public SecurityUtils.Secret getSecret(){ + if (secret==null) { + LOG.debug("Secret does not yet exist. Create user session secret!"); + secret = SecurityUtils.generatePrivateSymmetricKey(); + LOG.debug("User session secret created!"); + } + return secret; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -37,7 +52,6 @@ public class SMPAuthenticationToken extends UsernamePasswordAuthenticationToken @Override public int hashCode() { - return Objects.hash(super.hashCode(), user); } } \ No newline at end of file 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 index ba9c5797841b9586e3db72266f27e03917550584..98f92104c8efa353e38aa6e57092b5002f603b91 100644 --- 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 @@ -3,6 +3,7 @@ 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 eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; @@ -23,15 +24,20 @@ public class DBUserToUserROConverter implements Converter<DBUser, UserRO> { @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.setAccessTokenId(source.getAccessTokenIdentifier()); + target.setPasswordExpireOn(source.getPasswordExpireOn()); + target.setAccessTokenExpireOn(source.getAccessTokenExpireOn()); target.setPasswordExpired(isPasswordExpired(source)); + target.setActive(source.isActive()); - target.setId(source.getId()); + // do not expose internal id + target.setUserId(SessionSecurityUtils.encryptedEntityId(source.getId())); if (source.getCertificate() != null) { CertificateRO certificateRO = conversionService.convert(source.getCertificate(), CertificateRO.class); target.setCertificate(certificateRO); @@ -45,6 +51,7 @@ public class DBUserToUserROConverter implements Converter<DBUser, UserRO> { return target; } + private boolean isPasswordExpired(DBUser source) { return StringUtils.isNotEmpty(source.getPassword()) && (isPasswordRecentlyReset(source) || isPasswordChangedLongerThanThreeMonthsAgo(source)); 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 index 325b6f585dfefa40a26a3a8c7a17f80d4027a163..2c11c86841069870e83820d7e5268d4dceb5b071 100644 --- 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 @@ -1,12 +1,16 @@ package eu.europa.ec.edelivery.smp.conversion; +import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; 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 eu.europa.ec.edelivery.smp.utils.SecurityUtils; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import java.util.Objects; @@ -26,13 +30,11 @@ public class UserROToDBUserConverter implements Converter<UserRO, DBUser> { target.setEmailAddress(source.getEmailAddress()); target.setUsername(source.getUsername()); target.setRole(source.getRole()); - target.setPassword(source.getPassword()); target.setActive(source.isActive()); - target.setId(source.getId()); + target.setId(SessionSecurityUtils.decryptEntityId(source.getUserId())); if (source.getCertificate() != null) { DBCertificate certData = conversionService.convert(source.getCertificate(), DBCertificate.class); target.setCertificate(certData); - if(StringUtils.isBlank(source.getUsername())) { // set username with certificate id. // username as cert id is set to database to force unique users @@ -42,4 +44,6 @@ public class UserROToDBUserConverter implements Converter<UserRO, DBUser> { } return target; } + + } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java index c3734d5dcd093ef9905e5349f2fde234ff02e2d8..ebffa082cab5a8b697a77c27141645f742bd0b7a 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java @@ -62,8 +62,8 @@ public class DBUser extends BaseEntity { @ColumnDescription(comment = "User email") private String emailAddress; // username - @Column(name = "USERNAME", length = CommonColumnsLengths.MAX_USERNAME_LENGTH, unique = true) - @ColumnDescription(comment = "Login username") + @Column(name = "USERNAME", length = CommonColumnsLengths.MAX_USERNAME_LENGTH, unique = true, nullable = false) + @ColumnDescription(comment = "Unique username identifier. The Username must not be null") private String username; @Column(name = "PASSWORD", length = CommonColumnsLengths.MAX_PASSWORD_LENGTH) @ColumnDescription(comment = "BCrypted password for username/password login") @@ -71,17 +71,28 @@ public class DBUser extends BaseEntity { @Column(name = "PASSWORD_CHANGED") @ColumnDescription(comment = "Last date when password was changed") LocalDateTime passwordChanged; - // Personal access token + + @Column(name = "PASSWORD_EXPIRE_ON") + @ColumnDescription(comment = "Date when password will expire") + LocalDateTime passwordExpireOn; + + // Personal access token @Column(name = "ACCESS_TOKEN_ID", length = CommonColumnsLengths.MAX_USERNAME_LENGTH, unique = true) @ColumnDescription(comment = "Personal access token id") private String accessTokenIdentifier; @Column(name = "ACCESS_TOKEN", length = CommonColumnsLengths.MAX_PASSWORD_LENGTH) @ColumnDescription(comment = "BCrypted personal access token") private String accessToken; - @Column(name = "PAT_GENERATED") + @Column(name = "ACCESS_TOKEN_GENERATED_ON") @ColumnDescription(comment = "Date when personal access token was generated") LocalDateTime accessTokenGeneratedOn; + @Column(name = "ACCESS_TOKEN_EXPIRE_ON") + @ColumnDescription(comment = "Date when personal access token will expire") + LocalDateTime accessTokenExpireOn; + + + @Column(name = "ACTIVE", nullable = false) @ColumnDescription(comment = "Is user active") private boolean active = true; @@ -159,6 +170,22 @@ public class DBUser extends BaseEntity { this.accessTokenGeneratedOn = accessTokenGeneratedOn; } + public LocalDateTime getPasswordExpireOn() { + return passwordExpireOn; + } + + public void setPasswordExpireOn(LocalDateTime passwordExpireOn) { + this.passwordExpireOn = passwordExpireOn; + } + + public LocalDateTime getAccessTokenExpireOn() { + return accessTokenExpireOn; + } + + public void setAccessTokenExpireOn(LocalDateTime accessTokenExpireOn) { + this.accessTokenExpireOn = accessTokenExpireOn; + } + public String getRole() { return role; } @@ -213,7 +240,7 @@ public class DBUser extends BaseEntity { @Override public int hashCode() { - return Objects.hash(super.hashCode(), id, username, certificate); + return Objects.hash(super.hashCode(), id, username); } @PrePersist diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AccessTokenRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AccessTokenRO.java index ca89868aad6bc73ddaf8243429c3a076c7cea3ab..ca8f7a31e8c9d5bcf520cbf4c224bbdb47efb9b1 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AccessTokenRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AccessTokenRO.java @@ -1,5 +1,9 @@ package eu.europa.ec.edelivery.smp.data.ui; +import com.fasterxml.jackson.annotation.JsonFormat; +import eu.europa.ec.edelivery.smp.utils.SMPConstants; +import org.springframework.format.annotation.DateTimeFormat; + import java.io.Serializable; import java.time.LocalDateTime; @@ -9,7 +13,10 @@ public class AccessTokenRO implements Serializable { private String identifier; private String value; + @JsonFormat(pattern = SMPConstants.JSON_DATETIME_ISO) LocalDateTime generatedOn; + @JsonFormat(pattern = SMPConstants.JSON_DATETIME_ISO) + LocalDateTime expireOn; public String getIdentifier() { return identifier; @@ -34,4 +41,12 @@ public class AccessTokenRO implements Serializable { public void setGeneratedOn(LocalDateTime generatedOn) { this.generatedOn = generatedOn; } + + public LocalDateTime getExpireOn() { + return expireOn; + } + + public void setExpireOn(LocalDateTime expireOn) { + this.expireOn = expireOn; + } } \ No newline at end of file diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/CertificateRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/CertificateRO.java index b09b32e022d28760a21092faa949b3dae3a9a836..5b55027aafcf8502df3f9bf51a595d3d03ed16f6 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/CertificateRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/CertificateRO.java @@ -1,6 +1,7 @@ package eu.europa.ec.edelivery.smp.data.ui; import com.fasterxml.jackson.annotation.JsonFormat; +import eu.europa.ec.edelivery.smp.utils.SMPConstants; import java.util.Date; @@ -24,9 +25,9 @@ public class CertificateRO extends BaseRO { private String invalidReason; - @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:mm", timezone="CET") + @JsonFormat(pattern = SMPConstants.JSON_DATETIME_ISO) private Date validFrom; - @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:mm", timezone="CET") + @JsonFormat(pattern = SMPConstants.JSON_DATETIME_ISO) private Date validTo; public CertificateRO() { diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DomainPublicRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DomainPublicRO.java new file mode 100644 index 0000000000000000000000000000000000000000..30699e799f63bf178a690d8d28c075e497e1018f --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DomainPublicRO.java @@ -0,0 +1,34 @@ +package eu.europa.ec.edelivery.smp.data.ui; + + +/** + * Domain resource object containing only public data + * + * @author Joze Rihtarsic + * @since 5.0 + */ +public class DomainPublicRO extends BaseRO { + + private static final long serialVersionUID = -9008583888835630561L; + + String domainCode; + String smlSubdomain; + + public String getDomainCode() { + return domainCode; + } + + public void setDomainCode(String domainCode) { + this.domainCode = domainCode; + } + + public String getSmlSubdomain() { + return smlSubdomain; + } + + public void setSmlSubdomain(String smlSubdomain) { + this.smlSubdomain = smlSubdomain; + } + + +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/PasswordChangeRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/PasswordChangeRO.java new file mode 100644 index 0000000000000000000000000000000000000000..c00b5668f1065f6178d32efaaf7300750e8c2025 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/PasswordChangeRO.java @@ -0,0 +1,31 @@ +package eu.europa.ec.edelivery.smp.data.ui; + +import java.io.Serializable; + + +/** + * Password change request + * + * @author Joze Rihtarsic + * @since 4.2 + */ +public class PasswordChangeRO implements Serializable { + String currentPassword; + String newPassword; + + public String getCurrentPassword() { + return currentPassword; + } + + public void setCurrentPassword(String currentPassword) { + this.currentPassword = currentPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpConfigRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpConfigRO.java index 7714fe5c6fe105234ffaa28aaf808a309c47d643..2602e70b87e8bdcb5d37446f7c607b8db90035c5 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpConfigRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpConfigRO.java @@ -11,6 +11,10 @@ public class SmpConfigRO implements Serializable { String participantSchemaRegExpMessage; + String passwordValidationRegExp; + String passwordValidationRegExpMessage; + + public boolean isSmlIntegrationOn() { return smlIntegrationOn; } @@ -42,4 +46,20 @@ public class SmpConfigRO implements Serializable { public void setParticipantSchemaRegExpMessage(String participantSchemaRegExpMessage) { this.participantSchemaRegExpMessage = participantSchemaRegExpMessage; } + + public String getPasswordValidationRegExp() { + return passwordValidationRegExp; + } + + public void setPasswordValidationRegExp(String passwordValidationRegExp) { + this.passwordValidationRegExp = passwordValidationRegExp; + } + + public String getPasswordValidationRegExpMessage() { + return passwordValidationRegExpMessage; + } + + public void setPasswordValidationRegExpMessage(String passwordValidationRegExpMessage) { + this.passwordValidationRegExpMessage = passwordValidationRegExpMessage; + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java index 77a17abe16bb7094766a828ddbd71375547caec8..71cf350703e043dafddaf2b6aea91dc7a4eb3ffa 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java @@ -1,11 +1,15 @@ package eu.europa.ec.edelivery.smp.data.ui; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.utils.SMPConstants; import org.springframework.security.core.userdetails.UserDetails; +import java.time.LocalDateTime; import java.util.Collection; +import java.util.Date; /** @@ -14,26 +18,37 @@ import java.util.Collection; */ public class UserRO extends BaseRO implements UserDetails { - private static final long serialVersionUID = 2821447495333163882L; + static final long serialVersionUID = 2821447495333163882L; - private String username; - private String password; - private String accessTokenId; - private String emailAddress; - private Collection<SMPAuthority> authorities; - private boolean active = true; - private String role; - private Long id; - private CertificateRO certificate; - private int statusPassword = EntityROStatus.PERSISTED.getStatusNumber(); - private boolean passwordExpired; + String username; - public Long getId() { - return id; + String password; + @JsonFormat(pattern = SMPConstants.JSON_DATETIME_ISO) + LocalDateTime passwordExpireOn; + String accessTokenId; + @JsonFormat(pattern = SMPConstants.JSON_DATETIME_ISO) + LocalDateTime accessTokenExpireOn; + String emailAddress; + Collection<SMPAuthority> authorities; + boolean active = true; + String role; + String userId; + CertificateRO certificate; + int statusPassword = EntityROStatus.PERSISTED.getStatusNumber(); + boolean passwordExpired; + + /** + * Get DB user hash value. It can be used as unique ID for the user. Use hash value for the webservice/ui and do not + * expose internal database user identity + * + * @return hash value of database user entity. + */ + public String getUserId() { + return userId; } - public void setId(Long id) { - this.id = id; + public void setUserId(String userId) { + this.userId = userId; } public String getUsername() { @@ -84,6 +99,22 @@ public class UserRO extends BaseRO implements UserDetails { this.active = active; } + public LocalDateTime getPasswordExpireOn() { + return passwordExpireOn; + } + + public void setPasswordExpireOn(LocalDateTime passwordExpireOn) { + this.passwordExpireOn = passwordExpireOn; + } + + public LocalDateTime getAccessTokenExpireOn() { + return accessTokenExpireOn; + } + + public void setAccessTokenExpireOn(LocalDateTime accessTokenExpireOn) { + this.accessTokenExpireOn = accessTokenExpireOn; + } + public String getRole() { return role; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPAuthority.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPAuthority.java index 55ea765aaeb0faa2057586c38355f3225674d8a9..962621e9de77bb7f9d85b1cd66e539bf30e80fe8 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPAuthority.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPAuthority.java @@ -24,6 +24,10 @@ public class SMPAuthority implements GrantedAuthority { public static final SMPAuthority S_AUTHORITY_SERVICE_GROUP = new SMPAuthority(SMPRole.SERVICE_GROUP_ADMIN.getCode()); public static final SMPAuthority S_AUTHORITY_ANONYMOUS = new SMPAuthority(SMPRole.ANONYMOUS.getCode()); + public static final SMPAuthority S_AUTHORITY_WS_SYSTEM_ADMIN = new SMPAuthority(SMPRole.WS_SYSTEM_ADMIN.getCode()); + public static final SMPAuthority S_AUTHORITY_WS_SMP_ADMIN = new SMPAuthority(SMPRole.WS_SMP_ADMIN.getCode()); + public static final SMPAuthority S_AUTHORITY_WS_SERVICE_GROUP = new SMPAuthority(SMPRole.WS_SERVICE_GROUP_ADMIN.getCode()); + String role; public SMPAuthority(String role) { diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPRole.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPRole.java index 6ec4a3ad3609becb7a43550793699f8d47d09b36..d9b41097160c2e1029c23f1a0f0359424e61fb67 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPRole.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/auth/SMPRole.java @@ -3,9 +3,13 @@ package eu.europa.ec.edelivery.smp.data.ui.auth; public enum SMPRole { ANONYMOUS("ANONYMOUS"), - SMP_ADMIN("SMP_ADMIN"), + GROUP_ADMIN("GROUP_ADMIN"), SERVICE_GROUP_ADMIN("SERVICE_GROUP_ADMIN"), - SYSTEM_ADMIN("SYSTEM_ADMIN"); + SMP_ADMIN("SMP_ADMIN"), + SYSTEM_ADMIN("SYSTEM_ADMIN"), + WS_SERVICE_GROUP_ADMIN("WS_SERVICE_GROUP_ADMIN"), + WS_SMP_ADMIN("WS_SMP_ADMIN"), + WS_SYSTEM_ADMIN("WS_SYSTEM_ADMIN"); String code; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java index 85ffaa2243d14b04f2166438669a9679643a9583..167216dcb50bd9b244dbc85393274f17fa6c255e 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java @@ -5,77 +5,92 @@ import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.Optional; +import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyTypeEnum.*; + public enum SMPPropertyEnum { BLUE_COAT_ENABLED("authentication.blueCoat.enabled", "false", "Authentication with Blue Coat means that all HTTP requests " + "having 'Client-Cert' header will be authenticated as username placed in the header.Never expose SMP to the WEB " + - "without properly configured reverse-proxy and active blue coat.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + "without properly configured reverse-proxy and active blue coat.", false, false, false, BOOLEAN), - OUTPUT_CONTEXT_PATH("contextPath.output", "true", "This property controls pattern of URLs produced by SMP in GET ServiceGroup responses.", true, false, true, SMPPropertyTypeEnum.BOOLEAN), - HTTP_FORWARDED_HEADERS_ENABLED("smp.http.forwarded.headers.enabled", "false", "Use (value true) or remove (value false) forwarded headers! There are security considerations for forwarded headers since an application cannot know if the headers were added by a proxy, as intended, or by a malicious client.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), - HTTP_HSTS_MAX_AGE("smp.http.httpStrictTransportSecurity.maxAge", "31536000", "How long(in seconds) HSTS should last in the browser's cache(default one year)", false, false, true, SMPPropertyTypeEnum.INTEGER), - HTTP_HEADER_SEC_POLICY("smp.http.header.security.policy", "default-src 'self'; script-src 'self'; child-src 'none'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; form-action 'self';", "Content Security Policy (CSP)", false, false, true, SMPPropertyTypeEnum.INTEGER), + OUTPUT_CONTEXT_PATH("contextPath.output", "true", "This property controls pattern of URLs produced by SMP in GET ServiceGroup responses.", true, false, true, BOOLEAN), + HTTP_FORWARDED_HEADERS_ENABLED("smp.http.forwarded.headers.enabled", "false", "Use (value true) or remove (value false) forwarded headers! There are security considerations for forwarded headers since an application cannot know if the headers were added by a proxy, as intended, or by a malicious client.", false, false, false, BOOLEAN), + HTTP_HSTS_MAX_AGE("smp.http.httpStrictTransportSecurity.maxAge", "31536000", "How long(in seconds) HSTS should last in the browser's cache(default one year)", false, false, true, INTEGER), + HTTP_HEADER_SEC_POLICY("smp.http.header.security.policy", "default-src 'self'; script-src 'self'; child-src 'none'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; form-action 'self';", "Content Security Policy (CSP)", false, false, true, STRING), // http proxy configuration - HTTP_PROXY_HOST("smp.proxy.host", "", "The http proxy host", false, false, false, SMPPropertyTypeEnum.STRING), - HTTP_NO_PROXY_HOSTS("smp.noproxy.hosts", "localhost|127.0.0.1", "list of nor proxy hosts. Ex.: localhost|127.0.0.1", false, false, false, SMPPropertyTypeEnum.STRING), - HTTP_PROXY_PASSWORD("smp.proxy.password", "", "Base64 encrypted password for Proxy.", false, true, false, SMPPropertyTypeEnum.STRING), - HTTP_PROXY_PORT("smp.proxy.port", "80", "The http proxy port", false, false, false, SMPPropertyTypeEnum.INTEGER), - HTTP_PROXY_USER("smp.proxy.user", "", "The proxy user", false, false, false, SMPPropertyTypeEnum.STRING), + HTTP_PROXY_HOST("smp.proxy.host", "", "The http proxy host", false, false, false, STRING), + HTTP_NO_PROXY_HOSTS("smp.noproxy.hosts", "localhost|127.0.0.1", "list of nor proxy hosts. Ex.: localhost|127.0.0.1", false, false, false, STRING), + HTTP_PROXY_PASSWORD("smp.proxy.password", "", "Base64 encrypted password for Proxy.", false, true, false, STRING), + HTTP_PROXY_PORT("smp.proxy.port", "80", "The http proxy port", false, false, false, INTEGER), + HTTP_PROXY_USER("smp.proxy.user", "", "The proxy user", false, false, false, STRING), - PARTC_SCH_REGEXP("identifiersBehaviour.ParticipantIdentifierScheme.validationRegex", "^((?!^.{26})([a-z0-9]+-[a-z0-9]+-[a-z0-9]+)|urn:oasis:names:tc:ebcore:partyid-type:(iso6523|unregistered)(:.+)?$)", "Participant Identifier Schema of each PUT ServiceGroup request is validated against this schema.", false, false, false, SMPPropertyTypeEnum.REGEXP), + PARTC_SCH_REGEXP("identifiersBehaviour.ParticipantIdentifierScheme.validationRegex", "^((?!^.{26})([a-z0-9]+-[a-z0-9]+-[a-z0-9]+)|urn:oasis:names:tc:ebcore:partyid-type:(iso6523|unregistered)(:.+)?$)", "Participant Identifier Schema of each PUT ServiceGroup request is validated against this schema.", false, false, false, REGEXP), PARTC_SCH_REGEXP_MSG("identifiersBehaviour.ParticipantIdentifierScheme.validationRegexMessage", - "Participant scheme must start with:urn:oasis:names:tc:ebcore:partyid-type:(iso6523:|unregistered:) OR must be up to 25 characters long with form [domain]-[identifierArea]-[identifierType] (ex.: 'busdox-actorid-upis') and may only contain the following characters: [a-z0-9].", "Error message for UI", false, false, false, SMPPropertyTypeEnum.STRING), - CS_PARTICIPANTS("identifiersBehaviour.caseSensitive.ParticipantIdentifierSchemes", "sensitive-participant-sc1|sensitive-participant-sc2", "Specifies schemes of participant identifiers that must be considered CASE-SENSITIVE.", false, false, false, SMPPropertyTypeEnum.LIST_STRING), - CS_DOCUMENTS("identifiersBehaviour.caseSensitive.DocumentIdentifierSchemes", "casesensitive-doc-scheme1|casesensitive-doc-scheme2", "Specifies schemes of document identifiers that must be considered CASE-SENSITIVE.", false, false, false, SMPPropertyTypeEnum.LIST_STRING), + "Participant scheme must start with:urn:oasis:names:tc:ebcore:partyid-type:(iso6523:|unregistered:) OR must be up to 25 characters long with form [domain]-[identifierArea]-[identifierType] (ex.: 'busdox-actorid-upis') and may only contain the following characters: [a-z0-9].", "Error message for UI", false, false, false, STRING), + CS_PARTICIPANTS("identifiersBehaviour.caseSensitive.ParticipantIdentifierSchemes", "sensitive-participant-sc1|sensitive-participant-sc2", "Specifies schemes of participant identifiers that must be considered CASE-SENSITIVE.", false, false, false, LIST_STRING), + CS_DOCUMENTS("identifiersBehaviour.caseSensitive.DocumentIdentifierSchemes", "casesensitive-doc-scheme1|casesensitive-doc-scheme2", "Specifies schemes of document identifiers that must be considered CASE-SENSITIVE.", false, false, false, LIST_STRING), // SML integration! - SML_ENABLED("bdmsl.integration.enabled", "false", "BDMSL (SML) integration ON/OFF switch", false, false, false, SMPPropertyTypeEnum.BOOLEAN), - SML_PARTICIPANT_MULTIDOMAIN("bdmsl.participant.multidomain.enabled", "false", "Set to true if SML support participant on multidomain", false, false, true, SMPPropertyTypeEnum.BOOLEAN), - SML_URL("bdmsl.integration.url", "http://localhost:8080/edelivery-sml", "BDMSL (SML) endpoint", false, false, false, SMPPropertyTypeEnum.URL), - SML_TLS_DISABLE_CN_CHECK("bdmsl.integration.tls.disableCNCheck", "false", "If SML Url is HTTPs - Disable CN check if needed.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), - SML_TLS_SERVER_CERT_SUBJECT_REGEXP("bdmsl.integration.tls.serverSubjectRegex", ".*", "Regular expression for server TLS certificate subject verification CertEx. .*CN=acc.edelivery.tech.ec.europa.eu.*.", false, false, false, SMPPropertyTypeEnum.REGEXP), - SML_LOGICAL_ADDRESS("bdmsl.integration.logical.address", "http://localhost:8080/smp/", "Logical SMP endpoint which will be registered on SML when registering new domain", false, false, false, SMPPropertyTypeEnum.URL), - SML_PHYSICAL_ADDRESS("bdmsl.integration.physical.address", "0.0.0.0", "Physical SMP endpoint which will be registered on SML when registering new domain.", false, false, false, SMPPropertyTypeEnum.STRING), + SML_ENABLED("bdmsl.integration.enabled", "false", "BDMSL (SML) integration ON/OFF switch", false, false, false, BOOLEAN), + SML_PARTICIPANT_MULTIDOMAIN("bdmsl.participant.multidomain.enabled", "false", "Set to true if SML support participant on multidomain", false, false, true, BOOLEAN), + SML_URL("bdmsl.integration.url", "http://localhost:8080/edelivery-sml", "BDMSL (SML) endpoint", false, false, false, URL), + SML_TLS_DISABLE_CN_CHECK("bdmsl.integration.tls.disableCNCheck", "false", "If SML Url is HTTPs - Disable CN check if needed.", false, false, false, BOOLEAN), + SML_TLS_SERVER_CERT_SUBJECT_REGEXP("bdmsl.integration.tls.serverSubjectRegex", ".*", "Regular expression for server TLS certificate subject verification CertEx. .*CN=acc.edelivery.tech.ec.europa.eu.*.", false, false, false, REGEXP), + SML_LOGICAL_ADDRESS("bdmsl.integration.logical.address", "http://localhost:8080/smp/", "Logical SMP endpoint which will be registered on SML when registering new domain", false, false, false, URL), + SML_PHYSICAL_ADDRESS("bdmsl.integration.physical.address", "0.0.0.0", "Physical SMP endpoint which will be registered on SML when registering new domain.", false, false, false, STRING), // keystore truststore - KEYSTORE_PASSWORD("smp.keystore.password", "", "Encrypted keystore (and keys) password ", false, true, false, SMPPropertyTypeEnum.STRING), - KEYSTORE_FILENAME("smp.keystore.filename", "smp-keystore.jks", "Keystore filename ", true, false, false, SMPPropertyTypeEnum.FILENAME), - TRUSTSTORE_PASSWORD("smp.truststore.password", "", "Encrypted truststore password ", false, true, false, SMPPropertyTypeEnum.STRING), - TRUSTSTORE_FILENAME("smp.truststore.filename", "", "Truststore filename ", false, false, false, SMPPropertyTypeEnum.FILENAME), - CERTIFICATE_CRL_FORCE("smp.certificate.crl.force", "false", "If false then if CRL is not reachable ignore CRL validation", false, false, false, SMPPropertyTypeEnum.BOOLEAN), - CONFIGURATION_DIR("configuration.dir", "smp", "Path to the folder containing all the configuration files (keystore and encryption key)", true, false, true, SMPPropertyTypeEnum.PATH), - ENCRYPTION_FILENAME("encryption.key.filename", "encryptionPrivateKey.private", "Key filename to encrypt passwords", false, false, true, SMPPropertyTypeEnum.FILENAME), - KEYSTORE_PASSWORD_DECRYPTED("smp.keystore.password.decrypted", "", "Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false, false, SMPPropertyTypeEnum.STRING), - TRUSTSTORE_PASSWORD_DECRYPTED("smp.truststore.password.decrypted", "", "Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false, false, SMPPropertyTypeEnum.STRING), - CERTIFICATE_ALLOWED_CERTIFICATEPOLICY_OIDS("smp.certificate.validation.allowedCertificatePolicyOIDs","","List of certificate policy OIDs separated by comma where at least one must be in the CertifictePolicy extension", false, false,false, SMPPropertyTypeEnum.STRING), - CERTIFICATE_SUBJECT_REGULAR_EXPRESSION("smp.certificate.validation.subjectRegex",".*","Regular expression to validate subject of the certificate", false, false,false, SMPPropertyTypeEnum.REGEXP), - - SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression", "0 48 */1 * * *", "Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false, true, SMPPropertyTypeEnum.STRING), + KEYSTORE_PASSWORD("smp.keystore.password", "", "Encrypted keystore (and keys) password ", false, true, false, STRING), + KEYSTORE_FILENAME("smp.keystore.filename", "smp-keystore.jks", "Keystore filename ", true, false, false, FILENAME), + TRUSTSTORE_PASSWORD("smp.truststore.password", "", "Encrypted truststore password ", false, true, false, STRING), + TRUSTSTORE_FILENAME("smp.truststore.filename", "", "Truststore filename ", false, false, false, FILENAME), + CERTIFICATE_CRL_FORCE("smp.certificate.crl.force", "false", "If false then if CRL is not reachable ignore CRL validation", false, false, false, BOOLEAN), + CONFIGURATION_DIR("configuration.dir", "smp", "Path to the folder containing all the configuration files (keystore and encryption key)", true, false, true, PATH), + ENCRYPTION_FILENAME("encryption.key.filename", "encryptionPrivateKey.private", "Key filename to encrypt passwords", false, false, true, FILENAME), + KEYSTORE_PASSWORD_DECRYPTED("smp.keystore.password.decrypted", "", "Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false, false, STRING), + TRUSTSTORE_PASSWORD_DECRYPTED("smp.truststore.password.decrypted", "", "Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false, false, STRING), + CERTIFICATE_ALLOWED_CERTIFICATEPOLICY_OIDS("smp.certificate.validation.allowedCertificatePolicyOIDs","","List of certificate policy OIDs separated by comma where at least one must be in the CertifictePolicy extension", false, false,false, STRING), + CERTIFICATE_SUBJECT_REGULAR_EXPRESSION("smp.certificate.validation.subjectRegex",".*","Regular expression to validate subject of the certificate", false, false,false, REGEXP), + + SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression", "0 48 */1 * * *", "Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false, true, STRING), // UI COOKIE configuration - UI_COOKIE_SESSION_SECURE("smp.ui.session.secure", "false", "Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistant to man-in-the-middle attacks.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), - UI_COOKIE_SESSION_MAX_AGE("smp.ui.session.max-age", "", "Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. Empty value will not set parameter", false, false, false, SMPPropertyTypeEnum.INTEGER), - UI_COOKIE_SESSION_SITE("smp.ui.session.strict", "Lax", "Controls whether a cookie is sent with cross-origin requests, providing some protection against cross-site request forgery attacks. Possible values are: Strict, None, Lax. (Cookies with SameSite=None require a secure context/HTTPS)!!)", false, false, false, SMPPropertyTypeEnum.STRING), - UI_COOKIE_SESSION_PATH("smp.ui.session.path", "", "A path that must exist in the requested URL, or the browser won't send the Cookie header. Null/Empty value sets the authentication requests context by default. The forward slash (/) character is interpreted as a directory separator, and subdirectories will be matched as well: for Path=/docs, /docs, /docs/Web/, and /docs/Web/HTTP will all match", false, false, false, SMPPropertyTypeEnum.STRING), - UI_COOKIE_SESSION_IDLE_TIMEOUT_ADMIN("smp.ui.session.idle_timeout.admin", "300", "Specifies the time, in seconds, between client requests before the SMP will invalidate session for ADMIN users (System)!", false, false, false, SMPPropertyTypeEnum.INTEGER), - UI_COOKIE_SESSION_IDLE_TIMEOUT_USER("smp.ui.session.idle_timeout.user", "1800", "Specifies the time, in seconds, between client requests before the SMP will invalidate session for users (Service group, SMP Admin)", false, false, false, SMPPropertyTypeEnum.INTEGER), - // authentication - UI_AUTHENTICATION_TYPES("smp.ui.authentication.types", "PASSWORD", "Set list of '|' separated authentication types: PASSWORD|SSO.", false, false, false, SMPPropertyTypeEnum.LIST_STRING), - AUTOMATION_AUTHENTICATION_TYPES("smp.automation.authentication.types", "PASSWORD|CERTIFICATE", "Set list of '|' separated application-automation authentication types (Web-Service integration). Currently supported PASSWORD, CERT: ex. PASSWORD|CERT", false, false, false, SMPPropertyTypeEnum.LIST_STRING), + UI_COOKIE_SESSION_SECURE("smp.ui.session.secure", "false", "Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistant to man-in-the-middle attacks.", false, false, false, BOOLEAN), + UI_COOKIE_SESSION_MAX_AGE("smp.ui.session.max-age", "", "Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. Empty value will not set parameter", false, false, false, INTEGER), + UI_COOKIE_SESSION_SITE("smp.ui.session.strict", "Lax", "Controls whether a cookie is sent with cross-origin requests, providing some protection against cross-site request forgery attacks. Possible values are: Strict, None, Lax. (Cookies with SameSite=None require a secure context/HTTPS)!!)", false, false, false, STRING), + UI_COOKIE_SESSION_PATH("smp.ui.session.path", "", "A path that must exist in the requested URL, or the browser won't send the Cookie header. Null/Empty value sets the authentication requests context by default. The forward slash (/) character is interpreted as a directory separator, and subdirectories will be matched as well: for Path=/docs, /docs, /docs/Web/, and /docs/Web/HTTP will all match", false, false, false, STRING), + UI_COOKIE_SESSION_IDLE_TIMEOUT_ADMIN("smp.ui.session.idle_timeout.admin", "300", "Specifies the time, in seconds, between client requests before the SMP will invalidate session for ADMIN users (System)!", false, false, false, INTEGER), + UI_COOKIE_SESSION_IDLE_TIMEOUT_USER("smp.ui.session.idle_timeout.user", "1800", "Specifies the time, in seconds, between client requests before the SMP will invalidate session for users (Service group, SMP Admin)", false, false, false, INTEGER), + + PASSWORD_POLICY_REGULAR_EXPRESSION("smp.passwordPolicy.validationRegex","^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~`!@#$%^&+=\\-_<>.,?:;*/()|\\[\\]{}'\"\\\\]).{16,32}$", + "Password minimum complexity rules!", false, false,false, REGEXP), + + PASSWORD_POLICY_MESSAGE("smp.passwordPolicy.validationMessage","Minimum length: 16 characters;Maximum length: 32 characters;At least one letter in lowercase;At least one letter in uppercase;At least one digit;At least one special character", + "The error message shown to the user in case the password does not follow the regex put in the domibus.passwordPolicy.pattern property", false, false,false, STRING), + PASSWORD_POLICY_VALID_DAYS("smp.passwordPolicy.validDays","90", + "Number of days password is valid", false, false,false, INTEGER), + + ACCESS_TOKEN_POLICY_VALID_DAYS("smp.accessToken.validDays","60", + "Number of days access token is valid is valid", false, false,false, INTEGER), + + + // authentication + UI_AUTHENTICATION_TYPES("smp.ui.authentication.types", "PASSWORD", "Set list of '|' separated authentication types: PASSWORD|SSO.", false, false, false, LIST_STRING), + AUTOMATION_AUTHENTICATION_TYPES("smp.automation.authentication.types", "PASSWORD|CERTIFICATE", "Set list of '|' separated application-automation authentication types (Web-Service integration). Currently supported PASSWORD, CERT: ex. PASSWORD|CERT", false, false, false, LIST_STRING), // SSO configuration - SSO_CAS_UI_LABEL("smp.sso.cas.ui.label", "EU Login", "The SSO service provider label.", false, false, true, SMPPropertyTypeEnum.STRING), - SSO_CAS_URL("smp.sso.cas.url", "http://localhost:8080/cas/", "The SSO CAS URL enpoint", false, false, true, SMPPropertyTypeEnum.URL), - SSO_CAS_URLPATH_LOGIN("smp.sso.cas.urlpath.login", "login", "The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.login}.", false, false, true, SMPPropertyTypeEnum.STRING), - SSO_CAS_CALLBACK_URL("smp.sso.cas.callback.url", "http://localhost:8080/smp/ui/rest/security/cas", "The URL is the callback URL belonging to the local SMP Security System. If using RP make sure it target SMP path '/ui/rest/security/cas'", false, false, true, SMPPropertyTypeEnum.URL), - SSO_CAS_TOKEN_VALIDATION_URLPATH("smp.sso.cas.token.validation.urlpath", "http://localhost:8080/cas/", "The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.token.validation}.", false, false, true, SMPPropertyTypeEnum.STRING), - SSO_CAS_TOKEN_VALIDATION_PARAMS("smp.sso.cas.token.validation.params", "acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP", "The CAS token validation key:value properties separated with '|'.Ex: 'acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP'", false, false, true, SMPPropertyTypeEnum.MAP_STRING), - SSO_CAS_TOKEN_VALIDATION_GROUPS("smp.sso.cas.token.validation.groups", "DIGIT_SMP|DIGIT_ADMIN", "'|' separated CAS groups user must belong to.", false, false, true, SMPPropertyTypeEnum.LIST_STRING), + SSO_CAS_UI_LABEL("smp.sso.cas.ui.label", "EU Login", "The SSO service provider label.", false, false, true, STRING), + SSO_CAS_URL("smp.sso.cas.url", "http://localhost:8080/cas/", "The SSO CAS URL enpoint", false, false, true, URL), + SSO_CAS_URLPATH_LOGIN("smp.sso.cas.urlpath.login", "login", "The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.login}.", false, false, true, STRING), + SSO_CAS_CALLBACK_URL("smp.sso.cas.callback.url", "http://localhost:8080/smp/ui/rest/security/cas", "The URL is the callback URL belonging to the local SMP Security System. If using RP make sure it target SMP path '/ui/rest/security/cas'", false, false, true, URL), + SSO_CAS_TOKEN_VALIDATION_URLPATH("smp.sso.cas.token.validation.urlpath", "http://localhost:8080/cas/", "The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.token.validation}.", false, false, true, STRING), + SSO_CAS_TOKEN_VALIDATION_PARAMS("smp.sso.cas.token.validation.params", "acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP", "The CAS token validation key:value properties separated with '|'.Ex: 'acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP'", false, false, true, MAP_STRING), + SSO_CAS_TOKEN_VALIDATION_GROUPS("smp.sso.cas.token.validation.groups", "DIGIT_SMP|DIGIT_ADMIN", "'|' separated CAS groups user must belong to.", false, false, true, LIST_STRING), //deprecated properties - SML_KEYSTORE_PASSWORD("bdmsl.integration.keystore.password", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), - SML_KEYSTORE_PATH("bdmsl.integration.keystore.path", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), - SIGNATURE_KEYSTORE_PASSWORD("xmldsig.keystore.password", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), - SIGNATURE_KEYSTORE_PATH("xmldsig.keystore.classpath", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), - SML_PROXY_HOST("bdmsl.integration.proxy.server", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), - SML_PROXY_PORT("bdmsl.integration.proxy.port", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.INTEGER), - SML_PROXY_USER("bdmsl.integration.proxy.user", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), - SML_PROXY_PASSWORD("bdmsl.integration.proxy.password", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SML_KEYSTORE_PASSWORD("bdmsl.integration.keystore.password", "", "Deprecated", false, false, false, STRING), + SML_KEYSTORE_PATH("bdmsl.integration.keystore.path", "", "Deprecated", false, false, false, STRING), + SIGNATURE_KEYSTORE_PASSWORD("xmldsig.keystore.password", "", "Deprecated", false, false, false, STRING), + SIGNATURE_KEYSTORE_PATH("xmldsig.keystore.classpath", "", "Deprecated", false, false, false, STRING), + SML_PROXY_HOST("bdmsl.integration.proxy.server", "", "Deprecated", false, false, false, STRING), + SML_PROXY_PORT("bdmsl.integration.proxy.port", "", "Deprecated", false, false, false, INTEGER), + SML_PROXY_USER("bdmsl.integration.proxy.user", "", "Deprecated", false, false, false, STRING), + SML_PROXY_PASSWORD("bdmsl.integration.proxy.password", "", "Deprecated", false, false, false, STRING), ; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/exceptions/ErrorResponseRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/exceptions/ErrorResponseRO.java new file mode 100644 index 0000000000000000000000000000000000000000..cdc776e9ae99b9013a5c0e236775df350b1b7fae --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/exceptions/ErrorResponseRO.java @@ -0,0 +1,86 @@ +package eu.europa.ec.edelivery.smp.data.ui.exceptions; + + +import java.util.Objects; + + +/** + * @author Joze Rihtarsic + * @since 4.2 + */ +public class ErrorResponseRO { + protected String businessCode; + protected String errorDescription; + protected String errorUniqueId; + + /** + * Default no-arg constructor + */ + public ErrorResponseRO() { + + } + + /** + * Fully-initialising value constructor + */ + public ErrorResponseRO(final String businessCode, final String errorDescription, final String errorUniqueId) { + this.businessCode = businessCode; + this.errorDescription = errorDescription; + this.errorUniqueId = errorUniqueId; + } + + + public String getBusinessCode() { + return businessCode; + } + + + public void setBusinessCode(String value) { + this.businessCode = value; + } + + + public String getErrorDescription() { + return errorDescription; + } + + + public void setErrorDescription(String value) { + this.errorDescription = value; + } + + + public String getErrorUniqueId() { + return errorUniqueId; + } + + + public void setErrorUniqueId(String value) { + this.errorUniqueId = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ErrorResponseRO that = (ErrorResponseRO) o; + return businessCode.equals(that.businessCode) && + Objects.equals(errorDescription, that.errorDescription) && + errorUniqueId.equals(that.errorUniqueId); + } + + @Override + public int hashCode() { + return Objects.hash(businessCode, errorDescription, errorUniqueId); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("ErrorResponseRO{"); + sb.append("'businessCode'='").append(businessCode).append('\''); + sb.append(", 'errorDescription'='").append(errorDescription).append('\''); + sb.append(", 'errorUniqueId'='").append(errorUniqueId).append('\''); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPLogger.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPLogger.java index d306a34e5e931731376d3736ec4e59a5457908f9..9eaedaa4494c4663655fef933282924c57a2aec4 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPLogger.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPLogger.java @@ -19,13 +19,13 @@ import java.util.Map; */ public class SMPLogger extends CategoryLogger { - public static final String MDC_USER = "userId"; - public static final String MDC_SESSION_ID = "messageId"; - public static final String MDC_DOMAIN = "domain"; - public static final String MDC_PROPERTY_PREFIX = "smp_"; + public static final String MDC_USER = MDC_PROPERTY_PREFIX+"user"; + public static final String MDC_REQUEST_ID = MDC_PROPERTY_PREFIX+"request_id"; + public static final String MDC_SESSION_ID = MDC_PROPERTY_PREFIX+"session_id"; + public static final Marker BUSINESS_MARKER = MarkerFactory.getMarker("BUSINESS"); public static final Marker SECURITY_MARKER = MarkerFactory.getMarker("SECURITY"); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java index 83bd3547bb73ea2728daa431627df7ebd9f087f3..6d8cc577ced60c2af9c9d109e9d0b910cb15f8be 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java @@ -69,6 +69,23 @@ public class ConfigurationService { public String getParticipantIdentifierSchemeRexExpMessage() { return configurationDAO.getCachedProperty(PARTC_SCH_REGEXP_MSG); } + public Pattern getPasswordPolicyRexExp() { + return (Pattern) configurationDAO.getCachedPropertyValue(PASSWORD_POLICY_REGULAR_EXPRESSION); + } + public String getPasswordPolicyRexExpPattern() { + return configurationDAO.getCachedProperty(PASSWORD_POLICY_REGULAR_EXPRESSION); + } + + public String getPasswordPolicyValidationMessage() { + return configurationDAO.getCachedProperty(PASSWORD_POLICY_MESSAGE); + } + + public Integer getPasswordPolicyValidDays() { + return (Integer) configurationDAO.getCachedPropertyValue(PASSWORD_POLICY_VALID_DAYS); + } + public Integer getAccessTokenPolicyValidDays() { + return (Integer) configurationDAO.getCachedPropertyValue(ACCESS_TOKEN_POLICY_VALID_DAYS); + } public Integer getHttpHeaderHstsMaxAge() { return (Integer) configurationDAO.getCachedPropertyValue(HTTP_HSTS_MAX_AGE); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java new file mode 100644 index 0000000000000000000000000000000000000000..3f6d59b1957b70a05a53cee7370e0022645173cb --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java @@ -0,0 +1,57 @@ +package eu.europa.ec.edelivery.smp.services.ui; + +import eu.europa.ec.edelivery.smp.data.dao.BaseDao; +import eu.europa.ec.edelivery.smp.data.dao.DomainDao; +import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.DBDomainDeleteValidation; +import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; +import eu.europa.ec.edelivery.smp.data.ui.DomainPublicRO; +import eu.europa.ec.edelivery.smp.data.ui.DomainRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.sml.SmlConnector; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.util.List; + +/** + * Service bean provides only public domain entity data for the Domain. + * + * @author Joze Rihtarsic + * @since 4.2 + */ +@Service +public class UIDomainPublicService extends UIServiceBase<DBDomain, DomainPublicRO> { + + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UIDomainPublicService.class); + @Autowired + DomainDao domainDao; + + @Override + protected BaseDao<DBDomain> getDatabaseDao() { + return domainDao; + } + + /** + * Method returns Domain resource object list for page. + * + * @param page + * @param pageSize + * @param sortField + * @param sortOrder + * @param filter + * @return + */ + public ServiceResult<DomainPublicRO> getTableList(int page, int pageSize, + String sortField, + String sortOrder, Object filter) { + LOG.debug("Query for public domain data: page: [{}], page size [{}], sort: [{}], filter: [{}].", page, pageSize, sortField, filter); + return super.getTableList(page, pageSize, sortField, sortOrder, filter); + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java index 55ba05e2ff476db5a2cc5c3a56c32a10a49b60c5..f645aaf1e84f4ba17bf8a037cd567c5f23e62ca0 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java @@ -5,6 +5,7 @@ import eu.europa.ec.edelivery.smp.data.dao.DomainDao; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBDomainDeleteValidation; import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; +import eu.europa.ec.edelivery.smp.data.ui.DomainPublicRO; import eu.europa.ec.edelivery.smp.data.ui.DomainRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; @@ -52,6 +53,7 @@ public class UIDomainService extends UIServiceBase<DBDomain, DomainRO> { return super.getTableList(page, pageSize, sortField, sortOrder, filter); } + @Transactional public void updateDomainList(List<DomainRO> lst) { boolean suc = false; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceBase.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceBase.java index 5e752a48944a005a1dcf72ff9ecd01ccdf9e92c8..4b35cbe3b298f4e76b94f78a1b84695def24f2ca 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceBase.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceBase.java @@ -44,8 +44,6 @@ abstract class UIServiceBase<E extends BaseEntity, R> { * @param sortOrder * @return */ - - @Transactional public ServiceResult<R> getTableList(int page, int pageSize, String sortField, String sortOrder, diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java index b1f94840113da7343bf42dfca6d07a80b9824267..697363e34be439c2af513f6b155514d2652b8de5 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java @@ -499,12 +499,12 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service dbServiceGroup.getUsers().clear(); List<UserRO> lstUsers = serviceGroupRO.getUsers(); for (UserRO userRO : lstUsers) { - DBUser du = userDao.find(userRO.getId()); - if (du==null) { + Optional<DBUser> optUser = userDao.findUserByUsername(userRO.getUsername()); + if (!optUser.isPresent()) { throw new SMPRuntimeException(INTERNAL_ERROR, "Database changed", "User "+userRO.getUsername()+ " not exists! (Refresh data)"); } - dbServiceGroup.getUsers().add(du); + dbServiceGroup.getUsers().add(optUser.get()); } } @@ -589,7 +589,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service // add users dbServiceGroup.getUsers().forEach(usr -> { UserRO userRO = new UserRO(); - userRO.setId(usr.getId()); + userRO.setUserId(usr.getId()+""); userRO.setUsername(usr.getUsername()); userRO.setActive(usr.isActive()); userRO.setEmailAddress(usr.getEmailAddress()); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java index 16d10272a5b925e46bcb396e61ccbb49b50e3241..33977c8b6868e2c0b76dc9c5f26351d2c1e6192a 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java @@ -165,7 +165,16 @@ public class UITruststoreService { return getCertificateData(buff, false); } - public CertificateRO getCertificateData(byte[] buff, boolean validate) throws CertificateException, IOException { + /** + * Validate certificate! + * + * @param buff - bytearray of the certificate (pem of or der) + * @param validate + * @return + * @throws CertificateException + * @throws IOException + */ + public CertificateRO getCertificateData(byte[] buff, boolean validate) { X509Certificate cert = X509CertificateUtils.getX509Certificate(buff); CertificateRO cro = convertToRo(cert); if (validate) { @@ -184,6 +193,8 @@ public class UITruststoreService { cro.setInvalidReason("Certificate is revoked!"); } catch (CertificateNotTrustedException ex) { cro.setInvalidReason("Certificate is not trusted!"); + } catch (CertificateException e) { + cro.setInvalidReason("Can not read the certificate!"); } } return cro; 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 8a7a792061bc17b1865b11dd9a1384982c3bfcf9..836a768a44f2f660b43163604693004537c31320 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 @@ -11,19 +11,31 @@ 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 eu.europa.ec.edelivery.smp.services.ConfigurationService; import eu.europa.ec.edelivery.smp.utils.BCryptPasswordHash; import eu.europa.ec.edelivery.smp.utils.SecurityUtils; +import eu.europa.ec.edelivery.smp.utils.X509CertificateUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.io.StringWriter; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.Base64; import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; @Service public class UIUserService extends UIServiceBase<DBUser, UserRO> { @@ -33,6 +45,8 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { @Autowired private UserDao userDao; + @Autowired + private ConfigurationService configurationService; @Autowired private ConversionService conversionService; @@ -79,70 +93,155 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { } /** - * Method regenerate access token for user and returns access token - * In the database the access token value is saved in format BCryptPasswordHash + * Method regenerate access token for user and returns access token + * In the database the access token value is saved in format BCryptPasswordHash * - * @param userRO + * @param userId * @return generated AccessToken. */ @Transactional - public AccessTokenRO generateAccessTokenForUser(Long userId) { + public AccessTokenRO generateAccessTokenForUser(Long userId, String currentPassword) { + DBUser dbUser = userDao.find(userId); + if (dbUser == null) { + LOG.error("Can not update user password because user for id [{}] does not exist!", userId); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "UserId", "Can not find user id!"); + } + if (!BCrypt.checkpw(currentPassword, dbUser.getPassword())) { + throw new BadCredentialsException("Password change failed; Invalid current password!"); + } + // setup new daes AccessTokenRO token = SecurityUtils.generateAccessToken(); + LocalDateTime generatedTime = token.getGeneratedOn(); + token.setExpireOn(generatedTime.plusDays(configurationService.getAccessTokenPolicyValidDays())); dbUser.setAccessTokenIdentifier(token.getIdentifier()); dbUser.setAccessToken(BCryptPasswordHash.hashPassword(token.getValue())); - dbUser.setAccessTokenGeneratedOn(token.getGeneratedOn()); - userDao.update(dbUser); + dbUser.setAccessTokenGeneratedOn(generatedTime); + dbUser.setPasswordExpireOn(token.getExpireOn()); return token; } + /** + * Method regenerate access token for user and returns access token + * In the database the access token value is saved in format BCryptPasswordHash + * + * @param userId + * @return generated AccessToken. + */ + @Transactional + public boolean updateUserPassword(Long userId, String currentPassword, String newPassword) { + + Pattern pattern = configurationService.getPasswordPolicyRexExp(); + if (!pattern.matcher(newPassword).matches()) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "PasswordChange", configurationService.getPasswordPolicyValidationMessage()); + } + DBUser dbUser = userDao.find(userId); + if (dbUser == null) { + LOG.error("Can not update user password because user for id [{}] does not exist!", userId); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "UserId", "Can not find user id!"); + } + + if (!BCrypt.checkpw(currentPassword, dbUser.getPassword())) { + throw new BadCredentialsException("Password change failed; Invalid current password!"); + } + dbUser.setPassword(BCryptPasswordHash.hashPassword(newPassword)); + LocalDateTime currentTime = LocalDateTime.now(); + dbUser.setPasswordChanged(currentTime); + dbUser.setPasswordExpireOn(currentTime.plusDays(configurationService.getPasswordPolicyValidDays())); + return true; + } + @Transactional public void updateUserList(List<UserRO> lst, LocalDateTime passwordChange) { for (UserRO userRO : lst) { - if (userRO.getStatus() == EntityROStatus.NEW.getStatusNumber()) { - DBUser dbUser = convertFromRo(userRO); - if (!StringUtils.isBlank(userRO.getPassword())) { - dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword())); + createOrUpdateUser(userRO, passwordChange); + } + } + + @Transactional + public void updateUserdata(Long userId, UserRO user) { + DBUser dbUser = userDao.find(userId); + if (dbUser == null) { + LOG.error("Can not update user because user for id [{}] does not exist!", userId); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "UserId", "Can not find user id!"); + } + + dbUser.setEmailAddress(user.getEmailAddress()); + if (user.getCertificate() != null && (dbUser.getCertificate() == null + || !StringUtils.equals(dbUser.getCertificate().getCertificateId(), user.getCertificate().getCertificateId()))) { + CertificateRO certRo = user.getCertificate(); + LOG.info(certRo.getEncodedValue() ); + if (user.getCertificate().getEncodedValue()!=null ){ + X509Certificate x509Certificate = X509CertificateUtils.getX509Certificate(Base64.getMimeDecoder().decode(certRo.getEncodedValue())); + String certificateAlias; + try { + certificateAlias = truststoreService.addCertificate(certRo.getAlias(), x509Certificate); + } catch (NoSuchAlgorithmException | KeyStoreException | IOException | CertificateException e) { + LOG.error("Error occurred while adding certificate to truststore.", e); + throw new SMPRuntimeException(ErrorCode.INTERNAL_ERROR, "AddUserCertificate", ExceptionUtils.getRootCauseMessage(e)); } - userDao.persistFlushDetach(dbUser); - } else if (userRO.getStatus() == EntityROStatus.UPDATED.getStatusNumber()) { - DBUser dbUser = userDao.find(userRO.getId()); - dbUser.setEmailAddress(userRO.getEmailAddress()); - dbUser.setRole(userRO.getRole()); - dbUser.setActive(userRO.isActive()); - dbUser.setUsername(userRO.getUsername()); - if (StringUtils.isBlank(userRO.getUsername())) { - // if username is empty than clear the password - dbUser.setPassword(""); - } else if (!StringUtils.isBlank(userRO.getPassword())) { - // check for new password - dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword())); - dbUser.setPasswordChanged(passwordChange); + certRo.setAlias(certificateAlias); + } + // first + DBCertificate certificate = conversionService.convert(user.getCertificate(), DBCertificate.class); + dbUser.setCertificate(certificate); + } + } + + protected void createOrUpdateUser(UserRO userRO, LocalDateTime passwordChange) { + if (userRO.getStatus() == EntityROStatus.NEW.getStatusNumber()) { + DBUser dbUser = convertFromRo(userRO); + if (!StringUtils.isBlank(userRO.getPassword())) { + dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword())); + } + userDao.persistFlushDetach(dbUser); + return; + } + Optional<DBUser> optionalDBUser = userDao.findUserByUsername(userRO.getUsername()); + if (!optionalDBUser.isPresent()) { + return; + } + DBUser dbUser = optionalDBUser.get(); + + + if (userRO.getStatus() == EntityROStatus.UPDATED.getStatusNumber()) { + + dbUser.setEmailAddress(userRO.getEmailAddress()); + dbUser.setRole(userRO.getRole()); + dbUser.setActive(userRO.isActive()); + dbUser.setUsername(userRO.getUsername()); + if (StringUtils.isBlank(userRO.getUsername())) { + // if username is empty than clear the password + dbUser.setPassword(""); + } else if (!StringUtils.isBlank(userRO.getPassword())) { + // check for new password + dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword())); + dbUser.setPasswordChanged(passwordChange); + } + // update certificate data + if (userRO.getCertificate() == null || StringUtils.isBlank(userRO.getCertificate().getCertificateId())) { + dbUser.setCertificate(null); + } else { + CertificateRO certificateRO = userRO.getCertificate(); + DBCertificate dbCertificate = dbUser.getCertificate() != null ? dbUser.getCertificate() : new DBCertificate(); + dbUser.setCertificate(dbCertificate); + if (certificateRO.getValidFrom() != null) { + dbCertificate.setValidFrom(LocalDateTime.ofInstant(certificateRO.getValidFrom().toInstant(), ZoneId.systemDefault())); } - // update certificate data - if (userRO.getCertificate() == null || StringUtils.isBlank(userRO.getCertificate().getCertificateId())) { - dbUser.setCertificate(null); - } else { - CertificateRO certificateRO = userRO.getCertificate(); - DBCertificate dbCertificate = dbUser.getCertificate() != null ? dbUser.getCertificate() : new DBCertificate(); - dbUser.setCertificate(dbCertificate); - if (certificateRO.getValidFrom() != null) { - dbCertificate.setValidFrom(LocalDateTime.ofInstant(certificateRO.getValidFrom().toInstant(), ZoneId.systemDefault())); - } - if (certificateRO.getValidTo() != null) { - dbCertificate.setValidTo(LocalDateTime.ofInstant(certificateRO.getValidTo().toInstant(), ZoneId.systemDefault())); - } - dbCertificate.setCertificateId(certificateRO.getCertificateId()); - dbCertificate.setSerialNumber(certificateRO.getSerialNumber()); - dbCertificate.setSubject(certificateRO.getSubject()); - dbCertificate.setIssuer(certificateRO.getIssuer()); + if (certificateRO.getValidTo() != null) { + dbCertificate.setValidTo(LocalDateTime.ofInstant(certificateRO.getValidTo().toInstant(), ZoneId.systemDefault())); } - dbUser.setLastUpdatedOn(LocalDateTime.now()); - userDao.update(dbUser); - } else if (userRO.getStatus() == EntityROStatus.REMOVE.getStatusNumber()) { - userDao.removeById(userRO.getId()); + dbCertificate.setCertificateId(certificateRO.getCertificateId()); + dbCertificate.setSerialNumber(certificateRO.getSerialNumber()); + dbCertificate.setSubject(certificateRO.getSubject()); + dbCertificate.setIssuer(certificateRO.getIssuer()); } + dbUser.setLastUpdatedOn(LocalDateTime.now()); + userDao.update(dbUser); + } else if (userRO.getStatus() == EntityROStatus.REMOVE.getStatusNumber()) { + userDao.removeById(dbUser.getId()); } + } /** diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SMPConstants.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SMPConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..df009bc230cd94564b7dbae647bb2295af28b645 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SMPConstants.java @@ -0,0 +1,6 @@ +package eu.europa.ec.edelivery.smp.utils; + +public class SMPConstants { + public static final String JSON_DATETIME_ISO="yyyy-MM-dd'T'HH:mm:ss"; +} + diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SecurityUtils.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SecurityUtils.java index 7f975c18e099d1bca7efeb694c58fb987277a433..a69de10200025c8ac905abb15d51ff615e3a633a 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SecurityUtils.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SecurityUtils.java @@ -12,6 +12,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.nio.file.Files; import java.security.*; import java.security.cert.Certificate; @@ -25,6 +26,33 @@ import static eu.europa.ec.edelivery.smp.exceptions.ErrorCode.INTERNAL_ERROR; public class SecurityUtils { + public static class Secret { + final byte[] vector; + final SecretKey key; + AlgorithmParameterSpec ivParameter = null; + + public Secret(byte[] vector, SecretKey key) { + this.vector = vector; + this.key = key; + + } + + public byte[] getVector() { + return vector; + } + + public AlgorithmParameterSpec getIVParameter() { + if (ivParameter == null && vector != null) { + this.ivParameter = new GCMParameterSpec(GCM_TAG_LENGTH_BIT, vector); + } + return ivParameter; + } + + public SecretKey getKey() { + return key; + } + } + private static final String VALID_PW_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+{}[]|:;<>?,./"; private static final String VALID_USER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; private static final int DEFAULT_PASSWORD_LENGTH = 16; @@ -123,35 +151,44 @@ public class SecurityUtils { return accessToken; } - public static void generatePrivateSymmetricKey(File path) { - + public static Secret generatePrivateSymmetricKey() { + // Generates a random key + KeyGenerator keyGenerator = null; try { - // Generates a random key - KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_KEY); - keyGenerator.init(KEY_SIZE); - SecretKey privateKey = keyGenerator.generateKey(); - - SecureRandom rnd = new SecureRandom(); - // Using setSeed(byte[]) to reseed a Random object - byte[] seed = rnd.generateSeed(IV_GCM_SIZE); - rnd.setSeed(seed); - - byte[] buffIV = new byte[IV_GCM_SIZE]; - rnd.nextBytes(buffIV); - - try (FileOutputStream out = new FileOutputStream(path)) { - // first write IV - out.write('#'); - out.write(buffIV); - out.write('#'); - out.write(privateKey.getEncoded()); - out.flush(); - } - } catch (Exception exc) { + keyGenerator = KeyGenerator.getInstance(ALGORITHM_KEY); + } catch (NoSuchAlgorithmException exc) { + throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while generating secret key for encryption", exc.getMessage()); + } + keyGenerator.init(KEY_SIZE); + SecretKey privateKey = keyGenerator.generateKey(); + + SecureRandom rnd = new SecureRandom(); + // Using setSeed(byte[]) to reseed a Random object + byte[] seed = rnd.generateSeed(IV_GCM_SIZE); + rnd.setSeed(seed); + + byte[] buffIV = new byte[IV_GCM_SIZE]; + rnd.nextBytes(buffIV); + + return new Secret(buffIV, privateKey); + } + + public static void generatePrivateSymmetricKey(File path) { + Secret secret = generatePrivateSymmetricKey(); + try (FileOutputStream out = new FileOutputStream(path)) { + // first write IV + out.write('#'); + out.write(secret.getVector()); + out.write('#'); + out.write(secret.getKey().getEncoded()); + out.flush(); + + } catch (IOException exc) { throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while saving key for encryption", exc.getMessage()); } } + public static String encryptWrappedToken(File encKeyFile, String token) { if (!StringUtils.isBlank(token) && token.startsWith(SecurityUtils.DECRYPTED_TOKEN_PREFIX)) { String unWrapToken = getNonEncryptedValue(token); @@ -160,38 +197,75 @@ public class SecurityUtils { return token; } - public static String encrypt(File path, String plainToken) { - try { - byte[] buff = Files.readAllBytes(path.toPath()); - AlgorithmParameterSpec iv = getSaltParameter(buff); - SecretKey privateKey = getSecretKey(buff); + public static String encryptURLSafe(Secret secret, String plainToken) { + return encrypt(secret, plainToken, Base64.getUrlEncoder().withoutPadding()); + } + + public static String encrypt(Secret secret, String plainToken) { + return encrypt(secret.getKey(), secret.getIVParameter(), plainToken, Base64.getEncoder()); + } + public static String encrypt(Secret secret, String plainToken, Base64.Encoder encoder) { + return encrypt(secret.getKey(), secret.getIVParameter(), plainToken, encoder); + } + + public static String encrypt(SecretKey privateKey, AlgorithmParameterSpec iv, String plainToken, Base64.Encoder encoder) { + try { Cipher cipher = Cipher.getInstance(iv == NULL_IV ? ALGORITHM_ENCRYPTION_OBSOLETE : ALGORITHM_ENCRYPTION); cipher.init(Cipher.ENCRYPT_MODE, privateKey, iv); byte[] encryptedData = cipher.doFinal(plainToken.getBytes()); - return new String(Base64.getEncoder().encode(encryptedData)); + return new String(encoder.encode(encryptedData)); } catch (Exception exc) { throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while encrypting the password", exc.getMessage()); } } - public static String decrypt(File keyPath, String encryptedPassword) { + public static String encrypt(File path, String plainToken) { + byte[] buff; try { - byte[] buff = Files.readAllBytes(keyPath.toPath()); + buff = Files.readAllBytes(path.toPath()); + } catch (Exception exc) { + throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while reading encryption key [" + path.getAbsolutePath() + "]! Root cause: " + ExceptionUtils.getRootCauseMessage(exc), exc.getMessage()); + } + AlgorithmParameterSpec iv = getSaltParameter(buff); + SecretKey privateKey = getSecretKey(buff); + return encrypt(privateKey, iv, plainToken, Base64.getEncoder()); + } + + public static String decrypt(File keyPath, String encryptedToken) { + + byte[] buff; + try { + buff = Files.readAllBytes(keyPath.toPath()); + } catch (IOException exc) { + throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while reading the the key: '" + keyPath.getAbsolutePath() + "'! Root cause: " + ExceptionUtils.getRootCauseMessage(exc), exc.getMessage()); + } + AlgorithmParameterSpec iv = getSaltParameter(buff); + SecretKey privateKey = getSecretKey(buff); + return decrypt(privateKey, iv, encryptedToken, Base64.getDecoder()); - AlgorithmParameterSpec iv = getSaltParameter(buff); - SecretKey privateKey = getSecretKey(buff); + } - byte[] decodedEncryptedPassword = Base64.getDecoder().decode(encryptedPassword.getBytes()); + public static String decrypt(Secret secret, String encryptedToken) { + return decrypt(secret.getKey(), secret.ivParameter, encryptedToken, Base64.getDecoder()); + } + + public static String decryptUrlSafe(Secret secret, String encryptedToken) { + return decrypt(secret.getKey(), secret.ivParameter, encryptedToken, Base64.getUrlDecoder()); + } + + public static String decrypt(SecretKey privateKey, AlgorithmParameterSpec iv, String encryptedToken, Base64.Decoder decoder) { + try { + byte[] decodedEncryptedPassword = decoder.decode(encryptedToken.getBytes()); // this is for back-compatibility - if key parameter is IV than is CBC else ie GCM Cipher cipher = Cipher.getInstance(iv instanceof IvParameterSpec ? ALGORITHM_ENCRYPTION_OBSOLETE : ALGORITHM_ENCRYPTION); cipher.init(Cipher.DECRYPT_MODE, privateKey, iv); byte[] decrypted = cipher.doFinal(decodedEncryptedPassword); return new String(decrypted); } catch (BadPaddingException | IllegalBlockSizeException ibse) { - throw new SMPRuntimeException(INTERNAL_ERROR, ibse, "Either private key '" + keyPath.getAbsolutePath() + "' or encrypted password might not be correct. Please check both. Root cause: " + ExceptionUtils.getRootCauseMessage(ibse), ibse.getMessage()); + throw new SMPRuntimeException(INTERNAL_ERROR, ibse, "Either private key or encrypted password might not be correct. Please check both. Root cause: " + ExceptionUtils.getRootCauseMessage(ibse), ibse.getMessage()); } catch (Exception exc) { - throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while decrypting the password with the key: '" + keyPath.getAbsolutePath() + "'! Root cause: " + ExceptionUtils.getRootCauseMessage(exc), exc.getMessage()); + throw new SMPRuntimeException(INTERNAL_ERROR, exc, "Error occurred while decrypting the password with the key! Root cause: " + ExceptionUtils.getRootCauseMessage(exc), exc.getMessage()); } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SessionSecurityUtils.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SessionSecurityUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6628a098e57c0fe9c077a01b48342a219fcc4d2d --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/SessionSecurityUtils.java @@ -0,0 +1,62 @@ +package eu.europa.ec.edelivery.smp.utils; + +import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + + +public class SessionSecurityUtils { + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SessionSecurityUtils.class); + + public static String encryptedEntityId(Long id) { + if (id == null) { + return null; + } + SecurityUtils.Secret secret = getAuthenticationSecret(); + String idValue = id.toString(); + return secret != null ? SecurityUtils.encryptURLSafe(secret, idValue) : idValue; + } + + + public static Long decryptEntityId(String id) { + if (id == null) { + return null; + } + SecurityUtils.Secret secret = getAuthenticationSecret(); + String value = secret != null ? SecurityUtils.decryptUrlSafe(secret, id) : id; + return new Long(value); + } + + public static SecurityUtils.Secret getAuthenticationSecret() { + if (SecurityContextHolder.getContext() == null) { + LOG.warn("No Security context!"); + return null; + } + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + LOG.warn("No active Authentication!"); + return null; + } + if (!(authentication instanceof SMPAuthenticationToken)) { + LOG.warn("Authentication is not class type: SMPAuthenticationToken!"); + return null; + } + return ((SMPAuthenticationToken) authentication).getSecret(); + } + + public static String getAuthenticationName() { + if (SecurityContextHolder.getContext() == null) { + LOG.debug("No Security context!"); + return null; + } + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + LOG.debug("No active Authentication!"); + return null; + } + return authentication.getName(); + + } +} 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 5a92120381f46149f0ba66897a88d835c19a93a6..464fc0098f5a8898bebee4668319b7fd033866ee 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 @@ -10,7 +10,6 @@ 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.services.AbstractServiceIntegrationTest; import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; -import org.apache.commons.io.IOUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -18,8 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.test.context.ContextConfiguration; -import java.io.IOException; -import java.security.cert.CertificateException; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Calendar; @@ -91,34 +88,13 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest // all table properties should not be null assertNotNull(res); - assertNotNull(res.getServiceEntities().get(0).getId()); + assertNotNull(res.getServiceEntities().get(0).getUserId()); assertNotNull(res.getServiceEntities().get(0).getUsername()); assertNotNull(res.getServiceEntities().get(0).getEmailAddress()); assertNull(res.getServiceEntities().get(0).getPassword()); // Service list must not return passwords assertNotNull(res.getServiceEntities().get(0).getRole()); } - @Test - public void testUpdateUserPassword() { - // given - insertDataObjects(1); - String newPassword = "TestPasswd!@#" + Calendar.getInstance().getTime(); - ServiceResult<UserRO> urTest = testInstance.getTableList(-1,-1,null, null, null); - assertEquals(1, urTest.getServiceEntities().size()); - - UserRO usr = urTest.getServiceEntities().get(0); - - //when - usr.setPassword(newPassword); - usr.setStatus(EntityROStatus.UPDATED.getStatusNumber()); - testInstance.updateUserList(Collections.singletonList(usr), null); - - // then - DBUser dbuser = userDao.find(usr.getId()); - assertTrue(BCrypt.checkpw(newPassword, dbuser.getPassword())); - } - - @Test public void testAddUserWithoutCertificate() { // given diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java index a3e3e6387b5a0162509eef4ff559e9ada046f1ff..81eb0fff2ad9fc0e931b7069bcf12572f50d9094 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java @@ -102,6 +102,7 @@ public class TestDBUtils { public static DBUser createDBUserByCertificate(String certId) { DBUser dbuser = new DBUser(); dbuser.setRole("test"); + dbuser.setUsername("test-"+certId); DBCertificate dbcert = new DBCertificate(); dbcert.setCertificateId(certId); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/X509CertificateUtilsTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/X509CertificateUtilsTest.java index bff469f1d200cf6c15bf6eda28ee11deb616f49b..5166855b9bca663f0d44a7042b25e756c933b5a3 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/X509CertificateUtilsTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/X509CertificateUtilsTest.java @@ -57,10 +57,10 @@ public class X509CertificateUtilsTest { private static final Object[] crlExtractHTTPSTestListCases() { return new Object[][]{ - {"ldap://localhost/clr,https://localhost/clr,http://localhost/clr","https://localhost/clr"}, - { "https://localhost/clr","https://localhost/clr"}, - { "http://localhost/clr","http://localhost/clr"}, - { "ldap://localhost/clr", null}, + {"ldap://localhost/clr,https://localhost/clr,http://localhost/clr", "https://localhost/clr"}, + {"https://localhost/clr", "https://localhost/clr"}, + {"http://localhost/clr", "http://localhost/clr"}, + {"ldap://localhost/clr", null}, {"", null}, }; } @@ -103,10 +103,9 @@ public class X509CertificateUtilsTest { } - @Test @Parameters(method = "crlExtractHTTPSTestListCases") - public void extractHttpCrlDistributionPoints(String clrLists, String value){ + public void extractHttpCrlDistributionPoints(String clrLists, String value) { //given List<String> urlList = clrLists == null ? Collections.emptyList() : Arrays.asList(clrLists.split(",")); // when @@ -116,7 +115,6 @@ public class X509CertificateUtilsTest { } - public static X509Certificate loadCertificate(String filename) throws CertificateException { CertificateFactory fact = CertificateFactory.getInstance("X.509"); @@ -130,5 +128,4 @@ public class X509CertificateUtilsTest { } - } diff --git a/smp-server-library/src/test/resources/logback-test.xml b/smp-server-library/src/test/resources/logback-test.xml index 1abeeedf25fb4f60193e0877d6da2d37c88bf2b4..596f79545321b98cea6f6826743816622f3e9bc5 100644 --- a/smp-server-library/src/test/resources/logback-test.xml +++ b/smp-server-library/src/test/resources/logback-test.xml @@ -2,8 +2,8 @@ <configuration> <!-- pattern definition --> - <property name="encoderPattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> - <property name="consolePattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="encoderPattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="consolePattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${project.build.directory}/logs/edelivery-smp.log</file> diff --git a/smp-soapui-tests/groovy/mysql-4.1_integration_test_data.sql b/smp-soapui-tests/groovy/mysql-4.1_integration_test_data.sql index 8a03a034262f28d3e5464b0bc00e869106f5e463..dca3e393779d3dd33791e19e01472a59b1a27667 100644 --- a/smp-soapui-tests/groovy/mysql-4.1_integration_test_data.sql +++ b/smp-soapui-tests/groovy/mysql-4.1_integration_test_data.sql @@ -10,7 +10,7 @@ insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROL insert into SMP_USER (ID, USERNAME, PASSWORD, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (10, 'EHEALTH_SMP_EC', '', 'SERVICE_GROUP_ADMIN', 1, NOW(), NOW()); -insert into SMP_CERTIFICATE (ID, CERTIFICATE_ID, VALID_FROM, VALID_TO, CREATED_ON, LAST_UPDATED_ON) values (10, 'CN=EHEALTH_SMP_EC,O=European Commission,C=BE:f71ee8b11cb3b787', NOW()- 1000000,NOW() + 1000000, NOW(), NOW()); +insert into SMP_CERTIFICATE (ID, CERTIFICATE_ID, VALID_FROM, VALID_TO, CREATED_ON, LAST_UPDATED_ON) values (10, 'CN=EHEALTH_SMP_EC,O=European Commission,C=BE:f71ee8b11cb3b787', date_add(NOW(),interval -1 year), date_add(NOW(),interval 1 year), NOW(), NOW()); insert into SMP_USER (ID, USERNAME, PASSWORD, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (11, 'EHEALTH_ż_ẞ_Ẅ_,O', '', 'SMP_ADMIN', 1, NOW(), NOW()); insert into SMP_CERTIFICATE (ID, CERTIFICATE_ID, VALID_FROM, VALID_TO, CREATED_ON, LAST_UPDATED_ON) values (11, 'CN=EHEALTH_z_ẞ_W_,O=European_z_ẞ_W_Commission,C=BE:f71ee8b11cb3b787', null,null, NOW(), NOW()); diff --git a/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml b/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml index 862cf7cf479d01a77144a5ee89ac332480d65362..6b6124f46aef4e723abe337e9870d10ee60dc7a8 100644 --- a/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml +++ b/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<con:soapui-project activeEnvironment="Default" name="SMP_TESTS" resourceRoot="" soapui-version="5.5.0" abortOnError="false" runType="SEQUENTIAL" id="8147b356-07e4-4ff9-ade6-4e92e0597a38" xmlns:con="http://eviware.com/soapui/config"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.actions.iface.tools.soapui.ProTestRunnerAction@values-local"><![CDATA[<xml-fragment xmlns:con="http://eviware.com/soapui/config"> +<con:soapui-project activeEnvironment="Default" name="SMP_TESTS" resourceRoot="" soapui-version="5.6.0" abortOnError="false" runType="SEQUENTIAL" id="8147b356-07e4-4ff9-ade6-4e92e0597a38" xmlns:con="http://eviware.com/soapui/config"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.actions.iface.tools.soapui.ProTestRunnerAction@values-local"><![CDATA[<xml-fragment xmlns:con="http://eviware.com/soapui/config"> <con:entry key="Environment" value="Default"/> <con:entry key="Global Properties" value=""/> <con:entry key="TestSuite" value="<all>"/> diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationEventListener.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationEventListener.java index 51247078381a3b5701b48454dd011776c9b8ca15..c0864b2d04cc241debcc5141b7e270e140fb0885 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationEventListener.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationEventListener.java @@ -14,6 +14,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpSession; +import java.util.Arrays; import java.util.Collection; /** @@ -50,7 +51,7 @@ public class SMPAuthenticationEventListener implements ApplicationListener<Authe Collection<? extends GrantedAuthority> authorities = event.getAuthentication().getAuthorities(); HttpSession session = attr.getRequest().getSession(); int idleTimeout = getSessionTimeoutForRoles(authorities); - LOG.debug("Set session idle timeout [{}] for user [{}] with roles [{}]", idleTimeout, event.getAuthentication().getName(), authorities); + LOG.debug("Set session idle timeout [{}] for user [{}] with roles [{}]", idleTimeout, event.getAuthentication().getName(), authorities.stream().map(auth->auth.getAuthority()).toArray()); session.setMaxInactiveInterval(idleTimeout); } else { LOG.warn("Could not get ServletRequestAttributes attributes for authentication [{}]", event.getAuthentication()); @@ -60,8 +61,14 @@ public class SMPAuthenticationEventListener implements ApplicationListener<Authe public int getSessionTimeoutForRoles(Collection<? extends GrantedAuthority> authorities) { boolean hasAdminRole = authorities.stream().anyMatch(grantedAuthority -> StringUtils.equalsIgnoreCase(grantedAuthority.getAuthority(), SMPAuthority.S_AUTHORITY_SYSTEM_ADMIN.getAuthority()) - || StringUtils.equalsIgnoreCase(grantedAuthority.getAuthority(), SMPAuthority.S_AUTHORITY_SMP_ADMIN.getAuthority())); + || StringUtils.equalsIgnoreCase(grantedAuthority.getAuthority(), SMPAuthority.S_AUTHORITY_SMP_ADMIN.getAuthority()) + || StringUtils.equalsIgnoreCase(grantedAuthority.getAuthority(), SMPAuthority.S_AUTHORITY_WS_SYSTEM_ADMIN.getAuthority()) + || StringUtils.equalsIgnoreCase(grantedAuthority.getAuthority(), SMPAuthority.S_AUTHORITY_WS_SMP_ADMIN.getAuthority()) + ); + LOG.debug("has role [{}]", ((GrantedAuthority)Arrays.stream(authorities.toArray()).findFirst().get()).getAuthority()); LOG.debug("has admin role [{}]", hasAdminRole); - return hasAdminRole ? configurationService.getSessionIdleTimeoutForAdmin() : configurationService.getSessionIdleTimeoutForUser(); + LOG.debug("configurationService [{}]", configurationService); + return hasAdminRole ? configurationService.getSessionIdleTimeoutForAdmin() : + configurationService.getSessionIdleTimeoutForUser(); } } \ No newline at end of file diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationService.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationService.java index 2079a9b05789548c8ab8b28a918a9b0ddb4005f5..9f9f476fd197fa40f57a40866386a57161357ce8 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationService.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationService.java @@ -27,7 +27,7 @@ public class SMPAuthenticationService { public Authentication authenticate(String username, String password) { LOG.debug("Authenticate: [{}]", username); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); - UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) authenticationManager.authenticate(token); + SMPAuthenticationToken authentication = (SMPAuthenticationToken)authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authentication); return authentication; } 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 3500fd0aae07726c6e6f9659472cf140b33378bb..0b009d46a0621853dd9c22e6b7a3045a74c03c25 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 @@ -2,6 +2,11 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; +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 eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -15,6 +20,7 @@ import static eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority.S_AUTHORITY_T */ @Service("smpAuthorizationService") public class SMPAuthorizationService { + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPAuthorizationService.class); public boolean isSystemAdministrator() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -22,11 +28,18 @@ public class SMPAuthorizationService { && authentication.getAuthorities().stream().anyMatch(grantedAuthority -> S_AUTHORITY_TOKEN_SYSTEM_ADMIN.equals(grantedAuthority.getAuthority())); } - public boolean isCurrentlyLoggedIn(Long userId) { + public boolean isCurrentlyLoggedIn(String userId) { + Long entityId; + try { + entityId = SessionSecurityUtils.decryptEntityId(userId); + }catch (SMPRuntimeException | NumberFormatException ex){ + LOG.error("Error occurred while decrypting user-id:["+userId+"]", ex); + throw new BadCredentialsException("Login failed; Invalid userID or password"); + } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication instanceof SMPAuthenticationToken) { - Long loggedInUserId = ((SMPAuthenticationToken) authentication).getUser().getId(); - return loggedInUserId.equals(userId); + Long loggedUserId = ((SMPAuthenticationToken) authentication).getUser().getId(); + return entityId.equals(loggedUserId); } return false; } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/MDCLogRequestFilter.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/MDCLogRequestFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..bf7a8805045b3f12aeca99415c84b004fca94bed --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/MDCLogRequestFilter.java @@ -0,0 +1,55 @@ +package eu.europa.ec.edelivery.smp.config; + + +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.slf4j.MDC; +import org.springframework.web.filter.GenericFilterBean; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.UUID; + +public class MDCLogRequestFilter extends GenericFilterBean { + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(MDCLogRequestFilter.class); +/* + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String username = SessionSecurityUtils.getAuthenticationName(); + String requestId = UUID.randomUUID().toString(); + String sessionId =request.getSession() != null ? request.getSession().getId() : null; + LOG.debug("Set request MDC data: user: [{}], request: [{}], session: [{}]!", username,requestId,sessionId); + + MDC.put(SMPLogger.MDC_USER, username); + MDC.put(SMPLogger.MDC_REQUEST_ID, UUID.randomUUID().toString()); + MDC.put(SMPLogger.MDC_SESSION_ID, request.getSession() != null ? request.getSession().getId() : null); + //filterChain.doFilter(request, response); + //MDC.clear(); + } +*/ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String username = SessionSecurityUtils.getAuthenticationName(); + String requestId = UUID.randomUUID().toString(); + String sessionId =null; + if (request instanceof HttpServletRequest){ + HttpServletRequest httpRequest = (HttpServletRequest) request; + sessionId =httpRequest.getSession() != null ? httpRequest.getSession().getId() : null; + } + MDC.put(SMPLogger.MDC_USER, username); + MDC.put(SMPLogger.MDC_REQUEST_ID, requestId); + MDC.put(SMPLogger.MDC_SESSION_ID, sessionId); + //doFilter + chain.doFilter(request, response); + MDC.clear(); + + + } +} \ No newline at end of file diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityConstants.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityConstants.java index 4f1c989e8b736424ae86e215016ce5de7fd42549..70d5c0029a50bcc0de7286898091ee6ee58c162c 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityConstants.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityConstants.java @@ -1,6 +1,6 @@ package eu.europa.ec.edelivery.smp.config; -import java.util.UUID; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; /** * SMP security constants as secured endpoints, beans... etc @@ -19,8 +19,7 @@ public class SMPSecurityConstants { public static final String SMP_CAS_KEY = "SMP_CAS_KEY_"; - - public static final String SMP_SECURITY_PATH = "/ui/rest/security"; - public static final String SMP_SECURITY_PATH_AUTHENTICATE = SMP_SECURITY_PATH +"/authentication"; - public static final String SMP_SECURITY_PATH_CAS_AUTHENTICATE = SMP_SECURITY_PATH +"/cas"; + public static final String SMP_SECURITY_PATH = ResourceConstants.CONTEXT_PATH_PUBLIC + "security"; + public static final String SMP_SECURITY_PATH_AUTHENTICATE = SMP_SECURITY_PATH + "/authentication"; + public static final String SMP_SECURITY_PATH_CAS_AUTHENTICATE = SMP_SECURITY_PATH + "/cas"; } 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 adcc3c15ac2b8d7f463b6bd02be2836f267c376f..1c55d9516ed6708be4f03271334489d646d66113 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 @@ -13,13 +13,12 @@ package eu.europa.ec.edelivery.smp.config; -import eu.europa.ec.edelivery.smp.error.ErrorMappingControllerAdvice; +import eu.europa.ec.edelivery.smp.error.ServiceErrorControllerAdvice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.util.UrlPathHelper; @@ -36,7 +35,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; "eu.europa.ec.edelivery.smp.conversion", "eu.europa.ec.edelivery.smp.monitor", "eu.europa.ec.edelivery.smp.ui"}) -@Import({GlobalMethodSecurityConfig.class, ErrorMappingControllerAdvice.class}) +@Import({GlobalMethodSecurityConfig.class, ServiceErrorControllerAdvice.class}) public class SmpWebAppConfig implements WebMvcConfigurer { private static final Logger LOG = LoggerFactory.getLogger(SmpWebAppConfig.class); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java index 3618c140fa093570b93fc989384d518ba349669e..e412ff515606f830195868d13fb889215c3ea981 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java @@ -19,10 +19,10 @@ import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationProvider; import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationProviderForUI; import eu.europa.ec.edelivery.smp.auth.URLCsrfMatcher; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; -import eu.europa.ec.edelivery.smp.error.SpringSecurityExceptionHandler; +import eu.europa.ec.edelivery.smp.error.SMPSecurityExceptionHandler; import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import eu.europa.ec.edelivery.smp.utils.SMPCookieWriter; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -57,10 +57,12 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer; import static eu.europa.ec.edelivery.smp.config.SMPSecurityConstants.*; + /** - * Created by gutowpa on 12/07/2017. + * SMP Security configuration + * @author gutowpa + * @since 3.0 */ - @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) @ComponentScan("eu.europa.ec.edelivery.smp.auth") @@ -73,6 +75,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { // Accounts supporting automated application functionalities ClientCertAuthenticationFilter clientCertAuthenticationFilter; EDeliveryX509AuthenticationFilter x509AuthenticationFilter; + MDCLogRequestFilter mdcLogRequestFilter; // User account CasAuthenticationFilter casAuthenticationFilter; CasAuthenticationEntryPoint casAuthenticationEntryPoint; @@ -87,19 +90,13 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Value("${encodedSlashesAllowedInUrl:true}") boolean encodedSlashesAllowedInUrl; - /** - * Initialize beans. Use lazy initialization for filter to avoid circular dependencies - * - * @param smpAuthenticationProvider - * @param clientCertAuthenticationFilter - * @param x509AuthenticationFilter - */ @Autowired public SpringSecurityConfig(SMPAuthenticationProvider smpAuthenticationProvider, SMPAuthenticationProviderForUI smpAuthenticationProviderForUI, ConfigurationService configurationService, @Lazy ClientCertAuthenticationFilter clientCertAuthenticationFilter, @Lazy EDeliveryX509AuthenticationFilter x509AuthenticationFilter, + @Lazy MDCLogRequestFilter mdcLogRequestFilter, @Lazy CsrfTokenRepository csrfTokenRepository, @Lazy RequestMatcher csrfURLMatcher, @Lazy HttpFirewall httpFirewall, @@ -116,6 +113,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { this.clientCertAuthenticationFilter = clientCertAuthenticationFilter; this.x509AuthenticationFilter = x509AuthenticationFilter; this.casAuthenticationFilter = casAuthenticationFilter; + this.mdcLogRequestFilter = mdcLogRequestFilter; this.casAuthenticationEntryPoint = casAuthenticationEntryPoint; this.csrfTokenRepository = csrfTokenRepository; this.csrfURLMatcher = csrfURLMatcher; @@ -132,9 +130,13 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { LOG.debug("The CAS authentication is enabled. Set casAuthenticationEntryPoint!"); exceptionHandlingConfigurer = exceptionHandlingConfigurer.defaultAuthenticationEntryPointFor(casAuthenticationEntryPoint, new AntPathRequestMatcher(SMP_SECURITY_PATH_CAS_AUTHENTICATE)); } - exceptionHandlingConfigurer.authenticationEntryPoint(new SpringSecurityExceptionHandler()); + + SMPSecurityExceptionHandler smpSecurityExceptionHandler = new SMPSecurityExceptionHandler(); + + exceptionHandlingConfigurer.authenticationEntryPoint(smpSecurityExceptionHandler); httpSecurity = exceptionHandlingConfigurer - .accessDeniedHandler(new SpringSecurityExceptionHandler()) + .accessDeniedHandler(smpSecurityExceptionHandler) + .and() .headers().frameOptions().deny() .contentTypeOptions().and() @@ -147,9 +149,11 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { } - httpSecurity.addFilter(clientCertAuthenticationFilter) + httpSecurity + .addFilterAfter(mdcLogRequestFilter, EDeliveryX509AuthenticationFilter.class) + .addFilter(clientCertAuthenticationFilter) .addFilter(x509AuthenticationFilter) - .httpBasic().authenticationEntryPoint(new SpringSecurityExceptionHandler()).and() // username + .httpBasic().authenticationEntryPoint(smpSecurityExceptionHandler).and() // username .anonymous().authorities(SMPAuthority.S_AUTHORITY_ANONYMOUS.getAuthority()).and() .authorizeRequests() .antMatchers(HttpMethod.DELETE, SMP_SECURITY_PATH_AUTHENTICATE).permitAll() @@ -211,11 +215,11 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { .maxAgeInSeconds(maxAge) .requestMatcher(AnyRequestMatcher.INSTANCE).and().and(); } - +/* String contentSecurityPolicy = configurationService.getHttpHeaderContentSecurityPolicy(); if (StringUtils.isNotBlank(contentSecurityPolicy)) { httpSecurity = httpSecurity.headers().contentSecurityPolicy(contentSecurityPolicy).and().and(); - } + }*/ } @Override @@ -260,6 +264,12 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { return ClientCertAuthenticationFilter; } + @Bean + public MDCLogRequestFilter getMDCLogRequestFilter() { + MDCLogRequestFilter filter= new MDCLogRequestFilter(); + return filter; + } + @Bean public EDeliveryX509AuthenticationFilter getEDeliveryX509AuthenticationFilter(@Qualifier(SMP_AUTHENTICATION_MANAGER_BEAN) AuthenticationManager authenticationManager) { EDeliveryX509AuthenticationFilter x509AuthenticationFilter = new EDeliveryX509AuthenticationFilter(); @@ -284,7 +294,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { // Csrf ignore "SMP API 'stateless' calls! (each call is authenticated and session is not used!)" requestMatcher.addIgnoreUrl("/.*:+.*(/services/?.*)?", HttpMethod.GET, HttpMethod.DELETE, HttpMethod.POST, HttpMethod.PUT); // ignore for login and logout - requestMatcher.addIgnoreUrl("/ui/rest/security/authentication", HttpMethod.DELETE, HttpMethod.POST); + requestMatcher.addIgnoreUrl(ResourceConstants.CONTEXT_PATH_PUBLIC_SECURITY+"/authentication", HttpMethod.DELETE, HttpMethod.POST); requestMatcher.addIgnoreUrl(SMP_SECURITY_PATH_CAS_AUTHENTICATE, HttpMethod.GET); // allow all gets diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..d786ca4ec01773d4ed4edd9124401dfa0a08ef71 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java @@ -0,0 +1,43 @@ +package eu.europa.ec.edelivery.smp.error; + +import ec.services.smp._1.ErrorResponse; +import eu.europa.ec.edelivery.smp.data.ui.exceptions.ErrorResponseRO; +import eu.europa.ec.edelivery.smp.error.exceptions.SMPResponseStatusException; +import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.AuthenticationException; + +import static eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode.TECHNICAL; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +abstract class AbstractErrorControllerAdvice { + + static final Logger LOG = LoggerFactory.getLogger(AbstractErrorControllerAdvice.class); + + public ResponseEntity handleRuntimeException(RuntimeException runtimeException) { + ResponseEntity response; + if (runtimeException instanceof SMPRuntimeException) { + SMPRuntimeException ex = (SMPRuntimeException)runtimeException; + response = buildAndLog(HttpStatus.resolve(ex.getErrorCode().getHttpCode()), ex.getErrorCode().getErrorBusinessCode(), ex.getMessage(), ex); + } else if (runtimeException instanceof SMPResponseStatusException ){ + SMPResponseStatusException ex = (SMPResponseStatusException)runtimeException; + response = buildAndLog(ex.getStatus(), ex.getErrorBusinessCode(), ex.getMessage(), ex); + } else if (runtimeException instanceof AuthenticationException ){ + AuthenticationException ex = (AuthenticationException)runtimeException; + response = buildAndLog(UNAUTHORIZED, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); + } + else { + response = buildAndLog(INTERNAL_SERVER_ERROR, TECHNICAL, "Unexpected technical error occurred.", runtimeException); + } + String errorCodeId = response.getBody() instanceof ErrorResponseRO?((ErrorResponseRO) response.getBody()).getErrorUniqueId():((ErrorResponse) response.getBody()).getErrorUniqueId(); + LOG.error("Unhandled exception occurred, unique ID: [{}]", errorCodeId, runtimeException); + return response; + } + + abstract ResponseEntity buildAndLog(HttpStatus status, ErrorBusinessCode businessCode, String msg, Exception exception); +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorResponseBuilder.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorResponseBuilder.java index 3e9a9b965b4aa2be2204e72b61a5cac2456b9b1a..43b7655c617dcdaddee7f0afaca103bdb347940b 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorResponseBuilder.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorResponseBuilder.java @@ -14,6 +14,7 @@ package eu.europa.ec.edelivery.smp.error; import ec.services.smp._1.ErrorResponse; +import eu.europa.ec.edelivery.smp.data.ui.exceptions.ErrorResponseRO; import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +23,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.UUID; @@ -33,17 +36,16 @@ import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; */ public class ErrorResponseBuilder { - private static final Logger log = LoggerFactory.getLogger(ErrorResponseBuilder.class); + private static final Logger LOG = LoggerFactory.getLogger(ErrorResponseBuilder.class); public static final MediaType CONTENT_TYPE_TEXT_XML_UTF8 = MediaType.valueOf("text/xml; charset=UTF-8"); private HttpStatus status = INTERNAL_SERVER_ERROR; private ErrorBusinessCode errorBusinessCode = TECHNICAL; private String strErrorDescription = "Unexpected technical error occurred."; - private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSz"; private static String getErrorUniqueId() { StringBuilder errId = new StringBuilder(); - errId.append(new SimpleDateFormat(TIMESTAMP_FORMAT).format(new Date())) + errId.append(LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)) .append(":") .append(UUID.randomUUID()); return String.valueOf(errId); @@ -59,7 +61,7 @@ public class ErrorResponseBuilder { return new ErrorResponseBuilder(status); } - private ErrorResponse buildBody() { + public ErrorResponse buildBody() { ErrorResponse err = new ErrorResponse(); err.setBusinessCode(errorBusinessCode.name()); err.setErrorDescription(strErrorDescription); @@ -68,6 +70,14 @@ public class ErrorResponseBuilder { return err; } + public ErrorResponseRO buildJSonBody() { + ErrorResponseRO err = new ErrorResponseRO(); + err.setBusinessCode(errorBusinessCode.name()); + err.setErrorDescription(strErrorDescription); + err.setErrorUniqueId(getErrorUniqueId()); + return err; + } + public ErrorResponseBuilder businessCode(ErrorBusinessCode newErrorBusinessCode) { this.errorBusinessCode = newErrorBusinessCode; return this; @@ -84,4 +94,11 @@ public class ErrorResponseBuilder { .body(this.buildBody()); } + public ResponseEntity buildJSon() { + return ResponseEntity.status(this.status) + .contentType(MediaType.APPLICATION_JSON) + .body(this.buildJSonBody()); + } + + } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SMPSecurityExceptionHandler.java similarity index 56% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SMPSecurityExceptionHandler.java index d9367721e284734681121b864ce6404e871d6b82..d2d31a48072e78079f203e6ebb74925d441ac9c2 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SpringSecurityExceptionHandler.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/SMPSecurityExceptionHandler.java @@ -13,8 +13,12 @@ package eu.europa.ec.edelivery.smp.error; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import ec.services.smp._1.ErrorResponse; import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -37,14 +41,17 @@ import static org.springframework.http.HttpStatus.UNAUTHORIZED; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; /** - * Created by gutowpa on 27/01/2017. + * SMPSecurityExceptionHandler + * + * @author gutowpa + * @author Joze Rihtarsic + * @since 3.0 */ +public class SMPSecurityExceptionHandler extends BasicAuthenticationEntryPoint implements AccessDeniedHandler { -public class SpringSecurityExceptionHandler extends BasicAuthenticationEntryPoint implements AccessDeniedHandler { - - private static final Logger LOG = LoggerFactory.getLogger(SpringSecurityExceptionHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(SMPSecurityExceptionHandler.class); - public SpringSecurityExceptionHandler() { + public SMPSecurityExceptionHandler() { this.setRealmName("SMPSecurityRealm"); } @@ -55,22 +62,23 @@ public class SpringSecurityExceptionHandler extends BasicAuthenticationEntryPoin if (authException instanceof BadCredentialsException) { errorMsg += " - Provided username/password or client certificate are invalid"; } - handle(response, authException, errorMsg); + handle(request, response, authException, errorMsg); } @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { - handle(response, accessDeniedException, accessDeniedException.getMessage()); + handle(request, response, accessDeniedException, accessDeniedException.getMessage()); } - private void handle(HttpServletResponse response, RuntimeException exception, String errorMsg) throws IOException { + private void handle(HttpServletRequest request, HttpServletResponse response, RuntimeException exception, String errorMsg) throws IOException { ResponseEntity respEntity = buildAndWarn(exception, errorMsg); - String errorBody = marshall((ErrorResponse) respEntity.getBody()); + String errorBody = marshall((ErrorResponse) respEntity.getBody(), request); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(TEXT_HTML_VALUE); response.getOutputStream().print(errorBody); } + private ResponseEntity buildAndWarn(RuntimeException exception, String errorMsg) { ResponseEntity response = ErrorResponseBuilder.status(UNAUTHORIZED) .businessCode(ErrorBusinessCode.UNAUTHORIZED) @@ -84,7 +92,39 @@ public class SpringSecurityExceptionHandler extends BasicAuthenticationEntryPoin return response; } - private static String marshall(ErrorResponse errorResponse) { + /** + * Method marshals the response. If the request endpoint is UI it marshal it to JSON else to XML format + + * @param errorResponse - the error to marshal + * @param request - The incoming HTTP request for the error + * @return string representation of the error + */ + protected String marshall(ErrorResponse errorResponse, HttpServletRequest request) { + return isUITRestRequest(request)? marshallToJSon(errorResponse):marshallToXML(errorResponse); + } + + /** + * Method validates if the request was submitted to UI or to "Oasis-SMP service" endpoint. If the endpoint is UI it returns + * true. + * @param request - HTTP request to SMP + * @return true if request targets the UI. + */ + protected boolean isUITRestRequest(HttpServletRequest request){ + String contextPath = request!=null?request.getRequestURI():null; + boolean result = StringUtils.isNotBlank(contextPath) + && StringUtils.containsAny(contextPath, ResourceConstants.CONTEXT_PATH_PUBLIC,ResourceConstants.CONTEXT_PATH_INTERNAL); + LOG.debug("Context path: [{}] is UI rest request: [{}]", contextPath, result); + return result; + } + + /** + * Marshal ErrorResponse to XML format + * + * @param errorResponse + * @return xml string representation of the Error + */ + protected String marshallToXML(ErrorResponse errorResponse) { + LOG.debug("Marshal error [{}] to XML format", errorResponse); try { StringWriter sw = new StringWriter(); JAXBContext jaxbContext = JAXBContext.newInstance(ErrorResponse.class); @@ -97,4 +137,20 @@ public class SpringSecurityExceptionHandler extends BasicAuthenticationEntryPoin return null; } + /** + * Marshal ErrorResponse to JSON format + * + * @param errorResponse + * @return json string representation of the Error + */ + protected String marshallToJSon(ErrorResponse errorResponse) { + LOG.debug("Marshal error [{}] to JSON format", errorResponse); + try { + return new ObjectMapper().writeValueAsString(errorResponse); + } catch (JsonProcessingException e) { + LOG.error("Error occurred while marshal the error [{}], code: [{}], desc [{}].", errorResponse.getBusinessCode(), errorResponse.getErrorUniqueId(), errorResponse.getErrorDescription()); + } + return null; + } + } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorMappingControllerAdvice.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ServiceErrorControllerAdvice.java similarity index 57% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorMappingControllerAdvice.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ServiceErrorControllerAdvice.java index fceb733ba05f87f8ca6ef52d61247c07f5c18b2e..a8391b5a7c9ad677cca172ccf4364631e09ebfc7 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ErrorMappingControllerAdvice.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/ServiceErrorControllerAdvice.java @@ -14,12 +14,14 @@ package eu.europa.ec.edelivery.smp.error; import ec.services.smp._1.ErrorResponse; -import eu.europa.ec.edelivery.smp.exceptions.*; import eu.europa.ec.edelivery.smp.error.exceptions.BadRequestException; +import eu.europa.ec.edelivery.smp.error.exceptions.SMPResponseStatusException; +import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +import eu.europa.ec.edelivery.smp.exceptions.InvalidOwnerException; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; +import eu.europa.ec.edelivery.smp.exceptions.WrongInputFieldException; import eu.europa.ec.smp.api.exceptions.MalformedIdentifierException; import eu.europa.ec.smp.api.exceptions.XmlInvalidAgainstSchemaException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; @@ -29,73 +31,55 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import static eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode.*; import static java.lang.String.format; -import static org.springframework.http.HttpStatus.*; -import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.UNAUTHORIZED; /** * Created by gutowpa on 14/09/2017. */ -@RestControllerAdvice -public class ErrorMappingControllerAdvice { +@RestControllerAdvice({"eu.europa.ec.edelivery.smp.controllers", "eu.europa.ec.edelivery.smp.controllers"}) +public class ServiceErrorControllerAdvice extends AbstractErrorControllerAdvice { - private static final Logger log = LoggerFactory.getLogger(ErrorMappingControllerAdvice.class); - - @ExceptionHandler(RuntimeException.class) + @ExceptionHandler({RuntimeException.class, SMPRuntimeException.class, SMPResponseStatusException.class, AuthenticationException.class,}) public ResponseEntity handleRuntimeException(RuntimeException ex) { - ResponseEntity response = buildAndWarn(INTERNAL_SERVER_ERROR, TECHNICAL, "Unexpected technical error occurred.", ex); - log.error("Unhandled exception occurred, unique ID: "+((ErrorResponse) response.getBody()).getErrorUniqueId(), ex); - return response; - } - - @ExceptionHandler(SMPRuntimeException.class) - public ResponseEntity handleSMPRuntimeException(SMPRuntimeException ex) { - ResponseEntity response = buildAndWarn(HttpStatus.resolve(ex.getErrorCode().getHttpCode()), ex.getErrorCode().getErrorBusinessCode(), ex.getMessage(), ex); - log.error( ex.getMessage() + ": "+((ErrorResponse) response.getBody()).getErrorUniqueId(), ex); - return response; + return super.handleRuntimeException(ex); } @ExceptionHandler(BadRequestException.class) public ResponseEntity handleBadRequestException(BadRequestException ex) { - return buildAndWarn(BAD_REQUEST, ex.getErrorBusinessCode(), ex.getMessage(), ex); + return buildAndLog(BAD_REQUEST, ex.getErrorBusinessCode(), ex.getMessage(), ex); } @ExceptionHandler(MalformedIdentifierException.class) public ResponseEntity handleMalformedIdentifierException(MalformedIdentifierException ex) { - return buildAndWarn(BAD_REQUEST, FORMAT_ERROR, ex.getMessage(), ex); + return buildAndLog(BAD_REQUEST, FORMAT_ERROR, ex.getMessage(), ex); } @ExceptionHandler(WrongInputFieldException.class) public ResponseEntity handleWrongInputFieldException(WrongInputFieldException ex) { - return buildAndWarn(BAD_REQUEST, WRONG_FIELD, ex.getMessage(), ex); - } - - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException(AuthenticationException ex) { - return buildAndWarn(UNAUTHORIZED, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); + return buildAndLog(BAD_REQUEST, WRONG_FIELD, ex.getMessage(), ex); } @ExceptionHandler(AccessDeniedException.class) public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { - return buildAndWarn(UNAUTHORIZED, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage() + " - Only SMP Admin or owner of given ServiceGroup is allowed to perform this action", ex); + return buildAndLog(UNAUTHORIZED, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage() + " - Only SMP Admin or owner of given ServiceGroup is allowed to perform this action", ex); } @ExceptionHandler(InvalidOwnerException.class) public ResponseEntity handleUnknownUserException(InvalidOwnerException ex) { - return buildAndWarn(BAD_REQUEST, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); + return buildAndLog(BAD_REQUEST, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); } @ExceptionHandler(XmlInvalidAgainstSchemaException.class) public ResponseEntity handleXmlInvalidAgainstSchemaException(XmlInvalidAgainstSchemaException ex) { - return buildAndWarn(BAD_REQUEST, XSD_INVALID, ex.getMessage(), ex); + return buildAndLog(BAD_REQUEST, XSD_INVALID, ex.getMessage(), ex); } - private ResponseEntity buildAndWarn(HttpStatus status, ErrorBusinessCode businessCode, String msg, Exception exception) { + ResponseEntity buildAndLog(HttpStatus status, ErrorBusinessCode businessCode, String msg, Exception exception) { ResponseEntity response = ErrorResponseBuilder.status(status) .businessCode(businessCode) @@ -105,8 +89,8 @@ public class ErrorMappingControllerAdvice { String errorUniqueId = ((ErrorResponse) response.getBody()).getErrorUniqueId(); String logMsg = format("Error unique ID: %s", errorUniqueId); - log.warn(logMsg, exception); + LOG.warn(logMsg, exception); return response; } - + } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/UIErrorControllerAdvice.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/UIErrorControllerAdvice.java new file mode 100644 index 0000000000000000000000000000000000000000..1e23a2330b1e9e6108604dfc5b1dc9e3a157c5d7 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/UIErrorControllerAdvice.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 European Commission | CEF eDelivery + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * + * You may obtain a copy of the Licence attached in file: LICENCE-EUPL-v1.2.pdf + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" basis, + * 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.error; + +import eu.europa.ec.edelivery.smp.data.ui.exceptions.ErrorResponseRO; +import eu.europa.ec.edelivery.smp.error.exceptions.SMPResponseStatusException; +import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import static java.lang.String.format; + + +/** + * Exception Handler for the UI package. Method returns JSON response objects. + * + * @author gutowpa + * @author Joze Rihtarsic + * @since 4.2 + */ +@RestControllerAdvice("eu.europa.ec.edelivery.smp.ui") +public class UIErrorControllerAdvice extends AbstractErrorControllerAdvice { + + + @ExceptionHandler({BadCredentialsException.class, RuntimeException.class, SMPRuntimeException.class, SMPResponseStatusException.class, AuthenticationException.class,}) + public ResponseEntity handleRuntimeException(RuntimeException ex) { + return super.handleRuntimeException(ex); + } + + ResponseEntity buildAndLog(HttpStatus status, ErrorBusinessCode businessCode, String msg, Exception exception) { + + ResponseEntity response = ErrorResponseBuilder.status(status) + .businessCode(businessCode) + .errorDescription(msg) + .buildJSon(); + + String errorUniqueId = ((ErrorResponseRO) response.getBody()).getErrorUniqueId(); + String logMsg = format("Error unique ID: %s", errorUniqueId); + + LOG.warn(logMsg, exception); + return response; + } + +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/exceptions/SMPResponseStatusException.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/exceptions/SMPResponseStatusException.java new file mode 100644 index 0000000000000000000000000000000000000000..37a25f35fe3eefb26ea032c586778af36d23ac71 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/exceptions/SMPResponseStatusException.java @@ -0,0 +1,25 @@ +package eu.europa.ec.edelivery.smp.error.exceptions; + +import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +/** + * Smp ResponseStatusException extension to hold also smp business error code. Exception is used for REST API "Fault" responses + * + * @author Joze Rihtarsic + * @since 4.2 + */ +public class SMPResponseStatusException extends ResponseStatusException { + private ErrorBusinessCode errorBusinessCode; + + public SMPResponseStatusException(ErrorBusinessCode errorBusinessCode, HttpStatus httpStatus, String sMsg) { + super(httpStatus, sMsg); + this.errorBusinessCode = errorBusinessCode; + } + + public ErrorBusinessCode getErrorBusinessCode() { + return errorBusinessCode; + } + +} 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 5f9e3bb433640c8dc7b66460e95f9a1d1911c85e..c5b41f8122cecd6f7c34417bb4c5f10f54aad09c 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 @@ -41,7 +41,7 @@ import static eu.europa.ec.edelivery.smp.utils.SMPCookieWriter.SESSION_COOKIE_NA * @since 4.0 */ @RestController -@RequestMapping(value = "/ui/rest/security") +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_PUBLIC_SECURITY) public class AuthenticationResource { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(AuthenticationResource.class); @@ -77,13 +77,6 @@ public class AuthenticationResource { this.uiUserService = uiUserService; } - @ResponseStatus(value = HttpStatus.FORBIDDEN) - @ExceptionHandler({AuthenticationException.class}) - public ErrorRO handleException(Exception ex) { - LOG.error(ex.getMessage(), ex); - return new ErrorRO(ex.getMessage()); - } - @PostMapping(value = "authentication") @Transactional(noRollbackFor = BadCredentialsException.class) public UserRO authenticate(@RequestBody LoginRO loginRO, HttpServletRequest request, HttpServletResponse response) { diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..f131e2eab6ae25c49e53be383af2e6a2235c8fff --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java @@ -0,0 +1,48 @@ +package eu.europa.ec.edelivery.smp.ui; + + +/** + * @author Joze Rihtarsic + * @since 4.2 + */ +public class ResourceConstants { + // -------------------------------------- + // context paths + public static final String CONTEXT_PATH_PUBLIC="/ui/public/rest/"; + public static final String CONTEXT_PATH_INTERNAL ="/ui/internal/rest/"; + // public + public static final String CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT = CONTEXT_PATH_PUBLIC + "search"; + public static final String CONTEXT_PATH_PUBLIC_DOMAIN = CONTEXT_PATH_PUBLIC + "domain"; + public static final String CONTEXT_PATH_PUBLIC_APPLICATION = CONTEXT_PATH_PUBLIC + "application"; + public static final String CONTEXT_PATH_PUBLIC_USER = CONTEXT_PATH_PUBLIC + "user"; + public static final String CONTEXT_PATH_PUBLIC_TRUSTSTORE = CONTEXT_PATH_PUBLIC + "truststore"; + + public static final String CONTEXT_PATH_PUBLIC_SERVICE_GROUP = CONTEXT_PATH_PUBLIC + "service-group"; + public static final String CONTEXT_PATH_PUBLIC_SERVICE_METADATA = CONTEXT_PATH_PUBLIC + "service-metadata"; + + + public static final String CONTEXT_PATH_PUBLIC_SECURITY = CONTEXT_PATH_PUBLIC + "security"; + + + //internal + public static final String CONTEXT_PATH_INTERNAL_DOMAIN = CONTEXT_PATH_INTERNAL + "domain"; + public static final String CONTEXT_PATH_INTERNAL_APPLICATION = CONTEXT_PATH_INTERNAL + "application"; + public static final String CONTEXT_PATH_INTERNAL_USER = CONTEXT_PATH_INTERNAL + "user"; + public static final String CONTEXT_PATH_INTERNAL_KEYSTORE = CONTEXT_PATH_INTERNAL + "keystore"; + public static final String CONTEXT_PATH_INTERNAL_TRUSTSTORE = CONTEXT_PATH_INTERNAL + "truststore"; + + + // -------------------------------------- + // parameters + public static final String PARAM_PAGINATION_PAGE="page"; + public static final String PARAM_PAGINATION_PAGE_SIZE="pageSize"; + public static final String PARAM_PAGINATION_ORDER_BY="orderBy"; + public static final String PARAM_PAGINATION_ORDER_TYPE="orderType"; + + + public static final String PARAM_QUERY_PARTC_ID="participantIdentifier"; + public static final String PARAM_QUERY_PARTC_SCHEME="participantScheme"; + public static final String PARAM_QUERY_DOMAIN_CODE ="domainCode"; + public static final String PARAM_QUERY_USER ="user"; + +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java index e49148be3be9a23b96e658abce76718d21e53605..423dad2cf40a72e8cbf693d6751c6e964e25174f 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java @@ -32,7 +32,7 @@ import java.util.Arrays; */ @RestController -@RequestMapping(value = "/ui/rest/servicegroup") +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_PUBLIC_SERVICE_GROUP) public class ServiceGroupResource { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(ServiceGroupResource.class); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceMetadataResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceMetadataResource.java index 2c0a8671fe19eeabfa8e5756c821eedd6aa07425..1f7094b4a9914f07c862343992b765c61fdaa99d 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceMetadataResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceMetadataResource.java @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.*; */ @RestController -@RequestMapping(value = "/ui/rest/servicemetadata") +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_PUBLIC_SERVICE_METADATA) public class ServiceMetadataResource { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(ServiceMetadataResource.class); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java similarity index 58% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java index 61e4734b576a2c1ff0f3ec477da35b8bc5aed934..adc04acee7b735e4343176450022fdacae405592 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java @@ -1,16 +1,14 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.external; -import eu.europa.ec.edelivery.smp.data.ui.SmpConfigRO; import eu.europa.ec.edelivery.smp.data.ui.SmpInfoRO; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; -import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.TimeZone; @@ -20,7 +18,7 @@ import java.util.TimeZone; * @since 4.1 */ @RestController -@RequestMapping(value = "/ui/rest/application") +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_PUBLIC_APPLICATION) public class ApplicationResource { @Autowired @@ -37,17 +35,17 @@ public class ApplicationResource { String buildTime; - @RequestMapping(method = RequestMethod.GET, path = "name") + @GetMapping(path = "name") public String getName() { return artifactName; } - @RequestMapping(method = RequestMethod.GET, path = "rootContext") - public String getRootContext() { + + protected String getRootContext() { return env.getProperty("server.contextPath", "/"); } - @RequestMapping(method = RequestMethod.GET, path = "info") + @GetMapping(path = "info") public SmpInfoRO getApplicationInfo() { SmpInfoRO info = new SmpInfoRO(); info.setVersion(getDisplayVersion()); @@ -59,21 +57,7 @@ public class ApplicationResource { return info; } - @RequestMapping(method = RequestMethod.GET, path = "config") - @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, - SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) - public SmpConfigRO getApplicationConfig() { - SmpConfigRO info = new SmpConfigRO(); - - info.setSmlIntegrationOn(configurationService.isSMLIntegrationEnabled()); - info.setSmlParticipantMultiDomainOn(configurationService.isSMLMultiDomainEnabled()); - info.setParticipantSchemaRegExp(configurationService.getParticipantIdentifierSchemeRexExpPattern()); - info.setParticipantSchemaRegExpMessage(configurationService.getParticipantIdentifierSchemeRexExpMessage()); - - return info; - } - - public String getDisplayVersion() { + protected String getDisplayVersion() { StringBuilder display = new StringBuilder(); display.append(artifactName); display.append(" Version ["); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java new file mode 100644 index 0000000000000000000000000000000000000000..5b8ab0514fee3d6c90e36b9a4f48c5726d79c5b0 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java @@ -0,0 +1,48 @@ +package eu.europa.ec.edelivery.smp.ui.external; + + +import eu.europa.ec.edelivery.smp.data.ui.DomainPublicRO; +import eu.europa.ec.edelivery.smp.data.ui.DomainRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.ui.UIDomainPublicService; +import org.springframework.util.MimeTypeUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; + +/** + * Purpose of the DomainResource is to provide search method to retrieve configured domains in SMP. + * + * @author Joze Rihtarsic + * @since 4.1 + */ +@RestController +@RequestMapping(value = CONTEXT_PATH_PUBLIC_DOMAIN) +public class DomainResource { + + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DomainResource.class); + + private UIDomainPublicService uiDomainService; + + public DomainResource(UIDomainPublicService uiDomainService) { + this.uiDomainService = uiDomainService; + } + + @GetMapping(produces = {MimeTypeUtils.APPLICATION_JSON_VALUE}) + public ServiceResult<DomainPublicRO> geDomainList( + @RequestParam(value = PARAM_PAGINATION_PAGE, defaultValue = "0") int page, + @RequestParam(value = PARAM_PAGINATION_PAGE_SIZE, defaultValue = "10") int pageSize, + @RequestParam(value = PARAM_PAGINATION_ORDER_BY, required = false) String orderBy, + @RequestParam(value = PARAM_PAGINATION_ORDER_TYPE, defaultValue = "asc", required = false) String orderType, + @RequestParam(value = PARAM_QUERY_USER, required = false) String user) { + + LOG.info("Search for page: {}, page size: {}, user: {}", page, pageSize, user); + ServiceResult<DomainPublicRO> result = uiDomainService.getTableList(page, pageSize, orderBy, orderType, null); + return result; + } +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SearchResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/SearchResource.java similarity index 59% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SearchResource.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/SearchResource.java index 0d80fca8816695a64d2293530d6101c868b57f26..16f89cc43db4b2599f1cebc53081f65766681aca 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/SearchResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/SearchResource.java @@ -1,4 +1,4 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.external; import eu.europa.ec.edelivery.smp.data.dao.DomainDao; @@ -9,41 +9,43 @@ import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.services.ui.UIServiceGroupSearchService; import eu.europa.ec.edelivery.smp.services.ui.filters.ServiceGroupFilter; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; + /** + * Purpose of the SearchResource is to provide search method public participant capabilities + * * @author Joze Rihtarsic * @since 4.1 */ - @RestController -@RequestMapping(value = "/ui/rest/search") +@RequestMapping(value = CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT) public class SearchResource { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SearchResource.class); - @Autowired - private UIServiceGroupSearchService uiServiceGroupService; - @Autowired - private DomainDao domainDao; + final UIServiceGroupSearchService uiServiceGroupService; + final DomainDao domainDao; + public SearchResource(UIServiceGroupSearchService uiServiceGroupService, DomainDao domainDao) { + this.uiServiceGroupService = uiServiceGroupService; + this.domainDao = domainDao; + } - @PutMapping(produces = {"application/json"}) - @ResponseBody - @RequestMapping(method = RequestMethod.GET) + @GetMapping(produces = {MimeTypeUtils.APPLICATION_JSON_VALUE}) public ServiceResult<ServiceGroupSearchRO> getServiceGroupList( - @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "pageSize", defaultValue = "10") int pageSize, - @RequestParam(value = "orderBy", required = false) String orderBy, - @RequestParam(value = "orderType", defaultValue = "asc", required = false) String orderType, - @RequestParam(value = "participantIdentifier", required = false) String participantIdentifier, - @RequestParam(value = "participantScheme", required = false) String participantScheme, - @RequestParam(value = "domain", required = false) String domainCode - ) { + @RequestParam(value = PARAM_PAGINATION_PAGE, defaultValue = "0") int page, + @RequestParam(value = PARAM_PAGINATION_PAGE_SIZE, defaultValue = "10") int pageSize, + @RequestParam(value = PARAM_PAGINATION_ORDER_BY, required = false) String orderBy, + @RequestParam(value = PARAM_PAGINATION_ORDER_TYPE, defaultValue = "asc", required = false) String orderType, + @RequestParam(value = PARAM_QUERY_PARTC_ID, required = false) String participantIdentifier, + @RequestParam(value = PARAM_QUERY_PARTC_SCHEME, required = false) String participantScheme, + @RequestParam(value = PARAM_QUERY_DOMAIN_CODE, required = false) String domainCode) { String participantIdentifierDecoded = decodeUrlToUTF8(participantIdentifier); String participantSchemeDecoded = decodeUrlToUTF8(participantScheme); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreResource.java new file mode 100644 index 0000000000000000000000000000000000000000..f686adf919dbf9dab87dc0d765710b2d7dcac53a --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreResource.java @@ -0,0 +1,47 @@ +package eu.europa.ec.edelivery.smp.ui.external; + +import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; +import eu.europa.ec.edelivery.smp.data.ui.KeystoreImportResult; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; +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 eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import eu.europa.ec.edelivery.smp.utils.X509CertificateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.MimeTypeUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * @author Joze Rihtarsic + * @since 4.1 + */ +@RestController +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_PUBLIC_TRUSTSTORE) +public class TruststoreResource { + + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(TruststoreResource.class); + + @Autowired + private UITruststoreService uiTruststoreService; + + + @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PostMapping(path = "/{user-id}/validate-certificate", consumes = MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public CertificateRO validateCertificate(@PathVariable("user-id") String userId, @RequestBody byte[] data) { + LOG.info("Got certificate data size: {}", data.length); + return uiTruststoreService.getCertificateData(data, true); + } + +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserResource.java new file mode 100644 index 0000000000000000000000000000000000000000..84e2dc74da9383df8de59c9c3325e9f813963e40 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserResource.java @@ -0,0 +1,97 @@ +package eu.europa.ec.edelivery.smp.ui.external; + +import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; +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.AccessTokenRO; +import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; +import eu.europa.ec.edelivery.smp.data.ui.PasswordChangeRO; +import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import eu.europa.ec.edelivery.smp.error.exceptions.SMPResponseStatusException; +import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +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 eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; +import eu.europa.ec.edelivery.smp.services.ui.UIUserService; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.util.MimeTypeUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.security.cert.CertificateException; +import java.time.LocalDateTime; +import java.util.Arrays; + +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_USER; + +/** + * @author Joze Rihtarsic + * @since 4.1 + */ +@RestController +@RequestMapping(value = CONTEXT_PATH_PUBLIC_USER) +public class UserResource { + + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UserResource.class); + + @Autowired + private UIUserService uiUserService; + @Autowired + protected SMPAuthorizationService authorizationService; + + + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PostMapping(value = "/{user-id}/generate-access-token", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public AccessTokenRO generateAccessToken(@PathVariable("user-id") String userId, @RequestBody String password) { + Long entityId = decryptEntityId(userId); + LOG.info("Generated access token for user:[{}] with id:[{}] ", userId, entityId); + + return uiUserService.generateAccessTokenForUser(entityId, password); + } + + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PutMapping(path = "/{user-id}/change-password", consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public boolean changePassword(@PathVariable("user-id") String userId, @RequestBody PasswordChangeRO newPassword) { + Long entityId = decryptEntityId(userId); + LOG.info("Validating the password of the currently logged in user:[{}] with id:[{}] ", userId, entityId); + return uiUserService.updateUserPassword(entityId, newPassword.getCurrentPassword(), newPassword.getNewPassword()); + } + /** + * Update the details of the currently logged in user (e.g. update the role, the credentials or add certificate details). + * + * @param userId 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 + */ + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PutMapping(path = "/{user-id}") + public UserRO updateCurrentUser(@PathVariable("user-id") String userId, @RequestBody UserRO user) { + LOG.info("Update current user: {}", user); + Long entityId = decryptEntityId(userId); + // Update the user and mark the password as changed at this very instant of time + uiUserService.updateUserdata(entityId, user); + + DBUser updatedUser = uiUserService.findUser(entityId); + UserRO userRO = uiUserService.convertToRo(updatedUser); + + return authorizationService.sanitize(userRO); + } + + public Long decryptEntityId(String userId){ + try{ + return SessionSecurityUtils.decryptEntityId(userId); + } catch (RuntimeException runtimeException) { + LOG.error("Error occurred while decryption entityId ["+userId+"]!", runtimeException ); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "UserId", "Invalid userId!"); + } + } +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/ApplicationAdminResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/ApplicationAdminResource.java new file mode 100644 index 0000000000000000000000000000000000000000..2b11f9fe1a75e18bbed50eed77fa4b864a2081a1 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/ApplicationAdminResource.java @@ -0,0 +1,48 @@ +package eu.europa.ec.edelivery.smp.ui.internal; + + +import eu.europa.ec.edelivery.smp.data.ui.SmpConfigRO; +import eu.europa.ec.edelivery.smp.data.ui.SmpInfoRO; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.TimeZone; + +/** + * @author Joze Rihtarsic + * @since 4.1 + */ +@RestController +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_INTERNAL_APPLICATION) +public class ApplicationAdminResource { + + final ConfigurationService configurationService; + + public ApplicationAdminResource(ConfigurationService configurationService) { + this.configurationService = configurationService; + } + + @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, + SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) + @GetMapping(path = "config") + public SmpConfigRO getApplicationConfig() { + SmpConfigRO info = new SmpConfigRO(); + info.setSmlIntegrationOn(configurationService.isSMLIntegrationEnabled()); + info.setSmlParticipantMultiDomainOn(configurationService.isSMLMultiDomainEnabled()); + info.setParticipantSchemaRegExp(configurationService.getParticipantIdentifierSchemeRexExpPattern()); + info.setParticipantSchemaRegExpMessage(configurationService.getParticipantIdentifierSchemeRexExpMessage()); + + info.setPasswordValidationRegExp(configurationService.getPasswordPolicyRexExpPattern()); + info.setPasswordValidationRegExpMessage(configurationService.getPasswordPolicyValidationMessage()); + return info; + } +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/DomainResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/DomainAdminResource.java similarity index 51% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/DomainResource.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/DomainAdminResource.java index b81527f5443a1d497c4123291d224357de13e6e1..4c49c7a7e23469bfa6cd616baf5f4945270cb976 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/DomainResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/DomainAdminResource.java @@ -1,12 +1,12 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; import eu.europa.ec.edelivery.smp.data.ui.DomainRO; import eu.europa.ec.edelivery.smp.data.ui.SMLIntegrationResult; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; @@ -15,69 +15,85 @@ import eu.europa.ec.edelivery.smp.services.ui.UIDomainService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; import java.util.Arrays; import java.util.List; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.PARAM_QUERY_USER; + /** + * DomainAdminResource provides admin services for managing the domains configured in SMP. The services defined in path + * ResourceConstants.CONTEXT_PATH_INTERNAL should not be exposed to internet. * @author Joze Rihtarsic * @since 4.1 */ @RestController -@RequestMapping(value = "/ui/rest/domain") -public class DomainResource { +@RequestMapping(value = CONTEXT_PATH_INTERNAL_DOMAIN) +public class DomainAdminResource { - private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DomainResource.class); + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DomainAdminResource.class); - @Autowired - private UIDomainService uiDomainService; + final UIDomainService uiDomainService; + final DomainService domainService; - @Autowired - private DomainService domainService; + public DomainAdminResource(UIDomainService uiDomainService, DomainService domainService) { + this.uiDomainService = uiDomainService; + this.domainService = domainService; + } - @PutMapping(produces = {"application/json"}) - @ResponseBody - @RequestMapping(method = RequestMethod.GET) + @GetMapping(produces = {MimeTypeUtils.APPLICATION_JSON_VALUE}) public ServiceResult<DomainRO> geDomainList( - @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "pageSize", defaultValue = "10") int pageSize, - @RequestParam(value = "orderBy", required = false) String orderBy, - @RequestParam(value = "orderType", defaultValue = "asc", required = false) String orderType, - @RequestParam(value = "user", required = false) String user - ) { + @RequestParam(value = PARAM_PAGINATION_PAGE, defaultValue = "0") int page, + @RequestParam(value = PARAM_PAGINATION_PAGE_SIZE, defaultValue = "10") int pageSize, + @RequestParam(value = PARAM_PAGINATION_ORDER_BY, required = false) String orderBy, + @RequestParam(value = PARAM_PAGINATION_ORDER_TYPE, defaultValue = "asc", required = false) String orderType, + @RequestParam(value = PARAM_QUERY_USER, required = false) String user) { + + LOG.info("Search for page: {}, page size: {}, user: {}", page, pageSize, user); return uiDomainService.getTableList(page, pageSize, orderBy, orderType, null); } - @PutMapping(produces = {"application/json"}) - @RequestMapping(method = RequestMethod.PUT) + /** + * List of domains to be added or updated + * + * @param updateEntities + */ + @PutMapping(produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) - public void updateDomainList(@RequestBody(required = true) DomainRO[] updateEntities) { + public void updateDomainList(@RequestBody DomainRO[] updateEntities) { LOG.info("GOT LIST OF DomainRO to UPDATE: " + updateEntities.length); uiDomainService.updateDomainList(Arrays.asList(updateEntities)); } - @PutMapping(produces = {"application/json"}) - @RequestMapping(path = "validateDelete", method = RequestMethod.POST) + /** + * Validated if domains with provided IDs can be deleted and returns the result in DeleteEntityValidation. + * @param listOfDomainIds + * @return + */ + @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) - public DeleteEntityValidation validateDeleteDomain(@RequestBody List<Long> query) { + @PutMapping(value = "validate-delete", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public DeleteEntityValidation validateDeleteDomain(@RequestBody List<Long> listOfDomainIds) { DeleteEntityValidation dres = new DeleteEntityValidation(); - dres.getListIds().addAll(query); + dres.getListIds().addAll(listOfDomainIds); return uiDomainService.validateDeleteRequest(dres); } - @PostMapping(value = "/{id}/smlregister/{domaincode}") - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") - public SMLIntegrationResult registerDomainAndParticipants(@PathVariable("id") Long id, - @PathVariable("domaincode") String domaincode + @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PutMapping(value = "/{user-id}/sml-register/{domain-code}") + public SMLIntegrationResult registerDomainAndParticipants(@PathVariable("user-id") Long userId, + @PathVariable("domain-code") String domainCode ) { - LOG.info("SML register domain code: {}, user id {}", domaincode, id); + LOG.info("SML register domain code: {}, user user-id {}", domainCode, userId); SMLIntegrationResult result = new SMLIntegrationResult(); try { - DBDomain dbDomain = domainService.getDomain(domaincode); + DBDomain dbDomain = domainService.getDomain(domainCode); domainService.registerDomainAndParticipants(dbDomain); result.setSuccess(true); } catch (SMPRuntimeException e) { @@ -88,16 +104,15 @@ public class DomainResource { } - @PostMapping(value = "/{id}/smlunregister/{domaincode}") - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") - public SMLIntegrationResult unregisterDomainAndParticipants(@PathVariable("id") Long id, - @PathVariable("domaincode") String domaincode - ) { - LOG.info("SML unregister domain code: {}, user id {}", domaincode, id); + @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PutMapping(value = "/{user-id}/sml-unregister/{domain-code}") + public SMLIntegrationResult unregisterDomainAndParticipants(@PathVariable("user-id") Long userId, + @PathVariable("domain-code") String domainCode) { + LOG.info("SML unregister domain code: {}, user id {}", domainCode, userId); // try to open keystore SMLIntegrationResult result = new SMLIntegrationResult(); try { - DBDomain dbDomain = domainService.getDomain(domaincode); + DBDomain dbDomain = domainService.getDomain(domainCode); domainService.unregisterDomainAndParticipantsFromSml(dbDomain); result.setSuccess(true); } catch (SMPRuntimeException e) { diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/KeystoreResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/KeystoreResource.java similarity index 77% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/KeystoreResource.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/KeystoreResource.java index 0d2b097f002d2489967d0eb9f52972df457f707a..1958d1206f9ab2d433a648ecc181a794d94b6fa8 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/KeystoreResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/KeystoreResource.java @@ -1,15 +1,16 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; import eu.europa.ec.edelivery.smp.data.ui.KeystoreImportResult; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.services.ui.UIKeystoreService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; import java.io.ByteArrayInputStream; @@ -21,12 +22,16 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.List; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_INTERNAL_KEYSTORE; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; +import static org.springframework.util.MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE; + /** * @author Joze Rihtarsic * @since 4.1 */ @RestController -@RequestMapping(value = "/ui/rest/keystore") +@RequestMapping(value = CONTEXT_PATH_INTERNAL_KEYSTORE) public class KeystoreResource { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(KeystoreResource.class); @@ -34,24 +39,22 @@ public class KeystoreResource { @Autowired private UIKeystoreService uiKeystoreService; - @PutMapping(produces = {"application/json"}) - @RequestMapping(method = RequestMethod.GET) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) + @GetMapping(produces = {MimeTypeUtils.APPLICATION_JSON_VALUE}) public ServiceResult<CertificateRO> getKeyCertificateList() { List<CertificateRO> lst = uiKeystoreService.getKeystoreEntriesList(); // clear encoded value to reduce http traffic lst.stream().forEach(certificateRO -> { certificateRO.setEncodedValue(null); }); - ServiceResult<CertificateRO> sg = new ServiceResult<>(); sg.getServiceEntities().addAll(lst); sg.setCount((long) lst.size()); return sg; } - @PostMapping(value = "/{id}/upload/{keystoreType}/{password}", produces = {"application/json"}, consumes = {"application/octet-stream"}) @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") + @PostMapping(path = "/{id}/upload/{keystoreType}/{password}", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_OCTET_STREAM_VALUE) public KeystoreImportResult uploadKeystore(@PathVariable("id") Long id, @PathVariable("keystoreType") String keystoreType, @PathVariable("password") String password, @@ -59,14 +62,14 @@ public class KeystoreResource { LOG.info("Got keystore data size: {}, type {}, password length {}", fileBytes.length, keystoreType, password.length()); // try to open keystore KeystoreImportResult keystoreImportResult = new KeystoreImportResult(); - KeyStore keyStore = null; + KeyStore keyStore = null; try { keyStore = KeyStore.getInstance(keystoreType); keyStore.load(new ByteArrayInputStream(fileBytes), password.toCharArray()); LOG.debug(keyStore.aliases().nextElement()); uiKeystoreService.importKeys(keyStore, password); } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e) { - String msg = e.getClass().getName() +" occurred while reading the keystore: " + e.getMessage(); + String msg = e.getClass().getName() + " occurred while reading the keystore: " + e.getMessage(); LOG.error(msg, e); keystoreImportResult.setErrorMessage(msg); } @@ -74,22 +77,19 @@ public class KeystoreResource { return keystoreImportResult; } - - @DeleteMapping(value = "/{id}/delete/{alias}", produces = {"application/json"}) @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") + @DeleteMapping(value = "/{id}/delete/{alias}", produces = APPLICATION_JSON_VALUE) public KeystoreImportResult deleteCertificate(@PathVariable("id") Long id, - @PathVariable("alias") String alias) { + @PathVariable("alias") String alias) { LOG.info("Remove alias by user id {}, alias {}.", id, alias); KeystoreImportResult keystoreImportResult = new KeystoreImportResult(); - try { uiKeystoreService.deleteKey(alias); } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { - String msg = e.getClass().getName() +" occurred while reading the keystore: " + e.getMessage(); + String msg = e.getClass().getName() + " occurred while reading the keystore: " + e.getMessage(); LOG.error(msg, e); keystoreImportResult.setErrorMessage(msg); } - return keystoreImportResult; } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/TruststoreResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/TruststoreAdminResource.java similarity index 80% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/TruststoreResource.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/TruststoreAdminResource.java index 07b9200f6d735ef5636ab0db6308b071967dcf91..09f0ec004a3626e84de441643c7fe0c1a2d9e62c 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/TruststoreResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/TruststoreAdminResource.java @@ -1,4 +1,4 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; @@ -8,10 +8,12 @@ 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 eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import eu.europa.ec.edelivery.smp.utils.X509CertificateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -26,10 +28,10 @@ import java.util.List; * @since 4.1 */ @RestController -@RequestMapping(value = "/ui/rest/truststore") -public class TruststoreResource { +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_INTERNAL_TRUSTSTORE) +public class TruststoreAdminResource { - private static final SMPLogger LOG = SMPLoggerFactory.getLogger(TruststoreResource.class); + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(TruststoreAdminResource.class); @Autowired private UITruststoreService uiTruststoreService; @@ -50,10 +52,11 @@ public class TruststoreResource { return sg; } - @PostMapping(value = "/{id}/certdata", produces = {"application/json"}, consumes = {"application/octet-stream"}) - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") - public CertificateRO uploadCertificate(@PathVariable("id") Long id, - @RequestBody byte[] fileBytes) { + + @PreAuthorize("@smpAuthorizationService.systemAdministrator") + @PostMapping(value = "/{user-id}/upload-certificate", consumes = MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public CertificateRO uploadCertificate(@PathVariable("user-id") String userId, + @RequestBody byte[] fileBytes) { LOG.info("Got truststore cert size: {}", fileBytes.length); X509Certificate x509Certificate; @@ -77,10 +80,10 @@ public class TruststoreResource { @DeleteMapping(value = "/{id}/delete/{alias}", produces = {"application/json"}) - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") - public KeystoreImportResult deleteCertificate(@PathVariable("id") Long id, + @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + public KeystoreImportResult deleteCertificate(@PathVariable("id") String userId, @PathVariable("alias") String alias) { - LOG.info("Remove alias by user id {}, alias {}.", id, alias); + LOG.info("Remove alias by user id {}, alias {}.", userId, alias); KeystoreImportResult keystoreImportResult = new KeystoreImportResult(); try { 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/internal/UserAdminResource.java similarity index 51% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResource.java index 8040dd2d3a87f77f658eba17020a7d41a5611535..791f86aed93c338e6e448428455730cfceda421d 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/internal/UserAdminResource.java @@ -1,38 +1,42 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; -import eu.europa.ec.edelivery.smp.data.ui.*; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; 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; +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.auth.SMPAuthority; +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 eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; import eu.europa.ec.edelivery.smp.services.ui.UIUserService; import eu.europa.ec.edelivery.smp.services.ui.filters.UserFilter; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; 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 java.io.IOException; -import java.security.cert.CertificateException; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_INTERNAL_USER; + /** * @author Joze Rihtarsic * @since 4.1 */ @RestController -@RequestMapping(value = "/ui/rest/user") -public class UserResource { +@RequestMapping(value = CONTEXT_PATH_INTERNAL_USER) +public class UserAdminResource { - private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UserResource.class); + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UserAdminResource.class); @Autowired private UIUserService uiUserService; @@ -52,82 +56,32 @@ public class UserResource { @RequestParam(value = "orderBy", required = false) String orderBy, @RequestParam(value = "orderType", defaultValue = "asc", required = false) String orderType, @RequestParam(value = "roles", required = false) String roleList - ) { + ) { UserFilter filter = null; if (roleList != null) { filter = new UserFilter(); filter.setRoleList(Arrays.asList(roleList.split(","))); } - return uiUserService.getTableList(page,pageSize, orderBy, orderType, filter); + 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); - - // Update the user and mark the password as changed at this very instant of time - uiUserService.updateUserList(Arrays.asList(user), LocalDateTime.now()); - - DBUser updatedUser = uiUserService.findUser(id); - UserRO userRO = uiUserService.convertToRo(updatedUser); - - return authorizationService.sanitize(userRO); - } @PutMapping(produces = {"application/json"}) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) - public void updateUserList(@RequestBody UserRO[] updateEntities ){ + public void updateUserList(@RequestBody UserRO[] updateEntities) { LOG.info("Update user list, count: {}", updateEntities.length); // Pass the users and mark the passwords of the ones being updated as expired by passing the passwordChange as null uiUserService.updateUserList(Arrays.asList(updateEntities), null); } - @PostMapping(value = "/{id}/certdata" ,produces = {"application/json"},consumes = {"application/octet-stream"}) - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#id)") - public CertificateRO uploadCertificate(@PathVariable("id") Long id, @RequestBody byte[] data) { - LOG.info("Got certificate data size: {}", data.length); - try { - return uiTruststoreService.getCertificateData(data, true); - } catch (IOException | CertificateException e) { - LOG.error("Error occurred while parsing certificate.", e); - } - return null; - } - - @PostMapping(value = "/{userId}/generate-access-token" ,produces = {"application/json"}) - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#userId)") - public AccessTokenRO generateAccessToken(@PathVariable("userId") Long userId,@RequestBody String password) { - AccessTokenRO accessToken = uiUserService.generateAccessTokenForUser(userId); - LOG.debug("Access token generated [{}]", accessToken.getIdentifier()); - accessToken.setGeneratedOn(null); - return accessToken; - } - - @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) + @RequestMapping(path = "validate-delete", method = RequestMethod.POST) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) public DeleteEntityValidation validateDeleteUsers(@RequestBody List<Long> query) { DBUser user = getCurrentUser(); DeleteEntityValidation dres = new DeleteEntityValidation(); - if (query.contains(user.getId())){ + if (query.contains(user.getId())) { dres.setValidOperation(false); dres.setStringMessage("Could not delete logged user!"); return dres; @@ -141,4 +95,13 @@ public class UserResource { SMPAuthenticationToken authToken = (SMPAuthenticationToken) authentication; return authToken.getUser(); } + + public Long decryptEntityId(String userId) { + try { + return SessionSecurityUtils.decryptEntityId(userId); + } catch (RuntimeException runtimeException) { + LOG.error("Error occurred while decryption entityId [" + userId + "]!", runtimeException); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "UserId", "Invalid userId!"); + } + } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/validation/ServiceGroupValidator.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/validation/ServiceGroupValidator.java index 226f94be6789d2d5053aca405f0d48ddd0000106..2e6c371ba890fdaae4c65dc19fa5c4380bba150c 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/validation/ServiceGroupValidator.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/validation/ServiceGroupValidator.java @@ -50,6 +50,7 @@ public class ServiceGroupValidator { String scheme = serviceGroup.getParticipantIdentifier().getScheme(); Pattern schemaPattern = configurationService.getParticipantIdentifierSchemeRexExp(); + if (!schemaPattern.matcher(scheme).matches()) { throw new BadRequestException(WRONG_FIELD, "Service Group scheme does not match allowed pattern: " + schemaPattern.pattern()); } diff --git a/smp-webapp/src/main/resources/logback.xml b/smp-webapp/src/main/resources/logback.xml index 80975ef8062427a829306c8deee59a8739f47b24..e8c6bfdc10ee6c94c6f892b81122d2aed3f8dd2c 100644 --- a/smp-webapp/src/main/resources/logback.xml +++ b/smp-webapp/src/main/resources/logback.xml @@ -2,8 +2,8 @@ <configuration> <!-- pattern definition --> - <property name="encoderPattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> - <property name="consolePattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="encoderPattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="consolePattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.folder:-logs}/edelivery-smp.log</file> diff --git a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl index 714e760f9b473eb63a83ab111326ffd015503de1..dc0553b8ba3f8664eb109218f1a1c3c4079d3917 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl @@ -233,7 +233,8 @@ create table SMP_USER ( ID bigint not null comment 'Unique user id', ACCESS_TOKEN varchar(256) CHARACTER SET utf8 COLLATE utf8_bin comment 'BCrypted personal access token', - PAT_GENERATED datetime comment 'Date when personal access token was generated', + ACCESS_TOKEN_EXPIRE_ON datetime comment 'Date when personal access token will expire', + ACCESS_TOKEN_GENERATED_ON datetime comment 'Date when personal access token was generated', ACCESS_TOKEN_ID varchar(256) CHARACTER SET utf8 COLLATE utf8_bin comment 'Personal access token id', ACTIVE bit not null comment 'Is user active', CREATED_ON datetime not null, @@ -241,8 +242,9 @@ LAST_UPDATED_ON datetime not null, PASSWORD varchar(256) CHARACTER SET utf8 COLLATE utf8_bin comment 'BCrypted password for username/password login', PASSWORD_CHANGED datetime comment 'Last date when password was changed', + PASSWORD_EXPIRE_ON datetime comment 'Date when password will expire', ROLE varchar(256) CHARACTER SET utf8 COLLATE utf8_bin comment 'User role', - USERNAME varchar(256) CHARACTER SET utf8 COLLATE utf8_bin comment 'Login username', + USERNAME varchar(256) CHARACTER SET utf8 COLLATE utf8_bin not null comment 'Unique username identifier. The Username must not be null', primary key (ID) ) comment='SMP can handle multiple domains. This table contains domain specific data' ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -251,7 +253,8 @@ REV bigint not null, REVTYPE tinyint, ACCESS_TOKEN varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, - PAT_GENERATED datetime, + ACCESS_TOKEN_EXPIRE_ON datetime, + ACCESS_TOKEN_GENERATED_ON datetime, ACCESS_TOKEN_ID varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, ACTIVE bit, CREATED_ON datetime, @@ -259,6 +262,7 @@ LAST_UPDATED_ON datetime, PASSWORD varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, PASSWORD_CHANGED datetime, + PASSWORD_EXPIRE_ON datetime, ROLE varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, USERNAME varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, primary key (ID, REV) diff --git a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl index bc1fad113181797ff2f63ddfe6e0d0e3e2916b80..190d4ee1343e01b65e9d465229d957b551e8dca6 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl @@ -314,7 +314,8 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; create table SMP_USER ( ID number(19,0) not null, ACCESS_TOKEN varchar2(256 char), - PAT_GENERATED timestamp, + ACCESS_TOKEN_EXPIRE_ON timestamp, + ACCESS_TOKEN_GENERATED_ON timestamp, ACCESS_TOKEN_ID varchar2(256 char), ACTIVE number(1,0) not null, CREATED_ON timestamp not null, @@ -322,8 +323,9 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; LAST_UPDATED_ON timestamp not null, PASSWORD varchar2(256 char), PASSWORD_CHANGED timestamp, + PASSWORD_EXPIRE_ON timestamp, ROLE varchar2(256 char), - USERNAME varchar2(256 char), + USERNAME varchar2(256 char) not null, primary key (ID) ); @@ -336,7 +338,10 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; comment on column SMP_USER.ACCESS_TOKEN is 'BCrypted personal access token'; - comment on column SMP_USER.PAT_GENERATED is + comment on column SMP_USER.ACCESS_TOKEN_EXPIRE_ON is + 'Date when personal access token will expire'; + + comment on column SMP_USER.ACCESS_TOKEN_GENERATED_ON is 'Date when personal access token was generated'; comment on column SMP_USER.ACCESS_TOKEN_ID is @@ -354,18 +359,22 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; comment on column SMP_USER.PASSWORD_CHANGED is 'Last date when password was changed'; + comment on column SMP_USER.PASSWORD_EXPIRE_ON is + 'Date when password will expire'; + comment on column SMP_USER.ROLE is 'User role'; comment on column SMP_USER.USERNAME is - 'Login username'; + 'Unique username identifier. The Username must not be null'; create table SMP_USER_AUD ( ID number(19,0) not null, REV number(19,0) not null, REVTYPE number(3,0), ACCESS_TOKEN varchar2(256 char), - PAT_GENERATED timestamp, + ACCESS_TOKEN_EXPIRE_ON timestamp, + ACCESS_TOKEN_GENERATED_ON timestamp, ACCESS_TOKEN_ID varchar2(256 char), ACTIVE number(1,0), CREATED_ON timestamp, @@ -373,6 +382,7 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; LAST_UPDATED_ON timestamp, PASSWORD varchar2(256 char), PASSWORD_CHANGED timestamp, + PASSWORD_EXPIRE_ON timestamp, ROLE varchar2(256 char), USERNAME varchar2(256 char), primary key (ID, REV) diff --git a/smp-webapp/src/main/smp-setup/logback.xml b/smp-webapp/src/main/smp-setup/logback.xml index 10dd2cf59904b282efdc42c1ed8cd220c06f6e6d..41767c18636663607bc909d8f94d1cf538ef4fd1 100644 --- a/smp-webapp/src/main/smp-setup/logback.xml +++ b/smp-webapp/src/main/smp-setup/logback.xml @@ -2,8 +2,8 @@ <configuration> <!-- pattern definition --> - <property name="encoderPattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> - <property name="consolePattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="encoderPattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="consolePattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.folder:-logs}/edelivery-smp.log</file> diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java index 9f00528cc2a2a6340ba9be066bf5301eaa3b8322..8301ba2792886b6a4306fdceb64a586862904d4d 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java @@ -2,8 +2,10 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.data.model.DBUser; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; import org.junit.Before; import org.junit.Test; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -16,17 +18,18 @@ import static org.junit.Assert.assertTrue; public class SMPAuthorizationServiceTest { - DBUser mockUser = null; + DBUser user = null; SecurityContext mockSecurityContextSystemAdmin = null; SecurityContext mockSecurityContextSGAdmin = null; SMPAuthorizationService testInstance = new SMPAuthorizationService(); + @Before public void setup() { - DBUser user = new DBUser(); + user = new DBUser(); user.setId((long) 10); @@ -74,13 +77,13 @@ public class SMPAuthorizationServiceTest { assertTrue(bVal); } - @Test + @Test(expected = BadCredentialsException.class) public void isCurrentlyLoggedInNotLogedIn() { // given SecurityContextHolder.setContext(mockSecurityContextSystemAdmin); - boolean bVal = testInstance.isCurrentlyLoggedIn((long) 1); - assertFalse(bVal); + testInstance.isCurrentlyLoggedIn("Not logged In"); + } @Test @@ -88,11 +91,7 @@ public class SMPAuthorizationServiceTest { // given SecurityContextHolder.setContext(mockSecurityContextSystemAdmin); // when then - boolean bVal = testInstance.isCurrentlyLoggedIn((long) 10); + boolean bVal = testInstance.isCurrentlyLoggedIn(SessionSecurityUtils.encryptedEntityId(10L)); assertTrue(bVal); } - - public void sanitize() { - - } } \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/SMPSecurityExceptionHandlerTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/SMPSecurityExceptionHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..65d893151d5567c2d07d8dc31fe5716c26db5b3a --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/SMPSecurityExceptionHandlerTest.java @@ -0,0 +1,137 @@ +package eu.europa.ec.edelivery.smp.error; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import ec.services.smp._1.ErrorResponse; +import eu.europa.ec.edelivery.smp.exceptions.ErrorBusinessCode; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.servlet.http.HttpServletRequest; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import java.io.IOException; +import java.io.StringReader; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +public class SMPSecurityExceptionHandlerTest { + + SMPSecurityExceptionHandler testInstance = new SMPSecurityExceptionHandler(); + + @Test + public void isUITRestRequestPublic() { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + Mockito.doReturn(ResourceConstants.CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT).when(request).getRequestURI(); + // when + boolean result = testInstance.isUITRestRequest(request); + // then + assertTrue(result); + } + + @Test + public void isUITRestRequestInternal() { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + Mockito.doReturn("smp" + ResourceConstants.CONTEXT_PATH_INTERNAL_APPLICATION).when(request).getRequestURI(); + // when + boolean result = testInstance.isUITRestRequest(request); + // then + assertTrue(result); + } + + @Test + public void isUITRestRequestSMPServiceEndpoint() { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + Mockito.doReturn("/smp").when(request).getContextPath(); + // when + boolean result = testInstance.isUITRestRequest(request); + // then + assertFalse(result); + } + + + @Test + public void marshallToXML() throws JAXBException { + ErrorResponse error = ErrorResponseBuilder.status(UNAUTHORIZED) + .businessCode(ErrorBusinessCode.UNAUTHORIZED) + .errorDescription("Test error Message") + .buildBody(); + // when + String resultString = testInstance.marshallToXML(error); + // then + assertNotNull(resultString); + //calling the unmarshall method + ErrorResponse result = (ErrorResponse) JAXBContext.newInstance(ErrorResponse.class) + .createUnmarshaller() + .unmarshal(new StringReader(resultString)); + + assertEquals(error.getBusinessCode(), result.getBusinessCode()); + assertEquals(error.getErrorDescription(), result.getErrorDescription()); + assertEquals(error.getErrorUniqueId(), result.getErrorUniqueId()); + } + + @Test + public void marshallToJSon() throws IOException { + ErrorResponse error = ErrorResponseBuilder.status(UNAUTHORIZED) + .businessCode(ErrorBusinessCode.UNAUTHORIZED) + .errorDescription("Test json error Message") + .buildBody(); + // when + String resultString = testInstance.marshallToJSon(error); + // then + assertNotNull(resultString); + //calling the unmarshall method + ErrorResponse result = (new ObjectMapper()).readValue(resultString, ErrorResponse.class); + + assertEquals(error.getBusinessCode(), result.getBusinessCode()); + assertEquals(error.getErrorDescription(), result.getErrorDescription()); + assertEquals(error.getErrorUniqueId(), result.getErrorUniqueId()); + } + @Test + public void marshallUIError() throws JsonProcessingException { + HttpServletRequest request = mock(HttpServletRequest.class); + Mockito.doReturn(ResourceConstants.CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT).when(request).getRequestURI(); + ErrorResponse error = ErrorResponseBuilder.status(UNAUTHORIZED) + .businessCode(ErrorBusinessCode.UNAUTHORIZED) + .errorDescription("Test error Message") + .buildBody(); + + String resultString = testInstance.marshall(error, request); + // then + assertNotNull(resultString); + //calling the unmarshall method for JSON + ErrorResponse result = (new ObjectMapper()).readValue(resultString, ErrorResponse.class); + + assertEquals(error.getBusinessCode(), result.getBusinessCode()); + assertEquals(error.getErrorDescription(), result.getErrorDescription()); + assertEquals(error.getErrorUniqueId(), result.getErrorUniqueId()); + } + + @Test + public void marshallXMLError() throws JAXBException { + HttpServletRequest request = mock(HttpServletRequest.class); + Mockito.doReturn("/smp/test-test-test::0001:test").when(request).getRequestURI(); + ErrorResponse error = ErrorResponseBuilder.status(UNAUTHORIZED) + .businessCode(ErrorBusinessCode.UNAUTHORIZED) + .errorDescription("Test error Message") + .buildBody(); + + String resultString = testInstance.marshall(error, request); + // then + assertNotNull(resultString); + //calling the unmarshall method for XML + ErrorResponse result = (ErrorResponse) JAXBContext.newInstance(ErrorResponse.class) + .createUnmarshaller() + .unmarshal(new StringReader(resultString)); + + assertEquals(error.getBusinessCode(), result.getBusinessCode()); + assertEquals(error.getErrorDescription(), result.getErrorDescription()); + assertEquals(error.getErrorUniqueId(), result.getErrorUniqueId()); + } +} \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/ErrorMappingControllerAdviceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/ServiceErrorControllerAdviceTest.java similarity index 92% rename from smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/ErrorMappingControllerAdviceTest.java rename to smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/ServiceErrorControllerAdviceTest.java index 2e5278c0f37b178b0669f5944bbe5d8f34cd540b..a97d1a6480e188a6cf6475583ae04fa0dec6c7ed 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/ErrorMappingControllerAdviceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/error/ServiceErrorControllerAdviceTest.java @@ -13,9 +13,9 @@ import org.springframework.security.core.AuthenticationException; import static org.junit.Assert.*; import static org.springframework.http.HttpStatus.*; -public class ErrorMappingControllerAdviceTest { +public class ServiceErrorControllerAdviceTest { - ErrorMappingControllerAdvice testIntance = new ErrorMappingControllerAdvice(); + ServiceErrorControllerAdvice testIntance = new ServiceErrorControllerAdvice(); @Test public void handleRuntimeException() { @@ -48,7 +48,7 @@ public class ErrorMappingControllerAdviceTest { @Test public void handleAuthenticationException() { - ResponseEntity re = testIntance.handleAuthenticationException(new AuthenticationException("AuthenticationException") { + ResponseEntity re = testIntance.handleRuntimeException(new AuthenticationException("AuthenticationException") { @Override public String getMessage() { return super.getMessage(); diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResourceTest.java index 8679a2aeed44a296b34945414647cbadf66bde88..df1be54540ac154dd23dab5fd92147ad2fc12b3c 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/AuthenticationResourceTest.java @@ -4,7 +4,10 @@ import eu.europa.ec.edelivery.smp.config.PropertiesTestConfig; import eu.europa.ec.edelivery.smp.config.SmpAppConfig; import eu.europa.ec.edelivery.smp.config.SmpWebAppConfig; import eu.europa.ec.edelivery.smp.config.SpringSecurityConfig; +import eu.europa.ec.edelivery.smp.error.ServiceErrorControllerAdvice; +import eu.europa.ec.edelivery.smp.error.UIErrorControllerAdvice; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -36,14 +39,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. PropertiesTestConfig.class, SmpAppConfig.class, SmpWebAppConfig.class, - SpringSecurityConfig.class}) + SpringSecurityConfig.class, + UIErrorControllerAdvice.class, + ServiceErrorControllerAdvice.class}) @WebAppConfiguration @Sql("classpath:/cleanup-database.sql") @Sql("classpath:/webapp_integration_test_data.sql") @SqlConfig(encoding = "UTF-8") public class AuthenticationResourceTest { - private static final String PATH = "/ui/rest/security/authentication"; + + private static final String PATH = ResourceConstants.CONTEXT_PATH_PUBLIC_SECURITY+"/authentication"; @Autowired private WebApplicationContext webAppContext; @@ -84,6 +90,7 @@ public class AuthenticationResourceTest { @Test + @Ignore public void authenticateInvalidPasswordTest() throws Exception { // given when then @@ -96,6 +103,7 @@ public class AuthenticationResourceTest { } @Test + @Ignore public void authenticateInvalidUsernameTest() throws Exception { // given when diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResourceTest.java index eb764c375a184f775831c25a915b78fb80954a1c..d5e050cd61c9cf9e37df52182cb9cfd0f5bf1bb3 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResourceTest.java @@ -62,7 +62,7 @@ public class ServiceGroupResourceTest { @Autowired ServiceGroupDao serviceGroupDao; - private static final String PATH = "/ui/rest/servicegroup"; + private static final String PATH_PUBLIC = ResourceConstants.CONTEXT_PATH_PUBLIC_SERVICE_GROUP; private static final String PARTICIPANT_IDENTIFIER = "urn:australia:ncpb"; private static final String PARTICIPANT_SCHEME = "ehealth-actorid-qns"; @@ -95,7 +95,7 @@ public class ServiceGroupResourceTest { @Test public void getServiceGroupListForSMPAdmin() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH) + MvcResult result = mvc.perform(get(PATH_PUBLIC) .with(SMP_ADMIN_CREDENTIALS).with(csrf()) ).andExpect(status().isOk()).andReturn(); @@ -119,7 +119,7 @@ public class ServiceGroupResourceTest { @Test public void getServiceGroupListForServiceGroupAdmin() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH) + MvcResult result = mvc.perform(get(PATH_PUBLIC) .with(SG_ADMIN_CREDENTIALS).with(csrf()) ).andExpect(status().isOk()).andReturn(); @@ -143,7 +143,7 @@ public class ServiceGroupResourceTest { public void getServiceGroupById() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH + "/100000") + MvcResult result = mvc.perform(get(PATH_PUBLIC + "/100000") .with(SMP_ADMIN_CREDENTIALS).with(csrf())). andExpect(status().isOk()).andReturn(); @@ -172,7 +172,7 @@ public class ServiceGroupResourceTest { serviceGroupDao.update(sg); // given when - MvcResult result = mvc.perform(get(PATH + "/extension/100000") + MvcResult result = mvc.perform(get(PATH_PUBLIC + "/extension/100000") .with(SMP_ADMIN_CREDENTIALS).with(csrf())) .andExpect(status().isOk()).andReturn(); @@ -194,7 +194,7 @@ public class ServiceGroupResourceTest { validate.setExtension(validExtension + "<ADFA>sdfadsf"); // given when - MvcResult result = mvc.perform(post(PATH + "/extension/validate") + MvcResult result = mvc.perform(post(PATH_PUBLIC + "/extension/validate") .with(SMP_ADMIN_CREDENTIALS) .header("Content-Type","application/json") .content(mapper.writeValueAsString(validate)) diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/UserResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/UserResourceTest.java index 0b2161a44e7506af30763f54295a9b25455532e2..7956ebbe95fe95db7dfc13f3b61dc0f62080ff2a 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/UserResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/UserResourceTest.java @@ -13,9 +13,11 @@ import eu.europa.ec.edelivery.smp.testutils.X509CertificateTestUtils; import org.apache.commons.io.IOUtils; import org.hamcrest.CoreMatchers; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; import org.springframework.test.context.ContextConfiguration; @@ -32,6 +34,7 @@ import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; +import javax.servlet.http.HttpSession; import javax.ws.rs.core.MediaType; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -60,7 +63,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SqlConfig(encoding = "UTF-8") public class UserResourceTest { - private static final String PATH = "/ui/rest/user"; + private static final String PATH_INTERNAL = ResourceConstants.CONTEXT_PATH_INTERNAL_USER; + private static final String PATH_PUBLIC = ResourceConstants.CONTEXT_PATH_PUBLIC_USER; + private static final String PATH_AUTHENTICATION = ResourceConstants.CONTEXT_PATH_PUBLIC_SECURITY+"/authentication"; + @Autowired private WebApplicationContext webAppContext; @@ -88,7 +94,7 @@ public class UserResourceTest { @Test public void getUserList() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH) + MvcResult result = mvc.perform(get(PATH_INTERNAL) .with(ADMIN_CREDENTIALS) .with(csrf())) .andExpect(status().isOk()).andReturn(); @@ -102,17 +108,18 @@ public class UserResourceTest { assertEquals(10, res.getServiceEntities().size()); res.getServiceEntities().forEach(sgMap -> { UserRO sgro = mapper.convertValue(sgMap, UserRO.class); - assertNotNull(sgro.getId()); + assertNotNull(sgro.getUserId()); assertNotNull(sgro.getUsername()); assertNotNull(sgro.getRole()); }); } @Test + @Ignore public void testUpdateCurrentUserOK() throws Exception { // given when - log as SMP admin - MvcResult result = mvc.perform(post("/ui/rest/security/authentication") + MvcResult result = mvc.perform(post(PATH_AUTHENTICATION) .header("Content-Type", "application/json") .content("{\"username\":\"smp_admin\",\"password\":\"test123\"}")) .andExpect(status().isOk()).andReturn(); @@ -120,6 +127,8 @@ public class UserResourceTest { UserRO userRO = mapper.readValue(result.getResponse().getContentAsString(), UserRO.class); assertNotNull(userRO); + MockHttpSession session = (MockHttpSession)result.getRequest().getSession(); + // when userRO.setActive(!userRO.isActive()); userRO.setEmailAddress("test@mail.com"); @@ -129,9 +138,10 @@ public class UserResourceTest { } userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - mvc.perform(put(PATH + "/" + userRO.getId()) + mvc.perform(put(PATH_PUBLIC + "/" + userRO.getUserId()) .with(ADMIN_CREDENTIALS) .with(csrf()) + .session(session) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsString(userRO)) ).andExpect(status().isOk()).andReturn(); @@ -142,7 +152,7 @@ public class UserResourceTest { // given when - log as SMP admin // then change values and list uses for changed value - MvcResult result = mvc.perform(post("/ui/rest/security/authentication") + MvcResult result = mvc.perform(post(PATH_AUTHENTICATION) .header("Content-Type", "application/json") .content("{\"username\":\"smp_admin\",\"password\":\"test123\"}")) .andExpect(status().isOk()).andReturn(); @@ -159,7 +169,7 @@ public class UserResourceTest { } userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - mvc.perform(put(PATH + "/" + userRO.getId()) + mvc.perform(put(PATH_PUBLIC + "/" + userRO.getUserId()) .with(SYSTEM_CREDENTIALS) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) @@ -170,7 +180,7 @@ public class UserResourceTest { @Test public void testUpdateUserList() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH) + MvcResult result = mvc.perform(get(PATH_INTERNAL) .with(SYSTEM_CREDENTIALS) .with(csrf())) .andExpect(status().isOk()).andReturn(); @@ -188,7 +198,7 @@ public class UserResourceTest { } userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - mvc.perform(put(PATH) + mvc.perform(put(PATH_INTERNAL) .with(SYSTEM_CREDENTIALS) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) @@ -199,7 +209,7 @@ public class UserResourceTest { @Test public void testUpdateUserListWrongAuthentication() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH) + MvcResult result = mvc.perform(get(PATH_INTERNAL) .with(SYSTEM_CREDENTIALS) .with(csrf())) .andExpect(status().isOk()).andReturn(); @@ -217,20 +227,20 @@ public class UserResourceTest { } userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); // anonymous - mvc.perform(put(PATH) + mvc.perform(put(PATH_INTERNAL) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsString(Arrays.asList(userRO))) ).andExpect(status().isUnauthorized()); - mvc.perform(put(PATH) + mvc.perform(put(PATH_INTERNAL) .with(ADMIN_CREDENTIALS) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsString(Arrays.asList(userRO))) ).andExpect(status().isUnauthorized()); - mvc.perform(put(PATH) + mvc.perform(put(PATH_INTERNAL) .with(SG_ADMIN_CREDENTIALS) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) @@ -238,114 +248,9 @@ public class UserResourceTest { ).andExpect(status().isUnauthorized()); } - @Test - public void uploadCertificateSystemAdmin() throws Exception { - byte[] buff = IOUtils.toByteArray(UserResourceTest.class.getResourceAsStream("/SMPtest.crt")); - - // given when - MvcResult result = mvc.perform(post(PATH + "/1098765430/certdata") - .with(SYSTEM_CREDENTIALS) - .with(csrf()) - .content(buff)) - .andExpect(status().isOk()).andReturn(); - - //then - ObjectMapper mapper = new ObjectMapper(); - CertificateRO res = mapper.readValue(result.getResponse().getContentAsString(), CertificateRO.class); - - assertNotNull(res); - assertEquals("CN=Intermediate CA,O=DIGIT,C=BE", res.getIssuer()); - assertEquals("1.2.840.113549.1.9.1=#160c736d7040746573742e636f6d,CN=SMP test,O=DIGIT,C=BE", res.getSubject()); - assertEquals("3", res.getSerialNumber()); - assertEquals("CN=SMP test,O=DIGIT,C=BE:0000000000000003", res.getCertificateId()); - assertEquals("sno=3&subject=1.2.840.113549.1.9.1%3D%23160c736d7040746573742e636f6d%2CCN%3DSMP+test%2CO%3DDIGIT%2CC%3DBE&validfrom=May+22+20%3A59%3A00+2018+GMT&validto=May+22+20%3A56%3A00+2019+GMT&issuer=CN%3DIntermediate+CA%2CO%3DDIGIT%2CC%3DBE", res.getBlueCoatHeader()); - } - - @Test - public void uploadInvalidCertificate() throws Exception { - byte[] buff = (new String("Not a certficate :) ")).getBytes(); - - // given when - mvc.perform(post(PATH + "/1098765430/certdata") - .with(SYSTEM_CREDENTIALS) - .with(csrf()) - .content(buff)) - .andExpect(status().is5xxServerError()) - .andExpect(content().string(CoreMatchers.containsString(" The certificate is not valid"))); - - } - - @Test - public void uploadCertificateIdWithEmailSerialNumberInSubjectCertIdTest() throws Exception { - String subject = "CN=common name,emailAddress=CEF-EDELIVERY-SUPPORT@ec.europa.eu,serialNumber=1,O=org,ST=My town,postalCode=2151, L=GreatTown,street=My Street. 20, C=BE"; - String serialNumber = "1234321"; - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(serialNumber, subject); - byte[] buff = certificate.getEncoded(); - // given when - MvcResult result = mvc.perform(post(PATH + "/1098765430/certdata") - .with(SYSTEM_CREDENTIALS) - .with(csrf()) - .content(buff)) - .andExpect(status().isOk()).andReturn(); - - //them - ObjectMapper mapper = new ObjectMapper(); - CertificateRO res = mapper.readValue(result.getResponse().getContentAsString(), CertificateRO.class); - - assertEquals("CN=common name,O=org,C=BE:0000000001234321", res.getCertificateId()); - } - - @Test - public void uploadCertificateInvalidUser() throws Exception { - byte[] buff = IOUtils.toByteArray(UserResourceTest.class.getResourceAsStream("/SMPtest.crt")); - // id and logged user not match - // given when - mvc.perform(post(PATH + "/34556655/certdata") - .with(ADMIN_CREDENTIALS) - .with(csrf()) - .content(buff)) - .andExpect(status().isUnauthorized()).andReturn(); - } - - @Test - public void samePreviousPasswordUsedTrue() throws Exception { - // 1 is id for smp_admin - MvcResult result = mvc.perform(post(PATH + "/1/samePreviousPasswordUsed") - .with(ADMIN_CREDENTIALS) - .with(csrf()) - .content("test123")) - .andExpect(status().isOk()).andReturn(); - - assertNotNull(result); - assertEquals("true", result.getResponse().getContentAsString()); - } - - @Test - public void samePreviousPasswordUsedFalse() throws Exception { - // 1 is id for smp_admin - MvcResult result = mvc.perform(post(PATH + "/1/samePreviousPasswordUsed") - .with(ADMIN_CREDENTIALS) - .with(csrf()) - .content("7777")) - .andExpect(status().isOk()).andReturn(); - - assertNotNull(result); - assertEquals("false", result.getResponse().getContentAsString()); - } - - @Test - public void samePreviousPasswordUsedUnauthorized() throws Exception { - // 1 is id for smp_admin so for 3 should be Unauthorized - MvcResult result = mvc.perform(post(PATH + "/3/samePreviousPasswordUsed") - .with(ADMIN_CREDENTIALS) - .with(csrf()) - .content("test123")) - .andExpect(status().isUnauthorized()).andReturn(); - } - @Test public void testValidateDeleteUserOK() throws Exception { - MvcResult result = mvc.perform(post(PATH + "/validateDelete") + MvcResult result = mvc.perform(post(PATH_INTERNAL + "/validate-delete") .with(SYSTEM_CREDENTIALS) .with(csrf()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON) @@ -363,7 +268,7 @@ public class UserResourceTest { @Test public void testValidateDeleteUserNotOK() throws Exception { // note system credential has id 3! - MvcResult result = mvc.perform(post(PATH + "/validateDelete") + MvcResult result = mvc.perform(post(PATH_INTERNAL + "/validate-delete") .with(SYSTEM_CREDENTIALS) .with(csrf()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON) diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a42b7d09297a1cc9690a27e7dc877425e8455e2e --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceTest.java @@ -0,0 +1,108 @@ +package eu.europa.ec.edelivery.smp.ui.external; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.europa.ec.edelivery.smp.config.PropertiesTestConfig; +import eu.europa.ec.edelivery.smp.config.SmpAppConfig; +import eu.europa.ec.edelivery.smp.config.SmpWebAppConfig; +import eu.europa.ec.edelivery.smp.config.SpringSecurityConfig; +import eu.europa.ec.edelivery.smp.data.ui.SmpInfoRO; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import static org.junit.Assert.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + PropertiesTestConfig.class, + SmpAppConfig.class, + SmpWebAppConfig.class, + SpringSecurityConfig.class}) +@WebAppConfiguration +@SqlConfig(encoding = "UTF-8") +@Sql(scripts = {"classpath:cleanup-database.sql", + "classpath:webapp_integration_test_data.sql" +}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +@TestPropertySource(properties = { + "smp.artifact.name=TestApplicationSmpName", + "smp.artifact.version=TestApplicationVersion", + "smp.artifact.build.time=2018-11-27 00:00:00", +}) +public class ApplicationResourceTest { + private static final String PATH = ResourceConstants.CONTEXT_PATH_PUBLIC_APPLICATION; + + @Autowired + private WebApplicationContext webAppContext; + + @Autowired + private ApplicationResource applicationResource; + + private MockMvc mvc; + + @Before + public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(webAppContext) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build(); + initServletContext(); + } + + private void initServletContext() { + MockServletContext sc = new MockServletContext(""); + ServletContextListener listener = new ContextLoaderListener(webAppContext); + ServletContextEvent event = new ServletContextEvent(sc); + } + + @Test + public void testGetName() throws Exception { + String value = mvc.perform(get(PATH + "/name")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals("TestApplicationSmpName", value); + } + + @Test + public void getDisplayName() throws Exception { + String value = applicationResource.getDisplayVersion(); + assertEquals("TestApplicationSmpName Version [TestApplicationVersion] Build-Time [2018-11-27 00:00:00|Central European Time]", value); + } + + @Test + public void getApplicationInfoTest() throws Exception { + String value = mvc.perform(get(PATH + "/info")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + ObjectMapper mapper = new ObjectMapper(); + SmpInfoRO info = mapper.readValue(value, SmpInfoRO.class); + + assertEquals("TestApplicationSmpName Version [TestApplicationVersion] Build-Time [2018-11-27 00:00:00|Central European Time]", info.getVersion()); + assertEquals(false, info.isSmlIntegrationOn()); + assertEquals("/", info.getContextPath()); + } +} diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/DomainResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/DomainResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..61858cb3d11a9a4523efcf83c46ea2a3e8de1242 --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/DomainResourceTest.java @@ -0,0 +1,105 @@ +package eu.europa.ec.edelivery.smp.ui.external; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.europa.ec.edelivery.smp.config.*; +import eu.europa.ec.edelivery.smp.data.dao.DomainDao; +import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; +import eu.europa.ec.edelivery.smp.data.ui.DomainRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + PropertiesTestConfig.class, + SmpAppConfig.class, + SmpWebAppConfig.class, + SpringSecurityConfig.class}) +@WebAppConfiguration +@Sql("classpath:/cleanup-database.sql") +@Sql("classpath:/webapp_integration_test_data.sql") +@SqlConfig(encoding = "UTF-8") +public class DomainResourceTest { + private static final String PATH = ResourceConstants.CONTEXT_PATH_PUBLIC_DOMAIN; + + @Autowired + private WebApplicationContext webAppContext; + @Autowired + DomainDao domainDao; + + private MockMvc mvc; + + @Before + public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(webAppContext) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build(); + + initServletContext(); + } + + private void initServletContext() { + MockServletContext sc = new MockServletContext(""); + ServletContextListener listener = new ContextLoaderListener(webAppContext); + ServletContextEvent event = new ServletContextEvent(sc); + } + + + @Test + public void geDomainPublicList() throws Exception { + + // given when + MvcResult result = mvc.perform(get(PATH) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + + //them + ObjectMapper mapper = new ObjectMapper(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + + + assertNotNull(res); + assertEquals(2, res.getServiceEntities().size()); + res.getServiceEntities().forEach(sgMap -> { + DomainRO sgro = mapper.convertValue(sgMap, DomainRO.class); + assertNotNull(sgro.getDomainCode()); + assertNotNull(sgro.getSmlSubdomain()); + // for public endpot all other data must be null! + assertNull(sgro.getId()); + assertNull(sgro.getSmlSmpId()); + assertNull(sgro.getSignatureKeyAlias()); + assertNull(sgro.getSmlParticipantIdentifierRegExp()); + }); + } +} \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/SearchResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/SearchResourceTest.java similarity index 91% rename from smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/SearchResourceTest.java rename to smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/SearchResourceTest.java index d80da95a0c6d63c7f9766b98316912e4c17c288f..7997213e400270e3bc02c18fe27dcdf2ff0c8c57 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/SearchResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/SearchResourceTest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.external; import com.fasterxml.jackson.databind.ObjectMapper; import eu.europa.ec.edelivery.smp.config.PropertiesTestConfig; @@ -7,6 +7,7 @@ import eu.europa.ec.edelivery.smp.config.SmpWebAppConfig; import eu.europa.ec.edelivery.smp.config.SpringSecurityConfig; import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +29,7 @@ import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -50,9 +52,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SqlConfig(encoding = "UTF-8") public class SearchResourceTest { - private static final String PATH="/ui/rest/search"; - - @Autowired private WebApplicationContext webAppContext; @@ -75,7 +74,7 @@ public class SearchResourceTest { @Test public void testSearchByAnonymous() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH) + MvcResult result = mvc.perform(get(CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT) ).andExpect(status().isOk()).andReturn(); //then diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ApplicationResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/ApplicationAdminResourceTest.java similarity index 72% rename from smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ApplicationResourceTest.java rename to smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/ApplicationAdminResourceTest.java index 3d4f71d27c6db37b01ba7facfb0e36a7d5df0a6a..fb067c09c5e73c45404554ab15a9b10769adad34 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/ApplicationResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/ApplicationAdminResourceTest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; import com.fasterxml.jackson.databind.ObjectMapper; import eu.europa.ec.edelivery.smp.config.PropertiesTestConfig; @@ -6,7 +6,7 @@ import eu.europa.ec.edelivery.smp.config.SmpAppConfig; import eu.europa.ec.edelivery.smp.config.SmpWebAppConfig; import eu.europa.ec.edelivery.smp.config.SpringSecurityConfig; import eu.europa.ec.edelivery.smp.data.ui.SmpConfigRO; -import eu.europa.ec.edelivery.smp.data.ui.SmpInfoRO; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,31 +51,21 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. "smp.artifact.version=TestApplicationVersion", "smp.artifact.build.time=2018-11-27 00:00:00", }) - -public class ApplicationResourceTest { - private static final String PATH = "/ui/rest/application"; - +public class ApplicationAdminResourceTest { + private static final String PATH = ResourceConstants.CONTEXT_PATH_INTERNAL_APPLICATION; private static final RequestPostProcessor SMP_ADMIN_CREDENTIALS = httpBasic("smp_admin", "test123"); private static final RequestPostProcessor SG_ADMIN_CREDENTIALS = httpBasic("sg_admin", "test123"); private static final RequestPostProcessor SYSTEM_CREDENTIALS = httpBasic("sys_admin", "test123"); @Autowired private WebApplicationContext webAppContext; - - @Autowired - private ApplicationResource applicationResource; - - private MockMvc mvc; - private static final RequestPostProcessor ADMIN_CREDENTIALS = httpBasic("smp_admin", "test123"); @Before public void setup() { mvc = MockMvcBuilders.webAppContextSetup(webAppContext) .apply(SecurityMockMvcConfigurers.springSecurity()) .build(); - - initServletContext(); } @@ -85,38 +75,16 @@ public class ApplicationResourceTest { ServletContextEvent event = new ServletContextEvent(sc); } - @Test - public void testGetName() throws Exception { - String value = mvc.perform(get(PATH + "/name")) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - - assertEquals("TestApplicationSmpName", value); - - } - - @Test - public void testGetRootContext() throws Exception { - String value = mvc.perform(get(PATH + "/rootContext")) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - - assertEquals("/", value); - } - @Test public void testGetApplicationConfigNotAuthorized() throws Exception { // when - mvc.perform(get(PATH + "/config") - .with(csrf())) + mvc.perform(get(PATH + "/config") + .with(csrf())) .andExpect(status().isUnauthorized()) .andReturn() .getResponse(); } + @Test public void testGetApplicationConfigAuthorized() throws Exception { // SMP admin @@ -158,36 +126,14 @@ public class ApplicationResourceTest { .getResponse() .getContentAsString(); - // then + // then ObjectMapper mapper = new ObjectMapper(); SmpConfigRO res = mapper.readValue(value, SmpConfigRO.class); - assertNotNull(res); - assertEquals("Participant scheme must start with:urn:oasis:names:tc:ebcore:partyid-type:(iso6523:|unregistered:) OR must be up to 25 characters long with form [domain]-[identifierArea]-[identifierType] (ex.: 'busdox-actorid-upis') and may only contain the following characters: [a-z0-9].",res.getParticipantSchemaRegExpMessage()); - assertEquals("^((?!^.{26})([a-z0-9]+-[a-z0-9]+-[a-z0-9]+)|urn:oasis:names:tc:ebcore:partyid-type:(iso6523|unregistered)(:.+)?$)",res.getParticipantSchemaRegExp()); + assertEquals("Participant scheme must start with:urn:oasis:names:tc:ebcore:partyid-type:(iso6523:|unregistered:) OR must be up to 25 characters long with form [domain]-[identifierArea]-[identifierType] (ex.: 'busdox-actorid-upis') and may only contain the following characters: [a-z0-9].", res.getParticipantSchemaRegExpMessage()); + assertEquals("^((?!^.{26})([a-z0-9]+-[a-z0-9]+-[a-z0-9]+)|urn:oasis:names:tc:ebcore:partyid-type:(iso6523|unregistered)(:.+)?$)", res.getParticipantSchemaRegExp()); assertFalse(res.isSmlIntegrationOn()); assertFalse(res.isSmlParticipantMultiDomainOn()); } - - @Test - public void getDisplayName() throws Exception { - String value = applicationResource.getDisplayVersion(); - assertEquals("TestApplicationSmpName Version [TestApplicationVersion] Build-Time [2018-11-27 00:00:00|Central European Time]", value); - } - - @Test - public void getApplicationInfoTest() throws Exception { - String value = mvc.perform(get(PATH + "/info")) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - ObjectMapper mapper = new ObjectMapper(); - SmpInfoRO info = mapper.readValue(value, SmpInfoRO.class); - - assertEquals("TestApplicationSmpName Version [TestApplicationVersion] Build-Time [2018-11-27 00:00:00|Central European Time]", info.getVersion()); - assertEquals(false, info.isSmlIntegrationOn()); - assertEquals("/", info.getContextPath()); - } } diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/DomainResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/DomainAdminResourceTest.java similarity index 83% rename from smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/DomainResourceTest.java rename to smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/DomainAdminResourceTest.java index 12b12d7c60f7bd6a9c95ec0a96b6e6493118e37e..aa6de7444cca5751d4b214d444c292bc4df267b9 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/DomainResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/DomainAdminResourceTest.java @@ -1,11 +1,15 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.europa.ec.edelivery.smp.config.*; +import eu.europa.ec.edelivery.smp.config.PropertiesTestConfig; +import eu.europa.ec.edelivery.smp.config.SmpAppConfig; +import eu.europa.ec.edelivery.smp.config.SmpWebAppConfig; +import eu.europa.ec.edelivery.smp.config.SpringSecurityConfig; import eu.europa.ec.edelivery.smp.data.dao.DomainDao; import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; import eu.europa.ec.edelivery.smp.data.ui.DomainRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,9 +36,7 @@ import static org.hamcrest.Matchers.stringContainsInOrder; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -49,18 +51,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @Sql("classpath:/cleanup-database.sql") @Sql("classpath:/webapp_integration_test_data.sql") @SqlConfig(encoding = "UTF-8") -@TestPropertySource(properties = { - "smp.artifact.name=TestApplicationSmpName", - "smp.artifact.version=TestApplicationVersion", - "smp.artifact.build.time=2018-11-27 00:00:00", - "bdmsl.integration.enabled=false"}) -public class DomainResourceTest { - private static final String PATH = "/ui/rest/domain"; +public class DomainAdminResourceTest { + private static final String PATH = ResourceConstants.CONTEXT_PATH_INTERNAL_DOMAIN; @Autowired private WebApplicationContext webAppContext; - @Autowired DomainDao domainDao; @@ -83,29 +79,6 @@ public class DomainResourceTest { } - @Test - public void geDomainList() throws Exception { - - // given when - MvcResult result = mvc.perform(get(PATH) - .with(SYSTEM_CREDENTIALS) - .with(csrf())) - .andExpect(status().isOk()).andReturn(); - - //them - ObjectMapper mapper = new ObjectMapper(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - - - assertNotNull(res); - assertEquals(2, res.getServiceEntities().size()); - res.getServiceEntities().forEach(sgMap -> { - DomainRO sgro = mapper.convertValue(sgMap, DomainRO.class); - assertNotNull(sgro.getId()); - assertNotNull(sgro.getDomainCode()); - assertNotNull(sgro.getSmlSmpId()); - }); - } @Test public void updateDomainListOkDelete() throws Exception { @@ -138,7 +111,7 @@ public class DomainResourceTest { @Test public void validateDeleteDomainOK() throws Exception { // given when - MvcResult result = mvc.perform(post(PATH + "/validateDelete") + MvcResult result = mvc.perform(put(PATH + "/validate-delete") .with(SYSTEM_CREDENTIALS) .with(csrf()) .header("Content-Type", " application/json") @@ -170,12 +143,11 @@ public class DomainResourceTest { // check if exists assertEquals("CEF-SMP-010", domainDao.getDomainByCode("domainTwo").get().getSmlSmpId()); - } @Test public void validateDeleteDomainFalse() throws Exception { // given when - MvcResult result = mvc.perform(post(PATH + "/validateDelete") + MvcResult result = mvc.perform(put(PATH + "/validate-delete") .with(SYSTEM_CREDENTIALS) .with(csrf()) .header("Content-Type", " application/json") @@ -198,7 +170,7 @@ public class DomainResourceTest { // given when // 3- user id // domainTwo - domain code - mvc.perform(post(PATH + "/3/smlregister/domainTwo") + mvc.perform(put(PATH + "/3/sml-register/domainTwo") .with(SYSTEM_CREDENTIALS) .with(csrf()) .header("Content-Type", " application/json")) @@ -211,7 +183,7 @@ public class DomainResourceTest { // given when // 3- user id // domainTwo - domain code - mvc.perform(post(PATH + "/3/smlunregister/domainTwo") + mvc.perform(put(PATH + "/3/sml-unregister/domainTwo") .with(SYSTEM_CREDENTIALS) .with(csrf()) .header("Content-Type", " application/json")) diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/KeystoreResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/KeystoreResourceTest.java similarity index 97% rename from smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/KeystoreResourceTest.java rename to smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/KeystoreResourceTest.java index e157dd6bbdb5aeeab4b1f3e49676804157dd9654..6f1d23f9eb47b6b450584f86f92122892d500add 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/KeystoreResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/KeystoreResourceTest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; import com.fasterxml.jackson.databind.ObjectMapper; @@ -39,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_INTERNAL_KEYSTORE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -62,11 +63,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. "classpath:webapp_integration_test_data.sql" }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) public class KeystoreResourceTest { - private static final String PATH = "/ui/rest/keystore"; - + private static final String PATH = CONTEXT_PATH_INTERNAL_KEYSTORE; Path keystore = Paths.get("src", "test", "resources", "keystores", "smp-keystore.jks"); - @Autowired private WebApplicationContext webAppContext; @@ -83,8 +82,6 @@ public class KeystoreResourceTest { mvc = MockMvcBuilders.webAppContextSetup(webAppContext) .apply(SecurityMockMvcConfigurers.springSecurity()) .build(); - - initServletContext(); uiKeystoreService.refreshData(); } diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/TruststoreResourceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/TruststoreAdminResourceTest.java similarity index 69% rename from smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/TruststoreResourceTest.java rename to smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/TruststoreAdminResourceTest.java index dd8f0c1c48156be4eacd1727f3cb034ccc6abc4a..2bc6e27fba62563868003d49e33cacbc0f41ebf4 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/TruststoreResourceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/TruststoreAdminResourceTest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.edelivery.smp.ui; +package eu.europa.ec.edelivery.smp.ui.internal; import com.fasterxml.jackson.databind.ObjectMapper; @@ -7,12 +7,15 @@ import eu.europa.ec.edelivery.smp.config.SmpAppConfig; import eu.europa.ec.edelivery.smp.config.SmpWebAppConfig; import eu.europa.ec.edelivery.smp.config.SpringSecurityConfig; import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; -import eu.europa.ec.edelivery.smp.data.ui.KeystoreImportResult; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.error.UIErrorControllerAdvice; import eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; import eu.europa.ec.edelivery.smp.testutils.X509CertificateTestUtils; +import eu.europa.ec.edelivery.smp.ui.UserResourceTest; import org.apache.commons.io.IOUtils; +import org.hamcrest.CoreMatchers; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -33,16 +36,17 @@ import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_INTERNAL_TRUSTSTORE; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_TRUSTSTORE; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -51,17 +55,16 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. PropertiesTestConfig.class, SmpAppConfig.class, SmpWebAppConfig.class, - SpringSecurityConfig.class}) + SpringSecurityConfig.class, + UIErrorControllerAdvice.class}) @WebAppConfiguration @SqlConfig(encoding = "UTF-8") @Sql(scripts = {"classpath:cleanup-database.sql", "classpath:webapp_integration_test_data.sql" }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) -public class TruststoreResourceTest { - private static final String PATH = "/ui/rest/truststore"; - - Path keystore = Paths.get("src", "test", "resources", "keystores", "smp-keystore.jks"); - +public class TruststoreAdminResourceTest { + private static final String PATH_INTERNAL = CONTEXT_PATH_INTERNAL_TRUSTSTORE; + private static final String PATH_PUBLIC = CONTEXT_PATH_PUBLIC_TRUSTSTORE; @Autowired private WebApplicationContext webAppContext; @@ -71,6 +74,8 @@ public class TruststoreResourceTest { private MockMvc mvc; private static final RequestPostProcessor SYSTEM_CREDENTIALS = httpBasic("sys_admin", "test123"); + private static final RequestPostProcessor ADMIN_CREDENTIALS = httpBasic("smp_admin", "test123"); + private static final RequestPostProcessor SG_ADMIN_CREDENTIALS = httpBasic("sg_admin", "test123"); @Before public void setup() throws IOException { @@ -92,39 +97,24 @@ public class TruststoreResourceTest { } @Test - public void getCertificateList() throws Exception { + public void validateInvalidCertificate() throws Exception { + byte[] buff = (new String("Not a certficate :) ")).getBytes(); + // given when - int countStart = uiTruststoreService.getCertificateROEntriesList().size(); - MvcResult result = mvc.perform(get(PATH) + mvc.perform(post(PATH_PUBLIC + "/1098765430/validate-certificate") .with(SYSTEM_CREDENTIALS) - .with(csrf())) - .andExpect(status().isOk()).andReturn(); - - //them - ObjectMapper mapper = new ObjectMapper(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - - - assertNotNull(res); - assertEquals(countStart, res.getServiceEntities().size()); - res.getServiceEntities().forEach(sgMap -> { - CertificateRO cert = mapper.convertValue(sgMap, CertificateRO.class); - assertNotNull(cert.getAlias()); - assertNotNull(cert.getCertificateId()); - assertNotNull(cert.getBlueCoatHeader()); - assertNull(cert.getEncodedValue()); // submit only metadata - }); + .with(csrf()) + .content(buff)) + .andExpect(status().is5xxServerError()) + .andExpect(content().string(CoreMatchers.containsString(" The certificate is not valid"))); } - - @Test - public void uploadCertificateSystemAdmin() throws Exception { + public void validateCertificateSystemAdmin() throws Exception { byte[] buff = IOUtils.toByteArray(UserResourceTest.class.getResourceAsStream("/SMPtest.crt")); - int countStart = uiTruststoreService.getNormalizedTrustedList().size(); // given when - MvcResult result = mvc.perform(post(PATH+"/3/certdata") + MvcResult result = mvc.perform(post(PATH_PUBLIC + "/1098765430/validate-certificate") .with(SYSTEM_CREDENTIALS) .with(csrf()) .content(buff)) @@ -133,7 +123,7 @@ public class TruststoreResourceTest { //then ObjectMapper mapper = new ObjectMapper(); CertificateRO res = mapper.readValue(result.getResponse().getContentAsString(), CertificateRO.class); - assertEquals(countStart+1, uiTruststoreService.getNormalizedTrustedList().size()); + assertNotNull(res); assertEquals("CN=Intermediate CA,O=DIGIT,C=BE", res.getIssuer()); assertEquals("1.2.840.113549.1.9.1=#160c736d7040746573742e636f6d,CN=SMP test,O=DIGIT,C=BE", res.getSubject()); @@ -142,12 +132,69 @@ public class TruststoreResourceTest { assertEquals("sno=3&subject=1.2.840.113549.1.9.1%3D%23160c736d7040746573742e636f6d%2CCN%3DSMP+test%2CO%3DDIGIT%2CC%3DBE&validfrom=May+22+20%3A59%3A00+2018+GMT&validto=May+22+20%3A56%3A00+2019+GMT&issuer=CN%3DIntermediate+CA%2CO%3DDIGIT%2CC%3DBE", res.getBlueCoatHeader()); } + @Test + public void validateCertificateIdWithEmailSerialNumberInSubjectCertIdTest() throws Exception { + String subject = "CN=common name,emailAddress=CEF-EDELIVERY-SUPPORT@ec.europa.eu,serialNumber=1,O=org,ST=My town,postalCode=2151, L=GreatTown,street=My Street. 20, C=BE"; + String serialNumber = "1234321"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(serialNumber, subject); + byte[] buff = certificate.getEncoded(); + // given when + MvcResult result = mvc.perform(post(PATH_PUBLIC + "/1098765430/validate-certificate") + .with(SYSTEM_CREDENTIALS) + .with(csrf()) + .content(buff)) + .andExpect(status().isOk()).andReturn(); + + //them + ObjectMapper mapper = new ObjectMapper(); + CertificateRO res = mapper.readValue(result.getResponse().getContentAsString(), CertificateRO.class); + + assertEquals("CN=common name,O=org,C=BE:0000000001234321", res.getCertificateId()); + } + + @Test + public void uploadCertificateInvalidUser() throws Exception { + byte[] buff = IOUtils.toByteArray(UserResourceTest.class.getResourceAsStream("/SMPtest.crt")); + // id and logged user not match + // given when + mvc.perform(post(PATH_PUBLIC + "/34556655/validate-certificate") + .with(ADMIN_CREDENTIALS) + .with(csrf()) + .content(buff)) + .andExpect(status().isUnauthorized()).andReturn(); + } + + @Test + public void getCertificateList() throws Exception { + // given when + int countStart = uiTruststoreService.getCertificateROEntriesList().size(); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .with(SYSTEM_CREDENTIALS) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + + //them + ObjectMapper mapper = new ObjectMapper(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + + + assertNotNull(res); + assertEquals(countStart, res.getServiceEntities().size()); + res.getServiceEntities().forEach(sgMap -> { + CertificateRO cert = mapper.convertValue(sgMap, CertificateRO.class); + assertNotNull(cert.getAlias()); + assertNotNull(cert.getCertificateId()); + assertNotNull(cert.getBlueCoatHeader()); + assertNull(cert.getEncodedValue()); // submit only metadata + }); + } + @Test public void deleteCertificateSystemAdmin() throws Exception { byte[] buff = IOUtils.toByteArray(UserResourceTest.class.getResourceAsStream("/SMPtest.crt")); - int countStart = uiTruststoreService.getNormalizedTrustedList().size(); - MvcResult prepRes = mvc.perform(post(PATH+"/3/certdata") + int countStart = uiTruststoreService.getNormalizedTrustedList().size(); + MvcResult prepRes = mvc.perform(post(PATH_INTERNAL + "/3/upload-certificate") .with(SYSTEM_CREDENTIALS) .with(csrf()) .content(buff)) @@ -158,10 +205,10 @@ public class TruststoreResourceTest { CertificateRO res = mapper.readValue(prepRes.getResponse().getContentAsString(), CertificateRO.class); assertNotNull(res); uiTruststoreService.refreshData(); - assertEquals(countStart+1, uiTruststoreService.getNormalizedTrustedList().size()); + assertEquals(countStart + 1, uiTruststoreService.getNormalizedTrustedList().size()); // then - MvcResult result = mvc.perform(delete(PATH+"/3/delete/"+res.getAlias()) + MvcResult result = mvc.perform(delete(PATH_INTERNAL + "/3/delete/" + res.getAlias()) .with(SYSTEM_CREDENTIALS) .with(csrf()) .content(buff)) @@ -213,7 +260,7 @@ public class TruststoreResourceTest { public List<CertificateRO> getCertificateFromEndpointList() throws Exception { // given when - MvcResult result = mvc.perform(get(PATH).with(SYSTEM_CREDENTIALS)). + MvcResult result = mvc.perform(get(PATH_INTERNAL).with(SYSTEM_CREDENTIALS)). andExpect(status().isOk()).andReturn(); //them diff --git a/smp-webapp/src/test/resources/logback-test.xml b/smp-webapp/src/test/resources/logback-test.xml index 14096e01d343057269fd34ce2fc0ee1d7459e49f..5ea50260f8a7af2e57bf8cc216e9399af8018040 100644 --- a/smp-webapp/src/test/resources/logback-test.xml +++ b/smp-webapp/src/test/resources/logback-test.xml @@ -2,8 +2,8 @@ <configuration> <!-- pattern definition --> - <property name="encoderPattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> - <property name="consolePattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="encoderPattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="consolePattern" value="%d{ISO8601} [%X{smp_user}] [%X{smp_session_id}] [%X{smp_request_id}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${project.build.directory}/logs/edelivery-smp.log</file> diff --git a/upgrade-info.txt b/upgrade-info.txt index 1606ec37aee7be6cb7bd386bfc3a2e32755a80f0..54fa9ba11ce2b8fe8c5ef5b32011a7ca8ef6983f 100644 --- a/upgrade-info.txt +++ b/upgrade-info.txt @@ -1,2 +1,4 @@ eDelivery SMP 4.2 from 4.1.1) - - Set: smp.http.forwarded.headers.enabled to true if SMP is set behind RP/LoadBalancer \ No newline at end of file + - Set: smp.http.forwarded.headers.enabled to true if SMP is set behind RP/LoadBalancer + - Make sure database user usernames are not null! + - Copy plugin users! \ No newline at end of file