diff --git a/README.md b/README.md index 95fd60362144b3306b6d953580f174122a08f5a4..70263b2550f704d4fee99d637c9f3647161d2825 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,67 @@ # Service Metadata Publishing -## Continuous Integration +[](https://ec.europa.eu/digital-building-blocks/wikis/download/attachments/52601883/eupl_v1.2_en%20.pdf?version=1&modificationDate=1507206778126&api=v2) +[](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/SMP) +[](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/Support+eDelivery) -[https://webgate.ec.europa.eu/CITnet/bamboo/browse/EDELIVERY-SMPDEV] + +## Introduction -## Building SMP -SMP requires Maven 3.6+ and Java 1.8. +This is the code repository for eDelivery SMP, the sample implementation, open source project of the European Commission [eDelivery SMP profile](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/SMP+specifications) implementation. - -## Source code history -This is a continuation of CIPA SMP Joinup repository, which was migrated here to GIT on 07.12.2016: -[https://joinup.ec.europa.eu/svn/cipaedelivery/trunk] +Any feedback on the application or the following documentation is highly welcome, including bugs, typos +or things you think should be included but aren't. You can use [JIRA](https://ec.europa.eu/digital-building-blocks/tracker/projects/EDELIVERY/issues) to provide feedback. -## Build SMP -Step 1: +Following documents are available on the [Domibus release page](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/SMP): +* Administration Guide +* Software Architecture Document +* Interface Control Documents -mvn clean install +[Top](#top) +## Overall description -## Execute integartion tests -By default integrations tests are executes on H2 database. -Any remote DB with preconfigured schema might be used as well. Sample build command: +To successfully send a business document in a (4-corner) network, an entity must be able to discover critical metadata about +the recipient (Access Point) of the business document, such as types of documents the Access Point is capable of receiving +and methods of transport supported. The recipient makes this metadata available to other entities in the network through +a Service Metadata Publisher service. The eDelivery SMP profile describes the request/response exchanges between a +Service Metadata Publisher and a client wishing to discover Access Point metadata. The profile is based on the +OASIS Service Metadata Publishing (SMP) Version 1.0 standard. - mvn clean install \ - -Djdbc.driver=oracle.jdbc.OracleDriver \ - -Djdbc.url=jdbc:oracle:thin:<HOST_AND_PORT_AND_SERVICENAME> \ - -Djdbc.user=<USERNAME> \ - -Djdbc.password=<PASSWORD> \ - -Dtarget-database=Oracle \ - -Djdbc.read-connections.max=10 \ No newline at end of file +The eDelivery SMP application is an implementation of the eDelivery SMP profile. The application also has a feature to +configure the integration to SML using [PEPPOL Transport Infrastructure SML specifications](https://docs.peppol.eu/edelivery/sml/ICT-Transport-SML_Service_Specification-101.pdf). + +eDelivery SMP is the Open Source project of the AS4 Access Point maintained by the European Commission. + +If this is your first contact with the eDelivery SMP, it is highly recommended to check the [SMP Software](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/SMP+software) page. + +[Top](#top) + +## Build + +In order to build eDelivery SMP : + + mvn clean install + + +[Top](#top) + +## Install and run + +How to install and run eDelivery SMP can be read in the Admin Guide available on the [eDelivery SMP Release Page](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/SMP+software). + +[Top](#top) + +## License + +eDelivery SMP is licensed under European Union Public Licence (EUPL) version 1.2. + +[Top](#top) + +## Support + +Have questions? Consult our [Q&A section](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/SMP+FAQs). +Still have questions? Contact [eDelivery support](https://ec.europa.eu/digital-building-blocks/tracker/plugins/servlet/desk/portal/6). + + +[Top](#top) \ No newline at end of file diff --git a/owasp-false-positive-warnings.xml b/owasp-false-positive-warnings.xml new file mode 100644 index 0000000000000000000000000000000000000000..9974d484fc1e2f7ac5a71bc5f07149bda195a605 --- /dev/null +++ b/owasp-false-positive-warnings.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd"> + <suppress> + <notes><![CDATA[ + file name: spring-security-crypto-5.7.2.jar + ]]></notes> + <packageUrl regex="true">^pkg:maven/org\.springframework\.security/spring\-security\-crypto@5.7.2$</packageUrl> + <vulnerabilityName>CVE-2020-5408</vulnerabilityName> + </suppress> + <suppress> + <notes><![CDATA[ + file names for spring framework: spring-*-5.3.21.jar + ]]></notes> + <packageUrl regex="true">^pkg:maven/org\.springframework/spring\-(core|web|oap)@5.3.21.*$</packageUrl> + <cve>CVE-2016-1000027</cve> + </suppress> +</suppressions> \ No newline at end of file diff --git a/pom.xml b/pom.xml index 343e54ff48ac5904c875971c0aff766a08788ff2..ba9ab083d368337ec395aa964b992db8f6e4bd45 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <edelivery.ssl-auth.version>1.10</edelivery.ssl-auth.version> + <edelivery.ssl-auth.version>1.11</edelivery.ssl-auth.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <ant-commons-net.version>1.6.5</ant-commons-net.version> @@ -50,8 +50,7 @@ <commons-validator.version>1.7</commons-validator.version> <cxf-xjc-runtime.version>3.3.2</cxf-xjc-runtime.version> <cxf.version>3.5.2</cxf.version> - <dnsjava.version>2.1.7</dnsjava.version> - <ehcache.version>2.10.9.2</ehcache.version> + <ehcache.version>2.10.9.2</ehcache.version> <freemarker.version>2.3.31</freemarker.version> <h2.version>1.4.200</h2.version> <hamcrest.version>2.0.0.0</hamcrest.version> @@ -74,12 +73,12 @@ <mockito.version>2.23.4</mockito.version> <orika.version>1.5.4</orika.version> <servlet-api.version>3.0.1</servlet-api.version> - <slf4j.version>1.7.32</slf4j.version> + <slf4j.version>1.7.36</slf4j.version> <soapui.plugin.version>5.1.2</soapui.plugin.version> <spring-modules-jakarta-commons.version>0.8</spring-modules-jakarta-commons.version> <spring.boot.version>2.7.0</spring.boot.version> - <spring.security.version>5.7.1</spring.security.version> - <spring.version>5.3.20</spring.version> + <spring.security.version>5.7.2</spring.security.version> + <spring.version>5.3.21</spring.version> <xmlunit.version>2.9.0</xmlunit.version> <!-- plugins --> @@ -102,11 +101,14 @@ **/smp-angular/node_modules/**, **/swagger*.js, **/web.xml, + **/smp-examples/**, </sonar.exclusions> <sonar.coverage.exclusions> **/*Entity.java, **/*RO.java, **/*Exception.java, + **/*Types.java, + **/*Constants.java, </sonar.coverage.exclusions> <!-- latest version compatible with SonarQube 5.6 is: 3.3.0.603--> <sonar.maven.plugin.version>3.5.0.1254</sonar.maven.plugin.version> @@ -332,6 +334,13 @@ <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>${spring.security.version}</version> + <exclusions> + <!-- current build is optimized for java 1.8: include bouncy castle jdk15to18 --> + <exclusion> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> @@ -452,20 +461,14 @@ </dependency> <dependency> <groupId>org.bouncycastle</groupId> - <artifactId>bcprov-jdk15on</artifactId> + <artifactId>bcprov-jdk15to18</artifactId> <version>${bouncycastle.version}</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk15on</artifactId> + <artifactId>bcpkix-jdk15to18</artifactId> <version>${bouncycastle.version}</version> </dependency> - <dependency> - <groupId>dnsjava</groupId> - <artifactId>dnsjava</artifactId> - <version>${dnsjava.version}</version> - </dependency> - <dependency> <groupId>ant</groupId> <artifactId>ant-commons-net</artifactId> @@ -717,7 +720,7 @@ <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> - <version>7.1.0</version> + <version>7.1.1</version> <inherited>false</inherited> <configuration> <skipProvidedScope>true</skipProvidedScope> @@ -729,6 +732,7 @@ <!-- Disable checking npm dev dependencies --> <nodeAuditSkipDevDependencies>true</nodeAuditSkipDevDependencies> <nodePackageSkipDevDependencies>true</nodePackageSkipDevDependencies> + <suppressionFile>owasp-false-positive-warnings.xml</suppressionFile> </configuration> <executions> <execution> diff --git a/smp-angular/src/app/alert/alert-controller.ts b/smp-angular/src/app/alert/alert-controller.ts index 6009413b48b295ff2db84889fcc5e0cd2c90d63e..c3078584a6d3b6ffb4f0716b2d7ae2c0d25573a1 100644 --- a/smp-angular/src/app/alert/alert-controller.ts +++ b/smp-angular/src/app/alert/alert-controller.ts @@ -33,7 +33,7 @@ export class AlertController implements SearchTableController { public showDetails(row: any) { this.dialog.open(ObjectPropertiesDialogComponent, { data: { - title: "Alert details!", + title: "Alert details", object: row.alertDetails, } diff --git a/smp-angular/src/app/alert/alert.component.ts b/smp-angular/src/app/alert/alert.component.ts index 797401c5c18afd868c21c93bd355a050b1f055d3..aab1e55702d41674d896e9524463a8bbdfc7b324 100644 --- a/smp-angular/src/app/alert/alert.component.ts +++ b/smp-angular/src/app/alert/alert.component.ts @@ -1,4 +1,12 @@ -import {AfterViewInit, Component, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog} from '@angular/material/dialog'; @@ -17,15 +25,15 @@ import {ObjectPropertiesDialogComponent} from "../common/dialogs/object-properti templateUrl: './alert.component.html', styleUrls: ['./alert.component.css'] }) -export class AlertComponent implements AfterViewInit { +export class AlertComponent implements OnInit, AfterViewInit, AfterViewChecked { @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; @ViewChild('rowActions') rowActions: TemplateRef<any>; @ViewChild('searchTable') searchTable: SearchTableComponent; - @ViewChild('dateTimeColumn') dateTimeColumn:TemplateRef<any>; - @ViewChild('truncateText') truncateText:TemplateRef<any>; - @ViewChild('credentialType') credentialType:TemplateRef<any>; - @ViewChild('forUser') forUser:TemplateRef<any>; + @ViewChild('dateTimeColumn') dateTimeColumn: TemplateRef<any>; + @ViewChild('truncateText') truncateText: TemplateRef<any>; + @ViewChild('credentialType') credentialType: TemplateRef<any>; + @ViewChild('forUser') forUser: TemplateRef<any>; readonly dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; @@ -42,19 +50,26 @@ export class AlertComponent implements AfterViewInit { protected lookups: GlobalLookups, protected http: HttpClient, protected alertService: AlertMessageService, - public dialog: MatDialog) { + public dialog: MatDialog, + private changeDetector: ChangeDetectorRef) { } - ngAfterViewInit() { + ngOnInit() { this.alertController = new AlertController(this.http, this.lookups, this.dialog); + } + ngAfterViewChecked() { + this.changeDetector.detectChanges(); + } + + initColumns() { this.columnPicker.allColumns = [ { name: 'Alert date', title: "Alert date", prop: 'reportingTime', showInitially: true, - maxWidth:100, + maxWidth: 100, cellTemplate: this.dateTimeColumn, }, { @@ -62,14 +77,14 @@ export class AlertComponent implements AfterViewInit { title: "For User", prop: 'username', cellTemplate: this.forUser, - maxWidth:200, + maxWidth: 200, showInitially: true, }, { name: 'Credential type', title: "Credential type.", prop: 'alertDetails', - maxWidth:200, + maxWidth: 200, cellTemplate: this.credentialType, showInitially: true, }, @@ -85,7 +100,7 @@ export class AlertComponent implements AfterViewInit { title: "Alert status.", prop: 'alertStatus', showInitially: true, - maxWidth:100, + maxWidth: 100, }, { name: 'Status desc.', @@ -100,23 +115,27 @@ export class AlertComponent implements AfterViewInit { title: "Alert level.", prop: 'alertLevel', showInitially: true, - maxWidth:80, + maxWidth: 80, }, ]; - this.searchTable.tableColumnInit(); this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); + this.searchTable.tableColumnInit(); + } + + ngAfterViewInit() { + this.initColumns(); } details(row: any) { - this.dialog.open(ObjectPropertiesDialogComponent, { - data: { - title: "Alert details!", - object: row.alertDetails, + this.dialog.open(ObjectPropertiesDialogComponent, { + data: { + title: "Alert details", + object: row.alertDetails, - } - }); + } + }); } // for dirty guard... diff --git a/smp-angular/src/app/app.component.html b/smp-angular/src/app/app.component.html index 78e7522dfdbdf7a3f368d03120ace6a50fb8a61d..6430c83989f01e0b148da751f896f65b9576e652 100644 --- a/smp-angular/src/app/app.component.html +++ b/smp-angular/src/app/app.component.html @@ -92,10 +92,10 @@ <mat-icon>person</mat-icon> <span>{{currentUser}}</span> </button> - <button mat-menu-item id="changePassword_id" (click)="changeCurrentUserPassword()"> + <button *ngIf="isUserAuthPasswdEnabled" mat-menu-item id="changePassword_id" (click)="changeCurrentUserPassword()"> <span>Change password</span> </button> - <button mat-menu-item id="getAccessToken_id" (click)="regenerateCurrentUserAccessToken()"> + <button *ngIf="isWebServiceUserTokenAuthPasswdEnabled" mat-menu-item id="getAccessToken_id" (click)="regenerateCurrentUserAccessToken()"> <span>Generated access token</span> </button> diff --git a/smp-angular/src/app/app.component.ts b/smp-angular/src/app/app.component.ts index c93ff665f835c876d554d4706cb96915fc59972b..f212b3e8e3cd970669b22a00cc9f73e07cca4692 100644 --- a/smp-angular/src/app/app.component.ts +++ b/smp-angular/src/app/app.component.ts @@ -34,6 +34,14 @@ export class AppComponent { this.userController = new UserController(this.http, this.lookups, this.dialog); } + get isWebServiceUserTokenAuthPasswdEnabled(): boolean { + return this.lookups.cachedApplicationConfig?.webServiceAuthTypes?.includes('TOKEN'); + } + + get isUserAuthPasswdEnabled(): boolean { + return this.lookups.cachedApplicationInfo?.authTypes.includes('PASSWORD'); + } + isCurrentUserSystemAdmin(): boolean { return this.securityService.isCurrentUserInRole([Authority.SYSTEM_ADMIN]); } diff --git a/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.html b/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.html index 53b5fa91b71fd11e427ba5af1268dc9f5dd4d32d..a6c28f91b7dc05b63507897c7890bf4c032655df 100644 --- a/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.html +++ b/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.html @@ -20,6 +20,14 @@ </mat-card> <mat-card> <mat-card-content> + <mat-form-field *ngIf="!securityService.getCurrentUser()?.casAuthenticated" style="width:100%"> + <input matInput [placeholder]="getPasswordTitle" [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-card-actions> <button mat-raised-button color="primary" (click)="regenerateAccessToken()" [disabled]="!dialogForm.valid"> @@ -30,13 +38,6 @@ Token will be generated immediately. </mat-label> </mat-card-actions> - <mat-form-field style="width:100%"> - <input matInput [placeholder]="getPasswordTitle" [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"> diff --git a/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.ts b/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.ts index 8ddc1334d90d9d711dc7282d1ec7d05091b6a205..cf5540f536a8d69e5373e8b0d4d5f9e6778bd443 100644 --- a/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/access-token-generation-dialog/access-token-generation-dialog.component.ts @@ -17,7 +17,7 @@ import {SearchTableEntityStatus} from "../../search-table/search-table-entity-st export class AccessTokenGenerationDialogComponent { dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; - formTitle = "Access token generation dialog!"; + formTitle = "Access token generation dialog"; dialogForm: FormGroup; hideCurrPwdFiled: boolean = true; hideNewPwdFiled: boolean = true; @@ -34,7 +34,7 @@ export class AccessTokenGenerationDialogComponent { @Inject(MAT_DIALOG_DATA) public data: any, private lookups: GlobalLookups, private userDetailsService: UserDetailsService, - private securityService: SecurityService, + public securityService: SecurityService, private fb: FormBuilder ) { dialogRef.disableClose = true;//disable default close operation @@ -48,7 +48,7 @@ export class AccessTokenGenerationDialogComponent { '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]), + 'current-password': new FormControl({value: null, readonly: false}, this.securityService.getCurrentUser().casAuthenticated?null:[Validators.required]), }); this.dialogForm.controls['email'].setValue(this.isEmptyEmailAddress ? "Empty email address!" : this.current.emailAddress); diff --git a/smp-angular/src/app/common/dialogs/information-dialog/information-dialog.component.html b/smp-angular/src/app/common/dialogs/information-dialog/information-dialog.component.html index e17c4b7460bf8b958fa0dc38b3fa427c9ca26407..a4ffd839db1c89660cb00d91dd781bda4a2ffa61 100644 --- a/smp-angular/src/app/common/dialogs/information-dialog/information-dialog.component.html +++ b/smp-angular/src/app/common/dialogs/information-dialog/information-dialog.component.html @@ -8,7 +8,7 @@ <div class="divTableCell"> - <button mat-raised-button color="primary" (click)="dialogRef.close(false)" id="nobuttondialog_id"> + <button mat-raised-button color="primary" (click)="dialogRef.close(false)" id="closebuttondialog_id"> <mat-icon>close</mat-icon> <span>Close</span> </button> diff --git a/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.html b/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.html index a238df605fe69fae26172692d34f29623d83e29f..23f2027565200a6d922dda2e28fbcc4f6f584b41 100644 --- a/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.html +++ b/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.html @@ -22,7 +22,7 @@ </mat-card> <mat-card class="password-panel"> <mat-card-content> - <mat-form-field style="width:100%"> + <mat-form-field *ngIf="showCurrentPasswordField" style="width:100%"> <input matInput [placeholder]="getPasswordTitle" [type]="hideCurrPwdFiled ? 'password' : 'text'" formControlName="current-password" required id="cp_id"> <mat-icon matSuffix @@ -35,7 +35,7 @@ <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 + <mat-error *ngIf="passwordError('new-password', 'error')">New password must not be equal than old current password! </mat-error> <mat-error *ngIf="passwordError('new-password', 'pattern')">{{passwordValidationMessage}}</mat-error> @@ -46,7 +46,7 @@ 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 *ngIf="passwordError('confirm-new-password', 'error')">Confirm valued does not match new password! </mat-error> <mat-error *ngIf="passwordError('confirm-new-password', 'required')">Confirm New password is required </mat-error> @@ -61,7 +61,7 @@ <button mat-raised-button color="primary" (click)="changeCurrentUserPassword()" [disabled]="!dialogForm.valid "> <mat-icon>check_circle</mat-icon> - <span>Change password</span> + <span>Set/change password</span> </button> <button *ngIf="!this.forceChange" mat-raised-button color="primary" mat-dialog-close> <mat-icon>cancel</mat-icon> diff --git a/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts b/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts index c28cd854a92d4d4c94501c20010678a0c997c14a..45614761129c1de2c1342d8fa3ba706637615580 100644 --- a/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts @@ -16,7 +16,7 @@ import {UserRo} from "../../../user/user-ro.model"; }) export class PasswordChangeDialogComponent { - formTitle = "Change password dialog"; + formTitle = "Set/Change password dialog"; dialogForm: FormGroup; hideCurrPwdFiled: boolean = true; hideNewPwdFiled: boolean = true; @@ -45,7 +45,8 @@ export class PasswordChangeDialogComponent { this.forceChange = this.current.forceChangeExpiredPassword; - let currentPasswdFormControl: FormControl = new FormControl({value: null, readonly: false}, [Validators.required]); + let currentPasswdFormControl: FormControl = new FormControl({value: null, readonly: false}, + this.securityService.getCurrentUser().casAuthenticated && this.adminUser ? null : [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}, @@ -66,6 +67,10 @@ export class PasswordChangeDialogComponent { this.dialogForm.controls['confirm-new-password'].setValue(''); } + get showCurrentPasswordField():boolean { + return !this.securityService.getCurrentUser()?.casAuthenticated || !this.adminUser + } + public passwordError = (controlName: string, errorName: string) => { return this.dialogForm.controls[controlName].hasError(errorName); } @@ -109,7 +114,6 @@ export class PasswordChangeDialogComponent { this.dialogForm.controls['new-password'].value, this.dialogForm.controls['current-password'].value).subscribe((res: boolean) => { this.showPassChangeDialog(); - close() }, (err) => { this.showErrorMessage(err.error.errorDescription); @@ -121,16 +125,15 @@ export class PasswordChangeDialogComponent { showPassChangeDialog() { this.dialog.open(InformationDialogComponent, { data: { - title: "Password changed!", - description: "Password has been successfully changed. " + - (!this.adminUser ? "Login again to the application with the new password!" : "") + title: "Password set/changed", + description: "Password has been successfully set/changed." + + (!this.adminUser ? " Login again to the application with the new password!" : "") } }).afterClosed().subscribe(result => { if (!this.adminUser) { // logout if changed for itself this.securityService.finalizeLogout(result); } - close(); }) } 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 f625d6304757b5651720699fee90cbf92493e0a9..17e356bdd0d451d46a48ef5f749a1854369947e7 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 @@ -86,7 +86,8 @@ export class SearchTableComponent implements OnInit { name: 'Index', width: 30, maxWidth: 80, - sortable: false + sortable: false, + showInitially: false }; this.columnActions = { @@ -102,7 +103,8 @@ export class SearchTableComponent implements OnInit { name: 'Upd.', width: 40, maxWidth: 50, - sortable: false + sortable: false, + showInitially: false }; } @@ -113,22 +115,25 @@ export class SearchTableComponent implements OnInit { // prepend columns if (!!this.tableRowDetailContainer) { console.log("show table row details!") + this.columnExpandDetails.showInitially = true this.columnPicker.allColumns.unshift(this.columnExpandDetails); - this.columnPicker.selectedColumns.unshift(this.columnExpandDetails); } if (this.showIndexColumn) { console.log("show table index!") + this.columnIndex.showInitially = true this.columnPicker.allColumns.unshift(this.columnIndex); - this.columnPicker.selectedColumns.unshift(this.columnIndex); } if (this.showActionButtons) { console.log("show action buttons!") this.columnActions.showInitially = true this.columnPicker.allColumns.push(this.columnActions); - this.columnPicker.selectedColumns.push(this.columnActions); } + this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); + } else { + console.log("Column picker is not registered for the table!") } + } getRowClass(row) { @@ -393,7 +398,7 @@ export class SearchTableComponent implements OnInit { private editSearchTableEntity(rowNumber: number) { const row = this.rows[rowNumber]; const formRef: MatDialogRef<any> = this.searchTableController.newDialog({ - data: {edit: true, row} + data: {edit: row?.status!=SearchTableEntityStatus.NEW, row} }); formRef.afterClosed().subscribe(result => { if (result) { diff --git a/smp-angular/src/app/domain/domain-details-dialog/domain-details-dialog.component.ts b/smp-angular/src/app/domain/domain-details-dialog/domain-details-dialog.component.ts index 5db36fe232ad899f9273007badd61f027a96f93f..e83361472923bd8e511d6d78a6fd59b074afc311 100644 --- a/smp-angular/src/app/domain/domain-details-dialog/domain-details-dialog.component.ts +++ b/smp-angular/src/app/domain/domain-details-dialog/domain-details-dialog.component.ts @@ -50,7 +50,7 @@ export class DomainDetailsDialogComponent { this.editMode = data.edit; this.formTitle = this.editMode ? DomainDetailsDialogComponent.EDIT_MODE : DomainDetailsDialogComponent.NEW_MODE; - this.current = this.editMode + this.current = !!data.row ? { ...data.row, } diff --git a/smp-angular/src/app/domain/domain.component.ts b/smp-angular/src/app/domain/domain.component.ts index 909990944986c06a8800d6761b5e4d28abf35828..152b8475c95aea43b6e9f2f65a9d30584157bc4a 100644 --- a/smp-angular/src/app/domain/domain.component.ts +++ b/smp-angular/src/app/domain/domain.component.ts @@ -1,4 +1,12 @@ -import {AfterViewInit, Component, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog, MatDialogRef} from '@angular/material/dialog'; @@ -22,7 +30,7 @@ import {SMLResult} from "./sml-result.model"; templateUrl: './domain.component.html', styleUrls: ['./domain.component.css'] }) -export class DomainComponent implements AfterViewInit { +export class DomainComponent implements OnInit, AfterViewInit, AfterViewChecked { @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; @ViewChild('certificateAliasTemplate') certificateAliasColumn: TemplateRef<any>; @@ -42,16 +50,19 @@ export class DomainComponent implements AfterViewInit { protected lookups: GlobalLookups, protected http: HttpClient, protected alertService: AlertMessageService, - public dialog: MatDialog) { + public dialog: MatDialog, + private changeDetector: ChangeDetectorRef) { // check application settings } - ngAfterViewInit() { + ngOnInit() { this.domainController = new DomainController(this.http, this.lookups, this.dialog); + } + initColumns() { this.columnPicker.allColumns = [ { name: 'Domain code', @@ -76,7 +87,6 @@ export class DomainComponent implements AfterViewInit { cellTemplate: this.certificateAliasColumn, width: 150 }, - { name: 'SML SMP Id', title: "SMP identifier for SML integration", @@ -105,13 +115,18 @@ export class DomainComponent implements AfterViewInit { }, ]; this.searchTable.tableColumnInit(); - this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); + } + ngAfterViewChecked() { + this.changeDetector.detectChanges(); + } + + ngAfterViewInit() { + this.initColumns(); // if system admin refresh certificate list! if (this.securityService.isCurrentUserSystemAdmin()) { this.lookups.refreshCertificateLookup(); } - } certificateAliasExists(alias: string): boolean { @@ -168,7 +183,8 @@ export class DomainComponent implements AfterViewInit { isDirty(): boolean { return this.searchTable.isDirty(); } - get isSMPIntegrationOn(){ + + get isSMPIntegrationOn() { return this.lookups.cachedApplicationConfig?.smlIntegrationOn } @@ -226,7 +242,7 @@ export class DomainComponent implements AfterViewInit { this.dialog.open(ConfirmationDialogComponent, { data: { - title: "Unregister domain to SML!", + title: "Unregister domain to SML", description: "Action will unregister domain: " + domainRo.domainCode + " and all its service groups from SML. Do you wish to continue?" } }).afterClosed().subscribe(result => { @@ -245,7 +261,7 @@ export class DomainComponent implements AfterViewInit { this.dialog.open(ConfirmationDialogComponent, { data: { - title: "Register domain to SML!", + title: "Register domain to SML", description: "Action will register domain: " + domainRo.domainCode + " and all its service groups to SML. Do you wish to continue?" } }).afterClosed().subscribe(result => { diff --git a/smp-angular/src/app/domain/keystore-edit-dialog/keystore-edit-dialog.component.ts b/smp-angular/src/app/domain/keystore-edit-dialog/keystore-edit-dialog.component.ts index ef16ddb0c6a29a478ae03880ce7852d06141f614..8950dd1ea60752b4259df11c4a425b677e855747 100644 --- a/smp-angular/src/app/domain/keystore-edit-dialog/keystore-edit-dialog.component.ts +++ b/smp-angular/src/app/domain/keystore-edit-dialog/keystore-edit-dialog.component.ts @@ -1,4 +1,4 @@ -import {AfterViewChecked, Component, Inject} from '@angular/core'; +import {AfterViewChecked, AfterViewInit, ChangeDetectorRef, Component, Inject} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; import {FormBuilder} from "@angular/forms"; import {AlertMessageService} from "../../common/alert-message/alert-message.service"; @@ -31,7 +31,8 @@ export class KeystoreEditDialogComponent implements AfterViewChecked{ private dialogRef: MatDialogRef<KeystoreEditDialogComponent>, private alertService: AlertMessageService, @Inject(MAT_DIALOG_DATA) public data: any, - private fb: FormBuilder) { + private fb: FormBuilder, + private changeDetector: ChangeDetectorRef) { this.formTitle = "Keystore edit dialog"; } @@ -39,6 +40,8 @@ export class KeystoreEditDialogComponent implements AfterViewChecked{ // fix bug updating the columns //https://github.com/swimlane/ngx-datatable/issues/1266 window.dispatchEvent(new Event('resize')); + this.changeDetector.detectChanges(); + } onDeleteCertificateRowActionClicked(row) { @@ -49,7 +52,7 @@ export class KeystoreEditDialogComponent implements AfterViewChecked{ if (listSignatureKeys.length > 0) { this.dialog.open(InformationDialogComponent, { data: { - title: "Delete key/certificate " + row.alias + " from keystore!", + title: "Delete key/certificate " + row.alias + " from keystore", description: "Key/certificate is in use by domains: " + listSignatureKeys + ". First replace/remove certificate from domains!" } }).afterClosed().subscribe(result => { @@ -60,7 +63,7 @@ export class KeystoreEditDialogComponent implements AfterViewChecked{ } else { this.dialog.open(ConfirmationDialogComponent, { data: { - title: "Delete key/certificate " + row.alias + " from keystore!", + title: "Delete key/certificate " + row.alias + " from keystore", description: "Action will permanently delete key/certificate from keystore! Do you wish to continue?" } }).afterClosed().subscribe(result => { diff --git a/smp-angular/src/app/domain/keystore-import-dialog/keystore-import-dialog.component.ts b/smp-angular/src/app/domain/keystore-import-dialog/keystore-import-dialog.component.ts index 349319dc4f6c2c44302b0f98f87da9a1ba76b9ce..56fb8b8d3b3757c0128c0dabb5630db5082295a1 100644 --- a/smp-angular/src/app/domain/keystore-import-dialog/keystore-import-dialog.component.ts +++ b/smp-angular/src/app/domain/keystore-import-dialog/keystore-import-dialog.component.ts @@ -64,70 +64,8 @@ export class KeystoreImportDialogComponent { } }, err => { - this.alertService.exception('Error uploading keystore file ' + this.selectedFile.name, err); + this.alertService.exception('Error uploading keystore file ' + this.selectedFile.name, err.error?.errorDescription); } ) - - } - /* - - importKeystore() { - const headers = new HttpHeaders() - .set("Content-Type", "application/octet-stream"); - - const params: HttpParams = new HttpParams(); - params.set('keystoreType', this.dialogForm.controls['keystoreType'].value); - params.set('password', this.dialogForm.controls['password'].value); - let keystoreType = this.dialogForm.controls['keystoreType'].value; - let password = encodeURIComponent(this.dialogForm.controls['password'].value); - - - const currentUser: User = this.securityService.getCurrentUser(); - this.http.post<CertificateRo>(`${SmpConstants.REST_KEYSTORE}/${currentUser.id}/upload/${keystoreType}/${password}`, this.selectedFile, {headers,params }).subscribe((res: CertificateRo) => { - if (res ) { - this.alertService.success("Keystore "+this.selectedFile.name+ " imported!"); - this.lookups.refreshCertificateLookup(); - } else { - this.alertService.exception("Error occurred while reading certificate.", "Check if uploaded file has valid certificate type.", false); - } - }, - err => { - this.alertService.exception('Error uploading certificate file ' + this.selectedFile.name, err); - } - ) - - } - - - importKeystoreMultipart() { - const headers = new HttpHeaders() - .set("Content-Type", "multipart/form-data"); - - const params: HttpParams = new HttpParams(); - params.set('keystoreType', this.dialogForm.controls['keystoreType'].value); - params.set('password', this.dialogForm.controls['password'].value); - let keystoreType = this.dialogForm.controls['keystoreType'].value; - let password = encodeURI(this.dialogForm.controls['password'].value); - - let input = new FormData(); -// Add your values in here - input.append('keystoreType',keystoreType); - input.append('password', password); - input.append('file', this.selectedFile); - - const currentUser: User = this.securityService.getCurrentUser(); - this.http.post<CertificateRo>(`${SmpConstants.REST_KEYSTORE}/${currentUser.id}/upload/`, input).subscribe((res: CertificateRo) => { - if (res ) { - this.alertService.success("Keystore "+this.selectedFile.name+ " imported!"); - } else { - this.alertService.exception("Error occurred while reading certificate.", "Check if uploaded file has valid certificate type.", false); - } - }, - err => { - this.alertService.exception('Error uploading certificate file ' + this.selectedFile.name, err); - } - ) - } -*/ } diff --git a/smp-angular/src/app/login/login.component.ts b/smp-angular/src/app/login/login.component.ts index 929b22a41b5d164d7efcd6cd8a5619db5d76595c..265e5729bea37114475381b8ff8cc44250e9cb45 100644 --- a/smp-angular/src/app/login/login.component.ts +++ b/smp-angular/src/app/login/login.component.ts @@ -105,7 +105,7 @@ export class LoginComponent implements OnInit, OnDestroy { showWarningBeforeExpire(user: User) { this.dialog.open(InformationDialogComponent, { data: { - title: "Warning! Your password is about to expire!", + title: "Warning! Your password is about to expire", description: "Your password is about to expire on " + formatDate(user.passwordExpireOn,"longDate","en-US")+"! Please change the password before the expiration date!" } }).afterClosed().subscribe(() => this.router.navigate([this.returnUrl])); diff --git a/smp-angular/src/app/property/property.component.ts b/smp-angular/src/app/property/property.component.ts index 31e3385da21d8f77ee0475596bc000c02e12077d..89e13c47042745ba0fce6c7949f04635c35124ff 100644 --- a/smp-angular/src/app/property/property.component.ts +++ b/smp-angular/src/app/property/property.component.ts @@ -1,7 +1,14 @@ -import {AfterViewInit, Component, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog} from '@angular/material/dialog'; -import {Injectable} from '@angular/core'; import {AlertMessageService} from '../common/alert-message/alert-message.service'; import {PropertyController} from './property-controller'; import {HttpClient} from '@angular/common/http'; @@ -17,7 +24,7 @@ import {SearchTableEntityStatus} from "../common/search-table/search-table-entit templateUrl: './property.component.html', styleUrls: ['./property.component.css'] }) -export class PropertyComponent implements AfterViewInit { +export class PropertyComponent implements OnInit, AfterViewInit, AfterViewChecked { @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; @ViewChild('searchTable') searchTable: SearchTableComponent; @@ -33,7 +40,8 @@ export class PropertyComponent implements AfterViewInit { protected lookups: GlobalLookups, protected http: HttpClient, protected alertService: AlertMessageService, - public dialog: MatDialog) { + public dialog: MatDialog, + private changeDetector: ChangeDetectorRef) { } @@ -41,9 +49,15 @@ export class PropertyComponent implements AfterViewInit { return JSON.stringify(val); } - ngAfterViewInit() { + ngOnInit() { this.propertyController = new PropertyController(this.http, this.lookups, this.dialog); + } + + ngAfterViewChecked() { + this.changeDetector.detectChanges(); + } + initColumns() { this.columnPicker.allColumns = [ { name: 'Property', @@ -62,11 +76,11 @@ export class PropertyComponent implements AfterViewInit { }, ]; - - this.searchTable.tableColumnInit(); this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); + } - + ngAfterViewInit() { + this.initColumns(); } searchPropertyChanged() { diff --git a/smp-angular/src/app/security/user.model.ts b/smp-angular/src/app/security/user.model.ts index 4a19dc78d21ea108eeeb0b301f5e9336b1b09416..3152e40cb3e9043677c9427266ca3bd8d592c75f 100644 --- a/smp-angular/src/app/security/user.model.ts +++ b/smp-angular/src/app/security/user.model.ts @@ -6,10 +6,18 @@ export interface User { username: string; accessTokenId?: string; accessTokenExpireOn?: Date; + sequentialTokenLoginFailureCount?:number; + lastTokenFailedLoginAttempt?:Date; + tokenSuspendedUtil?:Date; authorities: Array<Authority>; + casAuthenticated?: boolean; defaultPasswordUsed: boolean; forceChangeExpiredPassword?: boolean; showPasswordExpirationWarning?: boolean; passwordExpireOn?: Date; + sequentialLoginFailureCount?:number; + lastFailedLoginAttempt?:Date; + suspendedUtil?:Date; + casUserDataUrl?: string; } 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 a5c83f5841579ef58e9b37c63a66bbd3d7e3a7e9..c28eb7fe66b1e028779cd8276fd0268d90193136 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 @@ -23,12 +23,7 @@ </mat-form-field> <mat-form-field style="width:100%"> - <input *ngIf="lookups.cachedApplicationConfig.partyIDSchemeMandatory else notRequired" - matInput placeholder="Participant scheme" name="participantScheme" - id="participantSchemeMandatory_id" - [formControl]="dialogForm.controls['participantScheme']" - maxlength="255" required> - <input #notRequired + <input matInput placeholder="Participant scheme" name="participantScheme" id="participantScheme_id" [formControl]="dialogForm.controls['participantScheme']" @@ -67,7 +62,6 @@ </mat-panel-description> </mat-expansion-panel-header> <mat-selection-list #usersSelected - [disabled]="!securityService.isCurrentUserSMPAdmin()" [compareWith]="compareUserByUserId" [formControl]="dialogForm.controls['users']" style="min-height: 100px; height: 150px; overflow-y: scroll; overflow-x: auto;"> @@ -103,7 +97,6 @@ </mat-panel-description> </mat-expansion-panel-header> <mat-selection-list #domainSelector - [disabled]="!securityService.isCurrentUserSMPAdmin()" [compareWith]="compareDomain" [formControl]="dialogForm.controls['serviceGroupDomains']" (selectionChange)="onDomainSelectionChanged($event)" 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 6c865718b265b76e136285713317b14e47868afd..84580daf0630a2688662bd6527b1fa41854b6941 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 @@ -75,7 +75,7 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { this.editMode = this.data.edit; this.formTitle = this.editMode ? ServiceGroupDetailsDialogComponent.EDIT_MODE : ServiceGroupDetailsDialogComponent.NEW_MODE; - this.current = this.editMode + this.current = !!this.data.row ? { ...this.data.row, // copy serviceGroupDomain array @@ -111,15 +111,22 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { 'participantScheme': new FormControl({value: '', disabled: this.current.status !== SearchTableEntityStatus.NEW}, this.current.status === SearchTableEntityStatus.NEW ? [Validators.pattern(this.participantSchemePattern)] : null), - 'serviceGroupDomains': new FormControl({value: []}, [this.minSelectedListCount(1), - this.multiDomainOn(this.lookups.cachedApplicationConfig.smlParticipantMultiDomainOn)]), - 'users': new FormControl({value: []}, [this.minSelectedListCount(1)]), + 'serviceGroupDomains': new FormControl({ + value: [], + disabled: !securityService.isCurrentUserSMPAdmin() + }, + [this.minSelectedListCount(1), + this.multiDomainOn(this.lookups.cachedApplicationConfig.smlParticipantMultiDomainOn)]), + 'users': new FormControl({ + value: [], + disabled: !securityService.isCurrentUserSMPAdmin() + }, [this.minSelectedListCount(1)]), 'extension': new FormControl({value: ''}, []), }); - if (!!lookups.cachedApplicationConfig.partyIDSchemeMandatory && this.current.status == SearchTableEntityStatus.NEW){ - this.dialogForm.controls['participantScheme'].addValidators(Validators.required) ; + if (!!lookups.cachedApplicationConfig.partyIDSchemeMandatory && this.current.status == SearchTableEntityStatus.NEW) { + this.dialogForm.controls['participantScheme'].addValidators(Validators.required); } // update values @@ -134,7 +141,7 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { // retrieve xml extension for this service group if (this.current.status !== SearchTableEntityStatus.NEW && !this.current.extension) { // init domains - this.extensionObserver = this.http.get<ServiceGroupValidationRo>(SmpConstants.REST_PUBLIC_SERVICE_GROUP_ENTITY_EXTENSION.replace('{service-group-id}',this.current.id+"")); + this.extensionObserver = this.http.get<ServiceGroupValidationRo>(SmpConstants.REST_PUBLIC_SERVICE_GROUP_ENTITY_EXTENSION.replace('{service-group-id}', this.current.id + "")); this.extensionObserver.subscribe((res: ServiceGroupValidationRo) => { this.dialogForm.get('extension').setValue(res.extension); this.current.extension = res.extension; @@ -182,9 +189,7 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { } }).catch((err) => { console.log("Error occurred on Validation Extension: " + err); - });; - - + }); } checkValidity(g: FormGroup) { @@ -198,8 +203,6 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { Object.keys(g.controls).forEach(key => { g.get(key).updateValueAndValidity(); }); - - } @@ -218,13 +221,10 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { this.current.participantIdentifier = this.dialogForm.value['participantIdentifier']; this.current.participantScheme = this.dialogForm.value['participantScheme']; } else { - this.current.extensionStatus = - SearchTableEntityStatus.UPDATED; + this.current.extensionStatus = SearchTableEntityStatus.UPDATED; } this.current.users = this.dialogForm.value['users']; this.current.extension = this.dialogForm.value['extension']; - - let domainOptions = this.domainSelector.options._results; domainOptions.forEach(opt => { let domValue = opt.value; @@ -265,8 +265,8 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { return this.current.users !== this.dialogForm.value['users']; } - extensionChanged():boolean { - return !this.isEqual(this.current.extension, this.dialogForm.value['extension'].toString()); + extensionChanged(): boolean { + return !this.isEqual(this.current.extension, this.dialogForm.value['extension'].toString()); } onExtensionDelete() { @@ -313,20 +313,16 @@ export class ServiceGroupDetailsDialogComponent implements OnInit { } - onPrettyPrintExtension() { - - } - onDomainSelectionChanged(event) { // if deselected warn serviceMetadata will be deleted let domainCode = event.option.value.domainCode; if (!event.option.selected) { let smdCount = this.getServiceMetadataCountOnDomain(domainCode); - if (smdCount >0) { + if (smdCount > 0) { this.dialog.open(ConfirmationDialogComponent, { data: { - title: "Registered serviceMetadata on domain!", - description: "Unregistering service group from domain will also delete its serviceMetadata (count: "+smdCount+") from the domain! Do you want to continue?" + title: "Registered serviceMetadata on domain", + description: "Unregistering service group from domain will also delete its serviceMetadata (count: " + smdCount + ") from the domain! Do you want to continue?" } }).afterClosed().subscribe(result => { if (!result) { diff --git a/smp-angular/src/app/service-group-edit/service-group-edit-controller.ts b/smp-angular/src/app/service-group-edit/service-group-edit-controller.ts index b04b013d96f5b531f94b5753b23c9431dd0dadaf..35c61f9600725dbb81f3f0ec59fd997a48e0631e 100644 --- a/smp-angular/src/app/service-group-edit/service-group-edit-controller.ts +++ b/smp-angular/src/app/service-group-edit/service-group-edit-controller.ts @@ -11,7 +11,8 @@ import {SearchTableEntity} from "../common/search-table/search-table-entity.mode export class ServiceGroupEditController implements SearchTableController { - compareSGProperties = ["extension", "users", "serviceGroupDomains"]; + compareUpdateSGProperties = ["extension", "users", "serviceGroupDomains"]; + compareNewSGProperties = ["participantScheme", "participantIdentifier", "", "extension", "users", "serviceGroupDomains"]; constructor(public dialog: MatDialog) { } @@ -92,19 +93,24 @@ export class ServiceGroupEditController implements SearchTableController { } isRecordChanged(oldModel, newModel): boolean { + // different set of properties to compare in case if new entry is reedited or already saved entry is reedited. + let propsToCompare = newModel.status === SearchTableEntityStatus.NEW ? + this.compareNewSGProperties : this.compareUpdateSGProperties; + // check if other properties were changed - let propSize = this.compareSGProperties.length; + let propSize = propsToCompare.length; for (let i = 0; i < propSize; i++) { - let property = this.compareSGProperties[i]; + let property = propsToCompare[i]; let isEqual = false; - if (property ==='users' - || property ==='serviceGroupDomains') { - isEqual = this.isEqualListByAttribute(newModel[property], oldModel[property], "id"); - }else { - isEqual = this.isEqual(newModel[property], oldModel[property]); + if (property === 'users') { + isEqual = this.isEqualListByAttribute(newModel[property], oldModel[property], "userId"); + } else if (property === 'serviceGroupDomains') { + isEqual = this.isEqualListByAttribute(newModel[property], oldModel[property], "domainCode"); + } else { + isEqual = this.isEqual(JSON.stringify(newModel[property]), JSON.stringify(oldModel[property])); } - console.log("Property: "+property+" new: " +newModel[property] + "old: " +oldModel[property] + " val: " + isEqual ); + console.log("Property: " + property + " new: " + JSON.stringify(newModel[property]) + "old: " + JSON.stringify(oldModel[property]) + " val: " + isEqual); if (!isEqual) { return true; // Property has changed } @@ -113,16 +119,16 @@ export class ServiceGroupEditController implements SearchTableController { } isEqualListByAttribute(array1, array2, compareByAttribute): boolean { - let result1 = array1.filter(function(o1){ + let result1 = array1.filter(function (o1) { // filter out (!) items in result2 - return !array2.some(function(o2){ + return !array2.some(function (o2) { return o1[compareByAttribute] === o2[compareByAttribute]; // unique id }); }); - let result2 = array2.filter(function(o1){ + let result2 = array2.filter(function (o1) { // filter out (!) items in result2 - return !array1.some(function(o2){ + return !array1.some(function (o2) { return o1[compareByAttribute] === o2[compareByAttribute]; // unique id }); }); 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 86c2afaae013b9d1f9e808d776e79dfb562a1f43..44e51d3f607584fcdf9e21a6912241b00aafe827 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 @@ -1,4 +1,12 @@ -import {AfterViewInit, ChangeDetectorRef, Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {AlertMessageService} from '../common/alert-message/alert-message.service'; @@ -15,7 +23,7 @@ import {SecurityService} from "../security/security.service"; templateUrl: './service-group-edit.component.html', styleUrls: ['./service-group-edit.component.css'] }) -export class ServiceGroupEditComponent implements OnInit, AfterViewInit { +export class ServiceGroupEditComponent implements OnInit, AfterViewInit, AfterViewChecked { @ViewChild('rowMetadataAction', {static: true}) rowMetadataAction: TemplateRef<any> @ViewChild('rowActions', {static: true}) rowActions: TemplateRef<any>; @@ -45,7 +53,9 @@ export class ServiceGroupEditComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.serviceGroupEditController = new ServiceGroupEditController(this.dialog); + } + initColumns() { this.columnPicker.allColumns = [ { name: 'Metadata', @@ -86,11 +96,15 @@ export class ServiceGroupEditComponent implements OnInit, AfterViewInit { sortable: false }, ]; + this.searchTable.tableColumnInit(); + } + + ngAfterViewChecked() { + this.changeDetector.detectChanges(); } ngAfterViewInit(): void { - this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); - this.searchTable.tableColumnInit(); + this.initColumns(); } details(row: any) { @@ -130,7 +144,11 @@ export class ServiceGroupEditComponent implements OnInit, AfterViewInit { let metadataRowNumber = serviceGroupRow.serviceMetadata.indexOf(metaDataRow); const formRef: MatDialogRef<any> = this.serviceGroupEditController.newMetadataDialog({ - data: {edit: true, serviceGroup: serviceGroupRow, metadata: metaDataRow} + data: { + edit: metaDataRow.status !== SearchTableEntityStatus.NEW, + serviceGroup: serviceGroupRow, + metadata: metaDataRow + } }); formRef.afterClosed().subscribe(result => { if (result) { @@ -146,7 +164,6 @@ export class ServiceGroupEditComponent implements OnInit, AfterViewInit { ? SearchTableEntityStatus.UPDATED : metaDataRow; - metaDataRow.status = statusMetadata; metaDataRow = {...formRef.componentInstance.getCurrent()}; diff --git a/smp-angular/src/app/service-group-edit/service-group-metadata-dialog/service-group-metadata-dialog.component.ts b/smp-angular/src/app/service-group-edit/service-group-metadata-dialog/service-group-metadata-dialog.component.ts index 6e38137e0b520818a0774f67e6f11c819fc09229..b35b1981b1ddf95df5fde90e73a992bd924fc7dc 100644 --- a/smp-angular/src/app/service-group-edit/service-group-metadata-dialog/service-group-metadata-dialog.component.ts +++ b/smp-angular/src/app/service-group-edit/service-group-metadata-dialog/service-group-metadata-dialog.component.ts @@ -46,9 +46,9 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { private fb: FormBuilder) { this.editMode = data.edit; - this.formTitle = this.editMode ? ServiceGroupMetadataDialogComponent.EDIT_MODE : ServiceGroupMetadataDialogComponent.NEW_MODE; + this.formTitle = !!data.metadata ? ServiceGroupMetadataDialogComponent.EDIT_MODE : ServiceGroupMetadataDialogComponent.NEW_MODE; this.currentServiceGroup = data.serviceGroup; - this.current = this.editMode + this.current = !!data.metadata ? { ...data.metadata, } @@ -91,7 +91,7 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { this.xmlServiceMetadataObserver.subscribe((res: ServiceMetadataEditRo) => { this.dialogForm.get('xmlContent').setValue(res.xmlContent); // store init xml to current value for change validation - this.current.xmlContent=res.xmlContent; + this.current.xmlContent = res.xmlContent; }); } @@ -117,11 +117,11 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { let request: ServiceMetadataValidationEditRo = { participantScheme: this.dialogForm.controls['participantScheme'].value, participantIdentifier: this.dialogForm.controls['participantIdentifier'].value, - documentIdentifierScheme: !this.dialogForm.controls['documentIdentifierScheme'].value?null: - this.dialogForm.controls['documentIdentifierScheme'].value, + documentIdentifierScheme: !this.dialogForm.controls['documentIdentifierScheme'].value ? null : + this.dialogForm.controls['documentIdentifierScheme'].value, documentIdentifier: this.dialogForm.controls['documentIdentifier'].value, xmlContent: this.dialogForm.controls['xmlContent'].value, - statusAction: this.editMode?SearchTableEntityStatus.UPDATED:SearchTableEntityStatus.NEW, + statusAction: this.editMode ? SearchTableEntityStatus.UPDATED : SearchTableEntityStatus.NEW, } // let validationObservable = this.http.post<ServiceMetadataValidationEditRo>(SmpConstants.REST_METADATA_VALIDATE, request); @@ -168,14 +168,13 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { } - - const formRef: MatDialogRef<any> = this.dialog.open(ServiceMetadataWizardDialogComponent,wizardInit); + const formRef: MatDialogRef<any> = this.dialog.open(ServiceMetadataWizardDialogComponent, wizardInit); formRef.afterClosed().subscribe(result => { if (result) { - let smw: ServiceMetadataWizardRo = formRef.componentInstance.getCurrent(); + let smw: ServiceMetadataWizardRo = formRef.componentInstance.getCurrent(); this.dialogForm.controls['xmlContent'].setValue(smw.contentXML); - if(!this.editMode){ + if (!this.editMode) { this.dialogForm.controls['documentIdentifierScheme'].setValue(smw.documentIdentifierScheme); this.dialogForm.controls['documentIdentifier'].setValue(smw.documentIdentifier); } @@ -185,21 +184,21 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { getParticipantElementXML(): string { let schema = this.dialogForm.controls['participantScheme'].value; - let value= this.dialogForm.controls['participantIdentifier'].value; + let value = this.dialogForm.controls['participantIdentifier'].value; if (!!schema && this.lookups.cachedApplicationConfig.concatEBCorePartyId && - schema.startsWith(ServiceMetadataWizardDialogComponent.EBCORE_IDENTIFIER_PREFIX) ) { - value = schema + ":" + value; - schema =null; + schema.startsWith(ServiceMetadataWizardDialogComponent.EBCORE_IDENTIFIER_PREFIX)) { + value = schema + ":" + value; + schema = null; } - return '<ParticipantIdentifier ' + - (!schema?'': 'scheme="' + this.xmlSpecialChars(schema) + '"')+ '>' - + this.xmlSpecialChars(value)+ '</ParticipantIdentifier>'; + return '<ParticipantIdentifier ' + + (!schema ? '' : 'scheme="' + this.xmlSpecialChars(schema) + '"') + '>' + + this.xmlSpecialChars(value) + '</ParticipantIdentifier>'; } getDocumentElementXML(): string { - return ' <DocumentIdentifier ' + - (!this.dialogForm.controls['documentIdentifierScheme'].value?'': 'scheme="' + return ' <DocumentIdentifier ' + + (!this.dialogForm.controls['documentIdentifierScheme'].value ? '' : 'scheme="' + this.xmlSpecialChars(this.dialogForm.controls['documentIdentifierScheme'].value) + '"') + '>' + this.xmlSpecialChars(this.dialogForm.controls['documentIdentifier'].value) + '</DocumentIdentifier>'; } @@ -228,7 +227,7 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { } xmlSpecialChars(unsafe) { - return !unsafe?'':unsafe + return !unsafe ? '' : unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") @@ -241,11 +240,11 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { participantScheme: this.dialogForm.controls['participantScheme'].value, participantIdentifier: this.dialogForm.controls['participantIdentifier'].value, - documentIdentifierScheme: !this.dialogForm.controls['documentIdentifierScheme'].value?null: + documentIdentifierScheme: !this.dialogForm.controls['documentIdentifierScheme'].value ? null : this.dialogForm.controls['documentIdentifierScheme'].value, documentIdentifier: this.dialogForm.controls['documentIdentifier'].value, xmlContent: this.dialogForm.controls['xmlContent'].value, - statusAction: this.editMode?SearchTableEntityStatus.UPDATED:SearchTableEntityStatus.NEW, + statusAction: this.editMode ? SearchTableEntityStatus.UPDATED : SearchTableEntityStatus.NEW, } // let validationObservable = this.http.post<ServiceMetadataValidationEditRo>(SmpConstants.REST_METADATA_VALIDATE, request); @@ -280,11 +279,12 @@ export class ServiceGroupMetadataDialogComponent implements OnInit { } //!! this two method must be called before getCurrent - public isMetaDataXMLChanged():boolean{ - return this.dialogForm.value['xmlContent'] !== this.current.xmlContent; + public isMetaDataXMLChanged(): boolean { + return this.dialogForm.value['xmlContent'] !== this.current.xmlContent; } - public isServiceMetaDataChanged():boolean{ - return this.isMetaDataXMLChanged() || !this.isEqual(this.current.domainCode, this.domainList.selected.value.domainCode) ; + + public isServiceMetaDataChanged(): boolean { + return this.isMetaDataXMLChanged() || !this.isEqual(this.current.domainCode, this.domainList.selected.value.domainCode); } compareDomainCode(sgDomain: ServiceGroupDomainEditRo, domainCode: String): boolean { diff --git a/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts b/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts index 0b469e13c81ede7ae404c70be9523435d0214f9b..bd1448a586485b480b7abd5108da6dc470144f22 100644 --- a/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts +++ b/smp-angular/src/app/service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component.ts @@ -87,7 +87,7 @@ export class ServiceMetadataWizardDialogComponent { } }, err => { - this.alertService.exception('Error uploading certificate file ' + file.name, err); + this.alertService.exception('Error uploading certificate file ' +file.name, err.error?.errorDescription); } ); } 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 add526d7748b9b7a2f820032b403a46e1a64c4ae..8b37f78ae72747f0276ee99760cd75d95a5a0fce 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 @@ -1,5 +1,13 @@ ///<reference path="../smp.constants.ts"/> -import {AfterViewInit, Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog} from '@angular/material/dialog'; import {AlertMessageService} from '../common/alert-message/alert-message.service'; @@ -8,17 +16,16 @@ import {HttpClient} from '@angular/common/http'; import {SmpConstants} from "../smp.constants"; import {GlobalLookups} from "../common/global-lookups"; import {SearchTableComponent} from "../common/search-table/search-table.component"; -import {ServiceGroupEditController} from "../service-group-edit/service-group-edit-controller"; @Component({ moduleId: module.id, templateUrl: './service-group-search.component.html', styleUrls: ['./service-group-search.component.css'] }) -export class ServiceGroupSearchComponent implements OnInit, AfterViewInit { +export class ServiceGroupSearchComponent implements OnInit, AfterViewInit, AfterViewChecked { - @ViewChild('rowSMPUrlLinkAction', { static: true }) rowSMPUrlLinkAction: TemplateRef<any> - @ViewChild('rowActions', { static: true }) rowActions: TemplateRef<any>; + @ViewChild('rowSMPUrlLinkAction', {static: true}) rowSMPUrlLinkAction: TemplateRef<any> + @ViewChild('rowActions', {static: true}) rowActions: TemplateRef<any>; @ViewChild('searchTable', {static: true}) searchTable: SearchTableComponent; columnPicker: ColumnPicker = new ColumnPicker(); @@ -31,7 +38,8 @@ export class ServiceGroupSearchComponent implements OnInit, AfterViewInit { protected http: HttpClient, protected alertService: AlertMessageService, - public dialog: MatDialog) { + public dialog: MatDialog, + private changeDetector: ChangeDetectorRef) { this.baseUrl = SmpConstants.REST_PUBLIC_SEARCH_SERVICE_GROUP; } @@ -42,7 +50,9 @@ export class ServiceGroupSearchComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.serviceGroupSearchController = new ServiceGroupSearchController(this.dialog); + } + initColumns(): void { this.columnPicker.allColumns = [ { name: 'Metadata size', @@ -75,19 +85,23 @@ export class ServiceGroupSearchComponent implements OnInit, AfterViewInit { sortable: false }, ]; + this.searchTable.tableColumnInit(); } - ngAfterViewInit(): void { - this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); - this.searchTable.tableColumnInit(); + ngAfterViewChecked() { + this.changeDetector.detectChanges(); + } + + ngAfterViewInit() { + this.initColumns(); } - createServiceGroupURL(row: any){ - return encodeURIComponent((!row.participantScheme? '' : row.participantScheme)+'::'+row.participantIdentifier); + createServiceGroupURL(row: any) { + return encodeURIComponent((!row.participantScheme ? '' : row.participantScheme) + '::' + row.participantIdentifier); } - createServiceMetadataURL(row: any, rowSMD: any){ - return encodeURIComponent((!row.participantScheme? '': row.participantScheme)+'::'+row.participantIdentifier)+'/services/'+ encodeURIComponent((!rowSMD.documentIdentifierScheme?'':rowSMD.documentIdentifierScheme)+'::'+rowSMD.documentIdentifier); + createServiceMetadataURL(row: any, rowSMD: any) { + return encodeURIComponent((!row.participantScheme ? '' : row.participantScheme) + '::' + row.participantIdentifier) + '/services/' + encodeURIComponent((!rowSMD.documentIdentifierScheme ? '' : rowSMD.documentIdentifierScheme) + '::' + rowSMD.documentIdentifier); } details(row: any) { diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts index 9ac2158463661e31ae8847bdd07fc6f5904c7ab6..9b838bb9e15d98a3257bc6f14b817746f9c41da6 100644 --- a/smp-angular/src/app/smp.constants.ts +++ b/smp-angular/src/app/smp.constants.ts @@ -1,7 +1,8 @@ export class SmpConstants { - public static readonly DATE_TIME_FORMAT = 'dd/MM/yyyy, HH:mm:ss z'; - public static readonly DATE_FORMAT = 'dd/MM/yyyy'; + public static readonly NULL_VALUE: string = "-----------" + public static readonly DATE_TIME_FORMAT = 'dd/MM/yyyy HH:mm:ss z'; + public static readonly DATE_FORMAT = 'dd/MM/yyyy'; public static readonly PATH_PARAM_ENC_USER_ID = '{user-id}'; public static readonly PATH_PARAM_ENC_MANAGED_USER_ID = '{managed-user-id}'; @@ -21,7 +22,7 @@ export class SmpConstants { 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'; // truststore public services - public static readonly REST_PUBLIC_TRUSTSTORE = SmpConstants.REST_PUBLIC +"truststore/"+ "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/"; + public static readonly REST_PUBLIC_TRUSTSTORE = SmpConstants.REST_PUBLIC + "truststore/" + "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/"; public static readonly REST_PUBLIC_TRUSTSTORE_CERT_VALIDATE = SmpConstants.REST_PUBLIC_TRUSTSTORE + 'validate-certificate'; // public authentication services @@ -30,7 +31,7 @@ export class SmpConstants { public static readonly REST_PUBLIC_SECURITY_USER = SmpConstants.REST_PUBLIC_SECURITY + 'user'; public static readonly REST_PUBLIC_SERVICE_GROUP = SmpConstants.REST_PUBLIC + 'service-group'; - public static readonly REST_PUBLIC_SERVICE_GROUP_ENTITY = SmpConstants.REST_PUBLIC_SERVICE_GROUP + '/' + SmpConstants.PATH_PARAM_SRV_GROUP_ID; + public static readonly REST_PUBLIC_SERVICE_GROUP_ENTITY = SmpConstants.REST_PUBLIC_SERVICE_GROUP + '/' + SmpConstants.PATH_PARAM_SRV_GROUP_ID; public static readonly REST_PUBLIC_SERVICE_GROUP_ENTITY_EXTENSION = SmpConstants.REST_PUBLIC_SERVICE_GROUP_ENTITY + '/extension'; // service group extension tools public static readonly REST_SERVICE_GROUP_EXTENSION = `${SmpConstants.REST_PUBLIC_SERVICE_GROUP}/extension`; @@ -49,10 +50,10 @@ export class SmpConstants { 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_GENERATE_ACCESS_TOKEN = SmpConstants.REST_INTERNAL_USER_MANAGE + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + 'generate-access-token-for'+ '/' + SmpConstants.PATH_PARAM_ENC_MANAGED_USER_ID; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + 'generate-access-token-for' + '/' + SmpConstants.PATH_PARAM_ENC_MANAGED_USER_ID; public static readonly REST_INTERNAL_USER_CHANGE_PASSWORD = SmpConstants.REST_INTERNAL_USER_MANAGE + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + 'change-password-for'+ '/' + SmpConstants.PATH_PARAM_ENC_MANAGED_USER_ID; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + 'change-password-for' + '/' + SmpConstants.PATH_PARAM_ENC_MANAGED_USER_ID; public static readonly REST_INTERNAL_USER_VALIDATE_DELETE = `${SmpConstants.REST_INTERNAL_USER_MANAGE}/validate-delete`; public static readonly REST_INTERNAL_KEYSTORE = SmpConstants.REST_INTERNAL + 'keystore'; diff --git a/smp-angular/src/app/user/truststore-edit-dialog/truststore-edit-dialog.component.ts b/smp-angular/src/app/user/truststore-edit-dialog/truststore-edit-dialog.component.ts index 2e9589182435703560007c8b317a87d8cc60c98c..4c585acd1cb56b5c3fbbe55710507f09484e1042 100644 --- a/smp-angular/src/app/user/truststore-edit-dialog/truststore-edit-dialog.component.ts +++ b/smp-angular/src/app/user/truststore-edit-dialog/truststore-edit-dialog.component.ts @@ -1,4 +1,12 @@ -import {AfterViewChecked, AfterViewInit, Component, Inject, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + Inject, + TemplateRef, + ViewChild +} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; import {FormBuilder} from "@angular/forms"; import {AlertMessageService} from "../../common/alert-message/alert-message.service"; @@ -35,7 +43,8 @@ export class TruststoreEditDialogComponent implements AfterViewInit, AfterViewCh private dialogRef: MatDialogRef<TruststoreEditDialogComponent>, private alertService: AlertMessageService, @Inject(MAT_DIALOG_DATA) public data: any, - private fb: FormBuilder) { + private fb: FormBuilder, + private changeDetector: ChangeDetectorRef) { this.formTitle = "Truststore edit dialog"; // bind to trusted certificate list events this.lookups.onTrustedCertificateListRefreshEvent().subscribe((data) => { @@ -48,9 +57,16 @@ export class TruststoreEditDialogComponent implements AfterViewInit, AfterViewCh // fix bug updating the columns //https://github.com/swimlane/ngx-datatable/issues/1266 window.dispatchEvent(new Event('resize')); + this.changeDetector.detectChanges(); + } ngAfterViewInit(): void { + this.initColumns(); + this.refreshData(); + } + + initColumns(): void { this.tableColumns = [ { cellTemplate: this.rowIndex, @@ -75,8 +91,6 @@ export class TruststoreEditDialogComponent implements AfterViewInit, AfterViewCh cellTemplate: this.certificateRowActions, }, ]; - - this.refreshData(); } @@ -87,7 +101,7 @@ export class TruststoreEditDialogComponent implements AfterViewInit, AfterViewCh onDeleteCertificateRowActionClicked(row) { this.dialog.open(ConfirmationDialogComponent, { data: { - title: "Delete certificate " + row.alias + " from truststore!", + title: "Delete certificate " + row.alias + " from truststore", description: "Action will permanently delete certificate from truststore! Do you wish to continue?" } }).afterClosed().subscribe(result => { @@ -128,7 +142,7 @@ export class TruststoreEditDialogComponent implements AfterViewInit, AfterViewCh } }, err => { - this.alertService.exception('Error uploading certificate file ' + file.name, err); + this.alertService.exception('Error uploading certificate file ' + file.name, err.error?.errorDescription); } ); } diff --git a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.css b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.css index 7a432e3268a3813d4b74ef9df98af98bbdaf33a6..f507a6ddad228bb6f43ce6e93cfb001f6c213de4 100644 --- a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.css +++ b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.css @@ -11,8 +11,22 @@ height: 100%; } -.user-toggle, .email, .role-field, .role, .username, .password, .password-confirmation, -.certificate-subject, .certificate-issuer, .certificate-serial-number, .certificate-id { +.user-toggle { + width: 220px; +} + +.role-field { + width: 220px; +} +.username { + width: calc(100% - 250px); +} + +.email { + width: calc(100% - 250px); +} + +.role, .certificate-subject, .certificate-issuer, .certificate-serial-number, .certificate-id { width: 100%; } @@ -26,6 +40,6 @@ } .has-error { - color:red; + color: red; font-size: 70%; } diff --git a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html index 422ca26024601cc6628f4bf019b2048971c2634d..a0231ff640909d3e3448f3aa21add29f1a85c98c 100644 --- a/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html +++ b/smp-angular/src/app/user/user-details-dialog/user-details-dialog.component.html @@ -4,58 +4,65 @@ <mat-card> <mat-card-content> - <mat-form-field class="username"> - <input matInput placeholder="Username" [formControl]="userForm.controls['username']" - id="username_id" maxlength="255" required> - <div *ngIf="userForm.controls['username'].hasError('required') && userForm.controls['username'].touched" - class="has-error">You should type an username - </div> - <div *ngIf="userForm.controls['username'].hasError('pattern') && userForm.controls['username'].touched" - class="has-error">Username can only contain alphanumeric characters (letters A-Z, numbers 0-9) and must - have from 4 to 32 characters! - </div> - <div - *ngIf="(!editMode && userForm.controls['username'].touched || editMode) && userForm.controls['username'].hasError('notInList')" - class="has-error"> - Username already exists! - </div> - </mat-form-field> + <div style="display: flex;flex-flow: row;flex-wrap: wrap; justify-content: space-between;"> - <mat-slide-toggle *ngIf="!isPreferencesMode()" class="user-toggle" - mat-no-ink class="mat-primary" [formControl]="userForm.controls['active']" id="active_id"> - - Active - </mat-slide-toggle> + <mat-form-field class="username"> + <input matInput placeholder="Username" [formControl]="userForm.controls['username']" + id="username_id" maxlength="255" required> + <div *ngIf="userForm.controls['username'].hasError('required') && userForm.controls['username'].touched" + class="has-error">You should type an username + </div> + <div *ngIf="userForm.controls['username'].hasError('pattern') && userForm.controls['username'].touched" + class="has-error">Username is case insensitive and can only contain alphanumeric characters (letters + a-zA-Z, numbers 0-9) and must + have from 4 to 32 characters! + </div> + <div + *ngIf="(!editMode && userForm.controls['username'].touched || editMode) && userForm.controls['username'].hasError('notInList')" + class="has-error"> + Username already exists! + </div> + </mat-form-field> - <mat-form-field class="role-field"> - <mat-select matInput placeholder="Role" class="role" [formControl]="userForm.controls['role']" - id="role_id" required> - <mat-option *ngFor="let item of existingRoles" [value]="item">{{item}}</mat-option> - </mat-select> - <div *ngIf="userForm.controls['role'].hasError('required') && userForm.controls['role'].touched" - class="has-error">You need to choose at least one role for this user + <div class="user-toggle"> + <mat-slide-toggle *ngIf="!isPreferencesMode()" + mat-no-ink class="mat-primary" [formControl]="userForm.controls['active']" id="active_id"> + Active + </mat-slide-toggle> </div> - </mat-form-field> - <mat-form-field class="emailAddress" class="email"> - <input matInput placeholder="Email address" name="emailAddress" - [formControl]="userForm.controls['emailAddress']" - id="emailAddress_id" maxlength="255"> - </mat-form-field> - <div *ngIf="userForm.controls['emailAddress'].hasError('pattern') && userForm.controls['emailAddress'].touched" - class="has-error">Email is invalid! + </div> + <div style="display: flex;flex-flow: row;flex-wrap: wrap; justify-content: space-between;"> + <mat-form-field class="emailAddress" class="email"> + <input matInput placeholder="Email address" name="emailAddress" + [formControl]="userForm.controls['emailAddress']" + id="emailAddress_id" maxlength="255"> + <div + *ngIf="userForm.controls['emailAddress'].hasError('pattern') && userForm.controls['emailAddress'].touched" + class="has-error">Email is invalid! + </div> + </mat-form-field> + <mat-form-field class="role-field"> + <mat-select matInput placeholder="Role" class="role" [formControl]="userForm.controls['role']" + id="role_id" required> + <mat-option *ngFor="let item of existingRoles" [value]="item">{{item}}</mat-option> + </mat-select> + <div *ngIf="userForm.controls['role'].hasError('required') && userForm.controls['role'].touched" + class="has-error">You need to choose at least one role for this user + </div> + </mat-form-field> </div> </mat-card-content> </mat-card> <div style="display: flex;flex-flow: row;"> <mat-card style="flex-grow: 1"> <mat-card-title>UI authentication credentials</mat-card-title> - <mat-card-content *ngIf="isUserAuthPasswdEnabled()"> + <mat-card-content *ngIf="isUserAuthPasswdEnabled()"> <fieldset style="border: solid gray 1px;"> <legend>Username/password credentials</legend> <div style="display: flex;flex-flow: row wrap;"> <mat-form-field style="flex-grow: 2"> - <input matInput placeholder="Username" [formControl]="userForm.controls['username']" - id="username-password_id" maxlength="255" disabled> + <input matInput placeholder="Username" [value]="userForm.controls['username'].value" + id="username-password_id" maxlength="255" disabled readonly> </mat-form-field> <mat-form-field *ngIf="!!userForm.get('passwordExpireOn').value" style="flex-grow: 1"> <input matInput placeholder="Valid until" @@ -65,14 +72,36 @@ <mat-form-field *ngIf="!userForm.get('passwordExpireOn').value" style="flex-grow: 1"> <input matInput placeholder="Valid until" style="color: red" matTooltip="Default password set by system admin! User must change password immediately!" - value="Default password" + value="Default or null password" + maxlength="255" disabled> + </mat-form-field> + </div> + <div style="display: flex;flex-flow: row wrap;"> + <mat-form-field style="flex-grow: 2"> + <input matInput placeholder="Seq. failed attempts" + [value]="userForm.controls['sequentialLoginFailureCount'].value" + id="sequentialLoginFailureCount_id" maxlength="255" disabled readonly> + </mat-form-field> + <mat-form-field style="flex-grow: 1"> + <input matInput placeholder="Last failed attempt" + value="{{!userForm.get('lastFailedLoginAttempt').value?nullValue:userForm.get('lastFailedLoginAttempt').value | date:dateTimeFormat}}" + maxlength="255" disabled> + </mat-form-field> + </div> + <div style="display: flex;flex-flow: row wrap;"> + <mat-form-field style="flex-grow: 1"> + <input matInput placeholder="Suspended until" + value="{{!userForm.get('suspendedUtil').value?nullValue:userForm.get('suspendedUtil').value | date:dateTimeFormat}}" maxlength="255" disabled> </mat-form-field> </div> <button mat-flat-button color="primary" style="width: 100%" id="changePassword_id" - (click)="changeCurrentUserPassword()"> - <span>Change password</span> + (click)="changeCurrentUserPassword()" [disabled]="!editMode"> + <span>Set/change password</span> </button> + <div *ngIf="!editMode" + style="color: red"> Password can be set after the user is saved! + </div> </fieldset> </mat-card-content> <mat-card-content *ngIf="isUserAuthSSOEnabled()"> @@ -80,9 +109,10 @@ <legend>CAS authentication</legend> <mat-form-field style="width: 100%"> <input matInput placeholder="Cas identifier" [formControl]="userForm.controls['username']" - id="cas-user_id" maxlength="255" disabled> + id="cas-user_id" maxlength="255" disabled readonly> </mat-form-field> <button mat-flat-button color="primary" style="width: 100%" id="openCASData" + [disabled]="!this.current?.casUserDataUrl" (click)="openCurrentCasUserData()"> <span>Open CAS user data</span> </button> @@ -108,13 +138,36 @@ <mat-form-field *ngIf="!userForm.get('accessTokenExpireOn').value" style="flex-grow: 1"> <input matInput placeholder="Valid until" style="color: red" matTooltip="Default access token set by system admin! User must regenerate access token immediately!" - value="Default access token" + value="Default or null access token" maxlength="255" disabled> </mat-form-field> </div> - <button mat-flat-button color="primary" style="width: 100%" (click)="regenerateAccessToken()"> + <div style="display: flex;flex-flow: row wrap;"> + <mat-form-field style="flex-grow: 2"> + <input matInput placeholder="Seq. failed attempts" + [value]="userForm.controls['sequentialTokenLoginFailureCount'].value" + id="sequentialTokenLoginFailureCount_id" maxlength="255" disabled readonly> + </mat-form-field> + <mat-form-field style="flex-grow: 1"> + <input matInput placeholder="Last failed attempt" + value="{{!userForm.get('lastTokenFailedLoginAttempt').value?nullValue:userForm.get('lastTokenFailedLoginAttempt').value | date:dateTimeFormat}}" + maxlength="255" disabled> + </mat-form-field> + </div> + <div style="display: flex;flex-flow: row wrap;"> + <mat-form-field style="flex-grow: 1"> + <input matInput placeholder="Suspended until" + value="{{!userForm.get('tokenSuspendedUtil').value?nullValue:userForm.get('tokenSuspendedUtil').value | date:dateTimeFormat}}" + maxlength="255" disabled> + </mat-form-field> + </div> + <button mat-flat-button color="primary" style="width: 100%" [disabled]="!editMode" + (click)="regenerateAccessToken()"> <span>Regenerate access token</span> </button> + <div *ngIf="!editMode" + style="color: red"> Access token can be set after the user is saved! + </div> </fieldset> </mat-card-content> 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 aec81251c721d30ff7b335a4ff080d7a3ae52336..5ad6b1ca674d460183d76017d4a6bcd7c2040e2d 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 @@ -39,7 +39,7 @@ export class UserDetailsDialogComponent { readonly dateFormat: string = 'yyyy-MM-dd HH:mm:ssZ'; readonly usernamePattern = '^[a-zA-Z0-9]{4,32}$'; readonly dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; - + readonly nullValue: string = SmpConstants.NULL_VALUE; mode: UserDetailsDialogMode; editMode: boolean; userId: string; @@ -107,7 +107,7 @@ export class UserDetailsDialogComponent { this.userId = data.row && data.row.userId; this.editMode = this.mode !== UserDetailsDialogMode.NEW_MODE; - this.current = this.editMode + this.current = !!data.row ? { ...data.row, password: '', // ensures the user password is cleared before editing @@ -119,13 +119,18 @@ export class UserDetailsDialogComponent { emailAddress: '', password: '', confirmation: null, + sequentialLoginFailureCount: null, + lastFailedLoginAttempt: null, + suspendedUtil: null, + sequentialTokenLoginFailureCount: null, + lastTokenFailedLoginAttempt: null, + tokenSuspendedUtil: null, role: '', encodedValue: '', crlUrl: '', status: SearchTableEntityStatus.NEW, statusPassword: SearchTableEntityStatus.NEW, certificate: this.newCertificateRo(), - }; const bSetPassword: boolean = false; @@ -143,14 +148,20 @@ export class UserDetailsDialogComponent { disabled: this.mode === UserDetailsDialogMode.PREFERENCES_MODE }, Validators.required), // username/password authentication - 'username': new FormControl({value: '', disabled: this.editMode }, + 'username': new FormControl({value: '', disabled: this.editMode}, !this.editMode || !this.current.username ? [Validators.nullValidator, Validators.pattern(this.usernamePattern), this.notInList(this.lookups.cachedServiceGroupOwnerList.map(a => a.username ? a.username.toLowerCase() : null))] : null), 'passwordExpireOn': new FormControl({value: '', disabled: true}), - 'accessTokenId': new FormControl({value: '', disabled: true}), + 'sequentialLoginFailureCount': new FormControl({value: '', disabled: true}), + 'lastFailedLoginAttempt': new FormControl({value: '', disabled: true}), + 'suspendedUtil': new FormControl({value: '', disabled: true}), + 'accessTokenId': new FormControl({value: '', disabled: true}), 'accessTokenExpireOn': new FormControl({value: '', disabled: true}), + 'sequentialTokenLoginFailureCount': new FormControl({value: '', disabled: true}), + 'lastTokenFailedLoginAttempt': new FormControl({value: '', disabled: true}), + 'tokenSuspendedUtil': new FormControl({value: '', disabled: true}), 'casUserDataUrl': new FormControl({value: '', disabled: true}), @@ -179,8 +190,15 @@ export class UserDetailsDialogComponent { // username/password authentication this.userForm.controls['username'].setValue(this.current.username); this.userForm.controls['passwordExpireOn'].setValue(this.current.passwordExpireOn); + this.userForm.controls['sequentialLoginFailureCount'].setValue(!this.current.sequentialLoginFailureCount ? 0 : this.current.sequentialLoginFailureCount); + this.userForm.controls['lastFailedLoginAttempt'].setValue(this.current.lastFailedLoginAttempt); + this.userForm.controls['suspendedUtil'].setValue(this.current.suspendedUtil); + this.userForm.controls['accessTokenId'].setValue(this.current.accessTokenId); this.userForm.controls['accessTokenExpireOn'].setValue(this.current.accessTokenExpireOn); + this.userForm.controls['sequentialTokenLoginFailureCount'].setValue(!this.current.sequentialTokenLoginFailureCount ? 0 : this.current.sequentialTokenLoginFailureCount); + this.userForm.controls['lastTokenFailedLoginAttempt'].setValue(this.current.lastTokenFailedLoginAttempt); + this.userForm.controls['tokenSuspendedUtil'].setValue(this.current.tokenSuspendedUtil); this.userForm.controls['casUserDataUrl'].setValue(this.current.casUserDataUrl); @@ -300,7 +318,7 @@ export class UserDetailsDialogComponent { } }, err => { - this.alertService.exception('Error uploading certificate file ' + file.name, err); + this.alertService.exception('Error uploading certificate file ' + file.name, err.error?.errorDescription); } ); @@ -312,7 +330,7 @@ export class UserDetailsDialogComponent { } public getCurrent(): UserRo { - if (this.mode === UserDetailsDialogMode.NEW_MODE){ + if (this.mode === UserDetailsDialogMode.NEW_MODE) { this.current.username = this.userForm.get('username').value; } @@ -399,5 +417,5 @@ export class UserDetailsDialogComponent { export enum UserDetailsDialogMode { NEW_MODE = 'New User', EDIT_MODE = 'User Edit', - PREFERENCES_MODE = 'Edit', + PREFERENCES_MODE = 'User details', } 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 a65074ec6f4a2ae801d9c1bd400a89d442d889d2..344b7e1c9eaf4d5ffc1e63852c7d5582f2885df9 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 @@ -39,7 +39,6 @@ export class UserDetailsService { }); } - REST_INTERNAL_USER_CHANGE_PASSWORD /** * Submit request to regenerated request token! diff --git a/smp-angular/src/app/user/user-ro.model.ts b/smp-angular/src/app/user/user-ro.model.ts index 4ac8043a456536ad4221368c7f756d0d2c2e757a..056f7dbb4028c437dfa902018bc1843d29f069d1 100644 --- a/smp-angular/src/app/user/user-ro.model.ts +++ b/smp-angular/src/app/user/user-ro.model.ts @@ -14,4 +14,10 @@ export interface UserRo extends SearchTableEntity { certificate?: CertificateRo; statusPassword: number; casUserDataUrl?: string; + sequentialLoginFailureCount?:number; + lastFailedLoginAttempt?:Date; + suspendedUtil?:Date; + sequentialTokenLoginFailureCount?:number; + lastTokenFailedLoginAttempt?:Date; + tokenSuspendedUtil?:Date; } diff --git a/smp-angular/src/app/user/user.component.ts b/smp-angular/src/app/user/user.component.ts index 4dd3876b0f10ca8f7e97a189152a455473f959a0..67ba12aa16a9ae78f212919651ec5b345d59d97c 100644 --- a/smp-angular/src/app/user/user.component.ts +++ b/smp-angular/src/app/user/user.component.ts @@ -1,4 +1,12 @@ -import {AfterViewInit, Component, TemplateRef, ViewChild} from '@angular/core'; +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import {ColumnPicker} from '../common/column-picker/column-picker.model'; import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {AlertMessageService} from '../common/alert-message/alert-message.service'; @@ -15,7 +23,7 @@ import {SmpConstants} from "../smp.constants"; templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) -export class UserComponent implements AfterViewInit { +export class UserComponent implements OnInit, AfterViewInit, AfterViewChecked { @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; @ViewChild('rowExtensionAction') rowExtensionAction: TemplateRef<any>; @@ -32,12 +40,15 @@ export class UserComponent implements AfterViewInit { public securityService: SecurityService, protected http: HttpClient, protected alertService: AlertMessageService, - public dialog: MatDialog) { + public dialog: MatDialog, + private changeDetector: ChangeDetectorRef) { } - ngAfterViewInit() { + ngOnInit() { this.userController = new UserController(this.http, this.lookups, this.dialog); + } + initColumns() { this.columnPicker.allColumns = [ { name: 'Username', @@ -60,14 +71,20 @@ export class UserComponent implements AfterViewInit { }, ]; this.searchTable.tableColumnInit(); - this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); + } + ngAfterViewInit() { + this.initColumns(); // if system admin refresh trust certificate list! if (this.securityService.isCurrentUserSystemAdmin()) { this.lookups.refreshTrustedCertificateLookup(); } } + ngAfterViewChecked() { + this.changeDetector.detectChanges(); + } + certCssClass(row) { if (row.certificate?.invalid) { diff --git a/smp-angular/src/app/user/user.service.ts b/smp-angular/src/app/user/user.service.ts index 2de88b3227ca8cf4b0b4aca4c714c2b652ee513f..574837c73d6bcf3812ba8a56acb28c5da7e48624 100644 --- a/smp-angular/src/app/user/user.service.ts +++ b/smp-angular/src/app/user/user.service.ts @@ -1,12 +1,9 @@ import {Injectable} from '@angular/core'; -import {Observable, of, Subject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; -import {Role} from '../security/role.model'; import {SmpConstants} from "../smp.constants"; import {User} from "../security/user.model"; import {AlertMessageService} from "../common/alert-message/alert-message.service"; import {SecurityService} from "../security/security.service"; -import {AccessTokenRo} from "../common/dialogs/access-token-generation-dialog/access-token-ro.model"; @Injectable() export class UserService { @@ -15,7 +12,8 @@ export class UserService { private http: HttpClient, private securityService: SecurityService, private alertService: AlertMessageService, - ) { } + ) { + } updateUser(user: User) { this.http.put<User>(SmpConstants.REST_PUBLIC_USER_UPDATE.replace('{user-id}', user.userId), user).subscribe(response => { diff --git a/smp-docker/compose/tomcat-mysql-smp-sml/docker-compose.yml b/smp-docker/compose/tomcat-mysql-smp-sml/docker-compose.yml index d4bc79f1d1cc3e5eb9cee8769416d6e29e91ad91..6108fc3c4a120ff1a3b11de45d88616a514c11fd 100644 --- a/smp-docker/compose/tomcat-mysql-smp-sml/docker-compose.yml +++ b/smp-docker/compose/tomcat-mysql-smp-sml/docker-compose.yml @@ -33,8 +33,8 @@ services: - "3908:3306" - "8982:8080" - "6902:6901" - - "8953:53" - - "5005:5005" +# - "8953:53" +# - "5005:5005" eulogin-mock-server: image: edelivery-docker.devops.tech.ec.europa.eu/eulogin/mockserver:6.2.7 diff --git a/smp-docker/compose/weblogic-oracle/README.md b/smp-docker/compose/weblogic-oracle/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e4e3fb07a565cce515c117c812be224519446322 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/README.md @@ -0,0 +1,16 @@ +# WebLogic oracle docker plan +WebLogic Oracle database plan starts up clustered WebLogic 12.2c environment with the Oracle 11xe or oracle 19c database. +The cluster is consisted from two nodes and the admin served. DB Connection pool and the SMP application is deployed to all +servers. + +# start environment +execute bash script + + ./compose/weblogic-oracle/runCompose.sh + +**Note**: if the Nodes are not starting (Caused By: com.rsa.jsafe.JSAFE_PaddingException: Invalid padding.). Please make sure +the notes are using the same ./smp-docker/compose/weblogic-oracle/data/smp-cluster-4.2-RC2-SNAPSHOT.jar generated from the admin server! + + +Restart clean node-01 +docker-compose -f ./compose/weblogic-oracle/docker-compose.yml -p smp-wls-orcl up --force-recreate --no-deps smp-node-01 \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/smp/config/.disable_default_excludes b/smp-docker/compose/weblogic-oracle/data/smp/config/.disable_default_excludes new file mode 100644 index 0000000000000000000000000000000000000000..e5d41c1eff637329155b8c79af85d53dd0205b60 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/smp/config/.disable_default_excludes @@ -0,0 +1,4 @@ +.gitignore is excluded when sharing artifacts on bamboo +http://ant.apache.org/manual/dirtasks.html + +And we want this folder to be shared in order to keep the project structure. \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/smp/config/.gitignore b/smp-docker/compose/weblogic-oracle/data/smp/config/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c487cd0d3ce4a8ff43f8dc7383dd5aa15672a81 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/smp/config/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +.. +# Except the following +!.gitignore +!.disable_default_excludes \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/smp/security/.disable_default_excludes b/smp-docker/compose/weblogic-oracle/data/smp/security/.disable_default_excludes new file mode 100644 index 0000000000000000000000000000000000000000..e5d41c1eff637329155b8c79af85d53dd0205b60 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/smp/security/.disable_default_excludes @@ -0,0 +1,4 @@ +.gitignore is excluded when sharing artifacts on bamboo +http://ant.apache.org/manual/dirtasks.html + +And we want this folder to be shared in order to keep the project structure. \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/smp/security/.gitignore b/smp-docker/compose/weblogic-oracle/data/smp/security/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c487cd0d3ce4a8ff43f8dc7383dd5aa15672a81 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/smp/security/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +.. +# Except the following +!.gitignore +!.disable_default_excludes \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/weblogic/.disable_default_excludes b/smp-docker/compose/weblogic-oracle/data/weblogic/.disable_default_excludes new file mode 100644 index 0000000000000000000000000000000000000000..e5d41c1eff637329155b8c79af85d53dd0205b60 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/weblogic/.disable_default_excludes @@ -0,0 +1,4 @@ +.gitignore is excluded when sharing artifacts on bamboo +http://ant.apache.org/manual/dirtasks.html + +And we want this folder to be shared in order to keep the project structure. \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/weblogic/.gitignore b/smp-docker/compose/weblogic-oracle/data/weblogic/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c487cd0d3ce4a8ff43f8dc7383dd5aa15672a81 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/weblogic/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +.. +# Except the following +!.gitignore +!.disable_default_excludes \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/weblogic/keystores/.disable_default_excludes b/smp-docker/compose/weblogic-oracle/data/weblogic/keystores/.disable_default_excludes new file mode 100644 index 0000000000000000000000000000000000000000..e5d41c1eff637329155b8c79af85d53dd0205b60 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/weblogic/keystores/.disable_default_excludes @@ -0,0 +1,4 @@ +.gitignore is excluded when sharing artifacts on bamboo +http://ant.apache.org/manual/dirtasks.html + +And we want this folder to be shared in order to keep the project structure. \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/data/weblogic/keystores/.gitignore b/smp-docker/compose/weblogic-oracle/data/weblogic/keystores/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2c487cd0d3ce4a8ff43f8dc7383dd5aa15672a81 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/data/weblogic/keystores/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +.. +# Except the following +!.gitignore +!.disable_default_excludes \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/docker-compose.yml b/smp-docker/compose/weblogic-oracle/docker-compose.yml index 6f4f6b51f0b46dcda38071c14dc54afd27e9a482..3c40315cc94bdbd7b2c27abff7d4008cd5575fd5 100644 --- a/smp-docker/compose/weblogic-oracle/docker-compose.yml +++ b/smp-docker/compose/weblogic-oracle/docker-compose.yml @@ -1,33 +1,76 @@ version: "3.0" services: - database: + smp-oracle-db: image: smp-oradb-${ORA_VERSION}-${ORA_EDITION}:${SMP_VERSION} - container_name: smp_oracle_db - hostname: wlsadmin + hostname: smp-database.local environment: - ORACLE_CHARACTERSET=AL32UTF8 # set database encoding - - NLS_LANG=.AL32UTF8 # set sqlplus encoding for stating up scripts + - NLS_LANG=.AL32UTF8 # set sqlplus encoding for starting up scripts volumes: - ./properties/db-scripts:/docker-entrypoint-initdb.d/startup # init script. - ./status-folder:/u01/status/ ports: - 1921:1521 shm_size: '1gb' - weblogic: + + smp-wls-admin: depends_on: - - database -# command: [bash, -c, "for i in `seq 10`; do timeout 1 bash -c '</dev/tcp/database/1521'; if [ $$? -eq 0 ] ; then break;fi;sleep 1; done;"] - command: [bash, -c, "for i in `seq 150`; do timeout 1 bash -c 'echo \" $$(ls /u01/status/)\"'; if [ -f '/u01/status/database.status' ] && [ \"$$( cat /u01/status/database.status )\" == 'DATABASE IS READY TO USE!' ] ; then break;fi; echo \"$$i. Wait for database!\"; sleep 10; done; /u01/oracle/startAdminServer.sh"] - image: smp-weblogic-122:${SMP_VERSION} - container_name: wls-smp - hostname: wlsadmin + - smp-oracle-db + command: [bash, -c, "rm -rf /u01/status/wls-admin.started;for i in `seq 150`; do timeout 1 bash -c 'echo \" $$(ls /u01/status/)\"'; if [ -f '/u01/status/database.status' ] && [ \"$$( cat /u01/status/database.status )\" == 'DATABASE IS READY TO USE!' ] ; then break;fi; echo \"$$i. Wait for database!\"; sleep 10; done; /u01/oracle/startAdminServer.sh"] + image: smp-weblogic-122:${SMP_VERSION:-4.2-RC2-SNAPSHOT} + hostname: smp-wls-admin environment: - JAVA_OPTIONS: "-Dweblogic.webservice.i18n.charset=utf-8" + - JAVA_OPTIONS="-Dweblogic.webservice.i18n.charset=utf-8" + ports: + - 7901:7001 volumes: - - ./properties/weblogic:/u01/oracle/properties + - ./properties/weblogic-init:/u01/init/ + - ./data:/data - ./status-folder:/u01/status/ + + smp-node-01: + # depend of the startup of the db + depends_on: + - smp-wls-admin + command: [bash, -c, "for i in `seq 200`; do timeout 1 bash -c 'echo \" $$(ls /u01/status/)\"'; if [ -f '/u01/status/wls-admin.started' ] ; then break;fi; echo \"$$i. Wait for admin server!\"; sleep 10; done; /u01/oracle/startManagedServer.sh"] + environment: + - WL_ADMIN_HOST=smp-wls-admin + - WL_MANAGED_SERV_NAME=smp-node-1 + image: smp-weblogic-122:${SMP_VERSION:-4.2-RC2-SNAPSHOT} + # ports: + # - "18453:8453" + # - "18001:8001" + volumes: + - ./data:/data + - ./status-folder:/u01/status/ + smp-node-02: + # depend of the startup of the db + depends_on: + - smp-wls-admin + command: [bash, -c, "for i in `seq 200`; do timeout 1 bash -c 'echo \" $$(ls /u01/status/)\"'; if [ -f '/u01/status/wls-admin.started' ] ; then break;fi; echo \"$$i. Wait for admin server!\"; sleep 10; done; /u01/oracle/startManagedServer.sh"] + environment: + - WL_ADMIN_HOST=smp-wls-admin + - WL_MANAGED_SERV_NAME=smp-node-2 + image: smp-weblogic-122:${SMP_VERSION:-4.2-RC2-SNAPSHOT} + # ports: + # - "18453:8453" + # - "18001:8001" + volumes: + - ./data:/data + - ./status-folder:/u01/status/ + + smp-httpd: + depends_on: + - smp-node-01 + - smp-node-02 + image: edelivery-docker.devops.tech.ec.europa.eu/domibustest/edelivery-httpd:2.4.39 + environment: + - VHOST_CORNER_HOSTNAME=smp.edelivery.eu + - NODES_COUNT=2 + - NODE_HOSTNAMES=smp-node-01,smp-node-02 + - NODE_PORT_NUMBERS=8001,8001 ports: - - 7901:7001 -volumes: - shared-status-folder: + - "7980:80" + restart: always + diff --git a/smp-docker/compose/weblogic-oracle/properties/db-scripts/01_create_user.sql b/smp-docker/compose/weblogic-oracle/properties/db-scripts/01_create_user.sql deleted file mode 100644 index 6cdf900b67efa3426a48405ffdb0bb50e26b19e2..0000000000000000000000000000000000000000 --- a/smp-docker/compose/weblogic-oracle/properties/db-scripts/01_create_user.sql +++ /dev/null @@ -1,8 +0,0 @@ - -CREATE USER smp IDENTIFIED BY "test" DEFAULT TABLESPACE users QUOTA UNLIMITED ON users; -GRANT CREATE SESSION TO smp; -GRANT CREATE TABLE TO smp; -GRANT CREATE VIEW TO smp; -GRANT CREATE SEQUENCE TO smp; -GRANT SELECT ON PENDING_TRANS$ TO smp; - diff --git a/smp-docker/compose/weblogic-oracle/properties/weblogic-init/datasource.properties b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/datasource.properties new file mode 100644 index 0000000000000000000000000000000000000000..2bdd46c3d92e011c34841ad275453a5e18b5dc42 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/datasource.properties @@ -0,0 +1,8 @@ +dsname=eDeliverySmpDs +dsdbname=eDeliverySmpDs +dsjndiname=jdbc/eDeliverySmpDs +dsdriver=oracle.jdbc.OracleDriver +dsurl=jdbc:oracle:thin:@//smp-oracle-db:1521/xe +dsusername=smp +dspassword=test +dstestquery=SQL SELECT 1 FROM DUAL diff --git a/smp-docker/compose/weblogic-oracle/properties/weblogic-init/domain.properties b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/domain.properties new file mode 100644 index 0000000000000000000000000000000000000000..d7ac9c8f66a9e1e7d7a6aeca3663a9ba526d603f --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/domain.properties @@ -0,0 +1,4 @@ +CLUSTER_TYPE=DYNAMIC +JAVA_OPTIONS=-Dweblogic.StdoutDebugEnabled=false +T3_CHANNEL_PORT=30012 +T3_PUBLIC_ADDRESS=kubernetes \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/properties/weblogic-init/domain_security.properties b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/domain_security.properties new file mode 100644 index 0000000000000000000000000000000000000000..3674a461bf61a9110a01d14c09fca95560b9a252 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/domain_security.properties @@ -0,0 +1,2 @@ +username=wls-smp +password=wls-pass-01 \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/properties/weblogic-init/smp.config.properties b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/smp.config.properties new file mode 100644 index 0000000000000000000000000000000000000000..d6cc553618ae852e460ad306c10b7a94feeff7fc --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/properties/weblogic-init/smp.config.properties @@ -0,0 +1,8 @@ + +configuration.dir=/data/smp/security +hibernate.dialect=org.hibernate.dialect.Oracle10gDialect +datasource.jndi=jdbc/eDeliverySmpDs +authentication.blueCoat.enabled=true +smp.truststore.password={DEC}{test123} +smp.keystore.password={DEC}{test123} +log.folder=./logs/ diff --git a/smp-docker/compose/weblogic-oracle/properties/weblogic/security.properties b/smp-docker/compose/weblogic-oracle/properties/weblogic/security.properties deleted file mode 100644 index 80e8e77e701b87183866132d30b9415d2e994c67..0000000000000000000000000000000000000000 --- a/smp-docker/compose/weblogic-oracle/properties/weblogic/security.properties +++ /dev/null @@ -1,4 +0,0 @@ -username=weblogic -password=Weblogic1 -JAVA_OPTIONS=-Dweblogic.StdoutDebugEnabled=false - diff --git a/smp-docker/compose/weblogic-oracle/runCompose.sh b/smp-docker/compose/weblogic-oracle/runCompose.sh index df4be843100029cd5871cf5a8fbbe23b3edf71af..8184741cea38c5e936f9207898ce7512b70d55fb 100755 --- a/smp-docker/compose/weblogic-oracle/runCompose.sh +++ b/smp-docker/compose/weblogic-oracle/runCompose.sh @@ -1,6 +1,8 @@ #!/bin/bash -WORKING_DIR="$(dirname $0)" +#WORKING_DIR="$(dirname $0)" +WORKING_DIR="$(cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" + SMP_INIT_DATABASE="../../../smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl" #SMP_INIT_DATABASE_DATA="../../../smp-webapp/src/main/smp-setup/database-scripts/oracle10g-data.sql" SMP_INIT_DATABASE_DATA="../../../smp-soapui-tests/groovy/oracle-4.1_integration_test_data.sql" @@ -19,9 +21,7 @@ ORA_SERVICE="xe" SMP_DB_USERNAME=smp; SMP_DB_PASSWORD=test; - -# clear volume and containers - to run restart from strach - +SMP_DB_SCRIPTS=./properties/db-scripts # READ arguments while getopts i:v: option @@ -47,7 +47,8 @@ echo "Working Directory: ${WORKING_DIR}" echo "*************************************************************************" cd "$WORKING_DIR" - +echo "Create folder (if not exist) for database scripts ${SMP_DB_SCRIPTS}" +[ -d ${SMP_DB_SCRIPTS} ] || mkdir -p "${SMP_DB_SCRIPTS}" function createDatabaseSchemaForUser() { @@ -74,17 +75,19 @@ function createDatabaseSchemaForUser() { function clearOldContainers { echo "Clear containers and volumes" docker-compose -p "${PREFIX}" rm -s -f -v - docker volume rm "${PREFIX}_shared-status-folder" + echo "Clear container data ${WORKING_DIR}/data/" + rm -rf ${WORKING_DIR}/data/smp/config/*.* + rm -rf ${WORKING_DIR}/data/smp/security/*.* + rm -rf ${WORKING_DIR}/data/weblogic/keystores/*.* + rm -rf ${WORKING_DIR}/data/weblogic/security.properties + rm -rf ${WORKING_DIR}/data/*.jar } - - - -createDatabaseSchemaForUser $SMP_DB_USERNAME $SMP_DB_PASSWORD ./properties/db-scripts/01_create_user.sql +createDatabaseSchemaForUser $SMP_DB_USERNAME $SMP_DB_PASSWORD "${SMP_DB_SCRIPTS}/01_create_user.sql" # create database init script from -echo "CONNECT smp/test@//localhost:1521/${ORA_SERVICE};" > ./properties/db-scripts/02_oracle10g.sql -cat "${SMP_INIT_DATABASE}" >> ./properties/db-scripts/02_oracle10g.sql +echo "CONNECT ${SMP_DB_USERNAME}/${SMP_DB_PASSWORD}@//localhost:1521/${ORA_SERVICE};" > "${SMP_DB_SCRIPTS}/02_oracle10g.sql" +cat "${SMP_INIT_DATABASE}" >> "${SMP_DB_SCRIPTS}/02_oracle10g.sql" @@ -95,8 +98,8 @@ if [ ! -f "${SMP_INIT_DATABASE_DATA}" ] exit 1; else # copy artefact to docker build folder - echo "CONNECT smp/test@//localhost:1521/${ORA_SERVICE};" > ./properties/db-scripts/03_oracle10g-data.sql - cat "${SMP_INIT_DATABASE_DATA}" >> ./properties/db-scripts/03_oracle10g-data.sql + echo "CONNECT ${SMP_DB_USERNAME}/${SMP_DB_PASSWORD}@//localhost:1521/${ORA_SERVICE};" > "${SMP_DB_SCRIPTS}/03_oracle10g-data.sql" + cat "${SMP_INIT_DATABASE_DATA}" >> "${SMP_DB_SCRIPTS}/03_oracle10g-data.sql" fi @@ -113,5 +116,5 @@ docker-compose -p ${PREFIX} up -d --force-recreate # wait until service is up -for i in `seq 200`; do timeout 10 bash -c ' curl --silent --fail http://localhost:7901/smp/'; if [ $? -eq 0 ] ; then break;fi; echo "$i. Wait for weblogic to start!"; sleep 10; done; +for i in `seq 200`; do timeout 10 bash -c ' curl --silent --fail http://localhost:7980/smp/'; if [ $? -eq 0 ] ; then break;fi; echo "$i. Wait for weblogic to start!"; sleep 10; done; diff --git a/smp-docker/compose/weblogic-oracle/status-folder/.disable_default_excludes b/smp-docker/compose/weblogic-oracle/status-folder/.disable_default_excludes new file mode 100644 index 0000000000000000000000000000000000000000..e5d41c1eff637329155b8c79af85d53dd0205b60 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/status-folder/.disable_default_excludes @@ -0,0 +1,4 @@ +.gitignore is excluded when sharing artifacts on bamboo +http://ant.apache.org/manual/dirtasks.html + +And we want this folder to be shared in order to keep the project structure. \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/status-folder/.gitignore b/smp-docker/compose/weblogic-oracle/status-folder/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bfed6289068a6a26ac3d8ac28f177f624057ce63 --- /dev/null +++ b/smp-docker/compose/weblogic-oracle/status-folder/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except the following +!.gitignore +!.disable_default_excludes \ No newline at end of file diff --git a/smp-docker/compose/weblogic-oracle/status-folder/database.status b/smp-docker/compose/weblogic-oracle/status-folder/database.status deleted file mode 100644 index 85a548816b5c7737529c756f6b23d7b7bba932e3..0000000000000000000000000000000000000000 --- a/smp-docker/compose/weblogic-oracle/status-folder/database.status +++ /dev/null @@ -1 +0,0 @@ -DATABASE IS READY TO USE! diff --git a/smp-docker/images/build-docker-images.sh b/smp-docker/images/build-docker-images.sh index 6ae88b8633adb7bd70fd926e8c11dd514462eecf..9bf47f9a950a300f69046778d917891f10689fd1 100755 --- a/smp-docker/images/build-docker-images.sh +++ b/smp-docker/images/build-docker-images.sh @@ -27,8 +27,8 @@ ORACLE_DB19_FILE="LINUX.X64_193000_db_home.zip" ORACLE_DOCKERFILE="Dockerfile.xe" ORACLE_DB_FILE="${ORACLE_DB11_FILE}" -SERVER_JDK_FILE="server-jre-8u211-linux-x64.tar.gz" -WEBLOGIC_122_QUICK_FILE="fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip" +SERVER_JDK_FILE="server-jre-8u333-linux-x64.tar.gz" +WEBLOGIC_122_QUICK_FILE="fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip" SMP_VERSION= ORACLE_ARTEFACTS="/CEF/repo" @@ -53,7 +53,6 @@ while getopts v:o:s:c:p: option; do esac done - if [[ -z "${SMP_VERSION}" ]]; then # get version from setup file echo "Get version from the pom: $(pwd)" @@ -94,12 +93,10 @@ validateAndPrepareArtefacts() { ;; esac - export ORA_VERSION export ORA_EDITION export ORA_SERVICE - # check oracle database if [[ ! -f "${ORACLE_ARTEFACTS}/Oracle/OracleDatabase/${ORA_VERSION}/${ORACLE_DB_FILE}" ]]; then echo "Oracle database artefacts '${ORACLE_ARTEFACTS}/Oracle/OracleDatabase/${ORA_VERSION}/${ORACLE_DB_FILE}' not found." @@ -124,15 +121,17 @@ validateAndPrepareArtefacts() { exit 1 else # copy artefact to docker build folder - cp "${ORACLE_ARTEFACTS}/${WEBLOGIC_122_QUICK_FILE}" ./oracle/weblogic-12.2.1.3/ + cp "${ORACLE_ARTEFACTS}/${WEBLOGIC_122_QUICK_FILE}" ./oracle/weblogic-12.2.1.4/ fi - - if [[ ! -d "./tomcat-mysql-smp-sml/artefacts/" ]]; then mkdir -p "./tomcat-mysql-smp-sml/artefacts" fi + if [[ ! -d "./weblogic-12.2-smp/artefacts" ]]; then + mkdir -p "./weblogic-12.2-smp/artefacts" + fi + # SMP artefats if [[ ! -f "${SMP_ARTEFACTS}/smp.war" ]]; then echo "SMP artefact '${SMP_ARTEFACTS}/smp.war' not found. Was project built!" @@ -140,7 +139,7 @@ validateAndPrepareArtefacts() { else # copy artefact to docker build folder # for weblogic - cp "${SMP_ARTEFACTS}/smp.war" ./weblogic-12.2.1.3-smp/smp.war + cp "${SMP_ARTEFACTS}/smp.war" ./weblogic-12.2-smp/artefacts/smp.war # for mysql tomcat cp "${SMP_ARTEFACTS}/smp.war" ./tomcat-mysql-smp-sml/artefacts/smp.war fi @@ -151,7 +150,7 @@ validateAndPrepareArtefacts() { exit 1 else # copy artefact to docker build folder - cp "${SMP_ARTEFACTS}/smp-${SMP_VERSION}-setup.zip" ./weblogic-12.2.1.3-smp/smp-setup.zip + cp "${SMP_ARTEFACTS}/smp-${SMP_VERSION}-setup.zip" ./weblogic-12.2-smp/artefacts/smp-setup.zip cp "${SMP_ARTEFACTS}/smp-${SMP_VERSION}-setup.zip" ./tomcat-mysql-smp-sml/artefacts/smp-setup.zip fi @@ -174,26 +173,39 @@ buildImages() { # ----------------------------------------------------------------------------- # oracle 1.2.0.2-xe (https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance/dockerfiles/11.2.0.2) docker build -f ./oracle/oracle-db-${ORA_VERSION}/${ORACLE_DOCKERFILE} -t "smp-oradb-${ORA_VERSION}-${ORA_EDITION}:${SMP_VERSION}" --build-arg DB_EDITION=${ORA_EDITION} ./oracle/oracle-db-${ORA_VERSION}/ - + if [ $? -ne 0 ]; then + echo "Error occurred while building image [smp-oradb-${ORA_VERSION}-${ORA_EDITION}:${SMP_VERSION}]!" + exit 10 + fi # ----------------------------------------------------------------------------- # build docker image for oracle database # ----------------------------------------------------------------------------- - # create docker OS image with java (https://github.com/oracle/docker-images/tree/master/OracleJava/java-8) docker build -t oracle/serverjre:8 ./oracle/OracleJava/java-8/ + if [ $? -ne 0 ]; then + echo "Error occurred while building image for oracle server-java!" + exit 10 + fi - # create weblogic basic (https://github.com/oracle/docker-images/tree/master/OracleWebLogic/dockerfiles/12.2.1.3) - docker build -f ./oracle/weblogic-12.2.1.3/Dockerfile.developer -t oracle/weblogic:12.2.1.3-developer ./oracle/weblogic-12.2.1.3/ - - # create weblogic domain-home-in-image (https://github.com/oracle/docker-images/tree/master/OracleWebLogic/samples/12213-domain-home-in-image./) - ./oracle/weblogic-12213-domain-home-in-image/container-scripts/setEnv.sh ./oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain.properties - docker build $BUILD_ARG --force-rm=true -t oracle/12213-domain-home-in-image ./oracle/weblogic-12213-domain-home-in-image/ + # create weblogic basic (https://github.com/oracle/docker-images/tree/master/OracleWebLogic/dockerfiles/12.2.1.4) + docker build -f ./oracle/weblogic-12.2.1.4/Dockerfile.developer -t oracle/weblogic:12.2.1.4-developer ./oracle/weblogic-12.2.1.4/ + if [ $? -ne 0 ]; then + echo "Error occurred while building image for oracle weblogic:12.2.1.4 server!" + exit 10 + fi # build SMP deployment. - docker build -t "smp-weblogic-122:${SMP_VERSION}" ./weblogic-12.2.1.3-smp/ --build-arg SMP_VERSION="$SMP_VERSION" + docker build -t "smp-weblogic-122:${SMP_VERSION}" ./weblogic-12.2-smp/ --build-arg SMP_VERSION="$SMP_VERSION" + if [ $? -ne 0 ]; then + echo "Error occurred while building image [smp-weblogic-122:${SMP_VERSION}]!" + exit 10 + fi # build tomcat mysql image deployment. docker build -t "smp-sml-tomcat-mysql:${SMP_VERSION}" ./tomcat-mysql-smp-sml/ --build-arg SMP_VERSION=${SMP_VERSION} - + if [ $? -ne 0 ]; then + echo "Error occurred while building image [smp-sml-tomcat-mysql:${SMP_VERSION}]!" + exit 10 + fi } function pushImageToDockerhub() { @@ -226,11 +238,9 @@ function pushImageIfExisting() { cleanArtefacts() { rm "./oracle/oracle-db-${ORA_VERSION}/${ORACLE_DB_FILE}" # clean rm "./oracle/OracleJava/java-8/${SERVER_JDK_FILE}" # clean - rm "./oracle/weblogic-12.2.1.3/${WEBLOGIC_122_QUICK_FILE}" # clean - rm "./weblogic-12.2.1.3-smp/smp.war" - rm "./weblogic-12.2.1.3-smp/smp-setup.zip" + rm "./oracle/weblogic-12.2.1.4/${WEBLOGIC_122_QUICK_FILE}" # clean - # clear also the tomcat/mysql image + rm -rf "./weblogic-12.2-smp/artefacts/*.*" rm -rf "./tomcat-mysql-smp-sml/artefacts/*.*" if [[ "V$SMP_ARTEFACTS_CLEAR" == "Vtrue" ]]; then diff --git a/smp-docker/images/oracle/OracleJava/java-8/Dockerfile b/smp-docker/images/oracle/OracleJava/java-8/Dockerfile index 86cf7676382265ade68770c9de4cd10ae59524fb..021afc3f8a106b55df85516ec22dd9b795d0c754 100644 --- a/smp-docker/images/oracle/OracleJava/java-8/Dockerfile +++ b/smp-docker/images/oracle/OracleJava/java-8/Dockerfile @@ -1,19 +1,83 @@ -# LICENSE UPL 1.0 +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # -# Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. # -FROM oraclelinux:7-slim +# ORACLE DOCKERFILES PROJECT +# -------------------------- +# This is the Dockerfile for Oracle Server JRE 8 +# +# REQUIRED FILES TO BUILD THIS IMAGE +# ---------------------------------- +# +# (1) server-jre-8uXX-linux-x64.tar.gz +# Download from https://www.oracle.com/java/technologies/javase-server-jre8-downloads.html +# +# HOW TO BUILD THIS IMAGE +# ----------------------- +# Put all downloaded files in the same directory as this Dockerfile +# Run: +# $ docker build -t oracle/serverjre:8 . +# +# This command is already scripted in build.sh so you can alternatively run +# $ bash build.sh +# +# The builder image will be used to uncompress the tar.gz file with the Java Runtime. + +FROM oraclelinux:7-slim as builder MAINTAINER Aurelio Garcia-Ribeyro <aurelio.garciaribeyro@oracle.com> -ENV JAVA_PKG=server-jre-8u*-linux-x64.tar.gz \ - JAVA_HOME=/usr/java/default +# Since the files is compressed as tar.gz first yum install gzip and tar +RUN set -eux; \ + yum install -y \ + gzip \ + tar \ + ; \ + rm -rf /var/cache/yum + +# Default to UTF-8 file.encoding +ENV LANG en_US.UTF-8 + +# Environment variables for the builder image. +# Required to validate that you are using the correct file + +ENV JAVA_PKG=server-jre-8u333-linux-x64.tar.gz \ + JAVA_SHA256=e6383f75665f5674deeb7e5c366fc7c6fc93e990c638c224dc68c5ec2863b763 \ + JAVA_HOME=/usr/java/jdk-8 + +COPY $JAVA_PKG /tmp/jdk.tgz +RUN set -eux; \ + echo "$JAVA_SHA256 */tmp/jdk.tgz" | sha256sum -c -; \ + mkdir -p "$JAVA_HOME"; \ + tar --extract --file /tmp/jdk.tgz --directory "$JAVA_HOME" --strip-components 1; + +## Get a fresh version of SLIM for the final image + +FROM oraclelinux:7-slim + +# Default to UTF-8 file.encoding +ENV LANG en_US.UTF-8 + +ENV JAVA_VERSION=1.8.0_333 \ + JAVA_HOME=/usr/java/jdk-8 + +ENV PATH $JAVA_HOME/bin:$PATH -ADD $JAVA_PKG /usr/java/ +# Copy the uncompressed Java Runtime from the builder image +COPY --from=builder $JAVA_HOME $JAVA_HOME -RUN export JAVA_DIR=$(ls -1 -d /usr/java/*) && \ - ln -s $JAVA_DIR /usr/java/latest && \ - ln -s $JAVA_DIR /usr/java/default && \ - alternatives --install /usr/bin/java java $JAVA_DIR/bin/java 20000 && \ - alternatives --install /usr/bin/javac javac $JAVA_DIR/bin/javac 20000 && \ - alternatives --install /usr/bin/jar jar $JAVA_DIR/bin/jar 20000 +## +RUN yum -y update; \ + rm -rf /var/cache/yum; \ + ln -sfT "$JAVA_HOME" /usr/java/default; \ + ln -sfT "$JAVA_HOME" /usr/java/latest; \ + for bin in "$JAVA_HOME/bin/"*; do \ + base="$(basename "$bin")"; \ + [ ! -e "/usr/bin/$base" ]; \ + alternatives --install "/usr/bin/$base" "$base" "$bin" 20000; \ + done; \ +# -Xshare:dump will create a CDS archive to improve startup in subsequent runs +# the file will be stored as /usr/java/jdk-8/jre/lib/amd64/server/classes.jsa +# See https://docs.oracle.com/javase/8/docs/technotes/guides/vm/class-data-sharing.html + java -Xshare:dump; + \ No newline at end of file diff --git a/smp-docker/images/oracle/OracleJava/java-8/Dockerfile.8 b/smp-docker/images/oracle/OracleJava/java-8/Dockerfile.8 new file mode 100644 index 0000000000000000000000000000000000000000..71911a254b1344500e22d6c8255da56542deb421 --- /dev/null +++ b/smp-docker/images/oracle/OracleJava/java-8/Dockerfile.8 @@ -0,0 +1,79 @@ +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. +# +# ORACLE DOCKERFILES PROJECT +# -------------------------- +# This is the Dockerfile for Oracle Server JRE 8 +# +# REQUIRED FILES TO BUILD THIS IMAGE +# ---------------------------------- +# +# (1) server-jre-8uXX-linux-x64.tar.gz +# Download from https://www.oracle.com/java/technologies/javase-server-jre8-downloads.html +# +# HOW TO BUILD THIS IMAGE +# ----------------------- +# Put all downloaded files in the same directory as this Dockerfile +# Run: +# $ docker build -t oracle/serverjre:8 . +# +# This command is already scripted in build.sh so you can alternatively run +# $ bash build.sh +# +# The builder image will be used to uncompress the tar.gz file with the Java Runtime. + +FROM ghcr.io/oracle/oraclelinux8-compat:8-slim as builder + +MAINTAINER Aurelio Garcia-Ribeyro <aurelio.garciaribeyro@oracle.com> + +# Since the files is compressed as tar.gz first yum install tar. gzip is already in ghcr.io/oracle/oraclelinux8-compat:8-slim +RUN set -eux; \ + dnf install -y tar ; + +# Default to UTF-8 file.encoding +ENV LANG en_US.UTF-8 + +# Environment variables for the builder image. +# Required to validate that you are using the correct file + +ENV JAVA_PKG=server-jre-8u333-linux-x64.tar.gz \ + JAVA_SHA256=e6383f75665f5674deeb7e5c366fc7c6fc93e990c638c224dc68c5ec2863b763 \ + JAVA_HOME=/usr/java/jdk-8 + +COPY $JAVA_PKG /tmp/jdk.tgz +RUN set -eux; \ + echo "$JAVA_SHA256 */tmp/jdk.tgz" | sha256sum -c -; \ + mkdir -p "$JAVA_HOME"; \ + tar --extract --file /tmp/jdk.tgz --directory "$JAVA_HOME" --strip-components 1; + +## Get a fresh version of SLIM for the final image + +FROM ghcr.io/oracle/oraclelinux8-compat:8-slim + +# Default to UTF-8 file.encoding +ENV LANG en_US.UTF-8 + +ENV JAVA_VERSION=1.8.0_333 \ + JAVA_HOME=/usr/java/jdk-8 + +ENV PATH $JAVA_HOME/bin:$PATH + +# Copy the uncompressed Java Runtime from the builder image +COPY --from=builder $JAVA_HOME $JAVA_HOME + +## +RUN dnf -y update; \ + rm -rf /var/cache/dnf; \ + ln -sfT "$JAVA_HOME" /usr/java/default; \ + ln -sfT "$JAVA_HOME" /usr/java/latest; \ + for bin in "$JAVA_HOME/bin/"*; do \ + base="$(basename "$bin")"; \ + [ ! -e "/usr/bin/$base" ]; \ + alternatives --install "/usr/bin/$base" "$base" "$bin" 20000; \ + done; \ +# -Xshare:dump will create a CDS archive to improve startup in subsequent runs +# the file will be stored as /usr/java/jdk-8/jre/lib/amd64/server/classes.jsa +# See https://docs.oracle.com/javase/8/docs/technotes/guides/vm/class-data-sharing.html + java -Xshare:dump; + \ No newline at end of file diff --git a/smp-docker/images/oracle/OracleJava/java-8/build.sh b/smp-docker/images/oracle/OracleJava/java-8/build.sh index 5f9c79d9bbd846c2daf8fbe954a63b85ef18f340..8e74ba7ac068b444c716e2eb50dc625031ddef34 100755 --- a/smp-docker/images/oracle/OracleJava/java-8/build.sh +++ b/smp-docker/images/oracle/OracleJava/java-8/build.sh @@ -1,2 +1,15 @@ #!/bin/sh -docker build -t oracle/serverjre:8 . + +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +if test "$1" = "8" +then + echo "Building Oracle Server JRE 8 on Oracle Linux 8" + docker build --file Dockerfile.8 --tag oracle/serverjre:8-oraclelinux8 . +else + echo "Building Oracle Server JRE 8 on Oracle Linux 7 slim" + docker build --tag oracle/serverjre:8 --tag oracle/serverjre:8-oraclelinux7 . +fi + diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/Checksum.developer b/smp-docker/images/oracle/weblogic-12.2.1.3/Checksum.developer deleted file mode 100644 index 8b78a35955c5c8da78434d77120269035d7d338f..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/Checksum.developer +++ /dev/null @@ -1,6 +0,0 @@ -# Download WebLogic Server Quick Installer 12.2.1.3 -# -# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html -# -a5d4811b431b2166e3e16c20c36ede09 fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip - diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/Checksum.generic b/smp-docker/images/oracle/weblogic-12.2.1.3/Checksum.generic deleted file mode 100644 index 7290c15d43af7c680cd4073d2afbe9d473a2ea3d..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/Checksum.generic +++ /dev/null @@ -1,6 +0,0 @@ -# Download WebLogic Server Generic Installer 12.2.1.3 -# -# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html -# -ea1b961b8896ac2f4006921965e41ddf fmw_12.2.1.3.0_wls_Disk1_1of1.zip - diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile b/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile deleted file mode 100644 index 9b5a1fb6ea613840aa0e3e847ffdff3703bb5479..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile +++ /dev/null @@ -1,87 +0,0 @@ -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. -# -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -# ORACLE DOCKERFILES PROJECT -# -------------------------- -# This is the Dockerfile for WebLogic 12.2.1.3 Quick Install Distro -# -# REQUIRED FILES TO BUILD THIS IMAGE -# ---------------------------------- -# (1) fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip -# Download the Developer Quick installer from http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html -# -# (2) server-jre-8uXX-linux-x64.tar.gz -# Download from http://www.oracle.com/technetwork/java/javase/downloads/server-jre8-downloads-2133154.html -# -# HOW TO BUILD THIS IMAGE -# ----------------------- -# Put all downloaded files in the same directory as this Dockerfile -# Run: -# $ docker build -t oracle/weblogic:12.2.1.3-developer . -# -# IMPORTANT -# --------- -# The resulting image of this Dockerfile contains a WLS Empty Domain. -# -# Pull base image -# From the Oracle Registry -# ------------------------- -FROM oracle/serverjre:8 - -# Maintainer -# ---------- -MAINTAINER Monica Riccelli <monica.riccelli@oracle.com> - -# Common environment variables required for this build (do NOT change) -# -------------------------------------------------------------------- -ENV ORACLE_HOME=/u01/oracle \ - USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ - SCRIPT_FILE=/u01/oracle/createAndStartEmptyDomain.sh \ - PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin - -# Setup filesystem and oracle user -# Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation -# ------------------------------------------------------------ -RUN mkdir -p /u01 && \ - chmod a+xr /u01 && \ - useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle - -# Copy scripts -#------------- -COPY container-scripts/createAndStartEmptyDomain.sh container-scripts/create-wls-domain.py /u01/oracle/ - -# Domain and Server environment variables -# ------------------------------------------------------------ -ENV DOMAIN_NAME="${DOMAIN_NAME:-base_domain}" \ - ADMIN_LISTEN_PORT="${ADMIN_LISTEN_PORT:-7001}" \ - ADMIN_NAME="${ADMIN_NAME:-AdminServer}" \ - DEBUG_FLAG=true \ - PRODUCTION_MODE=dev \ - ADMINISTRATION_PORT_ENABLED="${ADMINISTRATION_PORT_ENABLED:-true}" \ - ADMINISTRATION_PORT="${ADMINISTRATION_PORT:-9002}" - - - -# Environment variables required for this build (do NOT change) -# ------------------------------------------------------------- -ENV FMW_PKG=fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip \ - FMW_JAR=fmw_12.2.1.3.0_wls_quick.jar - -# Copy packages -# ------------- -COPY $FMW_PKG install.file oraInst.loc /u01/ -RUN chown oracle:oracle -R /u01 && \ - chmod +xr $SCRIPT_FILE - -# Install -# ------------------------------------------------------------ -USER oracle -RUN cd /u01 && ${JAVA_HOME}/bin/jar xf /u01/$FMW_PKG && cd - && \ - ${JAVA_HOME}/bin/java -jar /u01/$FMW_JAR -silent -responseFile /u01/install.file -invPtrLoc /u01/oraInst.loc -jreLoc $JAVA_HOME -ignoreSysPrereqs -force -novalidation ORACLE_HOME=$ORACLE_HOME && \ - rm /u01/$FMW_JAR /u01/$FMW_PKG /u01/oraInst.loc /u01/install.file - -WORKDIR ${ORACLE_HOME} - -# Define default command to start script. -CMD ["/u01/oracle/createAndStartEmptyDomain.sh"] diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/README.md b/smp-docker/images/oracle/weblogic-12.2.1.3/README.md deleted file mode 100644 index 2c7cd83977e57d2c6f645d9abcaf28ad374d78a3..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/README.md +++ /dev/null @@ -1,98 +0,0 @@ -Oracle WebLogic Server on Docker -================================= -These Docker configurations have been used to create the Oracle WebLogic Server (WLS) image. Providing this WLS image facilitates the configuration and environment setup for DevOps users. This project includes the installation and creation of an empty WebLogic Server domain (an Administration Server only). These Oracle WebLogic Server 12.2.1.3 images are based on Oracle Linux and Oracle JRE 8 (Server). - -The certification of Oracle WebLogic Server on Docker does not require the use of any file presented in this repository. Customers and users are welcome to use them as starters, and customize, tweak, or create from scratch, new scripts and Dockerfiles. - -For more information on the certification, please see the [Oracle WebLogic Server on Docker certification whitepaper](http://www.oracle.com/technetwork/middleware/weblogic/overview/weblogic-server-docker-containers-2491959.pdf) and [The WebLogic Server Blog](https://blogs.oracle.com/WebLogicServer/) for updates. - -## How to build and run -This project offers sample Dockerfiles for Oracle WebLogic Server 12cR2 (12.2.1.3). It provides at least one Dockerfile for the 'developer' distribution, a second Dockerfile for the 'generic' distribution. To assist in building the images, you can use the [`buildDockerImage.sh`](dockerfiles/buildDockerImage.sh) script. See below for instructions and usage. - -The `buildDockerImage.sh` script is a utility shell script that performs MD5 checks and is an easy way for beginners to get started. Expert users are welcome to directly call `docker build` with their prefered set of parameters. - -### Building Oracle WebLogic Server Docker install images -**IMPORTANT:** You must download the binary of Oracle WebLogic Server and put it in place (see `.download` files inside `dockerfiles/<version>`). The WebLogic image extends the Oracle JRE Server 8 image. You must either build the image by using the Dockerfile in [`../../../OracleJava/java8`](https://github.com/oracle/docker-images/tree/master/OracleJava/java-8) or pull the latest image from the [Oracle Container Registry](https://container-registry.oracle.com) or the [Docker Store](https://store.docker.com). - -Before you build, select the version and distribution for which you want to build an image, then download the required packages (see `.download` files) and locate them in the folder of your distribution version of choice. Then, from the `dockerfiles` folder, run the `buildDockerImage.sh` script as root. - - $ sh buildDockerImage.sh - Usage: buildDockerImage.sh -v [version] [-d | -g ] [-s] - Builds a Docker Image for Oracle WebLogic Server. - - Parameters: - -v: version to build. Required. - Choose : 12.2.1.3 - -d: creates image based on 'developer' distribution - -g: creates image based on 'generic' distribution - -c: enables Docker image layer cache during build - -s: skips the MD5 check of packages - - * select one distribution only: -d, or -g - - LICENSE UPL 1.0 - - Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. - -**IMPORTANT:** The resulting images will have a single server domain (Administration Server only), by default. - - - 1. To build the `12.2.1.3`image, from `dockerfiles`, call: - - `$ sh buildDockerImage.sh -v 12.2.1.3 -d` - - 2. Verify that you now have this image in place with: - - `$ docker images` - -### Running a single server domain from the image -The WebLogic Server install image (built above) allows you to run a container with a single WebLogic Server domain. This makes it extremely simple to deploy applications and any resource the application might need. - -#### Providing the Administration Server user name and password -The user name and password must be supplied in a `domain.properties` file located in a HOST directory that you will map at Docker runtime with the `-v` option to the image directory `/u01/oracle/properties`. The properties file enables the scripts to configure the correct authentication for the WebLogic Administration Server. - -The format of the `domain.properties` file is key=value pair: - - username=myadminusername - password=myadminpassword - -**Note**: Oracle recommends that the `domain.properties` file be deleted or secured after the container and the WebLogic Server are started so that the user name and password are not inadvertently exposed. - -#### Start the container -Start a container from the image created in step 1. -You can override the default values of the following parameters during runtime with the `-e` option: - - * `ADMIN_NAME` (default: `AdminServer`) - * `ADMIN_LISTEN_PORT` (default: `7001`) - * `DOMAIN_NAME` (default: `base_domain`) - * `ADMINISTRATION_PORT_ENABLED` (default: `true`) - * `ADMINISTRATION_PORT` (default: `9002`) - -**NOTE**: For security, the Administration port 9002 is enabled by default, before running the container in WebLogic 12.2.1.3 the patch 27117282 must be applied. Please download the patch and apply it after you have built the 12.2.1.3 image. You can follow the sample https://github.com/oracle/docker-images/tree/master/OracleWebLogic/samples/12213-patch to see how to patch. An alternative is to not enable Administration port when you issue the docker run command, set `ADMINISTRTATION_PORT_ENABLED` to false. If you intend to run these images in production, then you must change the Production Mode to `production`. When you set the `DOMAIN_NAME`, the `DOMAIN_HOME=/u01/oracle/user_projects/domains/$DOMAIN_NAME`. - - $docker run -d -p 7001:7001 -p 9002:9002 -v `HOST PATH where the domain.properties file is`:/u01/oracle/properties -e ADMINISTRATION_PORT_ENABLED=true -e DOMAIN_NAME=abc_domain oracle/weblogic:12.2.1.3-developer - -Run the WLS Administration Console: - - $ docker inspect --format '{{.NetworkSettings.IPAddress}}' <container-name> - -In your browser, enter `https://xxx.xx.x.x:9002/console`. Your browser will request that you accept the Security Exception. To avoid the Security Exception, you must update the WebLogic Server SSL configuration with a custom identity certificate. - -## Choose your Oracle WebLogic Server distribution - -This project hosts two configurations (depending on the Oracle WebLogic Server version) for building Docker images with WebLogic Server 12c. - - * Quick Install Developer Distribution - - - For more information on the Oracle WebLogic Server 12cR2 Quick Install Developer Distribution, see [WLS Quick Install Distribution for Oracle WebLogic Server 12.2.1.3.0](http://download.oracle.com/otn/nt/middleware/12c/wls/12213/README.txt). - - - * Generic Distribution - - - For more information on the Oracle WebLogic Server 12cR2 Generic Full Distribution, see [WebLogic Server 12.2.1.3 Documentation](http://docs.oracle.com/middleware/12213/wls/index.html). - -## Samples for Oracle WebLogic Server domain creation -To give users an idea of how to create a WebLogic domain and cluster from a custom Dockerfile which extends the WebLogic Server install image, we provide a few samples for 12c versions of the developer distribution. For an example, look at the `12213-domain` sample. - -## Copyright -Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip.download b/smp-docker/images/oracle/weblogic-12.2.1.3/fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip.download deleted file mode 100644 index 29195504d4ab929242aaa55a33ddcf286fe782e8..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip.download +++ /dev/null @@ -1,5 +0,0 @@ -# Download WebLogic Server Quick Installer 12.2.1.3 -# -# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html -# -a5d4811b431b2166e3e16c20c36ede09 fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/properties/domain.properties b/smp-docker/images/oracle/weblogic-12.2.1.3/properties/domain.properties deleted file mode 100644 index ee2eecf41220207253a1176c65f36cb0d369e595..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/properties/domain.properties +++ /dev/null @@ -1,2 +0,0 @@ -username=weblogic -password=Weblogic1 diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.developer b/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.developer new file mode 100644 index 0000000000000000000000000000000000000000..169a46a598a0954a8b8dd672246d98cb562f6963 --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.developer @@ -0,0 +1,6 @@ +# Download WebLogic Server Quick Installer 12.2.1.4 +# +# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +8f88d91600ecb6826a91a3b72f832c6a fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip + diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.generic b/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.generic new file mode 100644 index 0000000000000000000000000000000000000000..bde0497488b42f46e09cdf2e926236ce570e313f --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.generic @@ -0,0 +1,6 @@ +# Download WebLogic Server Generic Installer 12.2.1.4 +# +# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +650109d90ab3da8f7a64b24a98e072b3 fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip + diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.slim b/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.slim new file mode 100644 index 0000000000000000000000000000000000000000..d5ae7956d965de5337d451f34987d346b4c4463e --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/Checksum.slim @@ -0,0 +1,5 @@ +# Download WebLogic Server Slim Installer 12.2.1.4 +# +# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +df55639e00817308be249af5aa602bfb fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile.developer b/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.developer similarity index 51% rename from smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile.developer rename to smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.developer index 9b5a1fb6ea613840aa0e3e847ffdff3703bb5479..ab4822880ca890ea0d2af0b644312ee8375d33c9 100644 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile.developer +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.developer @@ -1,14 +1,14 @@ -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. +#Copyright (c) 2014, 2020, Oracle and/or its affiliates. # -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. # # ORACLE DOCKERFILES PROJECT # -------------------------- -# This is the Dockerfile for WebLogic 12.2.1.3 Quick Install Distro +# This is the Dockerfile for WebLogic 12.2.1.4 Quick Install Distro # # REQUIRED FILES TO BUILD THIS IMAGE # ---------------------------------- -# (1) fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip +# (1) fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip # Download the Developer Quick installer from http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html # # (2) server-jre-8uXX-linux-x64.tar.gz @@ -18,38 +18,64 @@ # ----------------------- # Put all downloaded files in the same directory as this Dockerfile # Run: -# $ docker build -t oracle/weblogic:12.2.1.3-developer . +# $ docker build -f Dockerfile.developer -t oracle/weblogic:12.2.1.4-developer . # # IMPORTANT # --------- # The resulting image of this Dockerfile contains a WLS Empty Domain. # -# Pull base image -# From the Oracle Registry -# ------------------------- -FROM oracle/serverjre:8 +# Extend base JRE image +# You must build the image by using the Dockerfile in GitHub project `../../../OracleJava/java8` +# ---------------------------------------------------------------------------------------------- +FROM oracle/serverjre:8 as builder -# Maintainer -# ---------- -MAINTAINER Monica Riccelli <monica.riccelli@oracle.com> +# Labels +# ------ +LABEL "provider"="Oracle" \ + "maintainer"="Monica Riccelli <monica.riccelli@oracle.com>" \ + "issues"="https://github.com/oracle/docker-images/issues" \ + "port.admin.listen"="7001" \ + "port.administration"="9002" # Common environment variables required for this build (do NOT change) # -------------------------------------------------------------------- ENV ORACLE_HOME=/u01/oracle \ USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ - SCRIPT_FILE=/u01/oracle/createAndStartEmptyDomain.sh \ PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin # Setup filesystem and oracle user # Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation # ------------------------------------------------------------ -RUN mkdir -p /u01 && \ - chmod a+xr /u01 && \ - useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle +RUN mkdir /u01 && \ + useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle && \ + chown oracle:root -R /u01 && \ + chmod -R 775 /u01 -# Copy scripts -#------------- -COPY container-scripts/createAndStartEmptyDomain.sh container-scripts/create-wls-domain.py /u01/oracle/ +# Environment variables required for this build (do NOT change) +# ------------------------------------------------------------- +ENV FMW_PKG=fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip \ + FMW_JAR=fmw_12.2.1.4.0_wls_quick.jar + +# Copy packages +# ------------- +COPY --chown=oracle:root $FMW_PKG install.file oraInst.loc /u01/ + +# Install +# ------------------------------------------------------------ +USER oracle +RUN cd /u01 && ${JAVA_HOME}/bin/jar xf /u01/$FMW_PKG && cd - && \ + ${JAVA_HOME}/bin/java -jar /u01/$FMW_JAR -silent -responseFile /u01/install.file -invPtrLoc /u01/oraInst.loc -jreLoc $JAVA_HOME -ignoreSysPrereqs -force -novalidation ORACLE_HOME=$ORACLE_HOME && \ + rm /u01/$FMW_JAR /u01/$FMW_PKG /u01/install.file && \ + rm -rf /u01/oracle/cfgtoollogs + +# Final image stage +FROM oracle/serverjre:8 + +ENV ORACLE_HOME=/u01/oracle \ + USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ + SCRIPT_FILE=/u01/oracle/createAndStartEmptyDomain.sh \ + HEALTH_SCRIPT_FILE=/u01/oracle/get_healthcheck_url.sh \ + PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin # Domain and Server environment variables # ------------------------------------------------------------ @@ -61,26 +87,26 @@ ENV DOMAIN_NAME="${DOMAIN_NAME:-base_domain}" \ ADMINISTRATION_PORT_ENABLED="${ADMINISTRATION_PORT_ENABLED:-true}" \ ADMINISTRATION_PORT="${ADMINISTRATION_PORT:-9002}" +# Setup filesystem and oracle user +# Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation +# ------------------------------------------------------------ +RUN mkdir -p /u01 && \ + chmod 775 /u01 && \ + useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle && \ + chown oracle:root /u01 +COPY --from=builder --chown=oracle:root /u01 /u01 -# Environment variables required for this build (do NOT change) -# ------------------------------------------------------------- -ENV FMW_PKG=fmw_12.2.1.3.0_wls_quick_Disk1_1of1.zip \ - FMW_JAR=fmw_12.2.1.3.0_wls_quick.jar +# Copy scripts +#------------- +COPY container-scripts/createAndStartEmptyDomain.sh container-scripts/create-wls-domain.py container-scripts/get_healthcheck_url.sh /u01/oracle/ -# Copy packages -# ------------- -COPY $FMW_PKG install.file oraInst.loc /u01/ -RUN chown oracle:oracle -R /u01 && \ - chmod +xr $SCRIPT_FILE +RUN chmod +xr $SCRIPT_FILE $HEALTH_SCRIPT_FILE && \ + chown oracle:root $SCRIPT_FILE /u01/oracle/create-wls-domain.py $HEALTH_SCRIPT_FILE -# Install -# ------------------------------------------------------------ USER oracle -RUN cd /u01 && ${JAVA_HOME}/bin/jar xf /u01/$FMW_PKG && cd - && \ - ${JAVA_HOME}/bin/java -jar /u01/$FMW_JAR -silent -responseFile /u01/install.file -invPtrLoc /u01/oraInst.loc -jreLoc $JAVA_HOME -ignoreSysPrereqs -force -novalidation ORACLE_HOME=$ORACLE_HOME && \ - rm /u01/$FMW_JAR /u01/$FMW_PKG /u01/oraInst.loc /u01/install.file +HEALTHCHECK --start-period=10s --timeout=30s --retries=3 CMD curl -k -s --fail `$HEALTH_SCRIPT_FILE` || exit 1 WORKDIR ${ORACLE_HOME} # Define default command to start script. diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile.generic b/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.generic similarity index 53% rename from smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile.generic rename to smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.generic index cbe0d3ac3b3e966069c88153bc85299f833ef4a5..c5a4b58ac3360d5a22aad3b16d4e8877ca73e1ea 100644 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/Dockerfile.generic +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.generic @@ -1,14 +1,14 @@ -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. +#Copyright (c) 2014, 2020, Oracle and/or its affiliates. # -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. # # ORACLE DOCKERFILES PROJECT # -------------------------- -# This is the Dockerfile for Oracle WebLogic Server 12.2.1.3 Generic Distro +# This is the Dockerfile for Oracle WebLogic Server 12.2.1.4 Generic Distro # # REQUIRED FILES TO BUILD THIS IMAGE # ---------------------------------- -# (1) fmw_12.2.1.3.0_wls_Disk1_1of1.zip +# (1) fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip # Download the Generic installer from http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html # # (2) server-jre-8uXX-linux-x64.tar.gz @@ -18,67 +18,93 @@ # ----------------------- # Put all downloaded files in the same directory as this Dockerfile # Run: -# $ docker build -f Dockerfile.generic -t oracle/weblogic:12.2.1.3-generic . +# $ docker build -f Dockerfile.generic -t oracle/weblogic:12.2.1.4-generic . # # IMPORTANT # --------- # The resulting image of this Dockerfile contains a WLS Empty Domain. # -# Pull base image -# From the Oracle Registry -# ------------------------- -FROM oracle/serverjre:8 +# Extend base JRE image +# You must build the image by using the Dockerfile in GitHub project `../../../OracleJava/java8` +# ---------------------------------------------------------------------------------------------- +FROM oracle/serverjre:8 as builder -# Maintainer -# ---------- -MAINTAINER Monica Riccelli <monica.riccelli@oracle.com> +# Labels +# ------ +LABEL "provider"="Oracle" \ + "maintainer"="Monica Riccelli <monica.riccelli@oracle.com>" \ + "issues"="https://github.com/oracle/docker-images/issues" \ + "port.admin.listen"="7001" \ + "port.administration"="9002" # Common environment variables required for this build (do NOT change) # -------------------------------------------------------------------- ENV ORACLE_HOME=/u01/oracle \ USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ - SCRIPT_FILE=/u01/oracle/createAndStartEmptyDomain.sh \ PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin # Setup filesystem and oracle user # Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation # ------------------------------------------------------------ -RUN mkdir -p /u01 && \ - chmod a+xr /u01 && \ - useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle - -# Copy scripts -#------------- -COPY container-scripts/createAndStartEmptyDomain.sh container-scripts/create-wls-domain.py /u01/oracle/ - -# Domain and Server environment variables -# ------------------------------------------------------------ -ENV DOMAIN_NAME="${DOMAIN_NAME:-base_domain}" \ - ADMIN_LISTEN_PORT="${ADMIN_LISTEN_PORT:-7001}" \ - ADMIN_NAME="${ADMIN_NAME:-AdminServer}" \ - ADMINISTRATION_PORT_ENABLED="${ADMINISTRATION_PORT_ENABLED:-true}" \ - ADMINISTRATION_PORT="${ADMINISTRATION_PORT:-9002}" +RUN mkdir /u01 && \ + useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle && \ + chown oracle:root -R /u01 && \ + chmod -R 775 /u01 # Environment variables required for this build (do NOT change) # ------------------------------------------------------------- -ENV FMW_PKG=fmw_12.2.1.3.0_wls_Disk1_1of1.zip \ - FMW_JAR=fmw_12.2.1.3.0_wls.jar +ENV FMW_PKG=fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip \ + FMW_JAR=fmw_12.2.1.4.0_wls_lite_generic.jar # Copy packages # ------------- -COPY $FMW_PKG install.file oraInst.loc /u01/ -RUN chown oracle:oracle -R /u01 && \ - chmod +xr $SCRIPT_FILE +COPY --chown=oracle:root $FMW_PKG install.file oraInst.loc /u01/ # Install # ------------------------------------------------------------ USER oracle RUN cd /u01 && ${JAVA_HOME}/bin/jar xf /u01/$FMW_PKG && cd - && \ - ls /u01 && \ ${JAVA_HOME}/bin/java -jar /u01/$FMW_JAR -silent -responseFile /u01/install.file -invPtrLoc /u01/oraInst.loc -jreLoc $JAVA_HOME -ignoreSysPrereqs -force -novalidation ORACLE_HOME=$ORACLE_HOME INSTALL_TYPE="WebLogic Server" && \ - rm /u01/$FMW_JAR /u01/$FMW_PKG /u01/oraInst.loc /u01/install.file + rm /u01/$FMW_JAR /u01/$FMW_PKG /u01/install.file && \ + rm -rf /u01/oracle/cfgtoollogs + +# Final image stage +FROM oracle/serverjre:8 + +ENV ORACLE_HOME=/u01/oracle \ + USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ + SCRIPT_FILE=/u01/oracle/createAndStartEmptyDomain.sh \ + HEALTH_SCRIPT_FILE=/u01/oracle/get_healthcheck_url.sh \ + PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin + +# Domain and Server environment variables +# ------------------------------------------------------------ +ENV DOMAIN_NAME="${DOMAIN_NAME:-base_domain}" \ + ADMIN_LISTEN_PORT="${ADMIN_LISTEN_PORT:-7001}" \ + ADMIN_NAME="${ADMIN_NAME:-AdminServer}" \ + ADMINISTRATION_PORT_ENABLED="${ADMINISTRATION_PORT_ENABLED:-true}" \ + ADMINISTRATION_PORT="${ADMINISTRATION_PORT:-9002}" + +# Setup filesystem and oracle user +# Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation +# ------------------------------------------------------------ +RUN mkdir -p /u01 && \ + chmod 775 /u01 && \ + useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle && \ + chown oracle:root /u01 + +COPY --from=builder --chown=oracle:root /u01 /u01 +# Copy scripts +#------------- +COPY container-scripts/createAndStartEmptyDomain.sh container-scripts/create-wls-domain.py container-scripts/get_healthcheck_url.sh /u01/oracle/ + +RUN chmod +xr $SCRIPT_FILE $HEALTH_SCRIPT_FILE && \ + chown oracle:root $SCRIPT_FILE /u01/oracle/create-wls-domain.py $HEALTH_SCRIPT_FILE + +USER oracle +HEALTHCHECK --start-period=10s --timeout=30s --retries=3 CMD curl -k -s --fail `$HEALTH_SCRIPT_FILE` || exit 1 WORKDIR ${ORACLE_HOME} # Define default command to start script. diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.slim b/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.slim new file mode 100644 index 0000000000000000000000000000000000000000..96ecb17b201a71823feaf061852a3e4d3dc08593 --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/Dockerfile.slim @@ -0,0 +1,114 @@ +#Copyright (c) 2014, 2020, Oracle and/or its affiliates. +# +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. +# +# ORACLE DOCKERFILES PROJECT +# -------------------------- +# This is the Dockerfile for Oracle WebLogic Server 12.2.1.4 Slim Distro +# +# REQUIRED FILES TO BUILD THIS IMAGE +# ---------------------------------- +# (1) fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip +# Download the Slim installer from http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +# (2) server-jre-8uXX-linux-x64.tar.gz +# Download from http://www.oracle.com/technetwork/java/javase/downloads/server-jre8-downloads-2133154.html +# +# HOW TO BUILD THIS IMAGE +# ----------------------- +# Put all downloaded files in the same directory as this Dockerfile +# Run: +# $ docker build -f Dockerfile.slim -t oracle/weblogic:12.2.1.4-slim . +# +# IMPORTANT +# --------- +# The resulting image of this Dockerfile contains a WLS Empty Domain. +# +# Extend base JRE image +# You must build the image by using the Dockerfile in GitHub project `../../../OracleJava/java8` +# ---------------------------------------------------------------------------------------------- +FROM oracle/serverjre:8 as builder + +# Labels +# ------ +LABEL "provider"="Oracle" \ + "maintainer"="Monica Riccelli <monica.riccelli@oracle.com>" \ + "issues"="https://github.com/oracle/docker-images/issues" \ + "port.admin.listen"="7001" \ + "port.administration"="9002" + +# Common environment variables required for this build (do NOT change) +# -------------------------------------------------------------------- +ENV ORACLE_HOME=/u01/oracle \ + USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ + PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin + +# Setup filesystem and oracle user +# Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation +# ------------------------------------------------------------ +RUN mkdir /u01 && \ + useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle && \ + chown oracle:root -R /u01 && \ + chmod -R 775 /u01 + +# Environment variables required for this build (do NOT change) +# ------------------------------------------------------------- +ENV FMW_PKG=fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip \ + FMW_JAR=fmw_12.2.1.4.0_wls_quick_slim.jar + +# Copy packages +# ------------- +COPY --chown=oracle:root $FMW_PKG install.file oraInst.loc /u01/ + +# Install +# ------------------------------------------------------------ +USER oracle + +RUN cd /u01 && ${JAVA_HOME}/bin/jar xf /u01/$FMW_PKG && cd - && \ + ${JAVA_HOME}/bin/java -jar /u01/$FMW_JAR -silent -responseFile /u01/install.file -invPtrLoc /u01/oraInst.loc -jreLoc $JAVA_HOME -ignoreSysPrereqs -force -novalidation ORACLE_HOME=$ORACLE_HOME && \ + rm /u01/$FMW_JAR /u01/$FMW_PKG /u01/install.file && \ + rm -rf /u01/oracle/cfgtoollogs + +# Final image stage +FROM oracle/serverjre:8 + +ENV ORACLE_HOME=/u01/oracle \ + USER_MEM_ARGS="-Djava.security.egd=file:/dev/./urandom" \ + SCRIPT_FILE=/u01/oracle/createAndStartEmptyDomain.sh \ + HEALTH_SCRIPT_FILE=/u01/oracle/get_healthcheck_url.sh \ + PATH=$PATH:${JAVA_HOME}/bin:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin + +# Domain and Server environment variables +# ------------------------------------------------------------ +ENV DOMAIN_NAME="${DOMAIN_NAME:-base_domain}" \ + ADMIN_LISTEN_PORT="${ADMIN_LISTEN_PORT:-7001}" \ + ADMIN_NAME="${ADMIN_NAME:-AdminServer}" \ + ADMINISTRATION_PORT_ENABLED="${ADMINISTRATION_PORT_ENABLED:-true}" \ + ADMINISTRATION_PORT="${ADMINISTRATION_PORT:-9002}" \ + DEBUG_flag=false \ + PRODUCTION_MODE=prod + +# Setup filesystem and oracle user +# Adjust file permissions, go to /u01 as user 'oracle' to proceed with WLS installation +# ------------------------------------------------------------ +RUN mkdir -p /u01 && \ + chmod 775 /u01 && \ + useradd -b /u01 -d /u01/oracle -m -s /bin/bash oracle && \ + chown oracle:root /u01 + +COPY --from=builder --chown=oracle:root /u01 /u01 + +# Copy scripts +#------------- +COPY container-scripts/createAndStartEmptyDomain.sh container-scripts/create-wls-domain.py container-scripts/get_healthcheck_url.sh /u01/oracle/ + +RUN chmod +xr $SCRIPT_FILE $HEALTH_SCRIPT_FILE && \ + chown oracle:root $SCRIPT_FILE /u01/oracle/create-wls-domain.py $HEALTH_SCRIPT_FILE + +USER oracle + +HEALTHCHECK --start-period=10s --timeout=30s --retries=3 CMD curl -k -s --fail `$HEALTH_SCRIPT_FILE` || exit 1 +WORKDIR ${ORACLE_HOME} + +# Define default command to start script. +CMD ["/u01/oracle/createAndStartEmptyDomain.sh"] diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/README.md b/smp-docker/images/oracle/weblogic-12.2.1.4/README.md new file mode 100644 index 0000000000000000000000000000000000000000..88802108cac1535ecc74b28fdfdf15b843b0528f --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/README.md @@ -0,0 +1,99 @@ +Oracle WebLogic Server on Docker +================================= +These Docker configurations have been used to create the Oracle WebLogic Server (WLS) image. Providing this WLS image facilitates the configuration and environment setup for DevOps users. This project includes the installation and creation of an empty WebLogic Server domain (an Administration Server only). These Oracle WebLogic Server 12.2.1.4 images are based on Oracle Linux and Oracle JRE 8 (Server). + +The WebLogic Server install image allows you to run a WebLogic multi-server domain/cluster or a WebLogic single server domain. This makes it simple to deploy applications and any resources the application might need. + +The certification of Oracle WebLogic Server on Docker does not require the use of any file presented in this repository. Customers and users are welcome to use them as starters, and customize, tweak, or create from scratch, new scripts and Dockerfiles. + +For more information on the certification, please see the [Oracle WebLogic Server on Docker certification whitepaper](http://www.oracle.com/technetwork/middleware/weblogic/overview/weblogic-server-docker-containers-2491959.pdf) and [The WebLogic Server Blog](https://blogs.oracle.com/WebLogicServer/) for updates. + +## How to build and run +This project offers sample Dockerfiles for Oracle WebLogic Server 12cR2 (12.2.1.4). It provides at least one Dockerfile for the `developer` distribution, a second Dockerfile for the `generic` distribution, and a third Dockerfile for the `slim` distribution. + +1- The WebLogic `generic` image is supported for `development` and `production` deployment of WebLogic configurations using Docker. It contains the same binaries as those installed by the WebLogic generic installer. The WebLogic generic image is primarily intended for WebLogic domains managed with the WebLogic Kubernetes Operator, when WLS console-based monitoring, and possibly configuration, is required. All servers within a domain managed with the Operator will use the same WebLogic image. Support is also provided for environments where Kubernetes and/or the WebLogic Kubernetes Operator is not being used. + +2- The WebLogic `slim` image is supported for `development` and `production` deployment of WebLogic configurations using Docker. In order to reduce image size, it contains a subset of the binaries included in the WebLogic generic image. The WebLogic console, WebLogic examples, WebLogic clients, Maven plug-ins and Java DB have been removed - all binaries that remain included are the same as those in the WebLogic generic image. The WebLogic slim image is primarily intended for WebLogic domains managed with the WebLogic Kubernetes Operator, when WLS console-based monitoring and configuration is not required, and a smaller image size than the generic image is preferred. If there are requirements to monitor the WebLogic configuration, they should be addressed using Prometheus and Grafana or other alternatives. All servers within a domain managed with the Operator will use the same WebLogic image. Support is also provided for environments where Kubernetes and/or the WebLogic Kubernetes Operator is not being used. + +3- The WebLogic `developer` image is supported for `development` of WebLogic applications in Docker containers. In order to reduce image size, it contains a subset of the binaries included in the WebLogic generic image. WebLogic examples and WLS Console help files have been removed - all binaries that remain included are the same as those in the WebLogic generic image. The WebLogic developer image is primarily intended to provide a Docker image that is consistent with the WebLogic "quick installers" intended for `development` only. Production WebLogic domains should use the WebLogic generic or WebLogic slim images. + + +To assist in building the images, you can use the [`buildDockerImage.sh`](dockerfiles/buildDockerImage.sh) script. See below for instructions and usage. + +The `buildDockerImage.sh` script is a utility shell script that performs MD5 checks and is an easy way for beginners to get started. Expert users are welcome to directly call `docker build` with their prefered set of parameters. + +### Building Oracle WebLogic Server Docker install images +**IMPORTANT:** You must download the binary of Oracle WebLogic Server and put it in place (see `.download` files inside `dockerfiles/<version>`). The WebLogic image extends the Oracle JRE Server 8 image. You must build the image by using the Dockerfile in [`../../../OracleJava/java8`](https://github.com/oracle/docker-images/tree/master/OracleJava/java-8). + +**NOTE:** The Dockerfiles install WebLogic with `INSTALL_TYPE=WebLogic Server` which does not include the WebLogic samples in the installation. If you want to include the WebLogic samples you need to change `INSTALL_TYPE=Complete with Examples` in the install.file. + +Before you build, select the version and distribution for which you want to build an image, then download the required packages (see `.download` files) and locate them in the folder of your distribution version of choice. Then, from the `dockerfiles` folder, run the `buildDockerImage.sh` script as root. + + $ sh buildDockerImage.sh + Usage: buildDockerImage.sh -v [version] [-d | -g | -m] [-s] + Builds a Docker Image for Oracle WebLogic Server. + + Parameters: + -v: version to build. Required. + Choose : 12.2.1.4 + -d: creates image based on 'developer' distribution + -g: creates image based on 'generic' distribution + -m: creates image based on 'slim' distribution + -c: enables Docker image layer cache during build + -s: skips the MD5 check of packages + + * select one distribution only: -d, -g, or -m + + LICENSE UPL 1.0 + + Copyright (c) 2014-2019 Oracle and/or its affiliates. All rights reserved. + +**IMPORTANT:** The resulting images will have a single server domain (Administration Server only), by default. + + + 1. To build the `12.2.1.4`image, from `dockerfiles`, call: + + `$ sh buildDockerImage.sh -v 12.2.1.4 -d` + + 2. Verify that you now have this image in place with: + + `$ docker images` + +### Running a single server domain from the image +The WebLogic Server install image (built above) allows you to run a container with a single WebLogic Server domain. This makes it extremely simple to deploy applications and any resource the application might need. + +#### Providing the Administration Server user name and password +The user name and password must be supplied in a `domain.properties` file located in a HOST directory that you will map at Docker runtime with the `-v` option to the image directory `/u01/oracle/properties`. The properties file enables the scripts to configure the correct authentication for the WebLogic Server Administration Server. + +The format of the `domain.properties` file is key=value pair: + + username=myadminusername + password=myadminpassword + +**Note**: Oracle recommends that the `domain.properties` file be deleted or secured after the container and the WebLogic Server are started so that the user name and password are not inadvertently exposed. + +#### Start the container +Start a container from the image created in step 1. +You can override the default values of the following parameters during runtime with the `-e` option: + + * `ADMIN_NAME` (default: `AdminServer`) + * `ADMIN_LISTEN_PORT` (default: `7001`) + * `DOMAIN_NAME` (default: `base_domain`) + * `ADMINISTRATION_PORT_ENABLED` (default: `true`) + * `ADMINISTRATION_PORT` (default: `9002`) + +**NOTE**: For security, the Administration port 9002 is enabled by default, before running the container in WebLogic 12.2.1.4. If you prefer to not enable the Administration port when you issue the `docker run` command, set `ADMINISTRTATION_PORT_ENABLED` to false. If you intend to run these images in production, then you must change the Production Mode to `production`. When you set the `DOMAIN_NAME`, the `DOMAIN_HOME=/u01/oracle/user_projects/domains/$DOMAIN_NAME`. + + $ docker run -d -p 7001:7001 -p 9002:9002 -v `HOST PATH where the domain.properties file is`:/u01/oracle/properties -e ADMINISTRATION_PORT_ENABLED=true -e DOMAIN_NAME=docker_domain -e ADMIN_NAME=docker-AdminServer oracle/weblogic:12.2.1.4-developer + +Run the WLS Administration Console: + + $ docker inspect --format '{{.NetworkSettings.IPAddress}}' <container-name> + +In your browser, enter `https://xxx.xx.x.x:9002/console`. Your browser will request that you accept the Security Exception. To avoid the Security Exception, you must update the WebLogic Server SSL configuration with a custom identity certificate. + +## Samples for Oracle WebLogic Server domain creation +To give users an idea of how to create a WebLogic domain and cluster from a custom Dockerfile which extends the WebLogic Server install image, we provide a few samples for 12c versions of the developer distribution. For an example, look at the `12214-domain` sample. + +## Copyright +Copyright (c) 2014, 2022, Oracle and/or its affiliates. diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/container-scripts/create-wls-domain.py b/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/create-wls-domain.py similarity index 94% rename from smp-docker/images/oracle/weblogic-12.2.1.3/container-scripts/create-wls-domain.py rename to smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/create-wls-domain.py index a7c6cac5ff65c2870e96d6c9fca2ea5782e18223..3ebe5004ac289db4d8b493e2b5c82ea3a542c940 100644 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/container-scripts/create-wls-domain.py +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/create-wls-domain.py @@ -1,6 +1,6 @@ -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. +#Copyright (c) 2014, 2022, Oracle and/or its affiliates. # -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. # # WebLogic on Docker Default Domain # @@ -49,8 +49,8 @@ set('Name', admin_name) set('ListenAddress', '') set('ListenPort', admin_listen_port) if administration_port_enabled != "false": - create('AdminServer','SSL') - cd('SSL/AdminServer') + create(admin_name, 'SSL') + cd('SSL/' + admin_name) set('Enabled', 'True') # Define the user password for weblogic diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/container-scripts/createAndStartEmptyDomain.sh b/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/createAndStartEmptyDomain.sh similarity index 70% rename from smp-docker/images/oracle/weblogic-12.2.1.3/container-scripts/createAndStartEmptyDomain.sh rename to smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/createAndStartEmptyDomain.sh index d17d140167615abb30e30b721ea467e86c1fe0ad..d5eb9a79adfda1af8f23deb038aa471886acedf1 100755 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/container-scripts/createAndStartEmptyDomain.sh +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/createAndStartEmptyDomain.sh @@ -1,12 +1,9 @@ #!/bin/bash # -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2022, Oracle and/or its affiliates. # -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. -#Define DOMAIN_HOME -export DOMAIN_HOME=/u01/oracle/user_projects/domains/$DOMAIN_NAME -echo "Domain Home is: " $DOMAIN_HOME # If AdminServer.log does not exists, container is starting for 1st time # So it should start NM and also associate with AdminServer @@ -30,14 +27,14 @@ trap _term SIGTERM # Set SIGKILL handler trap _kill SIGKILL -ADD_DOMAIN=1 -if [ ! -f ${DOMAIN_HOME}/servers/AdminServer/logs/AdminServer.log ]; then - ADD_DOMAIN=0 -fi +#Define DOMAIN_HOME +export DOMAIN_HOME=/u01/oracle/user_projects/domains/$DOMAIN_NAME +echo "Domain Home is: " $DOMAIN_HOME mkdir -p $ORACLE_HOME/properties # Create Domain only if 1st execution -if [ $ADD_DOMAIN -eq 0 ]; then +if [ ! -e ${DOMAIN_HOME}/servers/${ADMIN_NAME}/logs/${ADMIN_NAME}.log ]; then + echo "Create Domain" PROPERTIES_FILE=/u01/oracle/properties/domain.properties if [ ! -e "$PROPERTIES_FILE" ]; then echo "A properties file with the username and password needs to be supplied." @@ -59,16 +56,20 @@ if [ $ADD_DOMAIN -eq 0 ]; then # Create an empty domain wlst.sh -skipWLSModuleScanning -loadProperties $PROPERTIES_FILE /u01/oracle/create-wls-domain.py - mkdir -p ${DOMAIN_HOME}/servers/AdminServer/security/ - echo "username=${USER}" >> $DOMAIN_HOME/servers/AdminServer/security/boot.properties - echo "password=${PASS}" >> $DOMAIN_HOME/servers/AdminServer/security/boot.properties + mkdir -p ${DOMAIN_HOME}/servers/${ADMIN_NAME}/security/ + chmod -R g+w ${DOMAIN_HOME} + echo "username=${USER}" >> $DOMAIN_HOME/servers/${ADMIN_NAME}/security/boot.properties + echo "password=${PASS}" >> $DOMAIN_HOME/servers/${ADMIN_NAME}/security/boot.properties ${DOMAIN_HOME}/bin/setDomainEnv.sh fi # Start Admin Server and tail the logs ${DOMAIN_HOME}/startWebLogic.sh -touch ${DOMAIN_HOME}/servers/AdminServer/logs/AdminServer.log -tail -f ${DOMAIN_HOME}/servers/AdminServer/logs/AdminServer.log & +if [ -e ${DOMAIN_HOME}/servers/${ADMIN_NAME}/logs/${ADMIN_NAME}.log ]; then + echo "${DOMAIN_HOME}/servers/${ADMIN_NAME}/logs/${ADMIN_NAME}.log" +fi +touch ${DOMAIN_HOME}/servers/${ADMIN_NAME}/logs/${ADMIN_NAME}.log +tail -f ${DOMAIN_HOME}/servers/${ADMIN_NAME}/logs/${ADMIN_NAME}.log childPID=$! wait $childPID diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/get_healthcheck_url.sh b/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/get_healthcheck_url.sh new file mode 100755 index 0000000000000000000000000000000000000000..4a2feef442d593949679c3306bbb558e8da7fd9b --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/container-scripts/get_healthcheck_url.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +#Copyright (c) 2020, Oracle and/or its affiliates. +# +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +if [ "$ADMINISTRATION_PORT_ENABLED" = "true" ] ; then + echo "https://{localhost:$ADMINISTRATION_PORT}/weblogic/ready" ; +else + echo "http://{localhost:$ADMIN_LISTEN_PORT}/weblogic/ready" ; +fi diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip.download b/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip.download new file mode 100644 index 0000000000000000000000000000000000000000..b64667e262f5389dd339bcb5129a3e83441142c6 --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip.download @@ -0,0 +1,5 @@ +# Download WebLogic Server Generic Installer 12.2.1.4 +# +# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +650109d90ab3da8f7a64b24a98e072b3 fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip.download b/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip.download new file mode 100644 index 0000000000000000000000000000000000000000..2fd58b5e8007f9d5def024c72dbd11fb88af152c --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip.download @@ -0,0 +1,5 @@ +# Download WebLogic Server Quick Installer 12.2.1.4 +# +# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +8f88d91600ecb6826a91a3b72f832c6a fmw_12.2.1.4.0_wls_quick_Disk1_1of1.zip diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip.download b/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip.download new file mode 100644 index 0000000000000000000000000000000000000000..9d4e14a6735721897c59f95cf42281fe017ff85b --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip.download @@ -0,0 +1,5 @@ +# Download WebLogic Server Slim Installer 12.2.1.4 +# +# - http://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html +# +df55639e00817308be249af5aa602bfb fmw_12.2.1.4.0_wls_quick_slim_Disk1_1of1.zip diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/install.file b/smp-docker/images/oracle/weblogic-12.2.1.4/install.file similarity index 76% rename from smp-docker/images/oracle/weblogic-12.2.1.3/install.file rename to smp-docker/images/oracle/weblogic-12.2.1.4/install.file index 23fdba63a87bd0fcbeff5b68978fee4ec81922fc..0ca964dbf486fa1485c1be66d3eaf92048ecf0ef 100644 --- a/smp-docker/images/oracle/weblogic-12.2.1.3/install.file +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/install.file @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014-2019 Oracle and/or its affiliates. All rights reserved. [ENGINE] #DO NOT CHANGE THIS. diff --git a/smp-docker/images/oracle/weblogic-12.2.1.3/oraInst.loc b/smp-docker/images/oracle/weblogic-12.2.1.4/oraInst.loc similarity index 100% rename from smp-docker/images/oracle/weblogic-12.2.1.3/oraInst.loc rename to smp-docker/images/oracle/weblogic-12.2.1.4/oraInst.loc diff --git a/smp-docker/images/oracle/weblogic-12.2.1.4/properties/domain.properties b/smp-docker/images/oracle/weblogic-12.2.1.4/properties/domain.properties new file mode 100644 index 0000000000000000000000000000000000000000..e4db42348c1bc4db7b2d3881434970640e3b4418 --- /dev/null +++ b/smp-docker/images/oracle/weblogic-12.2.1.4/properties/domain.properties @@ -0,0 +1,2 @@ +username=myweblogicuser +password=welcome1 diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/Dockerfile b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/Dockerfile deleted file mode 100644 index 2acf724ef3cbe9b73285de502678d48194c1294d..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/Dockerfile +++ /dev/null @@ -1,76 +0,0 @@ -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. -# -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -# ORACLE DOCKERFILES PROJECT -# -------------------------- -# This Dockerfile extends the Oracle WebLogic image by creating a sample domain. -# -# Util scripts are copied into the image enabling users to plug NodeManager -# automatically into the AdminServer running on another container. -# -# HOW TO BUILD THIS IMAGE -# ----------------------- -# Put all downloaded files in the same directory as this Dockerfile -# Run: -# $ sudo docker build -t 12213-domain-home-image -# -# Pull base image -# --------------- -FROM oracle/weblogic:12.2.1.3-developer - -# Maintainer -# ---------- -MAINTAINER Monica Riccelli <monica.riccelli@oracle.com> - -ARG CUSTOM_DOMAIN_NAME="${CUSTOM_DOMAIN_NAME:-domain1}" -ARG CUSTOM_ADMIN_PORT="${CUSTOM_ADMIN_PORT:-7001}" -ARG CUSTOM_MANAGED_SERVER_PORT="${CUSTOM_MANAGED_SERVER_PORT:-8001}" -ARG CUSTOM_DEBUG_PORT="${CUSTOM_DEBUG_PORT:-8453}" -ARG CUSTOM_ADMIN_NAME="${CUSTOM_ADMIN_NAME:-admin-server}" -ARG CUSTOM_ADMIN_HOST="${CUSTOM_ADMIN_HOST:-wlsadmin}" -ARG CUSTOM_CLUSTER_NAME="${CUSTOM_CLUSTER_NAME:-DockerCluster}" - -# WLS Configuration -# --------------------------- -ENV ORACLE_HOME=/u01/oracle \ - PROPERTIES_FILE_DIR="/u01/oracle/properties" \ - DOMAIN_NAME="${CUSTOM_DOMAIN_NAME}" \ - DOMAIN_HOME="/u01/oracle/user_projects/domains/${CUSTOM_DOMAIN_NAME}" \ - ADMIN_PORT="${CUSTOM_ADMIN_PORT}" \ - ADMIN_NAME="${CUSTOM_ADMIN_NAME}" \ - ADMIN_HOST="${CUSTOM_ADMIN_HOST}" \ - CLUSTER_NAME="${CUSTOM_CLUSTER_NAME}" \ - MANAGED_SERVER_PORT="${CUSTOM_MANAGED_SERVER_PORT}" \ - MANAGED_SERV_NAME="${MANAGED_SERV_NAME}" \ - DEBUG_PORT="8453" \ - PATH=$PATH:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin:${DOMAIN_HOME}:${DOMAIN_HOME}/bin:/u01/oracle - -# Add files required to build this image -COPY container-scripts/* /u01/oracle/ - -#Create directory where domain will be written to -USER root -RUN chmod +xw /u01/oracle/*.sh && \ - chmod +xw /u01/oracle/*.py && \ - mkdir -p ${PROPERTIES_FILE_DIR} && \ - chown -R oracle:oracle ${PROPERTIES_FILE_DIR} && \ - mkdir -p $DOMAIN_HOME && \ - chown -R oracle:oracle $DOMAIN_HOME && \ - chmod -R a+xwr $DOMAIN_HOME - -COPY properties/docker-build/domain*.properties ${PROPERTIES_FILE_DIR}/ - -# Configuration of WLS Domain -RUN /u01/oracle/createWLSDomain.sh && \ - echo ". $DOMAIN_HOME/bin/setDomainEnv.sh" >> /u01/oracle/.bashrc && \ - chmod -R a+x $DOMAIN_HOME/bin/*.sh && \ - rm ${PROPERTIES_FILE_DIR}/*.properties - -# Expose ports for admin, managed server, and debug -EXPOSE $ADMIN_PORT $MANAGED_SERVER_PORT $DEBUG_PORT - -WORKDIR $DOMAIN_HOME - -# Define default command to start bash. -CMD ["startAdminServer.sh"] diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/README.md b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/README.md deleted file mode 100644 index e9a731ed52adaa3aac4ce93d07da7a93a1ec9567..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/README.md +++ /dev/null @@ -1,92 +0,0 @@ -Example Image with a WebLogic Server Domain -============================================= -This Dockerfile extends the Oracle WebLogic image by creating a sample WebLogic Server 12.2.1.3 domain and cluster into a Docker image. - -A domain is created inside the image and utility scripts are copied into the image, enabling users to start an Administration Server and a Managed Server, each running in separate containers. - -**Note:** In this sample, the WebLogic Servers are configured with a blank listen address; when running JTA transactions, you must use a DNS server to configure the listen addresses to use DNS names. - -## Providing the Administration Server user name and password - - -**During Docker Build:** The user name and password must be supplied in the `domain_security.properties` file. The property file is located in the directory `docker-images/OracleWebLogic/samples/12213-domain-home-in-image/properties/docker_build` in the HOST. This property file gets copied into the image directory `/u01/oracle/properties`. - -**During Docker Run:** The user name and password must be supplied in a `security.properties` file. The property file is located in a `docker-images/OracleWebLogic/samples/12213-domain-home-in-image/properties/docker_run` directory in the HOST. On the Docker run command line, the `-v` option maps the property file into the image directory `/u01/oracle/properties`. - - -The security property files enable the scripts to configure the correct authentication for the WebLogic Administration Server and Managed Servers. The format of the `security.properties` and `domain_security.properties` files are key=value pairs, for example: - - username=myadminusername - password=myadminpassword - -**Note:** Oracle recommends that the `domain_security.properties` and `security.properties` files be deleted or secured after the container and the WebLogic Server are started so that the user name and password are not inadvertently exposed. - -## How to Build and Run -At build time, the `domain.properties` file is used to pass in the Docker arguments and configuration parameters for the WebLogic domain. - - -**During Docker Build:** The domain configuration parameters must be supplied in the `domain.properties` file. This file is located in the directory `properties/docker_build` in the HOST. This property file gets copied into the image directory `/u01/oracle/properties`. - - -The domain property file enables you to customize the parameters to configure the WebLogic domain. The format of the `domain.properties` are key=value pairs, for example: - - ADMIN_NAME=admin-server - ADMIN_HOST=wlsadmin - MANAGED_SERVER_NAME_BASE=managed-server - CONFIGURED_MANAGED_SERVER_COUNT=2 - CLUSTER_NAME=cluster-1 - DEBUG_FLAG=true - PRODUCTION_MODE_ENABLED=true - CLUSTER_TYPE=DYNAMIC - CLUSTER_NAME=cluster1 - -**NOTE:** Before invoking the build make sure you have built `oracle/weblogic:12.2.1.3-developer`. - - -Under the directory `docker-images/OracleWebLogic/samples/12213-domain-home-in-image/container_scripts` find the script `setEnv.sh`. This script extracts the following Docker arguments and passes them as a `--build-arg` to the Dockerfile. - - -* Domain Name: `DOMAIN_NAME` (default: `base_domain`) -* Admin Port: `ADMIN_PORT` (default: `7001`) -* Managed Server Port: `MANAGED_SERVER_PORT` (default: `8001`) -* Debug Port: `DEBUG_PORT` (default: `8453`) -* Database Port: `DB_PORT` (default: `1527`) -* Admin Server Name: `ADMIN_NAME` (default: `admin-server`) -* Admin Server Host: `ADMIN_HOST` (default: `wlsadmin`) -* Cluster Name: `CLUSTER_NAME` (default: `cluster1`) - -**NOTE:** The `DOMAIN_HOME` will be persisted in the image directory `/u01/oracle/user-projects/domains/$DOMAIN_NAME`. - -To build this sample, run: - - $ . container-scripts/setEnv.sh ./properties/docker-build/domain.properties - $ docker build $BUILD_ARG --force-rm=true -t 12213-domain-home-in-image . - - -**During Docker Run:** of the Administration and Managed Servers, the user name and password need to be passed in as well as some optional parameters. The property file is located in a `docker-images/OracleWebLogic/samples/12213-domain-home-in-image/properties/docker_run` in the HOST. On the Docker run command line, add the `-v` option which maps the property file into the image directory `/u01/oracle/properties`. - - -To start the containerized Administration Server, run: - - $ docker run -d --name wlsadmin --hostname wlsadmin -p 7001:7001 \ - -v <HOST DIRECTORY TO PROPERTIES FILE>/properties/docker-run:/u01/oracle/properties \ - 12213-domain-home-in-image - -To start a containerized Managed Server (MS1) to self-register with the Administration Server above, run: - - $ docker run -d --name MS1 --link wlsadmin:wlsadmin -p 8001:8001 \ - -v <HOST DIRECTORY TO PROPERTIES FILE>/properties/docker-run:/u01/oracle/properties \ - -e MANAGED_SERV_NAME=managed-server1 12213-domain-home-in-image startManagedServer.sh - -To start a second Managed Server (MS2), run: - - $ docker run -d --name MS2 --link wlsadmin:wlsadmin -p 8002:8001 \ - -v <HOST DIRECTORY TO PROPERTIES FILE>/properties/docker-run:/u01/oracle/properties \ - -e MANAGED_SERV_NAME=managed-server2 12213-domain-home-in-image startManagedServer.sh - -The above scenario from this sample will give you a WebLogic domain with a cluster set up on a single host environment. - -You may create more containerized Managed Servers by calling the `docker` command above - -# Copyright -Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/build.sh b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/build.sh deleted file mode 100755 index 3678ff1a4ae702a5e7a2cbd3ec7ec2c67f4e78cd..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. -# -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -# TAG_NAME - Tag the docker image with this name. This overrides the default of -# 12213-domain-home-in-imag:latest. -# -# There are three ways to tag the domain home image using this build script. -# -# . Do nothing and the image will be tagged with the default name. -# . Add an IMAGE_TAG variable to the properties file and allow the -# setEnv.sh to manage the tag. Overrides the default tag. -# . Set the TAG_NAME environment variable. Overrides the default tag. -# - - -# Determine the tag name for the resulting image using the value in the TAG_NAME. -# The setEnv.sh will set the TAG_NAME variable if the property is found in the -# properties file. This function should be called after the setEnv.sh is run -tag_name() { - tagName=${CUSTOM_IMAGE_TAG:-"12213-domain-home-in-image:latest"} - echo "CUSTOM_IMAGE_TAG ${tagName} " -} - -# The location where the script is running will be used as the Context for -# the docker build Dockerfile commands -set_context() { - scriptDir="$( cd "$( dirname "$0" )" && pwd )" - if [ ! -d "${scriptDir}" ]; then - echo "Unable to determine the working directory for the domain home in image sample" - echo "Using shell /bin/sh to determine and found ${scriptDir}" - clean_and_exit - fi - echo "Context for docker build is ${scriptDir}" -} - -set_context -. ${scriptDir}/container-scripts/setEnv.sh ${scriptDir}/properties/docker-build/domain.properties - -tag_name -docker build $BUILD_ARG -t ${tagName} ${scriptDir} diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/createWLSDomain.sh b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/createWLSDomain.sh deleted file mode 100755 index ec83e4babbb546629234b33e56f2f99934ca8de8..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/createWLSDomain.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash -# -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. -# -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. - -#Define DOMAIN_HOME -echo "Domain Home is: " $DOMAIN_HOME - -# If AdminServer.log does not exists, container is starting for 1st time -# So it should start NM and also associate with AdminServer -# Otherwise, only start NM (container restarted) -########### SIGTERM handler ############ -function _term() { - echo "Stopping container." - echo "SIGTERM received, shutting down the server!" - ${DOMAIN_HOME}/bin/stopWebLogic.sh -} - -########### SIGKILL handler ############ -function _kill() { - echo "SIGKILL received, shutting down the server!" - kill -9 $childPID -} - -# Set SIGTERM handler -trap _term SIGTERM - -# Set SIGKILL handler -trap _kill SIGKILL - -#Loop determining state of WLS -function check_wls { - action=$1 - host=$2 - port=$3 - sleeptime=$4 - while true - do - sleep $sleeptime - if [ "$action" == "started" ]; then - started_url="http://$host:$port/weblogic/ready" - echo -e "Waiting for WebLogic server to get $action, checking $started_url" - status=`/usr/bin/curl -s -i $started_url | grep "200 OK"` - echo "Status:" $status - if [ ! -z "$status" ]; then - break - fi - elif [ "$action" == "shutdown" ]; then - shutdown_url="http://$host:$port" - echo -e "Waiting for WebLogic server to get $action, checking $shutdown_url" - status=`/usr/bin/curl -s -i $shutdown_url | grep "500 Can't connect"` - if [ ! -z "$status" ]; then - break - fi - fi - done - echo -e "WebLogic Server has $action" -} - - -ADD_DOMAIN=1 -if [ -f ${DOMAIN_HOME}/servers/${ADMIN_NAME}/logs/${ADMIN_NAME}.log ]; then - exit -fi - -# Create Domain only if 1st execution -DOMAIN_PROPERTIES_FILE=${PROPERTIES_FILE_DIR}/domain.properties -SEC_PROPERTIES_FILE=${PROPERTIES_FILE_DIR}/domain_security.properties -if [ ! -e "${SEC_PROPERTIES_FILE}" ]; then - echo "A properties file with the username and password needs to be supplied." - exit -fi - -# Get Username -USER=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep username | cut -d "=" -f2` -if [ -z "${USER}" ]; then - echo "The domain username is blank. The Admin username must be set in the properties file." - exit -fi -# Get Password -PASS=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep password | cut -d "=" -f2` -if [ -z "${PASS}" ]; then - echo "The domain password is blank. The Admin password must be set in the properties file." - exit -fi - -# Create domain -wlst.sh -skipWLSModuleScanning -loadProperties ${DOMAIN_PROPERTIES_FILE} -loadProperties ${SEC_PROPERTIES_FILE} /u01/oracle/create-wls-domain.py diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/setEnv.sh b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/setEnv.sh deleted file mode 100755 index 3b601e27d32d3ecefc10b3a12f80b3237e149dc8..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/setEnv.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. -# -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. - -BUILD_ARG='' -if [ "$#" -eq "0" ] - then - echo "A properties file with variable definitions should be supplied." - exit 1 - else - PROPERTIES_FILE=$1 - echo Export environment variables from the ${PROPERTIES_FILE} properties file - fi - -extract_env() { - env_value=`awk '{print $1}' $2 | grep ^$1= | cut -d "=" -f2` - if [ -n "$env_value" ]; then - env_arg=`echo "CUSTOM_$1=$env_value"` - echo " env_arg: $env_arg" - export $env_arg - fi -} - -set_env_arg(){ - extract_env $1 $2 - if [ -n "$env_arg" ]; then - BUILD_ARG="$BUILD_ARG --build-arg $env_arg" - fi -} - -# Set DOMAIN_NAME -set_env_arg DOMAIN_NAME ${PROPERTIES_FILE} - -# Set ADMIN_NAME -set_env_arg ADMIN_NAME ${PROPERTIES_FILE} - -# Set ADMIN_HOST -set_env_arg ADMIN_HOST ${PROPERTIES_FILE} - -# Set ADMIN_PORT -set_env_arg ADMIN_LISTEN_PORT ${PROPERTIES_FILE} - -# Set MANAGED_SERVER_PORT -set_env_arg MANAGEDSERVER_PORT ${PROPERTIES_FILE} - -# Set DEBUG_PORT -set_env_arg DEBUG_PORT ${PROPERTIES_FILE} - -# Set IMAGE_TAG -extract_env IMAGE_TAG ${PROPERTIES_FILE} - -# Set CLUSTER_NAME -set_env_arg CLUSTER_NAME ${PROPERTIES_FILE} - -export BUILD_ARG=$BUILD_ARG -echo BUILD_ARG=$BUILD_ARG diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/startAdminServer.sh b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/startAdminServer.sh deleted file mode 100755 index 3960f760070b559973ac695ffc64ab1822f19e92..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/startAdminServer.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -# -#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. -# -#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. - -#Define DOMAIN_HOME -echo "Domain Home is: " $DOMAIN_HOME - -# If AdminServer.log does not exists, container is starting for 1st time -# So it should start NM and also associate with AdminServer -# Otherwise, only start NM (container restarted) -########### SIGTERM handler ############ -function _term() { - echo "Stopping container." - echo "SIGTERM received, shutting down the server!" - ${DOMAIN_HOME}/bin/stopWebLogic.sh -} - -########### SIGKILL handler ############ -function _kill() { - echo "SIGKILL received, shutting down the server!" - kill -9 $childPID -} - -# Set SIGTERM handler -trap _term SIGTERM - -# Set SIGKILL handler -trap _kill SIGKILL - -#Loop determining state of WLS -function check_wls { - action=$1 - host=$2 - port=$3 - sleeptime=$4 - while true - do - sleep $sleeptime - if [ "$action" == "started" ]; then - started_url="http://$host:$port/weblogic/ready" - echo -e "Waiting for WebLogic server to get $action, checking $started_url" - status=`/usr/bin/curl -s -i $started_url | grep "200 OK"` - echo "Status:" $status - if [ ! -z "$status" ]; then - break - fi - elif [ "$action" == "shutdown" ]; then - shutdown_url="http://$host:$port" - echo -e "Waiting for WebLogic server to get $action, checking $shutdown_url" - status=`/usr/bin/curl -s -i $shutdown_url | grep "500 Can't connect"` - if [ ! -z "$status" ]; then - break - fi - fi - done - echo -e "WebLogic Server has $action" -} - -export AS_HOME="${DOMAIN_HOME}/servers/${ADMIN_NAME}" -export AS_SECURITY="${AS_HOME}/security" - -if [ -f ${AS_HOME}/logs/${ADMIN_NAME}.log ]; then - exit -fi - -echo "Admin Server Home: ${AS_HOME}" -echo "Admin Server Security: ${AS_SECURITY}" - -SEC_PROPERTIES_FILE=${PROPERTIES_FILE_DIR}/security.properties -if [ ! -e "${SEC_PROPERTIES_FILE}" ]; then - echo "A security.properties file with the username and password needs to be supplied." - exit -fi - -# Get Username -USER=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep username | cut -d "=" -f2` -if [ -z "${USER}" ]; then - echo "The domain username is blank. The Admin username must be set in the properties file." - exit -fi -# Get Password -PASS=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep password | cut -d "=" -f2` -if [ -z "${PASS}" ]; then - echo "The domain password is blank. The Admin password must be set in the properties file." - exit -fi - -#Define Java Options -JAVA_OPTIONS=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep ^JAVA_OPTIONS= | cut -d "=" -f2` -if [ -z "${JAVA_OPTIONS}" ]; then - JAVA_OPTIONS="-Dweblogic.StdoutDebugEnabled=false" -fi -export JAVA_OPTIONS=${JAVA_OPTIONS} - -# Create domain -mkdir -p ${AS_SECURITY} -echo "username=${USER}" >> ${AS_SECURITY}/boot.properties -echo "password=${PASS}" >> ${AS_SECURITY}/boot.properties -${DOMAIN_HOME}/bin/setDomainEnv.sh - -#echo 'Running Admin Server in background' -${DOMAIN_HOME}/bin/startWebLogic.sh & - -#echo 'Waiting for Admin Server to reach RUNNING state' -check_wls "started" localhost ${ADMIN_PORT} 2 - -# tail the Admin Server Logs -tail -f ${AS_HOME}/logs/${ADMIN_NAME}.log & - -childPID=$! -wait $childPID diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain.properties b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain.properties deleted file mode 100644 index a0a7049c629bce362c87c4a3dc499f458764966d..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain.properties +++ /dev/null @@ -1,17 +0,0 @@ -DOMAIN_NAME=domain1 -ADMIN_PORT=7001 -ADMIN_NAME=admin-server -ADMIN_HOST=wlsadmin -MANAGED_SERVER_PORT=8001 -MANAGED_SERVER_NAME_BASE=managed-server -CONFIGURED_MANAGED_SERVER_COUNT=2 -CLUSTER_NAME=cluster-1 -DEBUG_PORT=8453 -DB_PORT=1527 -DEBUG_FLAG=true -PRODUCTION_MODE_ENABLED=true -CLUSTER_TYPE=DYNAMIC -JAVA_OPTIONS=-Dweblogic.StdoutDebugEnabled=false -T3_CHANNEL_PORT=30012 -T3_PUBLIC_ADDRESS=kubernetes -IMAGE_TAG=domain-home-in-image:12.2.1.3 diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain_security.properties b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain_security.properties deleted file mode 100644 index ee2eecf41220207253a1176c65f36cb0d369e595..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-build/domain_security.properties +++ /dev/null @@ -1,2 +0,0 @@ -username=weblogic -password=Weblogic1 diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-run/security.properties b/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-run/security.properties deleted file mode 100644 index 80e8e77e701b87183866132d30b9415d2e994c67..0000000000000000000000000000000000000000 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/properties/docker-run/security.properties +++ /dev/null @@ -1,4 +0,0 @@ -username=weblogic -password=Weblogic1 -JAVA_OPTIONS=-Dweblogic.StdoutDebugEnabled=false - diff --git a/smp-docker/images/weblogic-12.2-smp/Dockerfile b/smp-docker/images/weblogic-12.2-smp/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a40612256a78c975a05c4bb114c53f18819b0778 --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/Dockerfile @@ -0,0 +1,103 @@ +# LICENSE UPL 1.0 +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# ORACLE DOCKERFILES PROJECT +# -------------------------- +# This Dockerfile extends the Oracle WebLogic image built under 12213-doma-home-in-image. +# +# It will deploy any package defined in APP_PKG_FILE. +# into the DOMAIN_HOME with name defined in APP_NAME +# +# HOW TO BUILD THIS IMAGE +# ----------------------- +# Run: +# $ docker build -t weblogic-smp . +# +# create from base image FROM oracle/weblogic:12.2.1.4-developer +# Please build oracle/weblogic:12.2.1.4-developer (smp-docker/images/oracle/weblogic-12.2.1.4) first +# --------------- +FROM oracle/weblogic:12.2.1.4-developer + +# Define variable +ARG SMP_VERSION +ARG LC_ALL=en_US.UTF-8 +ARG LANG="en_US.UTF-8" +ARG LANGUAGE="en_US" +ARG ORACLE_HOME=/u01/oracle +ARG WL_DOMAIN_NAME="${WL_DOMAIN_NAME:-smp-domain}" +ARG WL_DOMAIN_HOME="${ORACLE_HOME}/user_projects/domains/${WL_DOMAIN_NAME}" +ARG WL_MANAGED_SERV_NAME=${WL_MANAGED_SERV_NAME:-smp-node-1} +ARG WL_MANAGED_SERV_BASE_NAME=${WL_MANAGED_SERV_BASE_NAME:-smp-node-} +ARG WL_MANAGED_SERVER_COUNT=${WL_MANAGED_SERVER_COUNT:-10} +ARG WL_ADMIN_NAME=${WL_ADMIN_NAME:-admin-server} +ARG WL_CLUSTER_NAME=${WL_CLUSTER_NAME:-smp-cluster} +ARG WL_DEBUG_PORT="${WL_DEBUG_PORT:-8453}" +ARG WL_ADMIN_PORT="${WL_ADMIN_PORT:-7001}" +ARG WL_ADMIN_PORT_HTTPS="${WL_ADMIN_PORT_HTTPS:-7002}" +ARG WL_MANAGED_SERVER_PORT="${WL_MANAGED_SERVER_PORT:-8001}" +ARG SMP_CONFIG_DIR=/data/smp +ARG WL_DATA_WEBLOGIC=/data/weblogic + +# set following build arguments also the environment arguments +ENV SMP_VERSION=$SMP_VERSION \ + LC_ALL=$LC_ALL \ + LANG=$LANG \ + LANGUAGE=$LANGUAGE \ + ORACLE_HOME=$ORACLE_HOME \ + DOCKER_DATA=/data \ + WL_INIT_PROPERTIES="/u01/init/" \ + WL_DOMAIN_NAME="${WL_DOMAIN_NAME}" \ + WL_DOMAIN_HOME="${ORACLE_HOME}/user_projects/domains/${WL_DOMAIN_NAME}" \ + WL_MANAGED_SERV_BASE_NAME="${WL_MANAGED_SERV_BASE_NAME}" \ + WL_MANAGED_SERVER_COUNT=${WL_MANAGED_SERVER_COUNT} \ + WL_DEBUG_PORT="${WL_DEBUG_PORT}" \ + WL_ADMIN_PORT="${WL_ADMIN_PORT}" \ + WL_ADMIN_PORT_HTTPS="${WL_ADMIN_PORT_HTTPS}" \ + WL_MANAGED_SERVER_PORT="${WL_MANAGED_SERVER_PORT}" \ + WL_ADMIN_NAME="${WL_ADMIN_NAME}" \ + WL_CLUSTER_NAME="${WL_CLUSTER_NAME}" \ + SMP_CONFIG_DIR=${SMP_CONFIG_DIR:-/data/smp} \ + SMP_SECURITY_DIR="${SMP_CONFIG_DIR}/security" \ + CLASSPATH="${SMP_CONFIG_DIR}/config" \ + WL_DATA_WEBLOGIC="${WL_DATA_WEBLOGIC:-/data/weblogic}" \ + WL_SECURITY_FILE="${WL_DATA_WEBLOGIC}/security.properties" \ + PATH="$PATH:/u01/oracle/oracle_common/common/bin:/u01/oracle/wlserver/common/bin:${WL_DOMAIN_HOME}:${WL_DOMAIN_HOME}/bin:${ORACLE_HOME}" \ + # configurable argumentst at containe start + WL_ADMIN_HOST="${WL_ADMIN_HOST:-localhost}" \ + WL_SERVER_TLS_KEYSTORE_PASS="${WL_SERVER_TLS_KEYSTORE_PASS:-test123}" \ + WL_DEBUG_FLAG="${WL_DEBUG_FLAG:-true}" \ + WL_PRODUCTION_MODE_ENABLED="${WL_PRODUCTION_MODE_ENABLED:-false}" \ + WL_MANAGED_SERV_NAME="${WL_MANAGED_SERV_NAME}" \ + WL_STATUS_FOLDER=${WL_STATUS_FOLDER:-/u01/status/} \ + ENABLE_JACOCO="${ENABLE_JACOCO:-false}" + + +# Add files required to build this image +COPY container-scripts/* ${ORACLE_HOME}/ +COPY container-scripts/init-scripts ${ORACLE_HOME}/init/scripts/ +COPY properties/init ${ORACLE_HOME}/init/properties/ +COPY artefacts/smp.war ${ORACLE_HOME}/init/ + + +#Create directory where domain will be written to +USER root +WORKDIR $ORACLE_HOME +RUN mkdir -p $WL_DOMAIN_HOME && \ + mkdir -p $DOCKER_DATA && \ + chown -R oracle:oracle $ORACLE_HOME && \ + chown -R oracle:oracle $WL_DOMAIN_HOME && \ + chown -R oracle:oracle $DOCKER_DATA && \ + chmod +xw ${ORACLE_HOME}/*.sh && \ + chmod +xw ${ORACLE_HOME}/init/scripts/*.sh && \ + chmod +xw ${ORACLE_HOME}/init/scripts/*.py && \ + chmod -R +xwr $WL_DOMAIN_HOME + + +USER oracle +WORKDIR $WL_DOMAIN_HOME +# Expose ports for admin, managed server, and debug +EXPOSE $WL_ADMIN_PORT $WL_ADMIN_HTTPS_PORT $WL_MANAGED_SERVER_PORT $WL_DEBUG_PORT + +# Define default command to start bash. +CMD ["startManagedServer.sh"] diff --git a/smp-docker/images/weblogic-12.2-smp/README.md b/smp-docker/images/weblogic-12.2-smp/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f7cd60eb03774ad9ee548c2acc10a61928f2b8a6 --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/README.md @@ -0,0 +1,133 @@ +SMP docker image +================================ +This Dockerfile extends the Oracle WebLogic image built from Oracle WebLogic Server 12cR2 (12.2.1.4). The image deploy the SMP application to admin +server and to the cluster. +The image does not initialize the domain nor deploy the SMP to the WebLogic domain. Instead, it sets all prerequisites to +create the WebLogic domain and deploy SMP at the first startup of the image. Initializing the domain at first startup allows +users more flexibility in the domain configuration and SMP deployment. When the domain initialization is completed, +the domain package is stored in the file: "${DOCKER_DATA}/${WL_CLUSTER_NAME}.jar" +(when using the default values, the file is: /data/smp-cluster.jar). The file must be mounted to the same location in the starting +nodes to be started as a cluster node for the same domain. The easiest way is to mount folder: ./data:/data to the +admin server and cluster nodes. + + +# How to build the image + +The following preconditions must be met to build the image: + - image [oracle/weblogic:12.2.1.4-developer](../oracle/weblogic-12.2.1.4) must be build or must be accessible via "docker pull registry" + - smp artefacts *smp.war* and *smp-setup.zip* must be added to subfolder *./artefacts*. + + +To build image executed the command (set the smp version accordingly) + + $ docker build -t "smp-weblogic-122:4.2-SNAPSHOT" . --build-arg SMP_VERSION=4.2-SNAPSHOT + + +# How to run the domain + +To start the containerized Administration Server, run: + + $ docker run -d --name wlsadmin --hostname smp-wls-admin -p 7001:7001 \ + -v <HOST DIRECTORY TO SHARED DATA>/dasta:/data \ + smp-weblogic-122:4.2-SNAPSHOT + +To start a containerized Managed Server (smp-node-1) to self-register with the Administration Server above, run: + + $ docker run -d --name smp-node-1 -p 8001:8001 \ + -v <HOST DIRECTORY TO SHARED DATA>/dasta:/data \ + -e WL_ADMIN_HOST=smp-wls-admin \ + -e WL_MANAGED_SERV_NAME=smp-node-1 smp-weblogic-122:4.2-SNAPSHOT startManagedServer.sh + +To start a second Managed Server (smp-node-2), run: + + $ docker run -d --name smp-node-2 -p 8001:8001 \ + -v <HOST DIRECTORY TO SHARED DATA>/dasta:/data \ + -e WL_ADMIN_HOST=smp-wls-admin \ + -e WL_MANAGED_SERV_NAME=smp-node-2 smp-weblogic-122:4.2-SNAPSHOT startManagedServer.sh + + +Run the WLS Administration Console: + +In your browser, enter `https://localhost:7001/console`. + +Run the sample application: + +To access the sample application, in your browser enter `http://localhost:7001/smp/`. + +# SMP and WebLogic configuration + +At the first startup of the admin server, the domain is initialized and stored into the file: +`${DOCKER_DATA}/${WL_CLUSTER_NAME}-${SMP_VERSION}.jar` + (the default values gives file path: `/data/smp-cluster-4.2-SNAPSHOT.jar`). The file is needed to create node deployment + on an empty WebLogic installation using the: unpack.sh command. Make sure the file is available on the same container + path when starting the nodes. + +## WebLogic domain init configuration +When the domain has initialized the file +`./weblogic-12.2-smp/properties/init/domain.properties` is used as domain base properties. To the file, the following +environment properties are appended: +See the: `weblogic-12.2-smp/container-scripts/init-scripts/createWLSDomain.sh` + + DOMAIN_NAME=${WL_DOMAIN_NAME} + ADMIN_PORT=${WL_ADMIN_PORT} + ADMIN_HTTPS_PORT=${WL_ADMIN_PORT_HTTPS} + ADMIN_NAME=${WL_ADMIN_NAME} + ADMIN_HOST=${WL_ADMIN_HOST} + MANAGED_SERVER_PORT=${WL_MANAGED_SERVER_PORT} + MANAGED_SERVER_NAME_BASE=${WL_MANAGED_SERV_BASE_NAME} + CONFIGURED_MANAGED_SERVER_COUNT=${WL_MANAGED_SERVER_COUNT} + CLUSTER_NAME=${WL_CLUSTER_NAME} + DEBUG_FLAG=${WL_DEBUG_FLAG} + PRODUCTION_MODE_ENABLED=${WL_PRODUCTION_MODE_ENABLED} + +In case other properties are needed, define your own "domain.properties" and map it to the container folder: `/u01/init/` +as example: + + volumes: + - ./properties/weblogic-init:/u01/init/ + + +## WebLogic admin username and password +The weblogi admin username and password credential are used to access `https://localhost:7001/console` and also +for the cluster nodes to connect to WebLogic admin. The credentials are not defined in folder +`/u01/init/domain_security.properties` as example: + + username=wls-smp + password=wls-pass-01 + +The default user name is used, and a random password is generated. The password is logged to the admin logs at the domain +initialization event. + + ‘/u01/oracle/init/properties/domain_security.properties’: No such file or directory + To increase security please provide custom admin username and password in /tmp/create-domain/properties/domain_security.properties. + Generated WebLogic admin user with credentials: weblogic/9HLS3cugQBlXyncNC0GcHuE3MNbhgOrrcR5kZluXAA68lTJapKeYxk7D4LbeYTwc + +The credentials are copied to the file `/data/weblogic/security.properties`, with intention to be used for node server. +After servers are started for the first time - the file can be removed/deleted + + # example of generated /data/weblogic/security.properties + username=weblogic + password=weblogic-custom-password + +## Weblogic Database configuration. +Weblogic database configured based on the file `/u01/init/datasource.properties` + + dsname=eDeliverySmpDs + dsdbname=eDeliverySmpDs + dsjndiname=jdbc/eDeliverySmpDs + dsdriver=oracle.jdbc.OracleDriver + dsurl=jdbc:oracle:thin:@//smp-oracle-db:1521/xe + dsusername=smp + dspassword=test + dstestquery=SQL SELECT 1 FROM DUAL + +## SMP initial configuration. +SMP initial configuration can be provided in file `/smp.config.properties` + + # example of the SMP configuration file (please note the example where SMP uses JNDI datasource!) + hibernate.dialect=org.hibernate.dialect.Oracle10gDialect + datasource.jndi=jdbc/eDeliverySmpDs + configuration.dir=/data/smp/security + authentication.blueCoat.enabled=true + log.folder=./logs/ + diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/create-wls-domain.py b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/create-wls-domain.py similarity index 59% rename from smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/create-wls-domain.py rename to smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/create-wls-domain.py index e0bcdab158067eca67b3c8fc0fd55e65ea9c70bd..c0a01ac2911146b5e8d0d74a06bffe9dba3b835a 100644 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/create-wls-domain.py +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/create-wls-domain.py @@ -22,41 +22,35 @@ def getEnvVar(var): # This python script is used to create a WebLogic domain -#domain_uid = DOMAIN_UID -server_port = int(os.environ.get("MANAGED_SERVER_PORT")) -domain_path = os.environ.get("DOMAIN_HOME") +server_port = int(MANAGED_SERVER_PORT) +domain_path = os.environ.get("WL_DOMAIN_HOME") cluster_name = CLUSTER_NAME -print('cluster_name : [%s]' % cluster_name); admin_server_name = ADMIN_NAME -#admin_server_name_svc = os.environ.get("ADMIN_SERVER_NAME_SVC") -admin_port = int(os.environ.get("ADMIN_PORT")) -domain_name = os.environ.get("DOMAIN_NAME") +admin_port = int(ADMIN_PORT) +admin_https_port = int(ADMIN_HTTPS_PORT) +domain_name = DOMAIN_NAME t3_channel_port = int(T3_CHANNEL_PORT) t3_public_address = T3_PUBLIC_ADDRESS number_of_ms = int(CONFIGURED_MANAGED_SERVER_COUNT) cluster_type = CLUSTER_TYPE managed_server_name_base = MANAGED_SERVER_NAME_BASE -#managed_server_name_base_svc = MANAGED_SERVER_NAME_BASE_SVC -#domain_logs = DOMAIN_LOGS_DIR -#script_dir = CREATE_DOMAIN_SCRIPT_DIR production_mode_enabled = PRODUCTION_MODE_ENABLED # Read the domain secrets from the common python file #execfile('%s/read-domain-secret.py' % script_dir) -print('domain_path : [%s]' % domain_path); -print('domain_name : [%s]' % domain_name); -print('admin_server_name : [%s]' % admin_server_name); -print('admin_port : [%s]' % admin_port); -print('cluster_name : [%s]' % cluster_name); -print('server_port : [%s]' % server_port); -print('number_of_ms : [%s]' % number_of_ms); -print('cluster_type : [%s]' % cluster_type); -print('managed_server_name_base : [%s]' % managed_server_name_base); -print('production_mode_enabled : [%s]' % production_mode_enabled); -#print('dsname : [%s]' % dsname); -print('t3_channel_port : [%s]' % t3_channel_port); -print('t3_public_address : [%s]' % t3_public_address); +print('domain_path : [%s]' % domain_path) +print('domain_name : [%s]' % domain_name) +print('admin_server_name : [%s]' % admin_server_name) +print('admin_port : [%s]' % admin_port) +print('cluster_name : [%s]' % cluster_name) +print('server_port : [%s]' % server_port) +print('number_of_ms : [%s]' % number_of_ms) +print('cluster_type : [%s]' % cluster_type) +print('managed_server_name_base : [%s]' % managed_server_name_base) +print('production_mode_enabled : [%s]' % production_mode_enabled) +print('t3_channel_port : [%s]' % t3_channel_port) +print('t3_public_address : [%s]' % t3_public_address) # Open default domain template # ============================ @@ -98,7 +92,6 @@ cmo.setPassword(password) # ============================================== setOption('OverwriteDomain', 'true') - # Create a cluster # ====================== cd('/') @@ -137,14 +130,7 @@ else: print('Done creating Server Template: %s' % templateName) cd('/ServerTemplates/%s' % templateName) cmo.setListenPort(server_port) -# cmo.setListenAddress('%s-%s${id}' % (domain_uid, managed_server_name_base_svc)) cmo.setCluster(cl) -# create(templateName,'Log') -# cd('Log/%s' % templateName) -# set('FileName', '%s${id}.log' % (managed_server_name_base)) -# print('Done setting attributes for Server Template: %s' % templateName); - - cd('/Clusters/%s' % cluster_name) create(cluster_name, 'DynamicServers') cd('DynamicServers/%s' % cluster_name) @@ -154,58 +140,11 @@ else: set('MaxDynamicClusterSize', number_of_ms) set('CalculatedListenPorts', false) - print('Done setting attributes for Dynamic Cluster: %s' % cluster_name); - -# Create a Data Source -# ====================== -#cd('/') -#print('Configuring a Data Source: %s' % dsname); -#create(dsname, 'JDBCSystemResource') -#cd('/JDBCSystemResource/' + dsname + '/JdbcResource/' + dsname) -#cmo.setName(dsname) - -#cd('/JDBCSystemResource/' + dsname + '/JdbcResource/' + dsname) -#create('myJdbcDataSourceParams','JDBCDataSourceParams') -#cd('JDBCDataSourceParams/NO_NAME_0') -#set('JNDIName', java.lang.String(dsjndiname)) -#set('GlobalTransactionsProtocol', java.lang.String('None')) - -#cd('/JDBCSystemResource/' + dsname + '/JdbcResource/' + dsname) -#create('myJdbcDriverParams','JDBCDriverParams') -#cd('JDBCDriverParams/NO_NAME_0') -#set('DriverName', dsdriver) -#set('URL', dsurl) -#set('PasswordEncrypted', dspassword) -#set('UseXADataSourceInterface', 'false') - -#print 'create JDBCDriverParams Properties' -#create('myProperties','Properties') -#cd('Properties/NO_NAME_0') -#create('user','Property') -#cd('Property/user') -#set('Value', dsusername) - -#cd('../../') -#create('databaseName','Property') -#cd('Property/databaseName') -#set('Value', dsdbname) - -#print 'create JDBCConnectionPoolParams' -#cd('/JDBCSystemResource/' + dsname + '/JdbcResource/' + dsname) -#create('myJdbcConnectionPoolParams','JDBCConnectionPoolParams') -#cd('JDBCConnectionPoolParams/NO_NAME_0') -#set('TestTableName','SQL SELECT 1 FROM DUAL') -#set('InitialCapacity',int(dsinitalcapacity)) - -#print('Done setting attributes for Data Source: %s' % dsname); - -# Assign -# ====== -# Uncomment to target and enable the data source for the cluster -# assign('JDBCSystemResource', dsname, 'Target', cluster_name) + print('Done setting attributes for Dynamic Cluster: %s' % cluster_name) # Write Domain # ============ +print('Write domain to: %s' % domain_path) writeDomain(domain_path) closeTemplate() print 'Domain Created' @@ -217,6 +156,7 @@ if production_mode_enabled == "true": cmo.setProductionModeEnabled(true) else: cmo.setProductionModeEnabled(false) + updateDomain() closeDomain() print 'Domain Updated' diff --git a/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/createWLSDomain.sh b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/createWLSDomain.sh new file mode 100755 index 0000000000000000000000000000000000000000..054f24b9950dee279942fbab56f93a809b4ed16f --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/createWLSDomain.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# +#Copyright (c) 2014, 2020, Oracle and/or its affiliates. +# +#Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#Define WL_DOMAIN_HOME +INIT_SCRIPTS=$1 +echo "Domain Home is: $WL_DOMAIN_HOME" +echo "Scripts folder is: $INIT_SCRIPTS" + +source "${INIT_SCRIPTS}/functions/keystore.functions" + +# If AdminServer.log does not exists, container is starting for 1st time +# So it should start NM and also associate with AdminServer +# Otherwise, only start NM (container restarted) +########### SIGTERM handler ############ +function _term() { + echo "Stopping container." + echo "SIGTERM received, shutting down the server!" + ${WL_DOMAIN_HOME}/bin/stopWebLogic.sh +} + +########### SIGKILL handler ############ +function _kill() { + echo "SIGKILL received, shutting down the server!" + kill -9 $childPID +} + +# Set SIGTERM handler +trap _term SIGTERM + +# Set SIGKILL handler +trap _kill SIGKILL + +#Loop determining state of WLS +function check_wls() { + action=$1 + host=$2 + port=$3 + sleeptime=$4 + while true; do + sleep $sleeptime + if [ "$action" == "started" ]; then + started_url="http://$host:$port/weblogic/ready" + echo -e "Waiting for WebLogic server to get $action, checking $started_url" + status=$(/usr/bin/curl -s -i $started_url | grep "200 OK") + echo "Status:" $status + if [ ! -z "$status" ]; then + break + fi + elif [ "$action" == "shutdown" ]; then + shutdown_url="http://$host:$port" + echo -e "Waiting for WebLogic server to get $action, checking $shutdown_url" + status=$(/usr/bin/curl -s -i $shutdown_url | grep "500 Can't connect") + if [ ! -z "$status" ]; then + break + fi + fi + done + echo -e "WebLogic Server has $action" +} + + +function init_server_https_keystore(){ + # configure https + CERTIFICATES=${CERTIFICATES:-/tmp/} + HOST_DOMAIN=${WL_ADMIN_HOST:-localhost} + # put keystore to wildfly configuration folder + [[ ! -d "${WL_DATA_WEBLOGIC}/keystores" ]] && mkdir -p "${WL_DATA_WEBLOGIC}/keystores" + KEYSTORE_PATH="${WL_DATA_WEBLOGIC}/keystores/admin-tls-keystore.p12" + + CLIENT_KEYSTORE_PATH="${WL_DATA_WEBLOGIC}/keystores/client-tls-keystore.p12" + TRUSTSTORE_PATH="${WL_DATA_WEBLOGIC}/keystores/admin-tls-truststore.p12" + + generateKeyStore "${HOST_DOMAIN}" "${WL_SERVER_TLS_KEYSTORE_PASS}" "${WL_SERVER_TLS_KEYSTORE_PASS}" "${KEYSTORE_PATH}" + generateKeyStore "Client-TLS-Certificate" "${WL_SERVER_TLS_KEYSTORE_PASS}" "${WL_SERVER_TLS_KEYSTORE_PASS}" "${TRUSTSTORE_PATH}" + + wlst.sh -skipWLSModuleScanning "$INIT_SCRIPTS/enable-server-https.py" "${KEYSTORE_PATH}" "${TRUSTSTORE_PATH}" +} + +if [ -f ${WL_DOMAIN_HOME}/servers/${WL_ADMIN_NAME}/logs/${WL_ADMIN_NAME}.log ]; then + echo "Admin log file: [${WL_DOMAIN_HOME}/servers/${WL_ADMIN_NAME}/logs/${WL_ADMIN_NAME}.log] already exists - Skip domain creation!" + exit +fi + +DOMAIN_PROPERTY_DIR=/tmp/create-domain/properties +mkdir -p "${DOMAIN_PROPERTY_DIR}" + +DOMAIN_PROPERTIES_FILE=${DOMAIN_PROPERTY_DIR}/domain.properties +SEC_PROPERTIES_FILE=${DOMAIN_PROPERTY_DIR}/domain_security.properties + +# copy domain properties - check first init folder else use default +if [ -e "${WL_INIT_PROPERTIES}/domain.properties" ]; then + cp -f "${WL_INIT_PROPERTIES}/domain.properties" "${DOMAIN_PROPERTIES_FILE}" +else + cp -f "${INIT_SCRIPTS}"/../properties/domain.properties "${DOMAIN_PROPERTIES_FILE}" +fi +# copy security properties - check first init folder else use default +if [ -e "${WL_INIT_PROPERTIES}/domain_security.properties" ]; then + cp -f "${WL_INIT_PROPERTIES}/domain_security.properties" "${SEC_PROPERTIES_FILE}" +elif [ -e "${INIT_SCRIPTS}/../properties/domain_security.properties" ]; then + cp -f "${INIT_SCRIPTS}/../properties/domain_security.properties" "${SEC_PROPERTIES_FILE}" +else + echo "To increase security please provide custom admin username and password in ${SEC_PROPERTIES_FILE}." + defUsername=weblogic + randPass=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 64) + echo "username=${defUsername}" >"${SEC_PROPERTIES_FILE}" + echo "password=${randPass}" >>"${SEC_PROPERTIES_FILE}" + echo "Generated WebLogic admin user with credentials: ${defUsername}/${randPass}" +fi + +# Get Username +USER=$(awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep username | cut -d "=" -f2) +if [ -z "${USER}" ]; then + echo "The domain username is blank. The Admin username must be set in the properties file." + exit +fi +# Get Password +PASS=$(awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep password | cut -d "=" -f2) +if [ -z "${PASS}" ]; then + echo "The domain password is blank. The Admin password must be set in the properties file." + exit +fi + + +cat <<EOT >> "${DOMAIN_PROPERTIES_FILE}" + +DOMAIN_NAME=${WL_DOMAIN_NAME} +ADMIN_PORT=${WL_ADMIN_PORT} +ADMIN_HTTPS_PORT=${WL_ADMIN_PORT_HTTPS} +ADMIN_NAME=${WL_ADMIN_NAME} +ADMIN_HOST=${WL_ADMIN_HOST} +MANAGED_SERVER_PORT=${WL_MANAGED_SERVER_PORT} +MANAGED_SERVER_NAME_BASE=${WL_MANAGED_SERV_BASE_NAME} +CONFIGURED_MANAGED_SERVER_COUNT=${WL_MANAGED_SERVER_COUNT} +CLUSTER_NAME=${WL_CLUSTER_NAME} +DEBUG_FLAG=${WL_DEBUG_FLAG} +PRODUCTION_MODE_ENABLED=${WL_PRODUCTION_MODE_ENABLED} +EOT + +echo "Init domain with following properties" +cat ${DOMAIN_PROPERTIES_FILE} +echo "Show domain home $WL_DOMAIN_HOME" +# Create domain +wlst.sh -skipWLSModuleScanning -loadProperties "${DOMAIN_PROPERTIES_FILE}" -loadProperties "${SEC_PROPERTIES_FILE}" "$INIT_SCRIPTS/create-wls-domain.py" + +ENC_PASS=$(java -cp $ORACLE_HOME/wlserver/server/lib/weblogic.jar -Dweblogic.RootDirectory=${WL_DOMAIN_HOME} weblogic.security.Encrypt ${PASS}); +echo "set cluster shared secret file $WL_SECURITY_FILE" +cat <<EOT > "$WL_SECURITY_FILE" +username=${USER} +password=${PASS} +EOT + + +if [ ! -z "$AS_SECURITY" ];then + mkdir -p ${AS_SECURITY} + cat <<EOT > "${AS_SECURITY}/boot.properties" +username=${USER} +password=${ENC_PASS} +EOT +fi + +init_server_https_keystore \ No newline at end of file diff --git a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/datasource.properties.oracle b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/datasource.properties.oracle similarity index 100% rename from smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/datasource.properties.oracle rename to smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/datasource.properties.oracle diff --git a/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/deploySMPToDomain.sh b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/deploySMPToDomain.sh new file mode 100755 index 0000000000000000000000000000000000000000..e3112a4ee3008b19b0dab462a4f9e6f4081ca6fc --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/deploySMPToDomain.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +#Define WL_DOMAIN_HOME +INIT_SCRIPTS=$1 +echo "Domain Home is: $WL_DOMAIN_HOME" +echo "Scripts folder is: $INIT_SCRIPTS" + +# set datasource property +DATA_SOURCE_PROPERTY_FILE="${INIT_SCRIPTS}/../properties/datasource.properties" +if [ -f "${WL_INIT_PROPERTIES}/datasource.properties" ]; then + DATA_SOURCE_PROPERTY_FILE="${WL_INIT_PROPERTIES}/datasource.properties" +fi + +#deploy smp datasource +wlst.sh -loadProperties "${DATA_SOURCE_PROPERTY_FILE}" "${INIT_SCRIPTS}/ds-deploy.py" + +# copy smp startup configuration - check first init folder else use default +if [ -f "${WL_INIT_PROPERTIES}/smp.config.properties" ]; then + cat "${WL_INIT_PROPERTIES}/smp.config.properties" > "${SMP_CONFIG_DIR}/config/smp.config.properties" +elif [ -f "${INIT_SCRIPTS}/../properties/smp.config.properties" ]; then + cat "${INIT_SCRIPTS}/../properties/smp.config.properties" > "${SMP_CONFIG_DIR}/config/smp.config.properties" +else + cat <<EOT >"${SMP_CONFIG_DIR}/config/smp.config.properties" +hibernate.dialect=org.hibernate.dialect.Oracle10gDialect +datasource.jndi=jdbc/eDeliverySmpDs +authentication.blueCoat.enabled=true +log.folder=./logs/ +configuration.dir=${SMP_SECURITY_DIR}/ +EOT +fi + +cp /u01/oracle/init/smp.war "${WL_DOMAIN_HOME}/" +ls -ltr "${WL_DOMAIN_HOME}/" + +# Deploy Application +wlst.sh -skipWLSModuleScanning /u01/oracle/smp-app-deploy.py diff --git a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/ds-deploy.py b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/ds-deploy.py similarity index 71% rename from smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/ds-deploy.py rename to smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/ds-deploy.py index 5c53fcefc3046bbfceff7987c3ae2c91947d4871..bcdc93c31c2314a70fe1e80ec9745cac960f5b14 100644 --- a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/ds-deploy.py +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/ds-deploy.py @@ -9,14 +9,25 @@ import os # Deployment Information -domainname = os.environ.get('DOMAIN_NAME', 'base_domain') -domainhome = os.environ.get('DOMAIN_HOME', '/u01/oracle/user_projects/domains/' + domainname) -cluster_name = "cluster-1" -admin_name = os.environ.get("ADMIN_NAME", "AdminServer") +domain_name = os.environ.get('WL_DOMAIN_NAME', 'base_domain') +domain_home = os.environ.get('WL_DOMAIN_HOME', '/u01/oracle/user_projects/domains/' + domain_name) +cluster_name = os.environ.get('WL_CLUSTER_NAME') +admin_name = os.environ.get("WL_ADMIN_NAME", "AdminServer") + + +print('Domain Home : [%s]' % domain_home) +print('Admin Name : [%s]' % admin_name) +print('Cluster Name : [%s]' % cluster_name) +print('Datasource name : [%s]' % dsname) +print('Datasource JNDI : [%s]' % dsjndiname) +print('Datasource URL : [%s]' % dsurl) +print('Datasource Driver: [%s]' % dsdriver) +print('Datasource User : [%s]' % dsusername) +print('Datasource Test : [%s]' % dstestquery) # Read Domain in Offline Mode # =========================== -readDomain(domainhome) +readDomain(domain_home) # Create Datasource # ================== @@ -54,7 +65,7 @@ print 'create JDBCConnectionPoolParams' cd('/JDBCSystemResource/' + dsname + '/JdbcResource/' + dsname) create('myJdbcConnectionPoolParams','JDBCConnectionPoolParams') cd('JDBCConnectionPoolParams/NO_NAME_0') -set('TestTableName','SQL SELECT 1 FROM DUAL') +set('TestTableName',dstestquery) # Assign # ====== diff --git a/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/enable-server-https.py b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/enable-server-https.py new file mode 100644 index 0000000000000000000000000000000000000000..bb3de92bf947a04f8879d85b08752b0e6b5b3e11 --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/enable-server-https.py @@ -0,0 +1,57 @@ +# pass keystore as the first parameter +keystore_path = sys.argv[1] +truststore_path = sys.argv[2] +domain_name = os.environ.get("WL_DOMAIN_NAME", "") +domain_path = os.environ.get("WL_DOMAIN_HOME", "") +admin_server_name = os.environ.get("WL_ADMIN_NAME", "") +admin_https_port = int(os.environ.get("WL_ADMIN_PORT_HTTPS", "7002")) +ksIdentityPassword=os.environ.get("WL_SERVER_TLS_KEYSTORE_PASS", "") +ksIdentityAlias=os.environ.get("WL_ADMIN_HOST", "") + +print('domain_name : [%s]' % domain_name) +print('domain_home : [%s]' % domain_path) +print('keystore_path : [%s]' % keystore_path) +print('truststore_path : [%s]' % truststore_path) +print('admin_server_name : [%s]' % admin_server_name) +print('admin_https_port : [%s]' % admin_https_port) +print('Configure : [%s]' % '/Servers/'+admin_server_name+'/TLS/' + admin_server_name) + +def configureHTTPS(): + # ------------------------------------ + try: + # configure HTTPS for admin server + cd('/Servers/%s/' % admin_server_name) + # set custom Identity and standards java Trust.. + cmo.setKeyStores('CustomIdentityAndCustomTrust') + cmo.setCustomIdentityKeyStoreFileName(keystore_path) + cmo.setCustomIdentityKeyStoreType('PKCS12') + set('CustomIdentityKeyStorePassPhraseEncrypted', ksIdentityPassword) + + # set truststore + cmo.setCustomTrustKeyStoreFileName(truststore_path) + set('CustomTrustKeyStorePassPhraseEncrypted', ksIdentityPassword) + cmo.setCustomTrustKeyStoreType('PKCS12') + + create(admin_server_name, 'SSL') + cd('/Servers/'+admin_server_name+'/SSL/' + admin_server_name) + cmo.setServerPrivateKeyAlias(ksIdentityAlias) + set('ServerPrivateKeyPassPhraseEncrypted', ksIdentityPassword) + + + cd('/Servers/'+admin_server_name+'/SSL/' + admin_server_name) + cmo.setEnabled(true) + cmo.setListenPort(admin_https_port) + except Exception, e: + print "Error occurred while configuring server keystore and HTTPS connector" + dumpStack() + print e + +# Enable Use Authorization Providers to Protect JMX Access by default +print('Enable server SSL ...') + +readDomain(domain_path) +configureHTTPS() +updateDomain() +closeDomain() + +exit() diff --git a/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/functions/keystore.functions b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/functions/keystore.functions new file mode 100644 index 0000000000000000000000000000000000000000..e339ff83267f1dc19da0f0d482c48d192e7d011a --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/functions/keystore.functions @@ -0,0 +1,301 @@ + + + +# +# Provides functions to help setup keystores that will be used in Docker images. +# + +# +# Downloads the certificate of an SSL server certificate into a file having the provided name. The ".pem" extension is +# automatically appended to the file name and the file will be created into the ${CERTIFICATES} location. +# +# Args: +# $1 - SSL server host and port, divided by the ':' character +# $2 - the name of the file into which to save the certificate +downloadSslCertificate() { + : "${CERTIFICATES:?Need to set CERTIFICATES non-empty}" + : "${1:?Please provide the SSL server host and port (i.e. host:port) as the first parameter}" + : "${2:?Please provide the certificate name as the second parameter (e.g. 'red_gw', 'be00000001')}" + + local serverHostAndPort="${1}" + local certificateName="${2}" + + echo "Downloading SSL certificate from ${serverHostAndPort} to the ${CERTIFICATES} location: certificateName=${certificateName}" + + keytool -printcert \ + -sslserver "${serverHostAndPort}" \ + -rfc 2>/dev/null >"${CERTIFICATES}"/"${certificateName}".pem +} + +# +# Checks whether a keystore entry exists in a keystore file. +# +# Args: +# $1 - keystore file name +# $2 - alias for the entry to be checked for its existence (e.g. 'red_gw', 'blue_gw') +# $3 - keystore password +# $4 - (optional) keystore type; "JKS" by default +containsKeystoreEntry() { + : "${1:?Please provide the keystore file name as the first parameter}" + : "${2:?Please provide the alias for the keystore entry as the second parameter (e.g. 'red_gw', 'blue_gw')}" + : "${3:?Please provide the keystore password as the third parameter}" + + local keystoreFileName="${1}" + local keystoreAlias="${2}" + local keystorePassword="${3}" + local keystoreType="${4:-JKS}" + + echo "Checking for the presence of the keystore entry ${keystoreAlias} inside keystore ${keystoreFileName}: \ +keystorePassword=${keystorePassword}, keystoreType=${keystoreType}" + + local result=$(keytool -list -alias "${keystoreAlias}" \ + -storetype "${keystoreType}" \ + -keystore "${keystoreFileName}" \ + -storepass "${keystorePassword}" \ + -rfc 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | wc -l) + + ((result > 0)) +} + +# +# Exports an entry from a keystore file to a certificate file. The certificate file will have the same name +# as the alias of the entry and will be exported to the ${CERTIFICATES} location, having a ".pem" extension. +# +# Args: +# $1 - keystore file name +# $2 - alias for the entry to be exported (e.g. 'red_gw', 'blue_gw') +# $3 - keystore password +# $4 - (optional) keystore type; "JKS" by default +exportCertificate() { + : "${CERTIFICATES:?Need to set CERTIFICATES non-empty}" + : "${1:?Please provide the keystore file name as the first parameter}" + : "${2:?Please provide the alias for the keystore entry as the second parameter (e.g. 'red_gw', 'blue_gw')}" + : "${3:?Please provide the keystore password as the third parameter}" + + local keystoreFileName="${1}" + local keystoreAlias="${2}" + local keystorePassword="${3}" + local keystoreType="${4:-JKS}" + + echo "Exporting entry from ${keystoreFileName}: keystoreAlias=${keystoreAlias}, keystorePassword=${keystorePassword}, \ +keystoreType=${keystoreType}" + + keytool -exportcert \ + -alias "${keystoreAlias}" \ + -file "${CERTIFICATES}/${keystoreAlias}.pem" \ + -storetype "${keystoreType}" \ + -keystore "${keystoreFileName}" \ + -storepass "${keystorePassword}" \ + -rfc \ + -v 2>/dev/null +} + +# +# Imports an entry into a keystore file from an existing certificate file. The certificate file MUST have the same name +# as the alias of the entry to be imported and MUST be present in the ${CERTIFICATES} location, having a ".pem" extension. +# +# Args: +# $1 - keystore file name +# $2 - alias for the entry to be imported (e.g. 'red_gw', 'blue_gw') +# $3 - keystore password +# $4 - private key password +# $5 - (optional) keystore type; "JKS" by default +importCertificate() { + : "${CERTIFICATES:?Need to set CERTIFICATES non-empty}" + : "${1:?Please provide the keystore file name as the first parameter}" + : "${2:?Please provide the alias for the keystore entry as the second parameter (e.g. 'red_gw', 'blue_gw')}" + : "${3:?Please provide the keystore password as the third parameter}" + : "${4:?Please provide the private key password for the keystore entry as the fourth parameter}" + + local keystoreFileName="${1}" + local keystoreAlias="${2}" + local keystorePassword="${3}" + local privateKeyPassword="${4}" + local keystoreType="${5:-JKS}" + + echo "Importing entry into ${keystoreFileName}: keystoreAlias=${keystoreAlias}, keystorePassword=${keystorePassword}, \ +privateKeyPassword=${privateKeyPassword}, keystoreType=${keystoreType}" + + keytool -importcert \ + -alias "${keystoreAlias}" \ + -file "${CERTIFICATES}/${keystoreAlias}.pem" \ + -keypass "${privateKeyPassword}" \ + -noprompt \ + -storetype "${keystoreType}" \ + -keystore "${keystoreFileName}" \ + -storepass "${keystorePassword}" \ + -v 2>/dev/null +} + +# +# Imports a single or all of the entries from a source keystore to a destination keystore. The source and destination +# keystores must be in the same directory. +# +# If the source alias is missing, all the entries are imported from the source keystore; otherwise, only a single entry +# is imported. If a destination alias is not provided, then source alias is used as the destination alias. +# +# Unless otherwise specified, the source and destination keystore are by default of type "JKS". +# +# Args: +# $1 - source keystore file name +# $2 - destination keystore file name +# $3 - source keystore password +# $4 - destination keystore password +# $5 - source private key password +# $6 - destination private key password +# $7 - (optional) source alias for the entry to be imported (e.g. 'red_gw', 'blue_gw'); +# $8 - (optional) destination alias for the entry to be imported (e.g. 'red_gw', 'blue_gw'); +# $9 - (optional) source keystore type; "JKS" by default +# $10 - (optional) destination keystore type; "JKS" by default +importKeystore() { + : "${1:?Please provide the source keystore file name as the first parameter}" + : "${2:?Please provide the destination keystore file name as the second parameter}" + : "${3:?Please provide the source keystore password as the third parameter}" + : "${4:?Please provide the destination keystore password as the fourth parameter}" + : "${5:?Please provide the private key password for the source keystore entry as the fifth parameter}" + : "${6:?Please provide the private key password for the destination keystore entry as the sixth parameter}" + + local srcKeystoreFileName="${1}" + local destKeystoreFileName="${2}" + local srcKeystorePassword="${3}" + local destKeystorePassword="${4}" + local sourcePrivateKeyPassword="${5}" + local destinationPrivateKeyPassword="${6}" + local sourceKeystoreAlias="${7}" + local destinationKeystoreAlias="${8}" + local sourceKeystoreType="${9:-JKS}" + local destinationKeystoreType="${10:-JKS}" + + echo "Importing keystore entries from ${srcKeystoreFileName} to ${destKeystoreFileName}: \ +srcKeystorePassword=${srcKeystorePassword}, destKeystorePassword=${destKeystorePassword}, \ +sourcePrivateKeyPassword=${sourcePrivateKeyPassword}, destinationPrivateKeyPassword=${destinationPrivateKeyPassword}, \ +sourceKeystoreAlias=${sourceKeystoreAlias}, destinationKeystoreAlias=${destinationKeystoreAlias}, \ +sourceKeystoreType=${sourceKeystoreType}, destinationKeystoreType=${destinationKeystoreType}" + + keytool -importkeystore \ + -srckeystore "${srcKeystoreFileName}" \ + -destkeystore "${destKeystoreFileName}" \ + -srcstorepass "${srcKeystorePassword}" \ + -deststorepass "${destKeystorePassword}" \ + -srckeypass "${sourcePrivateKeyPassword}" \ + -destkeypass "${destinationPrivateKeyPassword}" \ + -noprompt \ + ${sourceKeystoreAlias:+-srcalias "${sourceKeystoreAlias}"} \ + ${destinationKeystoreAlias:+-destalias "${destinationKeystoreAlias}"} \ + -srcstoretype "${sourceKeystoreType}" \ + -deststoretype "${destinationKeystoreType}" \ + -v 2>/dev/null +} + +# +# Removes an existing entry from a keystore file. +# +# Args: +# $1 - keystore file name +# $2 - alias for the entry to be removed (e.g. 'red_gw', 'blue_gw') +# $3 - keystore password +# $4 - (optional) keystore type; "JKS" by default +removeKeystoreEntry() { + : "${1:?Please provide the keystore file name as the first parameter}" + : "${2:?Please provide the alias for the keystore entry as the second parameter (e.g. 'red_gw', 'blue_gw')}" + : "${3:?Please provide the keystore password as the third parameter}" + + local keystoreFileName="${1}" + local keystoreAlias="${2}" + local keystorePassword="${3}" + local keystoreType="${4:-JKS}" + + echo "Removing entry from ${keystoreFileName}: keystoreAlias=${keystoreAlias}, keystorePassword=${keystorePassword}, \ +keystoreType=${keystoreType}" + + keytool -delete \ + -alias "${keystoreAlias}" \ + -storetype "${keystoreType}" \ + -keystore "${keystoreFileName}" \ + -storepass "${keystorePassword}" \ + -v 2>/dev/null +} + +# +# Creates a new keystore with one key/certificate C=EU,O=eDelivery,OU=SMP_TEST,CN=${keystoreAlias}". +# +# Args: +# $1 - alias for the keystore entry (e.g. 'red_gw', 'blue_gw') +# $2 - keystore password +# $3 - private key password +# $4 - domain name to use as prefix for the keystore files; empty for the default domain +generateKeyStore() { + : "${CERTIFICATES:?Need to set CERTIFICATES non-empty}" + : "${1:?Please provide the alias of the keystore entry as the first parameter (e.g. 'red_gw', 'blue_gw')}" + : "${2:?Please provide the keystore password as the second parameter}" + : "${3:?Please provide the private key password as the third parameter}" + : "${4:?Please provide the keystore path as the fourth parameter}" + + local keystoreAlias="${1}" + local keystorePassword="${2}" + local privateKeyPassword="${3}" + local keystoreFilePath="${4}" + + echo "Generating keystore ${keystoreFilePath} using: keystoreAlias=${keystoreAlias}" + + if containsKeystoreEntry "${keystoreFilePath}" "${keystoreAlias}" "${keystorePassword}"; then + echo "Cert with keystoreAlias=${keystoreAlias} already exists in ${keystoreFilePath}. Deleting keystore entry before creating it again!" + removeKeystoreEntry "${keystoreFilePath}" "${keystoreAlias}" "${keystorePassword}" + fi + + keytool -genkeypair \ + -dname "C=EU,O=eDelivery,OU=SMP_TEST,CN=${keystoreAlias}" \ + -alias "${keystoreAlias}" \ + -keyalg RSA \ + -keysize 2048 \ + -keypass "${privateKeyPassword}" \ + -validity 3652 \ + -storetype PKCS12 \ + -keystore "${keystoreFilePath}" \ + -storepass "${keystorePassword}" \ + -v 2>/dev/null + + if [ -f "${CERTIFICATES}/${keystoreAlias}.pem" ]; then + echo "The certificate ${CERTIFICATES}/${keystoreAlias}.pem shouldn't already exist" + exit 1 + fi + + exportCertificate "${keystoreFilePath}" "${keystoreAlias}" "${keystorePassword}" + + chmod a+w "${keystoreFilePath}" +} + +# +# Imports an existing public-key certificate into a truststore. If the truststore is missing, it will be created. The +# name of the truststore chosen as destination will be "gateway_truststore.jks" unless the optional domain name +# argument is provided - in this case the name of the truststore used will be "gateway_truststore_DOMAIN.jks" -. +# +# Args: +# $1 - alias for the destination entry (e.g. 'red_gw', 'blue_gw') +# $2 - keystore password for the destination truststore +# $3 - private key password for both the destination entry +# $4 - domain name to use as prefix for the truststore file; empty for the default domain +updateTrustStore() { + : "${CERTIFICATES:?Need to set CERTIFICATES non-empty}" + : "${1:?Please provide the alias for both the source and destination keystore entries as the first parameter (e.g. 'red_gw', 'blue_gw')}" + : "${2:?Please provide the keystore password for both the source and destination keystores as the second parameter}" + : "${3:?Please provide the private key password for both the source and destination keystore entries as the third parameter}" + : "${4:?Please provide the domain name as the fourth parameter}" + + local keystoreAlias="${1}" + local keystorePassword="${2}" + local privateKeyPassword="${3}" + local domainName="${4}" + local truststoreFileName="${domainName}-gateway_truststore.jks" + + echo "Updating truststore ${truststoreFileName} using: keystoreAlias=${keystoreAlias}, domainName=${domainName}" + + if containsKeystoreEntry "${truststoreFileName}" "${keystoreAlias}" "${keystorePassword}"; then + echo "Cert with keystoreAlias=${keystoreAlias} already exists in ${truststoreFileName}. Deleting truststore entry before importing certificate!" + removeKeystoreEntry "${truststoreFileName}" "${keystoreAlias}" "${keystorePassword}" + fi + + importCertificate "${truststoreFileName}" "${keystoreAlias}" "${keystorePassword}" "${privateKeyPassword}" + + chmod a+w "${truststoreFileName}" +} diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/shutdown-servers.py b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/shutdown-servers.py similarity index 100% rename from smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/shutdown-servers.py rename to smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/shutdown-servers.py diff --git a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/smp-app-deploy.py b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/smp-app-deploy.py similarity index 59% rename from smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/smp-app-deploy.py rename to smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/smp-app-deploy.py index 778ed668524964fbb2296297dfa0f10d36fdbcbd..db6bd1c8a3e599a8897a2ccdde159d3e092f5a36 100644 --- a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/smp-app-deploy.py +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/smp-app-deploy.py @@ -11,21 +11,21 @@ import os # Deployment Information -domainhome = os.environ.get('DOMAIN_HOME', '/u01/oracle/user_projects/domains/base_domain') -admin_name = os.environ.get('ADMIN_NAME', 'AdminServer') -appVersion = os.environ.get('APP_VERSION', '4.0.0') -appfilename = os.environ.get('APP_FILE_NAME', 'smp') -appname = os.environ.get('APP_NAME', appfilename+'#'+appVersion) +domainhome = os.environ.get('WL_DOMAIN_HOME', '/u01/oracle/user_projects/domains/base_domain') +admin_name = os.environ.get('WL_ADMIN_NAME', 'AdminServer') +appVersion = os.environ.get('SMP_VERSION', '4.2') +appfilename = os.environ.get('WL_APP_FILE_NAME', 'smp') +appname = os.environ.get('WL_APP_NAME', appfilename+'#'+appVersion) appfile = os.environ.get('APP_FILE', 'smp.war') -appdir = os.environ.get('DOMAIN_HOME') -cluster_name = "cluster-1" +appdir = os.environ.get('WL_DOMAIN_HOME') +cluster_name = os.environ.get('WL_CLUSTER_NAME') -print('Domain Home : [%s]' % domainhome); -print('Admin Name : [%s]' % admin_name); -print('Cluster Name : [%s]' % cluster_name); -print('Application Name : [%s]' % appname); -print('appfile : [%s]' % appfile); -print('appdir : [%s]' % appdir); +print('Domain Home : [%s]' % domainhome) +print('Admin Name : [%s]' % admin_name) +print('Cluster Name : [%s]' % cluster_name) +print('Application Name : [%s]' % appname) +print('appfile : [%s]' % appfile) +print('appdir : [%s]' % appdir) # Read Domain in Offline Mode # =========================== readDomain(domainhome) diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/wlst b/smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/wlst similarity index 100% rename from smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/wlst rename to smp-docker/images/weblogic-12.2-smp/container-scripts/init-scripts/wlst diff --git a/smp-docker/images/weblogic-12.2-smp/container-scripts/startAdminServer.sh b/smp-docker/images/weblogic-12.2-smp/container-scripts/startAdminServer.sh new file mode 100755 index 0000000000000000000000000000000000000000..8aa03c9769854d17aa7607c3c14a43bee9e78582 --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/startAdminServer.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# +#Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved. +# +#Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. + +#Define DOMAIN_HOME +echo "Oracle Home is: " "$ORACLE_HOME" +echo "Domain Home is: " "${WL_DOMAIN_HOME}" + +# init setup properties +STATUS_FILE=${WL_STATUS_FOLDER}/wls-admin.started +# delete status file if already exists.. +[ -e "${STATUS_FILE}" ] && rm "${STATUS_FILE}" + +function initWebLogicDomain(){ + echo "Init WebLogic domain" + INIT_SCRIPTS=$1 + echo "Start createWLSDomain.sh from ${INIT_SCRIPTS}" + "${INIT_SCRIPTS}"/createWLSDomain.sh "${INIT_SCRIPTS}" + echo "Set execution flag for all sh scripts in ${WL_DOMAIN_HOME}/bin" + chmod -R a+x ${WL_DOMAIN_HOME}/bin/*.sh +} + +function deploy_smp() { + echo "Deploy SMP" + INIT_SCRIPTS=$1 + "${INIT_SCRIPTS}"/deploySMPToDomain.sh "${INIT_SCRIPTS}" + + # set enforce-valid-basic-auth-credentials false to allow basic authentication for rest services + sed -i -e "s/<\/security-configuration>/ <enforce-valid-basic-auth-credentials>false<\/enforce-valid-basic-auth-credentials>\n<\/security-configuration>/g" "${WL_DOMAIN_HOME}/config/config.xml" + +} + +# If AdminServer.log does not exists, container is starting for 1st time +# So it should start NM and also associate with AdminServer +# Otherwise, only start NM (container restarted) +########### SIGTERM handler ############ +function _term() { + echo "Stopping container." + echo "SIGTERM received, shutting down the server!" + ${DOMAIN_HOME}/bin/stopWebLogic.sh +} + +########### SIGKILL handler ############ +function _kill() { + echo "SIGKILL received, shutting down the server!" + kill -9 $childPID +} + +# Set SIGTERM handler +trap _term SIGTERM + +# Set SIGKILL handler +trap _kill SIGKILL + +#Loop determining state of WLS +function check_wls() { + action=$1 + host=$2 + port=$3 + sleeptime=$4 + echo "action:$action,host:$host,port:$port,sleeptime:$sleeptime," + while true; do + sleep $sleeptime + if [ "$action" == "started" ]; then + started_url="http://$host:$port/weblogic/ready" + echo -e "Waiting for WebLogic server to get $action, checking $started_url" + status=$(/usr/bin/curl -s -i $started_url | grep "200 OK") + echo "Status:" $status + if [ ! -z "$status" ]; then + break + fi + elif [ "$action" == "shutdown" ]; then + shutdown_url="http://$host:$port" + echo -e "Waiting for WebLogic server to get $action, checking $shutdown_url" + status=$(/usr/bin/curl -s -i $shutdown_url | grep "500 Can't connect") + if [ ! -z "$status" ]; then + break + fi + fi + done + echo -e "WebLogic Server has $action" +} + +export AS_HOME="${WL_DOMAIN_HOME}/servers/${WL_ADMIN_NAME}" +export AS_SECURITY="${AS_HOME}/security" + + + +if [ -f ${AS_HOME}/logs/${ADMIN_NAME}.log ]; then + exit +fi +echo "Initialize domain and deploy smp" +# initialize docker image +cd ~ || exit 13 +if [ ! -f ".initialized" ]; then + echo "Initialize domain and deploy smp" + INIT_SCRIPTS=${ORACLE_HOME}/init/scripts + initWebLogicDomain "${INIT_SCRIPTS}" + deploy_smp "${INIT_SCRIPTS}" + [ -f "${DOCKER_DATA}/${WL_CLUSTER_NAME}.jar" ] && rm -rf "${DOCKER_DATA}/${WL_CLUSTER_NAME}-${SMP_VERSION}.jar" + pack.sh -domain="${WL_DOMAIN_HOME}" \ + -template="${DOCKER_DATA}/${WL_CLUSTER_NAME}-${SMP_VERSION}.jar" \ + -template_name="${WL_CLUSTER_NAME}" \ + -managed="true" \ + -template_desc="${WL_DOMAIN_NAME}-managed-template-for-SMP-${SMP_VERSION}" + + touch ~/.initialized +fi + +echo "Admin Server Home: ${AS_HOME}" +echo "Admin Server Security: ${AS_SECURITY}" + +# WL_SECURITY_FILE should be created in createWLSDomain script +SEC_PROPERTIES_FILE=${WL_SECURITY_FILE} +if [ ! -e "${SEC_PROPERTIES_FILE}" ]; then + echo "A security.properties file with the username and password needs to be supplied." + exit +fi + +#Define Java Options +JAVA_OPTIONS=$(awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep ^JAVA_OPTIONS= | cut -d "=" -f2) +if [ -z "${JAVA_OPTIONS}" ]; then + JAVA_OPTIONS="-Dweblogic.StdoutDebugEnabled=false" +fi +export JAVA_OPTIONS=${JAVA_OPTIONS} + + +#echo 'Running Admin Server in background' +${WL_DOMAIN_HOME}/bin/startWebLogic.sh & + +#echo 'Waiting for Admin Server to reach RUNNING state' +check_wls "started" localhost ${WL_ADMIN_PORT} 2 +echo "Smp admin server started" >>"$STATUS_FILE" + +# tail the Admin Server Logs +tail -f ${AS_HOME}/logs/${WL_ADMIN_NAME}.log & + +childPID=$! +wait $childPID diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/startManagedServer.sh b/smp-docker/images/weblogic-12.2-smp/container-scripts/startManagedServer.sh similarity index 51% rename from smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/startManagedServer.sh rename to smp-docker/images/weblogic-12.2-smp/container-scripts/startManagedServer.sh index c6335cffe59b8722940eb1e29cc96e2500687a6c..df1343d466b7a7c718f47abbac3f0aa0b57c422f 100755 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/startManagedServer.sh +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/startManagedServer.sh @@ -6,25 +6,37 @@ # So it should start NM and also associate with AdminServer, as well Managed Server # Otherwise, only start NM (container is being restarted)o -export MS_HOME="${DOMAIN_HOME}/servers/${MANAGED_SERV_NAME}" +function initWebLogicDomain(){ + echo "Init WebLogic domain" + INIT_SCRIPTS=$1 + echo "Start createWLSDomain.sh from ${INIT_SCRIPTS}" + "${INIT_SCRIPTS}"/createWLSDomain.sh "${INIT_SCRIPTS}" + echo "Set execution flag for all sh scripts in ${WL_DOMAIN_HOME}/bin" + chmod -R a+x ${WL_DOMAIN_HOME}/bin/*.sh +} + + +export MS_HOME="${WL_DOMAIN_HOME}/servers/${WL_MANAGED_SERV_NAME}" export MS_SECURITY="${MS_HOME}/security" -if [ -f ${MS_HOME}/logs/${MANAGED_SERV_NAME}.log ]; then +if [ -f ${MS_HOME}/logs/${WL_MANAGED_SERV_NAME}.log ]; then + echo "Log file already exists ${MS_HOME}/logs/${WL_MANAGED_SERV_NAME}.log" exit fi # Wait for AdminServer to become available for any subsequent operation /u01/oracle/waitForAdminServer.sh -echo "Managed Server Name: ${MANAGED_SERV_NAME}" +echo "Managed Server Name: ${WL_MANAGED_SERV_NAME}" echo "Managed Server Home: ${MS_HOME}" echo "Managed Server Security: ${MS_SECURITY}" -SEC_PROPERTIES_FILE=${PROPERTIES_FILE_DIR}/security.properties +SEC_PROPERTIES_FILE=${WL_SECURITY_FILE} if [ ! -e "${SEC_PROPERTIES_FILE}" ]; then - echo "A properties file with the username and password needs to be supplied." + echo "A properties file with the username and password needs to be supplied. Use default properties" exit fi + # Get Username USER=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep username | cut -d "=" -f2` if [ -z "${USER}" ]; then @@ -38,26 +50,41 @@ if [ -z "${PASS}" ]; then exit fi +# initialize docker image +cd ~ || exit 13 +if [ ! -f ".initialized" ]; then + INIT_SCRIPTS=${ORACLE_HOME}/init/scripts + echo "create domain folder ${WL_DOMAIN_HOME}" + unpack.sh -template="${DOCKER_DATA}/${WL_CLUSTER_NAME}-${SMP_VERSION}.jar" -domain="${WL_DOMAIN_HOME}" -app_dir="${WL_DOMAIN_HOME}" + touch ~/.initialized +fi + +cd ${WL_DOMAIN_HOME} + + + #Set Java Options JAVA_OPTIONS=`awk '{print $1}' ${SEC_PROPERTIES_FILE} | grep ^JAVA_OPTIONS= | cut -d "=" -f2` -if [ -z "${JAVA_OPTIONS}" ]; then +if [ -z "${JAVA_OPTIONS}" ]; then JAVA_OPTIONS="-Dweblogic.StdoutDebugEnabled=false" fi export JAVA_OPTIONS=${JAVA_OPTIONS} echo "Java Options: ${JAVA_OPTIONS}" # Create Managed Server -mkdir -p ${MS_SECURITY} -echo "username=${USER}" >> ${MS_SECURITY}/boot.properties -echo "password=${PASS}" >> ${MS_SECURITY}/boot.properties -${DOMAIN_HOME}/bin/setDomainEnv.sh +mkdir -p "${MS_SECURITY}" +echo "username=${USER}" >> "${MS_SECURITY}"/boot.properties +echo "password=${PASS}" >> "${MS_SECURITY}"/boot.properties + + +"${WL_DOMAIN_HOME}"/bin/setDomainEnv.sh # Start 'ManagedServer' echo "Start Managed Server" -${DOMAIN_HOME}/bin/startManagedWebLogic.sh ${MANAGED_SERV_NAME} http://${ADMIN_HOST}:${ADMIN_PORT} +"${WL_DOMAIN_HOME}"/bin/startManagedWebLogic.sh ${WL_MANAGED_SERV_NAME} http://${WL_ADMIN_HOST}:${WL_ADMIN_PORT} # tail Managed Server log -tail -f ${MS_HOME}/logs/${MANAGED_SERV_NAME}.log & +tail -f ${MS_HOME}/logs/"${WL_MANAGED_SERV_NAME}".log & childPID=$! wait $childPID diff --git a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/waitForAdminServer.sh b/smp-docker/images/weblogic-12.2-smp/container-scripts/waitForAdminServer.sh similarity index 72% rename from smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/waitForAdminServer.sh rename to smp-docker/images/weblogic-12.2-smp/container-scripts/waitForAdminServer.sh index e95a65c5042a6f8f9cc84de0e6687f4f53dc70e9..24da5874d6f2f0480f43fdec5ab3bc3d0b45320b 100755 --- a/smp-docker/images/oracle/weblogic-12213-domain-home-in-image/container-scripts/waitForAdminServer.sh +++ b/smp-docker/images/weblogic-12.2-smp/container-scripts/waitForAdminServer.sh @@ -7,10 +7,10 @@ # This script will wait until Admin Server is available. # There is no timeout! # -echo "Waiting for WebLogic Admin Server on $ADMIN_HOST:$ADMIN_PORT to become available..." +echo "Waiting for WebLogic Admin Server on $WL_ADMIN_HOST:$WL_ADMIN_PORT to become available..." while : do - (echo > /dev/tcp/$ADMIN_HOST/$ADMIN_PORT) >/dev/null 2>&1 + (echo > /dev/tcp/$WL_ADMIN_HOST/$WL_ADMIN_PORT) >/dev/null 2>&1 available=$? if [[ $available -eq 0 ]]; then echo "WebLogic Admin Server is now available. Proceeding..." diff --git a/smp-docker/images/weblogic-12.2-smp/properties/init/datasource.properties b/smp-docker/images/weblogic-12.2-smp/properties/init/datasource.properties new file mode 100644 index 0000000000000000000000000000000000000000..2bdd46c3d92e011c34841ad275453a5e18b5dc42 --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/properties/init/datasource.properties @@ -0,0 +1,8 @@ +dsname=eDeliverySmpDs +dsdbname=eDeliverySmpDs +dsjndiname=jdbc/eDeliverySmpDs +dsdriver=oracle.jdbc.OracleDriver +dsurl=jdbc:oracle:thin:@//smp-oracle-db:1521/xe +dsusername=smp +dspassword=test +dstestquery=SQL SELECT 1 FROM DUAL diff --git a/smp-docker/images/weblogic-12.2-smp/properties/init/domain.properties b/smp-docker/images/weblogic-12.2-smp/properties/init/domain.properties new file mode 100644 index 0000000000000000000000000000000000000000..d7ac9c8f66a9e1e7d7a6aeca3663a9ba526d603f --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/properties/init/domain.properties @@ -0,0 +1,4 @@ +CLUSTER_TYPE=DYNAMIC +JAVA_OPTIONS=-Dweblogic.StdoutDebugEnabled=false +T3_CHANNEL_PORT=30012 +T3_PUBLIC_ADDRESS=kubernetes \ No newline at end of file diff --git a/smp-docker/images/weblogic-12.2-smp/properties/init/smp.config.properties b/smp-docker/images/weblogic-12.2-smp/properties/init/smp.config.properties new file mode 100644 index 0000000000000000000000000000000000000000..d6cc553618ae852e460ad306c10b7a94feeff7fc --- /dev/null +++ b/smp-docker/images/weblogic-12.2-smp/properties/init/smp.config.properties @@ -0,0 +1,8 @@ + +configuration.dir=/data/smp/security +hibernate.dialect=org.hibernate.dialect.Oracle10gDialect +datasource.jndi=jdbc/eDeliverySmpDs +authentication.blueCoat.enabled=true +smp.truststore.password={DEC}{test123} +smp.keystore.password={DEC}{test123} +log.folder=./logs/ diff --git a/smp-docker/images/weblogic-12.2.1.3-smp/Dockerfile b/smp-docker/images/weblogic-12.2.1.3-smp/Dockerfile deleted file mode 100644 index b480d24e3d453321ef7522dd4cb66fa2327d6f98..0000000000000000000000000000000000000000 --- a/smp-docker/images/weblogic-12.2.1.3-smp/Dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -# LICENSE UPL 1.0 -# -# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. -# -# ORACLE DOCKERFILES PROJECT -# -------------------------- -# This Dockerfile extends the Oracle WebLogic image built under 12213-doma-home-in-image. -# -# It will deploy any package defined in APP_PKG_FILE. -# into the DOMAIN_HOME with name defined in APP_NAME -# -# HOW TO BUILD THIS IMAGE -# ----------------------- -# Run: -# $ docker build -t weblogic-smp . -# -# Pull base image -# --------------- -FROM oracle/12213-domain-home-in-image - -# Define variable -ARG SMP_VERSION -ENV APP_VERSION=$SMP_VERSION -ENV LC_ALL=en_US.UTF-8 -ENV LANG="en_US.UTF-8" -ENV LANGUAGE="en_US" - - - - -# Copy files and deploy application in WLST Offline mode -COPY container-scripts/* /u01/oracle/ -COPY smp.war /u01/oracle/ - -RUN /u01/oracle/deploySMPToDomain.sh && \ - wlst -loadProperties /u01/oracle/datasource.properties.oracle /u01/oracle/ds-deploy.py \ - # set enforce-valid-basic-auth-credentials false to allow basic authentication for rest services - && sed -i -e "s/<\/security-configuration>/ <enforce-valid-basic-auth-credentials>false<\/enforce-valid-basic-auth-credentials>\n<\/security-configuration>/g" "/u01/oracle/user_projects/domains/domain1/config/config.xml" - - - -# Define default command to start bash. -CMD ["startAdminServer.sh"] diff --git a/smp-docker/images/weblogic-12.2.1.3-smp/README.md b/smp-docker/images/weblogic-12.2.1.3-smp/README.md deleted file mode 100644 index 63dee32885e0c533bd2557238adea83e7b5b71ea..0000000000000000000000000000000000000000 --- a/smp-docker/images/weblogic-12.2.1.3-smp/README.md +++ /dev/null @@ -1,45 +0,0 @@ -Example of Image with WLS Domain -================================ -This Dockerfile extends the Oracle WebLogic image built under 12213-domain-home-in-image and deploy the sample application to the cluster. - -This sample deploys a simple, one-page web application contained in a ZIP archive. This archive needs to be built (one time only) before building the Docker image. - - $ ./build-archive.sh - -# How to build and run -To deploy an application to a domain where the domain home is inside the image you extend the image `12213-domain-home-in-image` and using WLST offline you deploy the sample application. First make sure you have built sample WebLogic domain image inside **12213-domain-home-in-image**. Now to build this sample, run: - - $ docker build --build-arg APPLICATION_NAME=sample --build-arg APPLICATION_PKG=archive.zip -t 12213-domain-with-app . - -# How to run the domain -Follow the instructions in the sample `OracleWebLogic/samples/12213-domain-home-in-image` to define your domain properties in the domain.properties and domain-security.properties files. - -To start the containerized Administration Server, run: - - $ docker run -d --name wlsadmin --hostname wlsadmin -p 7001:7001 \ - -v <HOST DIRECTORY TO PROPERTIES FILE>/properties/docker-run:/u01/oracle/properties \ - 12213-domain-with-app - -To start a containerized Managed Server (MS1) to self-register with the Administration Server above, run: - - $ docker run -d --name MS1 --link wlsadmin:wlsadmin -p 8001:8001 \ - -v <HOST DIRECTORY TO PROPERTIES FILE>/properties/docker-run:/u01/oracle/properties \ - -e MANAGED_SERV_NAME=managed-server1 12213-domain-with-app startManagedServer.sh - -To start a second Managed Server (MS2), run: - - $ docker run -d --name MS2 --link wlsadmin:wlsadmin -p 8002:8001 \ - -v <HOST DIRECTORY TO PROPERTIES FILE>/properties/docker-run:/u01/oracle/properties \ - -e MANAGED_SERV_NAME=managed-server2 12213-domain-with-app startManagedServer.sh - - -Run the WLS Administration Console: - -In your browser, enter `https://localhost:7001/console`. - -Run the sample application: - -To access the sample application, in your browser enter `http://localhost:7001/sample`. - -# Copyright -Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. diff --git a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/deploySMPToDomain.sh b/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/deploySMPToDomain.sh deleted file mode 100755 index f74d954fe7b4ed2f5f3c9ebc381681fb2944c231..0000000000000000000000000000000000000000 --- a/smp-docker/images/weblogic-12.2.1.3-smp/container-scripts/deploySMPToDomain.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -#Define DOMAIN_HOME -echo "Domain Home is: " $DOMAIN_HOME - - -if [ ! -d "$DOMAIN_HOME/classes" ]; then - mkdir -p "$DOMAIN_HOME/classes"; -fi - -# create smp property file -echo "hibernate.dialect=org.hibernate.dialect.Oracle10gDialect" > "$DOMAIN_HOME/classes/smp.config.properties" -echo "datasource.jndi=jdbc/eDeliverySmpDs" >> "$DOMAIN_HOME/classes/smp.config.properties" -echo "authentication.blueCoat.enabled=true" >> "$DOMAIN_HOME/classes/smp.config.properties" -echo "smp.truststore.password={DEC}{test123}" >> "$DOMAIN_HOME/classes/smp.config.properties" -echo "smp.keystore.password={DEC}{test123}" >> "$DOMAIN_HOME/classes/smp.config.properties" -echo "log.folder=./logs/" >> "$DOMAIN_HOME/classes/smp.config.properties" - -# create weblogic classpath to classes folder -echo "export CLASSPATH=\${CLASSPATH}:\${DOMAIN_HOME}/classes" >> "$DOMAIN_HOME/bin/setDomainEnv.sh" - - -cp /u01/oracle/smp.war "$DOMAIN_HOME/" - - -# Deploy Application -wlst.sh -skipWLSModuleScanning /u01/oracle/smp-app-deploy.py diff --git a/smp-docker/pom.xml b/smp-docker/pom.xml index 3c4fe5dcf88f58de90823dd563eef24122692d23..b2cd63cbcc7795212ae763636500e2c0522f3b6a 100644 --- a/smp-docker/pom.xml +++ b/smp-docker/pom.xml @@ -8,15 +8,45 @@ <parent> <groupId>eu.europa.ec.edelivery</groupId> <artifactId>smp-modules</artifactId> - <version>4.2-SNAPSHOT</version> + <version>4.2-RC2-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <properties> <!-- Only selected modules are deployed --> <maven.deploy.skip>false</maven.deploy.skip> + <docker.artefacts.folder>/CEF/repo</docker.artefacts.folder> </properties> <artifactId>smp-docker</artifactId> <packaging>jar</packaging> - <name>SMP Docker</name> + <name>SMP Docker</name> + <profiles> + <profile> + <id>build-docker-images</id> + <build> + <plugins> + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>build-docker-images</id> + <phase>package</phase> + <goals> + <goal>run</goal> + </goals> + <configuration> + <target name="buildDockerImages"> + <exec executable="/bin/bash"> + <arg value="${project.basedir}/images/build-docker-images.sh"/> + <arg value="-o ${docker.artefacts.folder}"/> + </exec> + </target> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> diff --git a/smp-examples/smp-spi-example/pom.xml b/smp-examples/smp-spi-example/pom.xml index f7e28913acc45e83ee808fc4735e725fedc4ada2..91eea6e7d2c8755def075b1c0a5b6d2c5ea2d701 100644 --- a/smp-examples/smp-spi-example/pom.xml +++ b/smp-examples/smp-spi-example/pom.xml @@ -26,7 +26,6 @@ <artifactId>smp-spi-example</artifactId> <name>smp-spi-example</name> - <packaging>jar</packaging> <description>Example of SMP's SPI Payload validation.</description> <dependencies> <dependency> diff --git a/smp-examples/smp-spi-example/src/main/java/eu/europa/ec/smp/spi/ExamplePayloadValidatorSpiImpl.java b/smp-examples/smp-spi-example/src/main/java/eu/europa/ec/smp/spi/ExamplePayloadValidatorSpiImpl.java index 520beb4a85dc3b51b40312a9a88504749b02bbe7..960012abee69a524ef48641afe8f26c6388a5ee4 100644 --- a/smp-examples/smp-spi-example/src/main/java/eu/europa/ec/smp/spi/ExamplePayloadValidatorSpiImpl.java +++ b/smp-examples/smp-spi-example/src/main/java/eu/europa/ec/smp/spi/ExamplePayloadValidatorSpiImpl.java @@ -30,10 +30,15 @@ public class ExamplePayloadValidatorSpiImpl implements PayloadValidatorSpi { LOG.info("*********************************************************************"); LOG.info("* Validate payload with size [{}] and mime type [{}]!", payload.available(), mimeType); LOG.info("**********************************************************************"); + if (payload.available() > 0 ) { + int firstChar = payload.read(); + // For the test if payload starts with an E throws and error + if (firstChar == (int)'E') { + throw new PayloadValidatorSpiException("This is invalid payload starting with E"); + } + } } catch (IOException e) { throw new PayloadValidatorSpiException("Can not read payload", e); } } - - ; } \ No newline at end of file diff --git a/smp-server-library/pom.xml b/smp-server-library/pom.xml index e5536dcc3e67f08adaeeebbb8d7edfd00370b34d..0dfd685d3bb854b3b64d85aafd4708d0c3a00ac8 100644 --- a/smp-server-library/pom.xml +++ b/smp-server-library/pom.xml @@ -165,11 +165,11 @@ </dependency> <dependency> <groupId>org.bouncycastle</groupId> - <artifactId>bcprov-jdk15on</artifactId> + <artifactId>bcprov-jdk15to18</artifactId> </dependency> <dependency> <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk15on</artifactId> + <artifactId>bcpkix-jdk15to18</artifactId> </dependency> <!-- Tests --> diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/FileProperty.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/FileProperty.java index ab78d7cbea3230cbe11cbee38f9a9d6b2db0a29c..39b5a8bbdaafc24a7f564a4a7a030c8f7e21b664 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/FileProperty.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/FileProperty.java @@ -7,6 +7,7 @@ import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; @@ -39,6 +40,9 @@ public class FileProperty { public static final String PROPERTY_LIB_FOLDER = "libraries.folder"; public static final String PROPERTY_SMP_MODE_DEVELOPMENT = "smp.mode.development"; + protected FileProperty() { + } + public static void updateLogConfiguration(String logFileFolder, String logPropertyFile, String configurationFolder) { if (StringUtils.isNotBlank(logFileFolder)) { @@ -51,20 +55,23 @@ public class FileProperty { File f = new File(logPropertyFile); if (!f.exists()) { - LOG.info("Log configuration file: {} not exists.", f.getAbsolutePath()); + LOG.info("Log configuration file: [{}] not exists.", f.getAbsolutePath()); f = new File(configurationFolder, logPropertyFile); - LOG.info("Set log configuration file: {}.", f.getAbsolutePath()); - + LOG.info("Try with the configuration file path: [{}].", f.getAbsolutePath()); } // if configuration file exist update configuration if (f.exists()) { setLogConfiguration(f); + } else { + LOG.info("File path: [{}] does not exists.", f.getAbsolutePath()); } } public static void setLogConfiguration(File configurationFile) { + LOG.info("Set log configuration properties from the file: [{}]", configurationFile.getAbsolutePath()); try (InputStream configStream = new FileInputStream(configurationFile)) { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + context.reset(); JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); configurator.doConfigure(configStream); // loads logback file diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/PropertyInitialization.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/PropertyInitialization.java index ed5ea5bd1cd1a282c917b462dfd50374075a5d39..5e28aeab3e1f5441bf1cee32cbc15bfe67239912 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/PropertyInitialization.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/PropertyInitialization.java @@ -141,7 +141,7 @@ public class PropertyInitialization { } public void initTruststore(String absolutePath, File fEncryption, EntityManager em, Properties properties, Properties fileProperties, boolean testMode) { - LOG.info("Start generating new truststore."); + LOG.info("Start initialization of the truststore."); String encTrustEncToken; if (fileProperties.containsKey(SMPPropertyEnum.TRUSTSTORE_PASSWORD.getProperty())) { LOG.info("get token from properties"); @@ -192,35 +192,63 @@ public class PropertyInitialization { } } - public void initAndMergeKeystore(String absolutePath, File fEncryption, EntityManager em, Properties initProperties, - Properties fileProperties, boolean testMode) { - LOG.info("Start generating new keystore."); - // store keystore password filename - String newKeyPassword = SecurityUtils.generateAuthenticationToken(testMode); - storeDBEntry(em, SMPPropertyEnum.KEYSTORE_PASSWORD_DECRYPTED, newKeyPassword); - String encPasswd = SecurityUtils.encrypt(fEncryption, newKeyPassword); - storeDBEntry(em, SMPPropertyEnum.KEYSTORE_PASSWORD, encPasswd); - initProperties.setProperty(SMPPropertyEnum.KEYSTORE_PASSWORD.getProperty(), encPasswd); - - //store new keystore - File keystore = getNewFile(absolutePath, SMPPropertyEnum.KEYSTORE_FILENAME.getDefValue()); + public void initKeystore(String absolutePath, File fEncryption, EntityManager em, Properties initProperties, + Properties fileProperties, boolean testMode) { + + LOG.info("Start initialization of the keystore."); + String encKeystoreToken; + if (fileProperties.containsKey(SMPPropertyEnum.KEYSTORE_PASSWORD.getProperty())) { + LOG.debug("Get keystore token from the init properties"); + encKeystoreToken = SecurityUtils.encryptWrappedToken(fEncryption, + fileProperties.getProperty(SMPPropertyEnum.KEYSTORE_PASSWORD.getProperty())); + } else { + // generate new token + LOG.debug("Generate keystore token"); + String trustToken = SecurityUtils.generateAuthenticationToken(testMode); + storeDBEntry(em, SMPPropertyEnum.KEYSTORE_PASSWORD_DECRYPTED, trustToken); + encKeystoreToken = SecurityUtils.encrypt(fEncryption, trustToken); + } + LOG.debug("Store keystore security token to database"); + // store token to database + storeDBEntry(em, SMPPropertyEnum.KEYSTORE_PASSWORD, encKeystoreToken); + initProperties.setProperty(SMPPropertyEnum.KEYSTORE_PASSWORD.getProperty(), encKeystoreToken); + + LOG.debug("Decode security token"); + String trustToken = SecurityUtils.decrypt(fEncryption, encKeystoreToken); + LOG.info("Initialize keystore file!"); + File keystore; + if (fileProperties.containsKey(SMPPropertyEnum.KEYSTORE_FILENAME.getProperty())) { + LOG.debug("Get keystore filename from property file"); + keystore = new File(absolutePath, fileProperties.getProperty( + SMPPropertyEnum.KEYSTORE_FILENAME.getProperty())); + + } else { + LOG.info("Create new keystore file "); + keystore = getNewFile(absolutePath, SMPPropertyEnum.KEYSTORE_FILENAME.getDefValue()); + } + LOG.debug("Set SMP keystore to file [{}]!", keystore.getAbsolutePath()); + // store file to database storeDBEntry(em, SMPPropertyEnum.KEYSTORE_FILENAME, keystore.getName()); initProperties.setProperty(SMPPropertyEnum.KEYSTORE_FILENAME.getProperty(), keystore.getName()); - try (FileOutputStream out = new FileOutputStream(keystore)) { - KeyStore newKeystore = KeyStore.getInstance(KeyStore.getDefaultType()); - // initialize keystore - newKeystore.load(null, newKeyPassword.toCharArray()); - // check if keystore is empty then generate cert for user - if (newKeystore.size() == 0) { - X509CertificateUtils.createAndStoreCertificateWithChain( - new String[]{TEST_CERT_ISSUER_DN, TEST_CERT_SUBJECT_DN}, - new String[]{TEST_CERT_ISSUER_ALIAS, TEST_CERT_CERT_ALIAS}, - newKeystore, newKeyPassword); + // if truststore does not exist create a new file + if (!keystore.exists()) { + LOG.info("Generate new truststore file {}.", keystore.getAbsolutePath()); + try (FileOutputStream out = new FileOutputStream(keystore)) { + KeyStore newKeystore = KeyStore.getInstance(KeyStore.getDefaultType()); + // initialize keystore + newKeystore.load(null, trustToken.toCharArray()); + // check if keystore is empty then generate cert for user + if (newKeystore.size() == 0) { + X509CertificateUtils.createAndStoreCertificateWithChain( + new String[]{TEST_CERT_ISSUER_DN, TEST_CERT_SUBJECT_DN}, + new String[]{TEST_CERT_ISSUER_ALIAS, TEST_CERT_CERT_ALIAS}, + newKeystore, trustToken); + } + newKeystore.store(out, trustToken.toCharArray()); + } catch (Exception e) { + throw new SMPRuntimeException(INTERNAL_ERROR, e, "Exception occurred while creating truststore", ExceptionUtils.getRootCauseMessage(e)); } - newKeystore.store(out, newKeyPassword.toCharArray()); - } catch (Exception e) { - throw new SMPRuntimeException(INTERNAL_ERROR, e, "Exception occurred while creating keystore", ExceptionUtils.getRootCauseMessage(e)); } } @@ -235,11 +263,13 @@ public class PropertyInitialization { } // if file is not existing yet - as is the case in getNewFile create file if (!fEncryption.exists()) { + LOG.debug("Generate encryption key."); SecurityUtils.generatePrivateSymmetricKey(fEncryption); + LOG.info("Encryption key generated."); + } else { + LOG.info("Use existing encryption key! [{}].", fEncryption.getAbsolutePath()); } - SecurityUtils.generatePrivateSymmetricKey(fEncryption); - LOG.info("Encryption key generated."); storeDBEntry(em, ENCRYPTION_FILENAME, fEncryption.getName()); initProperties.setProperty(ENCRYPTION_FILENAME.getProperty(), fEncryption.getName()); return fEncryption; @@ -275,7 +305,7 @@ public class PropertyInitialization { // init truststore initTruststore(absolutePath, fEncryption, em, initProperties, fileProperties, devMode); - initAndMergeKeystore(absolutePath, fEncryption, em, initProperties, fileProperties, devMode); + initKeystore(absolutePath, fEncryption, em, initProperties, fileProperties, devMode); return fEncryption; } 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 db38ba15b997fa81ab3216a500ef481e865bb90d..6054bd436516648cae396515a6cc3acdff60f860 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,9 +3,9 @@ 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.services.ConfigurationService; 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.stereotype.Component; @@ -19,9 +19,14 @@ import java.time.OffsetDateTime; @Component public class DBUserToUserROConverter implements Converter<DBUser, UserRO> { - @Autowired + private ConfigurationService configurationService; private ConversionService conversionService; + public DBUserToUserROConverter(ConfigurationService configurationService, ConversionService conversionService) { + this.configurationService = configurationService; + this.conversionService = conversionService; + } + @Override public UserRO convert(DBUser source) { @@ -35,6 +40,17 @@ public class DBUserToUserROConverter implements Converter<DBUser, UserRO> { target.setAccessTokenExpireOn(source.getAccessTokenExpireOn()); target.setPasswordExpired(isPasswordExpired(source)); + target.setSequentialLoginFailureCount(source.getSequentialLoginFailureCount()); + target.setLastFailedLoginAttempt(source.getLastFailedLoginAttempt()); + target.setSuspendedUtil(getSuspensionUntilDate(source.getLastFailedLoginAttempt(),source.getSequentialLoginFailureCount(), + configurationService.getLoginSuspensionTimeInSeconds(), configurationService.getLoginMaxAttempts())); + target.setSequentialTokenLoginFailureCount(source.getSequentialTokenLoginFailureCount()); + target.setLastTokenFailedLoginAttempt(source.getLastTokenFailedLoginAttempt()); + target.setTokenSuspendedUtil(getSuspensionUntilDate(source.getLastTokenFailedLoginAttempt(), + source.getSequentialTokenLoginFailureCount(), + configurationService.getAccessTokenLoginSuspensionTimeInSeconds(), + configurationService.getAccessTokenLoginMaxAttempts())); + target.setActive(source.isActive()); // do not expose internal id target.setUserId(SessionSecurityUtils.encryptedEntityId(source.getId())); @@ -51,6 +67,20 @@ public class DBUserToUserROConverter implements Converter<DBUser, UserRO> { return target; } + public OffsetDateTime getSuspensionUntilDate(OffsetDateTime lastAttempt, Integer currentCount, Integer suspendedForSec, Integer suspendedFromCount){ + if (lastAttempt ==null || currentCount ==null || suspendedForSec ==null || suspendedFromCount ==null){ + return null; + } + if (currentCount < suspendedFromCount){ + return null; + } + OffsetDateTime suspendedUtil = lastAttempt.plusSeconds(suspendedForSec); + if (suspendedUtil.isBefore(OffsetDateTime.now())){ + return null; + } + return suspendedUtil; + } + private boolean isPasswordExpired(DBUser source) { return StringUtils.isNotEmpty(source.getPassword()) && (source.getPasswordExpireOn() == null diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java index 1e049b0ef19c03890141173c1efedf8abd5810ee..2088fbca0e8bf14ef43eb853c97b79e2f02444b8 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java @@ -90,9 +90,13 @@ public class UserDao extends BaseDao<DBUser> { * @return resturns Optional DBUser for identifier */ public Optional<DBUser> findUserByIdentifier(String identifier) { - - Optional<DBUser> usr = findUserByAuthenticationToken(identifier); + Optional<DBUser> usr = findUserByUsername(identifier); + if (!usr.isPresent()) { + LOG.info("Service group owner [{}] not found by username. Try with the access token!", identifier); + usr = findUserByAuthenticationToken(identifier); + } if (!usr.isPresent()) { // try to retrieve by identifier + LOG.info("Service group owner [{}] not found by username. Try with certificate id!", identifier); usr = findUserByCertificateId(identifier); } return usr; 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 1a54b99c0da95822c52d91b2d0c2ea2f050b7096..d55a2a055d05e5e54a444f4c719f4875de65f23d 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 @@ -113,21 +113,21 @@ public class DBUser extends BaseEntity { private String password; @Column(name = "PASSWORD_CHANGED") @ColumnDescription(comment = "Last date when password was changed") - OffsetDateTime passwordChanged; + private OffsetDateTime passwordChanged; @Column(name = "PASSWORD_EXPIRE_ON") @ColumnDescription(comment = "Date when password will expire") - OffsetDateTime passwordExpireOn; + private OffsetDateTime passwordExpireOn; @Column(name = "PASSWORD_LAST_ALERT_ON") @ColumnDescription(comment = "Generated last password expire alert") - OffsetDateTime passwordExpireAlertOn; + private OffsetDateTime passwordExpireAlertOn; @Column(name = "LOGIN_FAILURE_COUNT") @ColumnDescription(comment = "Sequential login failure count") - Integer sequentialLoginFailureCount; + private Integer sequentialLoginFailureCount; @Column(name = "LAST_FAILED_LOGIN_ON") @ColumnDescription(comment = "Last failed login attempt") - OffsetDateTime lastFailedLoginAttempt; + private OffsetDateTime lastFailedLoginAttempt; // Personal access token @Column(name = "ACCESS_TOKEN_ID", length = CommonColumnsLengths.MAX_USERNAME_LENGTH, unique = true) @@ -138,19 +138,19 @@ public class DBUser extends BaseEntity { private String accessToken; @Column(name = "ACCESS_TOKEN_GENERATED_ON") @ColumnDescription(comment = "Date when personal access token was generated") - OffsetDateTime accessTokenGeneratedOn; + private OffsetDateTime accessTokenGeneratedOn; @Column(name = "ACCESS_TOKEN_EXPIRE_ON") @ColumnDescription(comment = "Date when personal access token will expire") - OffsetDateTime accessTokenExpireOn; + private OffsetDateTime accessTokenExpireOn; @Column(name = "ACCESS_TOKEN_LAST_ALERT_ON") @ColumnDescription(comment = "Generated last access token expire alert") - OffsetDateTime accessTokenExpireAlertOn; + private OffsetDateTime accessTokenExpireAlertOn; @Column(name = "AT_LOGIN_FAILURE_COUNT") @ColumnDescription(comment = "Sequential token login failure count") - Integer sequentialTokenLoginFailureCount; + private Integer sequentialTokenLoginFailureCount; @Column(name = "AT_LAST_FAILED_LOGIN_ON") @ColumnDescription(comment = "Last failed token login attempt") - OffsetDateTime lastTokenFailedLoginAttempt; + private OffsetDateTime lastTokenFailedLoginAttempt; @Column(name = "ACTIVE", nullable = false) @ColumnDescription(comment = "Is user active") diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java index 1d0d73a4077c00175e7068096cd3770a0ab8f7e8..416602a8cd19f3894ed890f5f0c4080ee28d9599 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java @@ -6,16 +6,17 @@ import java.util.List; /** * Public SmpInfoRO properties. + * * @author Joze Rihtarsic * @since 4.1 */ public class SmpInfoRO implements Serializable { private static final long serialVersionUID = -49712226560325302L; - String version; - String ssoAuthenticationLabel; - String ssoAuthenticationURI; - String contextPath; - List<String> authTypes = new ArrayList<>();; + private String version; + private String ssoAuthenticationLabel; + private String ssoAuthenticationURI; + private String contextPath; + private List<String> authTypes = new ArrayList<>(); public String getVersion() { return version; 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 686969d0ad4dc4f1acfaf945807cbdbaf35f7474..ca06b121ebf774b49fca4276de832523f3395d0f 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,8 +1,10 @@ package eu.europa.ec.edelivery.smp.data.ui; +import eu.europa.ec.edelivery.smp.data.dao.utils.ColumnDescription; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import javax.persistence.Column; import java.time.OffsetDateTime; import java.util.Collection; @@ -18,8 +20,14 @@ public class UserRO extends BaseRO { String username; String password; OffsetDateTime passwordExpireOn; + Integer sequentialLoginFailureCount; + OffsetDateTime lastFailedLoginAttempt; + OffsetDateTime suspendedUtil; String accessTokenId; OffsetDateTime accessTokenExpireOn; + Integer sequentialTokenLoginFailureCount; + OffsetDateTime lastTokenFailedLoginAttempt; + OffsetDateTime tokenSuspendedUtil; String emailAddress; Collection<SMPAuthority> authorities; boolean active = true; @@ -175,4 +183,52 @@ public class UserRO extends BaseRO { public void setCasAuthenticated(boolean casAuthenticated) { this.casAuthenticated = casAuthenticated; } + + public Integer getSequentialLoginFailureCount() { + return sequentialLoginFailureCount; + } + + public void setSequentialLoginFailureCount(Integer sequentialLoginFailureCount) { + this.sequentialLoginFailureCount = sequentialLoginFailureCount; + } + + public OffsetDateTime getLastFailedLoginAttempt() { + return lastFailedLoginAttempt; + } + + public void setLastFailedLoginAttempt(OffsetDateTime lastFailedLoginAttempt) { + this.lastFailedLoginAttempt = lastFailedLoginAttempt; + } + + public OffsetDateTime getSuspendedUtil() { + return suspendedUtil; + } + + public void setSuspendedUtil(OffsetDateTime suspendedUtil) { + this.suspendedUtil = suspendedUtil; + } + + public Integer getSequentialTokenLoginFailureCount() { + return sequentialTokenLoginFailureCount; + } + + public void setSequentialTokenLoginFailureCount(Integer sequentialTokenLoginFailureCount) { + this.sequentialTokenLoginFailureCount = sequentialTokenLoginFailureCount; + } + + public OffsetDateTime getLastTokenFailedLoginAttempt() { + return lastTokenFailedLoginAttempt; + } + + public void setLastTokenFailedLoginAttempt(OffsetDateTime lastTokenFailedLoginAttempt) { + this.lastTokenFailedLoginAttempt = lastTokenFailedLoginAttempt; + } + + public OffsetDateTime getTokenSuspendedUtil() { + return tokenSuspendedUtil; + } + + public void setTokenSuspendedUtil(OffsetDateTime tokenSuspendedUtil) { + this.tokenSuspendedUtil = tokenSuspendedUtil; + } } 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 909cb67bc4af7fa74114d179aa891c05eac2627b..3f87455cd5a0552716ac63018482af3110302f83 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,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyTypeEnum.*; @@ -40,7 +41,7 @@ public enum SMPPropertyEnum { 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_TLS_TRUSTSTORE_USE_SYSTEM_DEFAULT("bdmsl.integration.tls.userSystemDefaultTruststore", "false", "If true use system default truststore for trusting TLS server certificate (Legacy behaviour to SMP 4.1 version), else use SMP truststore", false, false, false, BOOLEAN), + SML_TLS_TRUSTSTORE_USE_SYSTEM_DEFAULT("bdmsl.integration.tls.useSystemDefaultTruststore", "false", "If true use system default truststore for trusting TLS server certificate (Legacy behaviour to SMP 4.1 version), else use SMP truststore", false, false, false, BOOLEAN), 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 @@ -55,7 +56,7 @@ public enum SMPPropertyEnum { 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 | where at least one must be in the CertifictePolicy extension", false, false,false, STRING), + CERTIFICATE_ALLOWED_CERTIFICATEPOLICY_OIDS("smp.certificate.validation.allowedCertificatePolicyOIDs","","List of certificate policy OIDs separated by | where at least one must be in the CertifictePolicy extension", false, false,false, LIST_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, false, CRON_EXPRESSION), @@ -85,7 +86,8 @@ public enum SMPPropertyEnum { "Delay response in ms on invalid username or password", false, false,false, INTEGER), USER_MAX_FAILED_ATTEMPTS("smp.user.login.maximum.attempt","5", - "Number of console login attempt before the user is deactivated", false, false,false, INTEGER), + "The number of sequence login attempts when the user credentials get suspended. The login attempt count as a sequence login" + + " if there is less time between login attempts than defined in property: smp.user.login.suspension.time!", false, false,false, INTEGER), USER_SUSPENSION_TIME("smp.user.login.suspension.time","3600", "Time in seconds for a suspended user to be reactivated. (if 0 the user will not be reactivated)", false, false,false, INTEGER), @@ -101,7 +103,8 @@ public enum SMPPropertyEnum { // 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", "TOKEN|CERTIFICATE", - "Set list of '|' separated application-automation authentication types (Web-Service integration). Currently supported TOKEN, CERTIFICATE: ex. TOKEN|CERTIFICATE", false, false, false, LIST_STRING), + "Set list of '|' separated application-automation authentication types (Web-Service integration). Currently supported TOKEN, CERTIFICATE: ex. TOKEN|CERTIFICATE", false, false, false, LIST_STRING + ), EXTERNAL_TLS_AUTHENTICATION_CLIENT_CERT_HEADER_ENABLED("smp.automation.authentication.external.tls.clientCert.enabled", "false", "Authentication with external module as: reverse proxy. Authenticated data are send send to application using 'Client-Cert' HTTP header. Do not enable this feature " + @@ -131,18 +134,23 @@ public enum SMPPropertyEnum { ALERT_USER_LOGIN_FAILURE_ENABLED("smp.alert.user.login_failure.enabled", "false", "Enable/disable the login failure alert of the authentication module.", false, false,false, BOOLEAN), ALERT_USER_LOGIN_FAILURE_LEVEL("smp.alert.user.login_failure.level", - "LOW", "Alert level for login failure.", false, false,false, STRING), + "LOW", "Alert level for login failure. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_USER_LOGIN_FAILURE_MAIL_SUBJECT("smp.alert.user.login_failure.mail.subject", - "Login failure", "Login failure mail subject. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "Login failure", "Login failure mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), ALERT_USER_SUSPENDED_ENABLED("smp.alert.user.suspended.enabled", "true", "Enable/disable the login suspended alert of the authentication module.", false, false,false, BOOLEAN), ALERT_USER_SUSPENDED_LEVEL("smp.alert.user.suspended.level", - "HIGH", "Alert level for login suspended. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "HIGH", "Alert level for login suspended. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_USER_SUSPENDED_MAIL_SUBJECT("smp.alert.user.suspended.mail.subject", - "Login credentials suspended", "Login suspended mail subject.", false, false,false, STRING), + "Login credentials suspended", "Login suspended mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character"), ALERT_USER_SUSPENDED_MOMENT("smp.alert.user.suspended.mail.moment", - "WHEN_BLOCKED", "#When should the account disabled alert be triggered. Values: AT_LOGON: An alert will be triggered each time a user tries to login to a disabled account. WHEN_BLOCKED: An alert will be triggered once when the account got suspended.", false, false,false, STRING), + "WHEN_BLOCKED", "When should the account disabled alert be triggered. Values: AT_LOGON: An alert will submit mail for all logon attempts to suspended account, WHEN_BLOCKED: An alert will be triggered only the first time when the account got suspended.", + false, false,false, STRING, "^(AT_LOGON|WHEN_BLOCKED)$", "Allowed values are: AT_LOGON,WHEN_BLOCKED" ), ALERT_PASSWORD_BEFORE_EXPIRATION_ENABLED("smp.alert.password.imminent_expiration.enabled", "true", "Enable/disable the imminent password expiration alert", false, false,false, BOOLEAN), @@ -151,9 +159,11 @@ public enum SMPPropertyEnum { ALERT_PASSWORD_BEFORE_EXPIRATION_INTERVAL("smp.alert.password.imminent_expiration.frequency_days", "5", "Interval between alerts.", false, false,false, INTEGER), ALERT_PASSWORD_BEFORE_EXPIRATION_LEVEL("smp.alert.password.imminent_expiration.level", - "LOW", "Password imminent expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "LOW", "Password imminent expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_PASSWORD_BEFORE_EXPIRATION_MAIL_SUBJECT("smp.alert.password.imminent_expiration.mail.subject", - "Password imminent expiration", "Password imminent expiration mail subject.", false, false,false, STRING), + "Password imminent expiration", "Password imminent expiration mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), ALERT_PASSWORD_EXPIRED_ENABLED("smp.alert.password.expired.enabled", "true", "Enable/disable the password expiration alert", false, false,false, BOOLEAN), @@ -162,9 +172,11 @@ public enum SMPPropertyEnum { ALERT_PASSWORD_EXPIRED_INTERVAL("smp.alert.password.expired.frequency_days", "5", "Frequency in days between alerts.", false, false,false, INTEGER), ALERT_PASSWORD_EXPIRED_LEVEL("smp.alert.password.expired.level", - "LOW", "Password expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "LOW", "Password expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_PASSWORD_EXPIRED_MAIL_SUBJECT("smp.alert.password.expired.mail.subject", - "Password expired", "Password expiration mail subject.", false, false,false, STRING), + "Password expired", "Password expiration mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), ALERT_ACCESS_TOKEN_BEFORE_EXPIRATION_ENABLED("smp.alert.accessToken.imminent_expiration.enabled", "true", "Enable/disable the imminent accessToken expiration alert", false, false,false, BOOLEAN), @@ -173,9 +185,11 @@ public enum SMPPropertyEnum { ALERT_ACCESS_TOKEN_BEFORE_EXPIRATION_INTERVAL("smp.alert.accessToken.imminent_expiration.frequency_days", "5", "Frequency in days between alerts.", false, false,false, INTEGER), ALERT_ACCESS_TOKEN_BEFORE_EXPIRATION_LEVEL("smp.alert.accessToken.imminent_expiration.level", - "LOW", "AccessToken imminent expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "LOW", "AccessToken imminent expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_ACCESS_TOKEN_BEFORE_EXPIRATION_MAIL_SUBJECT("smp.alert.accessToken.imminent_expiration.mail.subject", - "Access token imminent expiration", "accessToken imminent expiration mail subject.", false, false,false, STRING), + "Access token imminent expiration", "accessToken imminent expiration mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), ALERT_ACCESS_TOKEN_EXPIRED_ENABLED("smp.alert.accessToken.expired.enabled", "true", "Enable/disable the accessToken expiration alert", false, false,false, BOOLEAN), @@ -184,9 +198,11 @@ public enum SMPPropertyEnum { ALERT_ACCESS_TOKEN_EXPIRED_INTERVAL("smp.alert.accessToken.expired.frequency_days", "5", "Frequency in days between alerts.", false, false,false, INTEGER), ALERT_ACCESS_TOKEN_EXPIRED_LEVEL("smp.alert.accessToken.expired.level", - "LOW", "Access Token expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "LOW", "Access Token expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_ACCESS_TOKEN_EXPIRED_MAIL_SUBJECT("smp.alert.accessToken.expired.mail.subject", - "Access token expired", "Password expiration mail subject.", false, false,false, STRING), + "Access token expired", "Password expiration mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), ALERT_CERTIFICATE_BEFORE_EXPIRATION_ENABLED("smp.alert.certificate.imminent_expiration.enabled", "true", "Enable/disable the imminent certificate expiration alert", false, false,false, BOOLEAN), @@ -195,9 +211,11 @@ public enum SMPPropertyEnum { ALERT_CERTIFICATE_BEFORE_EXPIRATION_INTERVAL("smp.alert.certificate.imminent_expiration.frequency_days", "5", "Frequency in days between alerts.", false, false,false, INTEGER), ALERT_CERTIFICATE_BEFORE_EXPIRATION_LEVEL("smp.alert.certificate.imminent_expiration.level", - "LOW", "certificate imminent expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "LOW", "certificate imminent expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_CERTIFICATE_BEFORE_EXPIRATION_MAIL_SUBJECT("smp.alert.certificate.imminent_expiration.mail.subject", - "Certificate imminent expiration", "Certificate imminent expiration mail subject.", false, false,false, STRING), + "Certificate imminent expiration", "Certificate imminent expiration mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), ALERT_CERTIFICATE_EXPIRED_ENABLED("smp.alert.certificate.expired.enabled", "true", "Enable/disable the certificate expiration alert", false, false,false, BOOLEAN), @@ -206,9 +224,11 @@ public enum SMPPropertyEnum { ALERT_CERTIFICATE_EXPIRED_INTERVAL("smp.alert.certificate.expired.frequency_days", "5", "Frequency in days between alerts.", false, false,false, INTEGER), ALERT_CERTIFICATE_EXPIRED_LEVEL("smp.alert.certificate.expired.level", - "LOW", "Certificate expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING), + "LOW", "Certificate expiration alert level. Values: {LOW, MEDIUM, HIGH}", false, false,false, STRING, + "^(LOW|MEDIUM|HIGH)$", "Allowed values are: LOW, MEDIUM, HIGH"), ALERT_CERTIFICATE_EXPIRED_MAIL_SUBJECT("smp.alert.certificate.expired.mail.subject", - "Certificate expired", "Password expiration mail subject.", false, false,false, STRING), + "Certificate expired", "Certificate expiration mail subject.", false, false,false, STRING, + "^(.{0,255})$", "Subject must have less than 256 character" ), SMP_ALERT_CREDENTIALS_CRON("smp.alert.credentials.cronJobExpression", "0 52 4 */1 * *", "Property cron expression for triggering alert messages !", false, false, false, CRON_EXPRESSION), SMP_ALERT_CREDENTIALS_SERVER("smp.alert.credentials.serverInstance", "localhost", "If smp.cluster.enabled is set to true then then instance (hostname) to generate report.", false, false, false, STRING), @@ -221,14 +241,16 @@ public enum SMPPropertyEnum { String property; String defValue; String desc; - String valuePattern; + Pattern valuePattern; + String errorValueMessage; boolean isEncrypted; boolean isMandatory; boolean restartNeeded; SMPPropertyTypeEnum propertyType; - SMPPropertyEnum(String property, String defValue, String desc, boolean isMandatory, boolean isEncrypted, boolean restartNeeded, SMPPropertyTypeEnum propertyType,String valuePattern) { + SMPPropertyEnum(String property, String defValue, String desc, boolean isMandatory, boolean isEncrypted, boolean restartNeeded, + SMPPropertyTypeEnum propertyType,String valuePattern,String errorValueMessage ) { this.property = property; this.defValue = defValue; this.desc = desc; @@ -236,11 +258,12 @@ public enum SMPPropertyEnum { this.isMandatory = isMandatory; this.restartNeeded = restartNeeded; this.propertyType = propertyType; - this.valuePattern = valuePattern; + this.valuePattern = Pattern.compile(valuePattern); + this.errorValueMessage = errorValueMessage; } SMPPropertyEnum(String property, String defValue, String desc, boolean isMandatory, boolean isEncrypted, boolean restartNeeded, SMPPropertyTypeEnum propertyType) { - this(property, defValue, desc, isMandatory, isEncrypted, restartNeeded, propertyType, propertyType.errorTemplate); + this(property, defValue, desc, isMandatory, isEncrypted, restartNeeded, propertyType, propertyType.defValidationRegExp, propertyType.getErrorMessage(property)); } @@ -284,9 +307,13 @@ public enum SMPPropertyEnum { return Arrays.asList(values()).stream().filter(val -> val.isRestartNeeded()).collect(Collectors.toList()); } - public String getValuePattern() { + public Pattern getValuePattern() { return valuePattern; } + + public String getErrorValueMessage() { + return this.errorValueMessage; + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyTypeEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyTypeEnum.java index ad19834547a57755f863f1b9b44e465d49bd8e23..c05c65c324040ee79862a9480049cc405bfaf064 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyTypeEnum.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyTypeEnum.java @@ -1,17 +1,17 @@ package eu.europa.ec.edelivery.smp.data.ui.enums; public enum SMPPropertyTypeEnum { - STRING (".*","Property [%s] is not valid String type!"), - LIST_STRING(".*","Property [%s] is not valid LIST_STRING type!"), - MAP_STRING(".*","Property [%s] is not valid MAP_STRING type!"), - INTEGER("\\d*","Property [%s] is not valid Integer!"), + STRING (".{0,2000}","Property value [%s] must be less than 2000 characters!"), + LIST_STRING(".{0,2000}","Property [%s] is not valid LIST_STRING type!"), + MAP_STRING(".{0,2000}","Property [%s] is not valid MAP_STRING type!"), + INTEGER("\\d{0,12}","Property [%s] is not valid Integer!"), BOOLEAN("true|false","Property [%s] is not valid Boolean type!"), - REGEXP(".*", "Property [%s] is not valid Regular Expression type!"), - CRON_EXPRESSION(".*","Property [%s] is not valid Cron Expression type!"), - EMAIL(".*","Property [%s] is not valid Email address type!"), - FILENAME(".*","Property [%s] is not valid Filename type or it does not exists!"), - PATH(".*","Property [%s] is not valid Path type or it does not exists!"), - URL(".*","Property [%s] is not valid URL type or it does not exists!"), + REGEXP(".{0,2000}", "Property [%s] is not valid Regular Expression type!"), + CRON_EXPRESSION(".{0,2000}","Property [%s] is not valid Cron Expression type!"), + EMAIL(".{0,2000}","Property [%s] is not valid Email address type!"), + FILENAME(".{0,2000}","Property [%s] is not valid Filename type or it does not exists!"), + PATH(".{0,2000}","Property [%s] is not valid Path type or it does not exists!"), + URL(".{0,2000}","Property [%s] is not valid URL!"), ; String errorTemplate; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java index a15f4ce7bd2ce66a26a91dcc86ed08371d0250b2..f95794a63590e1840958d8b71a9bd28741b95277 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java @@ -57,11 +57,11 @@ public enum ErrorCode { // XML_SIGNING_EXCEPTION (500,"SMP:500",ErrorBusinessCode.TECHNICAL,"Error occurred while signing response!"), - JAXB_INITIALIZATION (500,"SMP:511",ErrorBusinessCode.TECHNICAL, "Could not create Unmarshaller for class %s!"), - XML_PARSE_EXCEPTION (500,"SMP:512",ErrorBusinessCode.TECHNICAL, "Error occurred while parsing input stream for %s. Error: %s!"), - INVALID_REQUEST(400,"SMP:513",ErrorBusinessCode.TECHNICAL, "Invalid request %s. Error: %s!"), - INTERNAL_ERROR (500,"SMP:514",ErrorBusinessCode.TECHNICAL, "Internal error %s. Error: %s!"), - CERTIFICATE_ERROR (500,"SMP:515",ErrorBusinessCode.TECHNICAL, "Certificate error %s. Error: %s!"), + JAXB_INITIALIZATION (500,"SMP:511",ErrorBusinessCode.TECHNICAL, "Could not create Unmarshaller for class [%s]!"), + XML_PARSE_EXCEPTION (500,"SMP:512",ErrorBusinessCode.TECHNICAL, "Error occurred while parsing input stream for [%s]. Error: %s!"), + INVALID_REQUEST(400,"SMP:513",ErrorBusinessCode.TECHNICAL, "Invalid request [%s]. Error: %s!"), + INTERNAL_ERROR (500,"SMP:514",ErrorBusinessCode.TECHNICAL, "Internal error [%s]. Error: %s!"), + CERTIFICATE_ERROR (500,"SMP:515",ErrorBusinessCode.TECHNICAL, "Certificate error [%s]. Error: %s!"), CONFIGURATION_ERROR (500,"SMP:516",ErrorBusinessCode.TECHNICAL, "Configuration error: %s!"), diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java index 0bf2341c134360e784fae906bf8665a1975d2160..5f0a72fcbf2809c35c6871167d2536e25c6e1fda 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java @@ -43,15 +43,15 @@ public enum SMPMessageCode implements MessageCode { BUS_INVALID_XML("BUS-030", "Invalid XML for {}. Error: [{}]"), SEC_UNSECURED_LOGIN_ALLOWED("SEC-001", "Unsecure login is allowed, no authentication will be performed"), - SEC_USER_AUTHENTICATED("SEC-002", "User {} is authenticated with role {}."), - SEC_USER_NOT_EXISTS("SEC-003", "User {} not exists."), - SEC_INVALID_PASSWORD("SEC-004", "User {} has invalid password."), - SEC_USER_CERT_NOT_EXISTS("SEC-005", "User certificate {} not exists."), - SEC_USER_CERT_INVALID("SEC-006", "User certificate {} is invalid: {}."), - SEC_USER_NOT_AUTHENTICATED("SEC-007", "User {}. Reason: {}."), - SEC_USER_SUSPENDED("SEC-008", "User {} is temporarily suspended."), - - + SEC_USER_AUTHENTICATED("SEC-002", "User [{}] is authenticated with role [{}]."), + SEC_USER_NOT_EXISTS("SEC-003", "User [{}] not exists."), + SEC_INVALID_PASSWORD("SEC-004", "User [{}] has invalid password."), + SEC_USER_CERT_NOT_EXISTS("SEC-005", "User certificate [{}] not exists."), + SEC_USER_CERT_INVALID("SEC-006", "User certificate [{}] is invalid: [{}]."), + SEC_USER_NOT_AUTHENTICATED("SEC-007", "User [{}]. Reason: [{}]."), + SEC_USER_SUSPENDED("SEC-008", "User [{}] is temporarily suspended."), + SEC_INVALID_TOKEN("SEC-009", "User [{}] has invalid token value for token id: [{}]."), + SEC_TRUSTSTORE_CERT_INVALID("SEC-010", "Truststore certificate with alias [{}] is invalid: [{}]."), ; String code; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CRLVerifierService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CRLVerifierService.java index 56ee3693a3dcd6cf34907df117f2b51bb66903a5..e69a57f7b034305277f0da39a240dbd51d03755c 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CRLVerifierService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/CRLVerifierService.java @@ -153,13 +153,13 @@ public class CRLVerifierService { crl = (X509CRL) cf.generateCRL(crlStream); } } catch (IOException e) { - exception = new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Can not download CRL '" + crlURL + exception = new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Can not download CRL '" + crlURL+"'" , ExceptionUtils.getRootCauseMessage(e), e); } catch (CertificateException e) { - exception = new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "CRL list is not supported '" + crlURL + exception = new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "CRL list is not supported '" + crlURL+"'" , ExceptionUtils.getRootCauseMessage(e), e); } catch (CRLException e) { - exception = new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "CRL can not be read: '" + crlURL + exception = new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "CRL can not be read: '" + crlURL+"'" , ExceptionUtils.getRootCauseMessage(e), e); } catch (SMPRuntimeException exc) { exception = exc; @@ -198,7 +198,7 @@ public class CRLVerifierService { } return inputStream; } catch (Exception exc) { - throw new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Error occurred while downloading CRL:'" + crlURL, ExceptionUtils.getRootCauseMessage(exc)); + throw new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Error occurred while downloading CRL:'" + crlURL+"'", ExceptionUtils.getRootCauseMessage(exc) ); } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorService.java index 239509e48bcd593037e5535327c4fe95f61dce41..7f8cecbe019d4d5f9c9acb64fef3e36ce15cae93 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorService.java @@ -14,6 +14,8 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import static eu.europa.ec.edelivery.smp.logging.SMPLogger.SECURITY_MARKER; + /** * @author Joze Rihtarsic @@ -50,7 +52,8 @@ public class PayloadValidatorService { validatorSpi.validatePayload(payload, mimeType); } } catch (PayloadValidatorSpiException e) { - throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Content validation failed", ExceptionUtils.getRootCauseMessage(e),e); + LOG.error(SECURITY_MARKER, "Content validation failed: [" + ExceptionUtils.getRootCauseMessage(e) + "]", e); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Upload payload", "Content validation failed"); } } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java index d12d8ad1dd4c51269c996fa8934d25eb6b896c2f..f7f22d8a777e5020f2193827d01ad44e0ef49787 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java @@ -100,8 +100,13 @@ public class UIPropertyService { property.setValuePattern(property.getValuePattern()); if (changedProps.containsKey(property.getProperty())) { - property.setNewValue(changedProps.get(propertyType.getProperty()).getValue()); - property.setUpdateDate(refreshPropertiesTrigger.getNextExecutionDate()); + String newVal = changedProps.get(propertyType.getProperty()).getValue(); + if (!StringUtils.equals(newVal, property.getValue())) { + property.setNewValue(changedProps.get(propertyType.getProperty()).getValue()); + property.setUpdateDate(refreshPropertiesTrigger.getNextExecutionDate()); + } else { + LOG.debug("Property [{}] has newer update time, but it has the same value as the current value!"); + } } return property; } 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 4b35cbe3b298f4e76b94f78a1b84695def24f2ca..8209a3d7ba3d569332972b28c64b28a39087e31f 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 @@ -82,8 +82,6 @@ abstract class UIServiceBase<E extends BaseEntity, R> { LOG.error(msg, e ); throw new SMPRuntimeException(INTERNAL_ERROR, "DB list query exception.", msg); } - - } sg.getServiceEntities().addAll(lstRo); } 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 388e362bd53f2de839bb6b026ad00b49a2f830bc..ec52ce985f945a51386b6398ab11127a05b41b42 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 @@ -25,16 +25,9 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.oasis_open.docs.bdxr.ns.smp._2016._05.DocumentIdentifier; import org.oasis_open.docs.bdxr.ns.smp._2016._05.ParticipantIdentifierType; import org.oasis_open.docs.bdxr.ns.smp._2016._05.ServiceMetadata; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.xml.XMLConstants; -import javax.xml.transform.*; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import java.io.StringReader; -import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; @@ -96,11 +89,11 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service sg.setCount(iCnt); if (iCnt > 0) { - int iStartIndex = pageSize<0?-1:page * pageSize; - if (iStartIndex >= iCnt && page > 0){ - page = page -1; + int iStartIndex = pageSize < 0 ? -1 : page * pageSize; + if (iStartIndex >= iCnt && page > 0) { + page = page - 1; sg.setPage(page); // go back for a page - iStartIndex = pageSize<0?-1:page * pageSize; + iStartIndex = pageSize < 0 ? -1 : page * pageSize; } List<DBServiceGroup> lst = serviceGroupDao.getServiceGroupList(iStartIndex, pageSize, sortField, sortOrder, filter); @@ -136,7 +129,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service return ex; } - private String getConvertExtensionToString(Long id, byte[] extension){ + private String getConvertExtensionToString(Long id, byte[] extension) { try { return new String(extension, "UTF-8"); } catch (UnsupportedEncodingException e) { @@ -169,34 +162,34 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service /** * Final process of SML records. If participant is to be unregistered it does not update status to database because * it should not be there anymore! For registering it update status! + * * @param lstRecords */ - public void processSMLRecords( List<ParticipantSMLRecord> lstRecords){ - if (!smlIntegrationService.isSMLIntegrationEnabled()){ + public void processSMLRecords(List<ParticipantSMLRecord> lstRecords) { + if (!smlIntegrationService.isSMLIntegrationEnabled()) { return; } - for (ParticipantSMLRecord record: lstRecords){ - if (record.getStatus()== SMLStatusEnum.REGISTER){ + for (ParticipantSMLRecord record : lstRecords) { + if (record.getStatus() == SMLStatusEnum.REGISTER) { boolean result = smlIntegrationService.registerParticipantToSML(record.getParticipantIdentifier(), record.getParticipantScheme(), record.getDomain()); updateServiceGroupDomainStatus(result, record); - }else if (record.getStatus()== SMLStatusEnum.UNREGISTER){ + } else if (record.getStatus() == SMLStatusEnum.UNREGISTER) { boolean result = smlIntegrationService.unregisterParticipantFromSML(record.getParticipantIdentifier(), record.getParticipantScheme(), record.getDomain()); // no need to update database because record is deleted updateServiceGroupDomainStatus(result, record); - } } } - protected void updateServiceGroupDomainStatus(boolean smlActionStatus, ParticipantSMLRecord record){ + protected void updateServiceGroupDomainStatus(boolean smlActionStatus, ParticipantSMLRecord record) { Optional<DBServiceGroupDomain> optionalServiceGroupDomain = serviceGroupDao.findServiceGroupDomain(record.getParticipantIdentifier(), record.getParticipantScheme(), record.getDomain().getDomainCode()); if (optionalServiceGroupDomain.isPresent()) { DBServiceGroupDomain serviceGroupDomain = optionalServiceGroupDomain.get(); - if (serviceGroupDomain.isSmlRegistered()!= smlActionStatus){ + if (serviceGroupDomain.isSmlRegistered() != smlActionStatus) { serviceGroupDomain.setSmlRegistered(smlActionStatus); serviceGroupDao.updateServiceGroupDomain(serviceGroupDomain); } @@ -206,25 +199,25 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service /** * Remove service group + * * @param dRo * @return */ - public List<ParticipantSMLRecord> removeServiceGroup(ServiceGroupRO dRo){ + public List<ParticipantSMLRecord> removeServiceGroup(ServiceGroupRO dRo) { List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>(); DBServiceGroup dbServiceGroup = getDatabaseDao().find(dRo.getId()); // first update domains List<DBServiceGroupDomain> dbServiceGroupDomainList = dbServiceGroup.getServiceGroupDomains(); dbServiceGroupDomainList.forEach(dro -> { - participantSMLRecordList.add( new ParticipantSMLRecord(SMLStatusEnum.UNREGISTER, dro.getServiceGroup().getParticipantIdentifier(), - dro.getServiceGroup().getParticipantScheme(),dro.getDomain())); + participantSMLRecordList.add(new ParticipantSMLRecord(SMLStatusEnum.UNREGISTER, dro.getServiceGroup().getParticipantIdentifier(), + dro.getServiceGroup().getParticipantScheme(), dro.getDomain())); }); serviceGroupDao.removeServiceGroup(dbServiceGroup); return participantSMLRecordList; } - /** * Method validates and converts UI resource object entity to database entity and persists it to database * @@ -297,8 +290,8 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service // everything ok find domain and add it to service group Optional<DBDomain> dmn = domainDao.getDomainByCode(dro.getDomainCode()); if (dmn.isPresent()) { - DBServiceGroupDomain domain = dbServiceGroup.addDomain(dmn.get()); - participantSMLRecordList.add( new ParticipantSMLRecord(SMLStatusEnum.REGISTER, + DBServiceGroupDomain domain = dbServiceGroup.addDomain(dmn.get()); + participantSMLRecordList.add(new ParticipantSMLRecord(SMLStatusEnum.REGISTER, serviceGroupRO.getParticipantIdentifier(), serviceGroupRO.getParticipantScheme(), domain.getDomain())); @@ -326,7 +319,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service updateUsersOnServiceGroup(serviceGroupRO, dbServiceGroup); // update domain - List<ParticipantSMLRecord> participantSMLRecordList = updateDomainsForServiceGroup(serviceGroupRO, dbServiceGroup); + List<ParticipantSMLRecord> participantSMLRecordList = updateDomainsForServiceGroup(serviceGroupRO, dbServiceGroup); //update service metadata List<ServiceMetadataRO> serviceMetadataROList = serviceGroupRO.getServiceMetadata(); @@ -369,28 +362,27 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service if (!Objects.equals(serviceMetadataRO.getDomainCode(), dbServiceGroupDomain.getDomain().getDomainCode())) { // remove from old domain - LOG.info("Move service metadata from domain {} to domain: {}" , dbServiceGroupDomain.getDomain().getDomainCode(), - serviceMetadataRO.getDomainCode( )); + LOG.info("Move service metadata from domain {} to domain: {}", dbServiceGroupDomain.getDomain().getDomainCode(), + serviceMetadataRO.getDomainCode()); DBServiceMetadata smd = dbServiceGroupDomain.removeServiceMetadata(serviceMetadataRO.getDocumentIdentifier(), serviceMetadataRO.getDocumentIdentifierScheme()); - // find new domain and add Optional<DBServiceGroupDomain> optNewDomain = dbServiceGroup.getServiceGroupForDomain(serviceMetadataRO.getDomainCode()); if (optNewDomain.isPresent()) { - LOG.info("ADD service metadata to domain {} " , optNewDomain.get().getDomain().getDomainCode(), - serviceMetadataRO.getDomainCode( )); - // create new because the old service metadata will be deleted - DBServiceMetadata smdNew = new DBServiceMetadata(); - smdNew.setDocumentIdentifier(dbServiceMetadata.getDocumentIdentifier()); - smdNew.setDocumentIdentifierScheme(dbServiceMetadata.getDocumentIdentifierScheme()); - smdNew.setServiceGroupDomain(optNewDomain.get()); - smdNew.setServiceMetadataXml(dbServiceMetadata.getServiceMetadataXml()); - smdNew.setCreatedOn(dbServiceMetadata.getCreatedOn()); - - optNewDomain.get().addServiceMetadata(smdNew); + LOG.info("ADD service metadata to domain {} ", optNewDomain.get().getDomain().getDomainCode(), + serviceMetadataRO.getDomainCode()); + // create new because the old service metadata will be deleted + DBServiceMetadata smdNew = new DBServiceMetadata(); + smdNew.setDocumentIdentifier(dbServiceMetadata.getDocumentIdentifier()); + smdNew.setDocumentIdentifierScheme(dbServiceMetadata.getDocumentIdentifierScheme()); + smdNew.setServiceGroupDomain(optNewDomain.get()); + smdNew.setServiceMetadataXml(dbServiceMetadata.getServiceMetadataXml()); + smdNew.setCreatedOn(dbServiceMetadata.getCreatedOn()); + + optNewDomain.get().addServiceMetadata(smdNew); } else { throw new SMPRuntimeException(SG_NOT_REGISTRED_FOR_DOMAIN, serviceMetadataRO.getDomainCode(), @@ -448,8 +440,8 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service Optional<DBDomain> dmn = domainDao.getDomainByCode(serviceGroupDomainRO.getDomainCode()); if (dmn.isPresent()) { - DBServiceGroupDomain sgd = dbServiceGroup.addDomain(dmn.get()); - participantSMLRecordList.add(new ParticipantSMLRecord( SMLStatusEnum.REGISTER, + DBServiceGroupDomain sgd = dbServiceGroup.addDomain(dmn.get()); + participantSMLRecordList.add(new ParticipantSMLRecord(SMLStatusEnum.REGISTER, sgd.getServiceGroup().getParticipantIdentifier(), sgd.getServiceGroup().getParticipantScheme(), sgd.getDomain())); @@ -460,7 +452,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service }); // remove old domains lstOldSGDomains.forEach(dbServiceGroupDomain -> { - participantSMLRecordList.add(new ParticipantSMLRecord( SMLStatusEnum.UNREGISTER, + participantSMLRecordList.add(new ParticipantSMLRecord(SMLStatusEnum.UNREGISTER, dbServiceGroupDomain.getServiceGroup().getParticipantIdentifier(), dbServiceGroupDomain.getServiceGroup().getParticipantScheme(), dbServiceGroupDomain.getDomain())); @@ -486,7 +478,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service Optional<DBUser> optUser = userDao.findUser(userid); if (!optUser.isPresent()) { throw new SMPRuntimeException(INTERNAL_ERROR, - "Database changed", "User "+userRO.getUsername()+ " not exists! (Refresh data)"); + "Database changed", "User " + userRO.getUsername() + " not exists! (Refresh data)"); } dbServiceGroup.getUsers().add(optUser.get()); } @@ -505,7 +497,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service } // validate service group id boolean schemeMandatory = configurationService.getParticipantSchemeMandatory(); - LOG.debug("Validate service group [{}] with [{}] scheme", serviceGroupRO.getParticipantIdentifier(), (schemeMandatory?"mandatory":"optional")); + LOG.debug("Validate service group [{}] with [{}] scheme", serviceGroupRO.getParticipantIdentifier(), (schemeMandatory ? "mandatory" : "optional")); DBServiceGroup dbServiceGroup = getDatabaseDao().find(serviceGroupRO.getId()); @@ -539,7 +531,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service } ServiceMetadata smd = ServiceMetadataConverter.unmarshal(buff); - if (smd.getServiceInformation()!=null) { + if (smd.getServiceInformation() != null) { DocumentIdentifier di = caseSensitivityNormalizer.normalize(smd.getServiceInformation().getDocumentIdentifier()); if (Objects.equals(di.getScheme(), serviceMetadataRO.getDocumentIdentifierScheme()) && Objects.equals(di.getValue(), serviceMetadataRO.getDocumentIdentifier())) { @@ -619,7 +611,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service byte[] buff = validateServiceMetadata(serviceMetadataRO); DBServiceMetadata dbServiceMetadata = new DBServiceMetadata(); - DocumentIdentifier docIdent= caseSensitivityNormalizer.normalizeDocumentIdentifier(serviceMetadataRO.getDocumentIdentifierScheme(),serviceMetadataRO.getDocumentIdentifier() ); + DocumentIdentifier docIdent = caseSensitivityNormalizer.normalizeDocumentIdentifier(serviceMetadataRO.getDocumentIdentifierScheme(), serviceMetadataRO.getDocumentIdentifier()); dbServiceMetadata.setDocumentIdentifier(docIdent.getValue()); dbServiceMetadata.setDocumentIdentifierScheme(docIdent.getScheme()); dbServiceMetadata.setXmlContent(buff); @@ -661,14 +653,14 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service throw new SMPRuntimeException(INVALID_REQUEST, "Validate extension", "Missing Extension parameter"); } // if new check if service group already exist - if (serviceGroup.getStatusAction() == EntityROStatus.NEW.getStatusNumber()){ + if (serviceGroup.getStatusAction() == EntityROStatus.NEW.getStatusNumber()) { ParticipantIdentifierType normalizedParticipant = caseSensitivityNormalizer.normalizeParticipantIdentifier( serviceGroup.getParticipantScheme(), serviceGroup.getParticipantIdentifier()); - Optional<DBServiceGroup> sg= serviceGroupDao.findServiceGroup(normalizedParticipant.getValue(), + Optional<DBServiceGroup> sg = serviceGroupDao.findServiceGroup(normalizedParticipant.getValue(), normalizedParticipant.getScheme()); if (sg.isPresent()) { - serviceGroup.setErrorMessage("Service group: " +serviceGroup.getParticipantScheme()+ ":"+serviceGroup.getParticipantIdentifier()+ + serviceGroup.setErrorMessage("Service group: " + serviceGroup.getParticipantScheme() + ":" + serviceGroup.getParticipantIdentifier() + " already exists!"); serviceGroup.setErrorCode(ERROR_CODE_SERVICE_GROUP_EXISTS); return serviceGroup; @@ -711,37 +703,4 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service serviceGroupRO.getParticipantScheme(), ExceptionUtils.getRootCauseMessage(e)); } } - - /** - * Method - * - * @param sgExtension - * @return - */ - public ServiceGroupValidationRO formatExtension(ServiceGroupValidationRO sgExtension) { - if (sgExtension == null) { - throw new SMPRuntimeException(INVALID_REQUEST, "Format extension", "Missing Extension parameter"); - } else if (StringUtils.isBlank(sgExtension.getExtension())) { - sgExtension.setErrorMessage("Empty extension"); - } else { - try { - Source xmlInput = new StreamSource(new StringReader(sgExtension.getExtension())); - StringWriter stringWriter = new StringWriter(); - StreamResult xmlOutput = new StreamResult(stringWriter); - - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - transformerFactory.setAttribute("indent-number", 4); - - Transformer transformer = transformerFactory.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.transform(xmlInput, xmlOutput); - sgExtension.setExtension(xmlOutput.getWriter().toString()); - } catch (TransformerException e) { - sgExtension.setErrorMessage(ExceptionUtils.getRootCauseMessage(e)); - } - } - return sgExtension; - } - } 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 747391c1da6797686e5b72ccc4f7b04bffcce3b3..6a8ef292ba784600060c1e3433c0213bc85632e5 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 @@ -1,5 +1,6 @@ package eu.europa.ec.edelivery.smp.services.ui; +import eu.europa.ec.edelivery.security.cert.CertificateValidator; import eu.europa.ec.edelivery.security.utils.X509CertificateUtils; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBUser; @@ -7,7 +8,6 @@ import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; import eu.europa.ec.edelivery.smp.exceptions.CertificateNotTrustedException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; -import eu.europa.ec.edelivery.smp.logging.SMPMessageCode; import eu.europa.ec.edelivery.smp.services.CRLVerifierService; import eu.europa.ec.edelivery.smp.services.ConfigurationService; import eu.europa.ec.edelivery.text.DistinguishedNamesCodingUtil; @@ -18,7 +18,6 @@ import org.bouncycastle.asn1.x509.CertificatePolicies; import org.bouncycastle.asn1.x509.PolicyInformation; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; @@ -30,6 +29,7 @@ import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.security.auth.x500.X500Principal; import java.io.*; import java.security.*; import java.security.cert.Certificate; @@ -40,6 +40,8 @@ import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static eu.europa.ec.edelivery.smp.logging.SMPMessageCode.SEC_TRUSTSTORE_CERT_INVALID; +import static eu.europa.ec.edelivery.smp.logging.SMPMessageCode.SEC_USER_CERT_INVALID; import static java.util.Collections.list; import static java.util.Locale.US; @@ -56,27 +58,26 @@ public class UITruststoreService { new SimpleDateFormat("MMM d hh:mm:ss yyyy zzz", US) ); - @Autowired - private ConfigurationService configurationService; - - @Autowired - CRLVerifierService crlVerifierService; - - @Autowired - ConversionService conversionService; - - @Autowired - UserDao userDao; + // dependand beans + private final ConfigurationService configurationService; + private final CRLVerifierService crlVerifierService; + private final ConversionService conversionService; + private final UserDao userDao; List<String> normalizedTrustedList = new ArrayList<>(); - Map<String, X509Certificate> truststoreCertificates = new HashMap(); List<CertificateRO> certificateROList = new ArrayList<>(); - long lastUpdateTrustoreFileTime = 0; + long lastUpdateTrustStoreFileTime = 0; File lastUpdateTrustStoreFile = null; TrustManager[] trustManagers; KeyStore trustStore = null; + public UITruststoreService(ConfigurationService configurationService, CRLVerifierService crlVerifierService, ConversionService conversionService, UserDao userDao) { + this.configurationService = configurationService; + this.crlVerifierService = crlVerifierService; + this.conversionService = conversionService; + this.userDao = userDao; + } @PostConstruct public void init() { @@ -145,13 +146,8 @@ public class UITruststoreService { DistinguishedNamesCodingUtil.getCommonAttributesDN()); tmpList.add(subject); hmCertificates.put(alias, x509Certificate); - try { - x509Certificate.checkValidity(); - } catch (CertificateExpiredException | CertificateNotYetValidException ex) { - LOG.warn("Certificate: [{}] from truststore is not valid anymore!", alias); - } + validateAndLogError(x509Certificate, alias); } - } } catch (Exception exception) { LOG.error("Could not load truststore certificates Error: " + ExceptionUtils.getRootCauseMessage(exception), exception); @@ -164,12 +160,20 @@ public class UITruststoreService { normalizedTrustedList.addAll(tmpList); truststoreCertificates.putAll(hmCertificates); - lastUpdateTrustoreFileTime = truststoreFile.lastModified(); + lastUpdateTrustStoreFileTime = truststoreFile.lastModified(); lastUpdateTrustStoreFile = truststoreFile; // clear list to reload RO when required certificateROList.clear(); } + protected void validateAndLogError(X509Certificate x509Certificate, String alias) { + try { + x509Certificate.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException ex) { + LOG.securityWarn(SEC_TRUSTSTORE_CERT_INVALID, alias, ExceptionUtils.getRootCauseMessage(ex)); + } + } + public CertificateRO getCertificateData(byte[] buff) throws CertificateException, IOException { return getCertificateData(buff, false); } @@ -188,7 +192,7 @@ public class UITruststoreService { CertificateRO cro; try { cert = X509CertificateUtils.getX509Certificate(buff); - } catch ( Throwable e) { + } catch (Throwable e) { LOG.debug("Error occurred while parsing the certificate ", e); LOG.warn("Can not parse the certificate with error:[{}]!", ExceptionUtils.getRootCauseMessage(e)); cro = new CertificateRO(); @@ -199,32 +203,69 @@ public class UITruststoreService { cro = convertToRo(cert); if (validate) { - // first expect the worst - cro.setInvalid(true); - cro.setInvalidReason("Certificate is not validated!"); - try { - checkFullCertificateValidity(cert); - validateCertificateNotUsed(cro); - cro.setInvalid(false); - cro.setInvalidReason(null); - } catch (CertificateExpiredException ex) { - cro.setInvalidReason("Certificate is expired!"); - } catch (CertificateNotYetValidException ex) { - cro.setInvalidReason("Certificate is not yet valid!"); - } catch (CertificateRevokedException ex) { - cro.setInvalidReason("Certificate is revoked!"); - } catch (CertificateNotTrustedException ex) { - cro.setInvalidReason("Certificate is not trusted!"); - } catch (CertificateException e) { - cro.setInvalidReason(ExceptionUtils.getRootCauseMessage(e)); - } + validateCertificate(cert, cro); } return cro; } + public void validateCertificate(X509Certificate cert, CertificateRO cro) { + // first expect the worst + cro.setInvalid(true); + cro.setInvalidReason("Certificate is not validated!"); + try { + checkFullCertificateValidity(cert); + validateCertificateNotUsed(cro); + cro.setInvalid(false); + cro.setInvalidReason(null); + } catch (CertificateExpiredException ex) { + LOG.securityError(SEC_USER_CERT_INVALID, cro.getCertificateId(), ex.getMessage()); + cro.setInvalidReason("Certificate is expired!"); + } catch (CertificateNotYetValidException ex) { + LOG.securityError(SEC_USER_CERT_INVALID, cro.getCertificateId(), ex.getMessage()); + cro.setInvalidReason("Certificate is not yet valid!"); + } catch (CertificateRevokedException ex) { + LOG.securityError(SEC_USER_CERT_INVALID, cro.getCertificateId(), ex.getMessage()); + cro.setInvalidReason("Certificate is revoked!"); + } catch (CertificateNotTrustedException ex) { + LOG.securityError(SEC_USER_CERT_INVALID, cro.getCertificateId(), ex.getMessage()); + cro.setInvalidReason("Certificate is not trusted!"); + } catch (CertificateException e) { + LOG.securityError(SEC_USER_CERT_INVALID, e, cro.getCertificateId(), e.getMessage()); + if (ExceptionUtils.getRootCause(e) instanceof CertPathValidatorException) { + cro.setInvalidReason("Certificate is not trusted! Invalid certificate policy path!"); + } else { + cro.setInvalidReason(e.getMessage()); + } + } + } + + public void validateCertificateWithTruststore(X509Certificate x509Certificate) throws CertificateException { + KeyStore truststore = getTrustStore(); + + if (x509Certificate == null) { + LOG.warn("The X509Certificate is null (Is the client cert header enabled?)! Skip trust validation against the truststore!"); + return; + } + + if (truststore == null) { + LOG.warn("Truststore is not configured! Skip trust validation against the truststore!"); + return; + } + + Pattern subjectRegExp = configurationService.getCertificateSubjectRegularExpression(); + List<String> allowedCertificatePolicies = configurationService.getAllowedCertificatePolicies(); + CertificateValidator certificateValidator = new CertificateValidator( + null, truststore, + subjectRegExp != null ? subjectRegExp.pattern() : null, + allowedCertificatePolicies != null ? allowedCertificatePolicies : Collections.emptyList()); + LOG.debug("Validate certificate with truststore, subject regexp [{}] and allowed certificate policies [{}]", subjectRegExp, allowedCertificatePolicies); + certificateValidator.validateCertificate(x509Certificate); + } + public void checkFullCertificateValidity(X509Certificate cert) throws CertificateException { // test if certificate is valid cert.checkValidity(); + // check if certificate or its issuer is on trusted list // check only issuer because using Client-cert header we do not have whole chain. // if the truststore is empty then truststore validation is ignored @@ -234,8 +275,16 @@ public class UITruststoreService { throw new CertificateNotTrustedException("Certificate is not trusted!"); } - validateCertificatePolicyMatch(cert); - validateCertificateSubjectExpression(cert); + + + if (trustStore != null) { + validateCertificateWithTruststore(cert); + } else { + LOG.warn("Use legacy certificate validation without truststore. Please configure truststore to increase security"); + validateCertificatePolicyMatchLegacy(cert); + validateCertificateSubjectExpressionLegacy(cert); + } + // check CRL - it is using only HTTP or https crlVerifierService.verifyCertificateCRLs(cert); } @@ -243,8 +292,7 @@ public class UITruststoreService { public void validateCertificateNotUsed(CertificateRO cert) throws CertificateException { Optional<DBUser> user = userDao.findUserByCertificateId(cert.getCertificateId()); if (user.isPresent()) { - String msg = "Certificate: '" + cert.getCertificateId() + "'" + - " is already used!"; + String msg = "Certificate: [" + cert.getCertificateId() + "] is already used!"; LOG.debug("Certificate with id: [{}] is already used by user with username [{}]", user.get().getUsername()); throw new CertificateException(msg); } @@ -253,6 +301,7 @@ public class UITruststoreService { public void checkFullCertificateValidity(CertificateRO cert) throws CertificateException { // trust data in database + Date currentDate = Calendar.getInstance().getTime(); if (cert.getValidFrom() != null && currentDate.before(cert.getValidFrom())) { throw new CertificateNotYetValidException("Certificate: " + cert.getCertificateId() + " is valid from: " @@ -281,7 +330,7 @@ public class UITruststoreService { } catch (CertificateRevokedException ex) { String msg = "Certificate: '" + cert.getCertificateId() + "'" + " is revoked!"; - LOG.securityWarn(SMPMessageCode.SEC_USER_CERT_INVALID, cert.getCertificateId(), msg, ex); + LOG.securityWarn(SEC_USER_CERT_INVALID, cert.getCertificateId(), msg, ex); throw new CertificateException(msg); } catch (Throwable th) { String msg = "Error occurred while validating CRL for certificate!"; @@ -294,7 +343,7 @@ public class UITruststoreService { boolean isTruststoreChanged() { File file = getTruststoreFile(); return !Objects.equals(lastUpdateTrustStoreFile, file) || - file != null && file.lastModified() != lastUpdateTrustoreFileTime; + file != null && file.lastModified() != lastUpdateTrustStoreFileTime; } public File getTruststoreFile() { @@ -311,7 +360,7 @@ public class UITruststoreService { } - private KeyStore loadTruststore(File truststoreFile) { + protected KeyStore loadTruststore(File truststoreFile) { if (truststoreFile == null) { LOG.error("Truststore file is not configured! Update SMP configuration!"); @@ -382,7 +431,6 @@ public class UITruststoreService { public String addCertificate(String alias, X509Certificate certificate) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException { KeyStore truststore = loadTruststore(getTruststoreFile()); - if (truststore != null) { String aliasPrivate = StringUtils.isBlank(alias) ? createAliasFromCert(certificate, truststore) : alias.trim(); @@ -455,7 +503,7 @@ public class UITruststoreService { alias = alias + "_" + iVal; } } catch (KeyStoreException e) { - LOG.error("Error occured while reading truststore for validating alias: " + alias, e); + LOG.error("Error occurred while reading truststore for validating existance of the alias: " + alias, e); } return alias; } @@ -528,7 +576,14 @@ public class UITruststoreService { .collect(Collectors.toList()); } - protected void validateCertificatePolicyMatch(X509Certificate certificate) throws CertificateException { + /** + * Method validates if the certificate contains one of allowed Certificate policy. At the moment it does not validates + * the whole chain. Because in some configuration cases does not use the truststore + * + * @param certificate + * @throws CertificateException + */ + protected void validateCertificatePolicyMatchLegacy(X509Certificate certificate) throws CertificateException { // allowed list List<String> allowedCertificatePolicyOIDList = configurationService.getAllowedCertificatePolicies(); @@ -543,7 +598,7 @@ public class UITruststoreService { throw new CertificateException(excMessage); } - Optional<String> result = certPolicyList.stream().filter(certPolicyOID -> allowedCertificatePolicyOIDList.contains(certPolicyOID)).findFirst(); + Optional<String> result = certPolicyList.stream().filter(allowedCertificatePolicyOIDList::contains).findFirst(); if (result.isPresent()) { LOG.info("Certificate [{}] is trusted with certificate policy [{}]", certificate, result.get()); return; @@ -552,22 +607,19 @@ public class UITruststoreService { throw new CertificateException(excMessage); } - protected void validateCertificateSubjectExpression(X509Certificate signingCertificate) throws CertificateException { + protected void validateCertificateSubjectExpressionLegacy(X509Certificate signingCertificate) throws CertificateException { LOG.debug("Validate certificate subject"); - - - String subject = signingCertificate.getSubjectDN().getName(); Pattern certSubjectExpression = configurationService.getCertificateSubjectRegularExpression(); if (certSubjectExpression == null) { LOG.debug("Certificate subject regular expression is empty, verification is disabled."); return; } + String subject = signingCertificate.getSubjectX500Principal().getName(X500Principal.RFC2253); if (!certSubjectExpression.matcher(subject).matches()) { String excMessage = String.format("Certificate subject [%s] does not match the regular expression configured [%s]", subject, certSubjectExpression); LOG.error(excMessage); throw new CertificateException(excMessage); } } - } 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 0ac759a0d76a52beb34d977eb429d445b6e8b9d7..6882361969d8b0a1ab5aba44fbe6de31cf16edcf 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 @@ -77,6 +77,7 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { * @return ServiceResult with list */ @Transactional + @Override public ServiceResult<UserRO> getTableList(int page, int pageSize, String sortField, String sortOrder, Object filter) { ServiceResult<UserRO> resUsers = super.getTableList(page, pageSize, sortField, sortOrder, filter); resUsers.getServiceEntities().forEach(this::updateUserStatus); @@ -85,18 +86,36 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { protected void updateUserStatus(UserRO user) { // never return password even if is hashed... + user.setPassword(null); if (user.getCertificate() != null && !StringUtils.isBlank(user.getCertificate().getCertificateId())) { // validate certificate - try { - truststoreService.checkFullCertificateValidity(user.getCertificate()); - } catch (CertificateException e) { - LOG.warn("Set invalid cert status: " + user.getCertificate().getCertificateId() + " reason: " + e.getMessage()); - user.getCertificate().setInvalid(true); - user.getCertificate().setInvalidReason(e.getMessage()); + X509Certificate cert = getX509CertificateFromCertificateRO(user.getCertificate()); + if (cert != null) { + truststoreService.validateCertificate(cert, user.getCertificate()); + } else { + // validate just the database data + try { + truststoreService.checkFullCertificateValidity(user.getCertificate()); + } catch (CertificateException e) { + LOG.warn("Set invalid cert status: " + user.getCertificate().getCertificateId() + " reason: " + e.getMessage()); + user.getCertificate().setInvalid(true); + user.getCertificate().setInvalidReason(e.getMessage()); + } } } + } + public X509Certificate getX509CertificateFromCertificateRO(CertificateRO certificateRO) { + if (certificateRO == null || certificateRO.getEncodedValue() == null) { + return null; + } + try { + return X509CertificateUtils.getX509Certificate(Base64.getMimeDecoder().decode(certificateRO.getEncodedValue())); + } catch (CertificateException e) { + LOG.error("Error occurred while parsing the certificate encoded value for certificate id:[" + certificateRO.getCertificateId() + "].", e); + return null; + } } /** @@ -105,18 +124,20 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { * * @param authorizedUserId which is authorized for update * @param userToUpdateId the user id to be updated + * @param currentPassword authorized password + * @param currentPassword do not validate password if CAS authenticated * @return generated AccessToken. */ @Transactional - public AccessTokenRO generateAccessTokenForUser(Long authorizedUserId, Long userToUpdateId, String currentPassword) { + public AccessTokenRO generateAccessTokenForUser(Long authorizedUserId, Long userToUpdateId, String currentPassword, boolean validateCurrentPassword) { DBUser dbUser = userDao.find(authorizedUserId); if (dbUser == null) { LOG.error("Can not update user password because authorized user with id [{}] does not exist!", authorizedUserId); 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!"); + if (validateCurrentPassword && !BCrypt.checkpw(currentPassword, dbUser.getPassword())) { + throw new BadCredentialsException("AccessToken generation failed: Invalid current password!"); } boolean adminUpdate = userToUpdateId != null && authorizedUserId != userToUpdateId; DBUser dbUserToUpdate = adminUpdate ? userDao.find(userToUpdateId) : dbUser; @@ -141,14 +162,30 @@ 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 * - * @param authorizedUserId - authorized user id + * @param authorizedUserId which is authorized for update + * @param userToUpdateId the user id to be updated * @return generated AccessToken. */ @Transactional - public DBUser updateUserPassword(Long authorizedUserId, Long userToUpdateId, String currentPassword, String newPassword) { + public AccessTokenRO generateAccessTokenForUser(Long authorizedUserId, Long userToUpdateId, String currentPassword) { + return generateAccessTokenForUser(authorizedUserId, userToUpdateId, currentPassword, true); + } + + /** + * Method updates the user password + * + * @param authorizedUserId - authorized user id + * @param userToUpdateId - user id to update password user id + * @param authorizationPassword - authorization password + * @param newPassword - new password for the userToUpdateId + * @param validateCurrentPassword - validate authorizationPassword - if CAS authenticated skip this part + * @return generated DBUser. + */ + @Transactional + public DBUser updateUserPassword(Long authorizedUserId, Long userToUpdateId, String authorizationPassword, String newPassword, boolean validateCurrentPassword) { Pattern pattern = configurationService.getPasswordPolicyRexExp(); - if (!pattern.matcher(newPassword).matches()) { + if (pattern != null && !pattern.matcher(newPassword).matches()) { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "PasswordChange", configurationService.getPasswordPolicyValidationMessage()); } DBUser dbAuthorizedUser = userDao.find(authorizedUserId); @@ -157,7 +194,7 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "UserId", "Can not find user id!"); } - if (!BCrypt.checkpw(currentPassword, dbAuthorizedUser.getPassword())) { + if (validateCurrentPassword && !BCrypt.checkpw(authorizationPassword, dbAuthorizedUser.getPassword())) { throw new BadCredentialsException("Password change failed; Invalid current password!"); } @@ -177,6 +214,20 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { return dbUserToUpdate; } + /** + * Method updates the user password + * + * @param authorizedUserId - authorized user id + * @param userToUpdateId - user id to update password user id + * @param authorizationPassword - authorization password + * @param newPassword - new password for the userToUpdateId + * @return generated DBUser. + */ + @Transactional + public DBUser updateUserPassword(Long authorizedUserId, Long userToUpdateId, String authorizationPassword, String newPassword) { + return updateUserPassword(authorizedUserId, userToUpdateId, authorizationPassword, newPassword, true); + } + @Transactional public void updateUserList(List<UserRO> lst, OffsetDateTime passwordChange) { for (UserRO userRO : lst) { @@ -191,53 +242,71 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { 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!"); } - + // the only think what user can update now on user dat dbUser.setEmailAddress(user.getEmailAddress()); - if (user.getCertificate() != null && (dbUser.getCertificate() == null - || !StringUtils.equals(dbUser.getCertificate().getCertificateId(), user.getCertificate().getCertificateId()))) { - CertificateRO certRo = user.getCertificate(); - - if (dbUser.getCertificate() != null) { - dbUser.getCertificate().setCertificateId(certRo.getCertificateId()); - dbUser.getCertificate().setCrlUrl(certRo.getCrlUrl()); - dbUser.getCertificate().setPemEncoding(certRo.getEncodedValue()); - dbUser.getCertificate().setSubject(certRo.getSubject()); - dbUser.getCertificate().setIssuer(certRo.getIssuer()); - dbUser.getCertificate().setSerialNumber(certRo.getSerialNumber()); - if (certRo.getValidTo() != null) { - dbUser.getCertificate().setValidTo(certRo.getValidTo().toInstant() - .atOffset(ZoneOffset.UTC)); - } - if (certRo.getValidFrom() != null) { - dbUser.getCertificate().setValidFrom(certRo.getValidFrom().toInstant() - .atOffset(ZoneOffset.UTC)); - } - } else { - DBCertificate certificate = conversionService.convert(certRo, DBCertificate.class); - dbUser.setCertificate(certificate); - } - if (user.getCertificate().getEncodedValue() == null) { - LOG.debug("User has certificate data without certificate bytearray. "); - return; - } + // update certificate + if (user.getCertificate() == null && dbUser.getCertificate() == null) { + return; + } - if (!configurationService.trustCertificateOnUserRegistration()) { - LOG.debug("User certificate is not automatically trusted! Certificate is not added to truststore!"); - return; - } + // clear certificate data + if (user.getCertificate() == null || StringUtils.isBlank(user.getCertificate().getCertificateId())) { + dbUser.setCertificate(null); + LOG.info("Clear certificate credentials from the user [{}] with id [{}]", dbUser.getUsername(), dbUser.getId()); + return; + } + + if (dbUser.getCertificate() != null && StringUtils.equals(dbUser.getCertificate().getCertificateId(), user.getCertificate().getCertificateId())) { + LOG.debug("Certificate id was not changed for the user [{}] with id [{}]. Skip updating the certificate data!", + dbUser.getCertificate().getCertificateId(), dbUser.getUsername(), dbUser.getId()); + return; + } - String certificateAlias; - try { - X509Certificate x509Certificate = X509CertificateUtils.getX509Certificate(Base64.getMimeDecoder().decode(certRo.getEncodedValue())); - certificateAlias = truststoreService.addCertificate(certRo.getAlias(), x509Certificate); - LOG.debug("User certificate is added to truststore!"); - } 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)); + CertificateRO certRo = user.getCertificate(); + if (dbUser.getCertificate() == null) { + DBCertificate certificate = conversionService.convert(certRo, DBCertificate.class); + dbUser.setCertificate(certificate); + } else { + LOG.info("Update certificate credentials for user:"); + dbUser.getCertificate().setCertificateId(certRo.getCertificateId()); + dbUser.getCertificate().setCrlUrl(certRo.getCrlUrl()); + dbUser.getCertificate().setPemEncoding(certRo.getEncodedValue()); + dbUser.getCertificate().setSubject(certRo.getSubject()); + dbUser.getCertificate().setIssuer(certRo.getIssuer()); + dbUser.getCertificate().setSerialNumber(certRo.getSerialNumber()); + if (certRo.getValidTo() != null) { + dbUser.getCertificate().setValidTo(certRo.getValidTo().toInstant() + .atOffset(ZoneOffset.UTC)); + } + if (certRo.getValidFrom() != null) { + dbUser.getCertificate().setValidFrom(certRo.getValidFrom().toInstant() + .atOffset(ZoneOffset.UTC)); } - certRo.setAlias(certificateAlias); } + + if (user.getCertificate().getEncodedValue() == null) { + LOG.debug("User has certificate data without certificate bytearray. "); + return; + } + + if (!configurationService.trustCertificateOnUserRegistration()) { + LOG.debug("User certificate is not automatically trusted! Certificate is not added to truststore!"); + return; + } + + String certificateAlias; + try { + X509Certificate x509Certificate = X509CertificateUtils.getX509Certificate(Base64.getMimeDecoder().decode(certRo.getEncodedValue())); + + certificateAlias = truststoreService.addCertificate(certRo.getAlias(), x509Certificate); + LOG.debug("User certificate is added to truststore!"); + } 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)); + } + certRo.setAlias(certificateAlias); + } protected void createOrUpdateUser(UserRO userRO, OffsetDateTime passwordChange) { @@ -318,8 +387,14 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { } + /** + * User can be deleted only if it does not own any of the service groups. + * + * @param dev + * @return + */ public DeleteEntityValidation validateDeleteRequest(DeleteEntityValidation dev) { - List<Long> idList = dev.getListIds().stream().map(encId -> SessionSecurityUtils.decryptEntityId(encId)).collect(Collectors.toList()); + List<Long> idList = dev.getListIds().stream().map(SessionSecurityUtils::decryptEntityId).collect(Collectors.toList()); List<DBUserDeleteValidation> lstMessages = userDao.validateUsersForDelete(idList); dev.setValidOperation(lstMessages.isEmpty()); StringWriter sw = new StringWriter(); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java index 887b306ce7de74593b5845339570024c2d27e5f0..6c5083061610ff0395c3009d8d1ed2fc0af02a07 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java @@ -367,20 +367,22 @@ public class SmlConnector implements ApplicationContextAware { * Sets the TrustManagers associated with this endpoint. * This parameter may be set to null for system default behavior. */ + LOG.debug("Set SMP truststore managers for the TLS certificate verification."); tlsParams.setTrustManagers(truststoreService.getTrustManagers()); } if (!clientCertAuthentication) { - LOG.info("SML X509 certificate authentication with alias {}.", smlClientAuthentication); + LOG.debug("SML X509 certificate authentication with alias {}.", smlClientAuthentication); tlsParams.setCertAlias(smlClientAuthentication); tlsParams.setKeyManagers(keystoreService.getKeyManagers()); - LOG.info("SET KEY MANAGERS! {}.", keystoreService.getKeyManagers()); } else { + LOG.debug("User Client cert header to authenticate to SML {}.", smlClientAuthentication); Map<String, List<String>> customHeaders = new HashMap<>(); customHeaders.put(CLIENT_CERT_HEADER_KEY, Arrays.asList(smlClientAuthentication)); requestContext.put(MessageContext.HTTP_REQUEST_HEADERS, customHeaders); } if (useTLS) { + LOG.debug("Set SMP TLS client parameters."); httpConduit.setTlsClientParameters(tlsParams); } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java index a79807b8e4a0d4ae9c85f936103255379a2fe302..d1ea833d06e7c5c2f5655e1008363152fb4162b9 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java @@ -42,6 +42,10 @@ public class PropertyUtils { } return null; } + if (!prop.getValuePattern().matcher(value).find()) { + LOG.debug("Value [{}] for property [{}] does not match [{}]", value, prop.getProperty(), prop.getValuePattern().pattern()); + throw new SMPRuntimeException(ErrorCode.CONFIGURATION_ERROR, prop.getErrorValueMessage()); + } SMPPropertyTypeEnum type = prop.getPropertyType(); return parsePropertyType(type, value, rootFolder); @@ -52,6 +56,11 @@ public class PropertyUtils { // empty/ null value is invalid return !prop.isMandatory(); } + + if (!prop.getValuePattern().matcher(value).matches()) { + LOG.debug("Value [{}] for property [{}] does not match [{}]", value, prop.getProperty(), prop.getValuePattern().pattern()); + throw new SMPRuntimeException(ErrorCode.CONFIGURATION_ERROR, prop.getErrorValueMessage()); + } SMPPropertyTypeEnum type = prop.getPropertyType(); return isValidPropertyType(type, value, confFolder); } @@ -64,7 +73,7 @@ public class PropertyUtils { parsePropertyType(type, value, confFolder); return true; } catch (SMPRuntimeException ex) { - LOG.debug("Invalid property value [{}] for type [{}]. Error: " , value, type, ExceptionUtils.getRootCauseMessage(ex)); + LOG.debug("Invalid property value [{}] for type [{}]. Error: ", value, type, ExceptionUtils.getRootCauseMessage(ex)); return false; } } @@ -74,9 +83,13 @@ public class PropertyUtils { return null; } + if (StringUtils.length(value) > 2000) { + throw new SMPRuntimeException(ErrorCode.CONFIGURATION_ERROR, "Invalid property value! Error: Value to long. Max. allowed size 2000 characters!"); + } + switch (type) { case BOOLEAN: - if(StringUtils.equalsAnyIgnoreCase(trim(value),"true","false")) { + if (StringUtils.equalsAnyIgnoreCase(trim(value), "true", "false")) { return Boolean.valueOf(value.trim()); } throw new SMPRuntimeException(ErrorCode.CONFIGURATION_ERROR, "Invalid boolean value: [" @@ -121,7 +134,7 @@ public class PropertyUtils { case FILENAME: File file = new File(rootFolder, value); if (!file.exists()) { - LOG.warn("File: [{}] does not exist. Full path: [{}].",value, file.getAbsolutePath()); + LOG.warn("File: [{}] does not exist. Full path: [{}].", value, file.getAbsolutePath()); } return file; case EMAIL: diff --git a/smp-server-library/src/main/resources/alert-mail-templates/credential_expired.ftl b/smp-server-library/src/main/resources/alert-mail-templates/credential_expired.ftl index 0e33f951d41ce81c546cdfeabf040d02a3255d9d..4bc555416e37baacd990355e76788b0fad1ef56b 100644 --- a/smp-server-library/src/main/resources/alert-mail-templates/credential_expired.ftl +++ b/smp-server-library/src/main/resources/alert-mail-templates/credential_expired.ftl @@ -59,7 +59,7 @@ <!-- TITLE --> <tr> <td valign="top" align="left" style=" font-size: 20px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> - User password expired</td> + Credential type: ${CREDENTIAL_TYPE} is expired</td> </tr> <!-- / TITLE --> @@ -78,8 +78,8 @@ <tr> <td valign="top" align="left" style=" font-size: 13px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> <br/> - <p><strong>User type:</strong> ${CREDENTIAL_TYPE}</p> - <p><strong>User:</strong> ${CREDENTIAL_ID}</p> + <p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> + <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Expiration date-time: </strong> ${EXPIRATION_DATETIME}</p> <p><strong>Reporting date-time:</strong> ${REPORTING_DATETIME}</p> <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> diff --git a/smp-server-library/src/main/resources/alert-mail-templates/credential_imminent_expiration.ftl b/smp-server-library/src/main/resources/alert-mail-templates/credential_imminent_expiration.ftl index 0405a8b0bef9568d158d76e5a1181b9d7a4b05f6..8a18f1fac3fdfefc1f82786517e3e15cee444c46 100644 --- a/smp-server-library/src/main/resources/alert-mail-templates/credential_imminent_expiration.ftl +++ b/smp-server-library/src/main/resources/alert-mail-templates/credential_imminent_expiration.ftl @@ -59,7 +59,7 @@ <!-- TITLE --> <tr> <td valign="top" align="left" style=" font-size: 20px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> - User password imminent expiration</td> + Credential type: ${CREDENTIAL_TYPE} imminent expiration</td> </tr> <!-- / TITLE --> @@ -78,8 +78,8 @@ <tr> <td valign="top" align="left" style=" font-size: 13px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> <br/> - <p><strong>User type:</strong> ${CREDENTIAL_TYPE}</p> - <p><strong>User:</strong> ${CREDENTIAL_ID}</p> + <p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> + <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Expiration date-time: </strong> ${EXPIRATION_DATETIME}</p> <p><strong>Reporting date-time:</strong> ${REPORTING_DATETIME}</p> <p><strong>Alert level:</strong> ${ALERT_LEVEL}</p> diff --git a/smp-server-library/src/main/resources/alert-mail-templates/credential_suspended.ftl b/smp-server-library/src/main/resources/alert-mail-templates/credential_suspended.ftl index b12b3a20d74c164f4210cd747b28c0ae8d620681..2847ec059df2cee28add8d407701e0a695f89c82 100644 --- a/smp-server-library/src/main/resources/alert-mail-templates/credential_suspended.ftl +++ b/smp-server-library/src/main/resources/alert-mail-templates/credential_suspended.ftl @@ -59,7 +59,7 @@ <!-- TITLE --> <tr> <td valign="top" align="left" style=" font-size: 20px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> - Account is temporarily suspended</td> + Credential type: ${CREDENTIAL_TYPE} is temporarily suspended</td> </tr> <!-- / TITLE --> @@ -79,7 +79,7 @@ <td valign="top" align="left" style=" font-size: 13px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> <br/> <p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> - <p><strong>Credential id:</strong> ${CREDENTIAL_ID}</p> + <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Failed login attempt count:</strong> ${FAILED_LOGIN_ATTEMPT}</p> <p><strong>Last failed login time:</strong> ${LAST_LOGIN_FAILURE_DATETIME}</p> <p><strong>Suspended util</strong> ${SUSPENDED_UNTIL_DATETIME}</p> diff --git a/smp-server-library/src/main/resources/alert-mail-templates/credential_verification_failed.ftl b/smp-server-library/src/main/resources/alert-mail-templates/credential_verification_failed.ftl index f51e982892cc030af5e324f0a608e65684770ce1..7e2b43928cdbadc89f19c096bc6537ca0cf4ce80 100644 --- a/smp-server-library/src/main/resources/alert-mail-templates/credential_verification_failed.ftl +++ b/smp-server-library/src/main/resources/alert-mail-templates/credential_verification_failed.ftl @@ -59,7 +59,7 @@ <!-- TITLE --> <tr> <td valign="top" align="left" style=" font-size: 20px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> - Mail verification failed!</td> + Credential type: ${CREDENTIAL_TYPE} verification failed!</td> </tr> <!-- / TITLE --> @@ -79,7 +79,7 @@ <td valign="top" align="left" style=" font-size: 13px; font-family: Arial, Helvetica, sans-serif; color: #000;"><br/> <br/> <p><strong>Credential type:</strong> ${CREDENTIAL_TYPE}</p> - <p><strong>Credential id:</strong> ${CREDENTIAL_ID}</p> + <p><strong>Credential identifier:</strong> ${CREDENTIAL_ID}</p> <p><strong>Failed login attempt count:</strong> ${FAILED_LOGIN_ATTEMPT}</p> <p><strong>Last failed login time:</strong> ${LAST_LOGIN_FAILURE_DATETIME}</p> <p><strong>Reporting time:</strong> ${REPORTING_DATETIME}</p> diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPUserDetailsTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPUserDetailsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..76ed44e37d73e837260e2dd587afd42ba3fefdb4 --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPUserDetailsTest.java @@ -0,0 +1,38 @@ +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.SecurityUtils; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + +public class SMPUserDetailsTest { + + @Test + public void testInitSMPUserDetailsTest() { + DBUser user = new DBUser(); + SecurityUtils.Secret secret = SecurityUtils.generatePrivateSymmetricKey(); + List<SMPAuthority> authorityList = Collections.singletonList(SMPAuthority.S_AUTHORITY_SERVICE_GROUP); + + SMPUserDetails testInstance = new SMPUserDetails(user,secret, authorityList); + testInstance.setCasAuthenticated(true); + + assertEquals(user, testInstance.getUser()); + assertEquals(secret, testInstance.getSessionSecret()); + assertEquals(1, testInstance.getAuthorities().size()); + assertTrue(testInstance.getAuthorities().contains(SMPAuthority.S_AUTHORITY_SERVICE_GROUP)); + assertTrue(testInstance.isCasAuthenticated()); + assertEquals(user.isActive(), testInstance.isEnabled()); + // default values + assertNull(testInstance.getPassword()); + assertTrue(testInstance.isAccountNonExpired()); + assertTrue(testInstance.isAccountNonLocked()); + assertTrue(testInstance.isCredentialsNonExpired()); + + } + +} \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/FilePropertyTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/FilePropertyTest.java index d09f9c066e7e5dd09404b94a68ee8b3369199d07..1dbe17d9fad82395b8439d6cc8dcb20b1c75abf3 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/FilePropertyTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/config/FilePropertyTest.java @@ -3,7 +3,9 @@ package eu.europa.ec.edelivery.smp.config; import org.junit.Test; import java.util.Properties; +import java.util.UUID; +import static eu.europa.ec.edelivery.smp.config.FileProperty.PROPERTY_LOG_FOLDER; import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.CLIENT_CERT_HEADER_ENABLED_DEPRECATED; import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.EXTERNAL_TLS_AUTHENTICATION_CLIENT_CERT_HEADER_ENABLED; import static org.junit.Assert.*; @@ -36,4 +38,19 @@ public class FilePropertyTest { // in the legacy fallback file the property is defined as: ${jdbc.user} assertEquals("This property is from fallback legacy file",result.getProperty("test.read.property")); } + + + @Test + public void updateLogConfigurationSetLogFolderProperty(){ + String newFolderVal = "NewVal-"+ UUID.randomUUID().toString(); + String currVal = System.getProperty(PROPERTY_LOG_FOLDER); + FileProperty.updateLogConfiguration(newFolderVal, null, null); + assertEquals(newFolderVal, System.getProperty(PROPERTY_LOG_FOLDER) ); + assertNotEquals(newFolderVal, currVal ); + if (currVal ==null) { + System.getProperties().remove(PROPERTY_LOG_FOLDER); + } else { + System.setProperty(PROPERTY_LOG_FOLDER, currVal); + } + } } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java index 013a77085d17acd7db3f1548390f8662a2f0dda7..e079967ff319437c3754c65bc686e0fc73715124 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java @@ -3,6 +3,7 @@ package eu.europa.ec.edelivery.smp.conversion; import eu.europa.ec.edelivery.smp.data.model.DBCertificate; import eu.europa.ec.edelivery.smp.data.model.DBUser; import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -27,9 +28,11 @@ public class DBUserToUserROConverterTest { @Mock private ConversionService conversionService; + @Mock + private ConfigurationService configurationService; @InjectMocks - private DBUserToUserROConverter converter = new DBUserToUserROConverter(); + private DBUserToUserROConverter converter = new DBUserToUserROConverter(configurationService, conversionService); @Test public void returnsThePasswordAsNotExpiredForCertificateOnlyUsers() { diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTriggerTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTriggerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..58bf9b89a64550a6926d57426048e9a02d396a4d --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTriggerTest.java @@ -0,0 +1,57 @@ +package eu.europa.ec.edelivery.smp.cron; + +import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.scheduling.TriggerContext; +import org.springframework.scheduling.support.CronExpression; + +import java.time.Clock; + +import static org.junit.Assert.*; + +public class SMPDynamicCronTriggerTest { + + @Test + public void nextExecutionTime() { + SMPPropertyEnum propertyEnum = SMPPropertyEnum.SMP_ALERT_CREDENTIALS_CRON; + SMPDynamicCronTrigger testInstance = new SMPDynamicCronTrigger(propertyEnum.getDefValue(), propertyEnum); + // not yet triggered + assertNull(testInstance.getNextExecutionDate()); + + TriggerContext triggerContext = Mockito.mock(TriggerContext.class); + Mockito.doReturn(Clock.systemDefaultZone()).when(triggerContext).getClock(); + testInstance.nextExecutionTime(triggerContext); + + assertNotNull(testInstance.getNextExecutionDate()); + } + + @Test + public void getExpression() { + SMPPropertyEnum propertyEnum = SMPPropertyEnum.SMP_ALERT_CREDENTIALS_CRON; + SMPDynamicCronTrigger testInstance = new SMPDynamicCronTrigger(propertyEnum.getDefValue(), propertyEnum); + + assertEquals(propertyEnum.getDefValue(), testInstance.getExpression()); + } + + @Test + public void updateCronExpression() { + String newCronExpression = "0 */10 * * * *"; + SMPPropertyEnum propertyEnum = SMPPropertyEnum.SMP_ALERT_CREDENTIALS_CRON; + SMPDynamicCronTrigger testInstance = new SMPDynamicCronTrigger(propertyEnum.getDefValue(), propertyEnum); + assertEquals(propertyEnum.getDefValue(), testInstance.getExpression()); + + testInstance.updateCronExpression(CronExpression.parse(newCronExpression)); + + assertEquals(newCronExpression, testInstance.getExpression()); + assertNotNull(testInstance.getNextExecutionDate()); + } + + @Test + public void getCronExpressionProperty() { + SMPPropertyEnum propertyEnum = SMPPropertyEnum.SMP_ALERT_CREDENTIALS_CRON; + SMPDynamicCronTrigger testInstance = new SMPDynamicCronTrigger(propertyEnum.getDefValue(), propertyEnum); + + assertEquals(propertyEnum, testInstance.getCronExpressionProperty()); + } +} \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractServiceGroupDaoIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractServiceGroupDaoIntegrationTest.java index ca22df9cfcd3772ac077178fcf1b9d9de82708e9..50168418be8a5c68cba2ad5c12bc0978016d7a35 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractServiceGroupDaoIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/AbstractServiceGroupDaoIntegrationTest.java @@ -22,7 +22,7 @@ import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*; * @since 4.1 */ -public abstract class AbstractServiceGroupDaoIntegrationTest extends AbstractBaseDao{ +public abstract class AbstractServiceGroupDaoIntegrationTest extends AbstractBaseDao { @Autowired ServiceGroupDao testInstance; @@ -50,39 +50,40 @@ public abstract class AbstractServiceGroupDaoIntegrationTest extends AbstractBas userDao.persistFlushDetach(u3); } - public DBServiceGroup createAndSaveNewServiceGroup(){ - return createAndSaveNewServiceGroup(TEST_DOMAIN_CODE_1, TestConstants.TEST_SG_ID_1, TestConstants.TEST_SG_SCHEMA_1); - } + public DBServiceGroup createAndSaveNewServiceGroup() { + return createAndSaveNewServiceGroup(TEST_DOMAIN_CODE_1, TestConstants.TEST_SG_ID_1, TestConstants.TEST_SG_SCHEMA_1); + } - private DBServiceGroup createAndSaveNewServiceGroup(String domain, String participantId, String participantSchema){ + private DBServiceGroup createAndSaveNewServiceGroup(String domain, String participantId, String participantSchema) { return createAndSaveNewServiceGroup(domain, participantId, participantSchema, null); } + @Transactional - public DBServiceGroup createAndSaveNewServiceGroup(String domain, String participantId, String participantSchema, DBUser usr){ - DBDomain d = domainDao.getDomainByCode(domain).get(); - DBServiceGroup sg = TestDBUtils.createDBServiceGroup(participantId, participantSchema); - if (usr!= null) { - sg.getUsers().add(usr); - } - sg.addDomain(d); - testInstance.persistFlushDetach(sg); - return sg; - } - - public void createAndSaveNewServiceGroups(int iCount, String domain, String participant){ + public DBServiceGroup createAndSaveNewServiceGroup(String domain, String participantId, String participantSchema, DBUser usr) { + DBDomain d = domainDao.getDomainByCode(domain).get(); + DBServiceGroup sg = TestDBUtils.createDBServiceGroup(participantId, participantSchema); + if (usr != null) { + sg.getUsers().add(usr); + } + sg.addDomain(d); + testInstance.persistFlushDetach(sg); + return sg; + } + + public void createAndSaveNewServiceGroups(int iCount, String domain, String participant) { createAndSaveNewServiceGroups(iCount, domain, participant, null); } @Transactional - public void createAndSaveNewServiceGroups(int iCount, String domain, String participant, DBUser usr){ - int i =0; - while (i++ < iCount){ - createAndSaveNewServiceGroup(domain, participant+":"+i, TestConstants.TEST_SG_SCHEMA_1, usr); + public void createAndSaveNewServiceGroups(int iCount, String domain, String participant, DBUser usr) { + int i = 0; + while (i++ < iCount) { + createAndSaveNewServiceGroup(domain, participant + ":" + i, TestConstants.TEST_SG_SCHEMA_1, usr); } } @Transactional - public DBServiceGroup createAndSaveNewServiceGroupWithMetadata(){ + public DBServiceGroup createAndSaveNewServiceGroupWithMetadata() { DBDomain d = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); DBServiceGroup sg = TestDBUtils.createDBServiceGroup(); @@ -94,7 +95,7 @@ public abstract class AbstractServiceGroupDaoIntegrationTest extends AbstractBas } @Transactional - public DBServiceGroup createAndSaveNewServiceGroupWithUsers(){ + public DBServiceGroup createAndSaveNewServiceGroupWithUsers() { DBUser u1 = userDao.findUserByUsername(USERNAME_1).get(); DBUser u2 = userDao.findUserByCertificateId(USER_CERT_2).get(); @@ -108,7 +109,7 @@ public abstract class AbstractServiceGroupDaoIntegrationTest extends AbstractBas } @Transactional - public void update(DBServiceGroup sg){ + public void update(DBServiceGroup sg) { testInstance.update(sg); } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java index 257fa28b1ff41bfff33937a9a6c8e1dba6833b2c..ca5d6ebe8133e78a9a4dd9e4c49770148de755f8 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java @@ -79,6 +79,9 @@ public abstract class AbstractServiceIntegrationTest { @Autowired protected UserDao userDao; + @Autowired + protected AlertDao alertDao; + @Autowired DBAssertion dbAssertion; diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CRLVerifierServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CRLVerifierServiceTest.java index 960086c64c2992ca949fb82acd215df9649d89cc..48c7d65a9476f98e344fa8495092fdf894d9b07c 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CRLVerifierServiceTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CRLVerifierServiceTest.java @@ -84,7 +84,7 @@ public class CRLVerifierServiceTest extends AbstractServiceIntegrationTest { X509Certificate certificate = loadCertificate("smp-crl-test-all.pem"); expectedEx.expect(SMPRuntimeException.class); - expectedEx.expectMessage("Certificate error Error occurred while downloading CRL:'https://localhost/clr. Error: ConnectException: Connection refused (Connection refused)!"); + expectedEx.expectMessage("Certificate error [Error occurred while downloading CRL:'https://localhost/clr']. Error: ConnectException: Connection refused (Connection refused)!"); // when-then crlVerifierServiceInstance.verifyCertificateCRLs(certificate); @@ -125,10 +125,10 @@ public class CRLVerifierServiceTest extends AbstractServiceIntegrationTest { public void verifyCertificateCRLsRevokedSerialTestThrowIOExceptionHttps() throws CertificateException, IOException, CRLException { String crlURL = "https://localhost/crl"; - Mockito.doThrow(new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Can not download CRL '" + crlURL, "IOException: Can not access URL")).when(crlVerifierServiceInstance).downloadCRL("https://localhost/crl", true); + Mockito.doThrow(new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Can not download CRL '" + crlURL+"'", "IOException: Can not access URL")).when(crlVerifierServiceInstance).downloadCRL("https://localhost/crl", true); expectedEx.expect(SMPRuntimeException.class); - expectedEx.expectMessage("Certificate error Can not download CRL 'https://localhost/crl. Error: IOException: Can not access URL!"); + expectedEx.expectMessage("Certificate error [Can not download CRL 'https://localhost/crl']. Error: IOException: Can not access URL!"); // when-then crlVerifierServiceInstance.verifyCertificateCRLs("11", "https://localhost/crl"); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceAllGetMethodsTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceAllGetMethodsTest.java index 7b250d995d3b269fc9706b41c994d944ae317afe..a7e0f320bdee8842ef5e04876f803ad811333882 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceAllGetMethodsTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceAllGetMethodsTest.java @@ -54,6 +54,7 @@ public class ConfigurationServiceAllGetMethodsTest { {HTTP_PROXY_USER, TEST_STRING, "getProxyUsername", true}, {PARTC_SCH_REGEXP, TEST_REXEXP,"getParticipantIdentifierSchemeRexExp", true}, {PARTC_SCH_REGEXP, TEST_STRING, "getParticipantIdentifierSchemeRexExpPattern", false}, + {PARTC_SCH_REGEXP_MSG, TEST_STRING, "getParticipantIdentifierSchemeRexExpMessage", true}, {PARTC_EBCOREPARTYID_CONCATENATE, Boolean.FALSE, "getForceConcatenateEBCorePartyId", true}, {CS_PARTICIPANTS, TEST_STRING_LIST, "getCaseSensitiveParticipantScheme", true}, {CS_DOCUMENTS, TEST_STRING_LIST, "getCaseSensitiveDocumentScheme", true}, @@ -110,6 +111,7 @@ public class ConfigurationServiceAllGetMethodsTest { {ENCODED_SLASHES_ALLOWED_IN_URL, Boolean.FALSE, "encodedSlashesAllowedInUrl", true}, {SMP_ALERT_CREDENTIALS_SERVER, TEST_STRING, "getTargetServerForCredentialValidation", true}, {SML_TLS_SERVER_CERT_SUBJECT_REGEXP, TEST_STRING, "getSMLIntegrationServerCertSubjectRegExpPattern", false}, + {SML_TLS_TRUSTSTORE_USE_SYSTEM_DEFAULT, Boolean.FALSE , "useSystemTruststoreForTLS", true}, {SSO_CAS_SMP_LOGIN_URI, TEST_STRING, "getCasSMPLoginRelativePath", true}, {ALERT_USER_LOGIN_FAILURE_ENABLED, Boolean.FALSE, "getAlertUserLoginFailureEnabled", true}, {ALERT_USER_SUSPENDED_ENABLED, Boolean.FALSE, "getAlertUserSuspendedEnabled", true}, diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..90a0f014039c94225dfada46957dc87a40334659 --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ConfigurationServiceTest.java @@ -0,0 +1,32 @@ +package eu.europa.ec.edelivery.smp.services; + +import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.junit.Test; + +import java.net.MalformedURLException; +import java.net.URL; + +import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class ConfigurationServiceTest { + + ConfigurationDao configurationDaoMock = mock(ConfigurationDao.class); + ConfigurationService testInstance = new ConfigurationService(configurationDaoMock); + + @Test + public void testGetCasUserDataURL() throws MalformedURLException { + String casUrl = "http://test:123/path"; + String casUserDataPath = "userdata/data.hsp"; + doReturn(new URL(casUrl)).when(configurationDaoMock).getCachedPropertyValue(SSO_CAS_URL); + doReturn(casUserDataPath).when(configurationDaoMock).getCachedPropertyValue(SSO_CAS_SMP_USER_DATA_URL_PATH); + + URL result = testInstance.getCasUserDataURL(); + assertNotNull(result); + // expected - the same server but different context path + assertEquals("http://test:123/" + casUserDataPath, result.toString()); + } +} \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorServiceTest.java index 5cdf7798831ef79a2f00f5d6cd3b1b4b3fa49e0e..3b3603a8b927f7d47861a77696e61e1da25ced3e 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorServiceTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/PayloadValidatorServiceTest.java @@ -77,6 +77,8 @@ public class PayloadValidatorServiceTest { assertThrows(SMPRuntimeException.class, () -> testInstance.validateUploadedContent(inputStream, mimeType)); assertEquals(ErrorCode.INVALID_REQUEST, smpRuntimeException.getErrorCode()); - MatcherAssert.assertThat(smpRuntimeException.getMessage(), CoreMatchers.containsString(spiException.getMessage())); + // generic error + assertEquals("Invalid request [Upload payload]. Error: Content validation failed!", smpRuntimeException.getMessage()); + } } \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertServiceIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..73c750fc12c6b0299b1a3c9e118a5b1eddace57b --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertServiceIntegrationTest.java @@ -0,0 +1,59 @@ +package eu.europa.ec.edelivery.smp.services.ui; + +import eu.europa.ec.edelivery.smp.data.model.DBAlert; +import eu.europa.ec.edelivery.smp.data.ui.AlertRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.services.AbstractServiceIntegrationTest; +import eu.europa.ec.edelivery.smp.services.AlertService; +import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import static org.junit.Assert.assertEquals; + +@ContextConfiguration(classes = UIAlertService.class) +public class UIAlertServiceIntegrationTest extends AbstractServiceIntegrationTest { + + @Autowired + protected UIAlertService testInstance; + + @Autowired + AlertService alertService; + + protected void insertDataObjects(int size) { + + String username = "username-intg-test"; + TestDBUtils.createDBAlert(username); + for (int i = 0; i < size; i++) { + DBAlert alert = TestDBUtils.createDBAlert(username); + alertDao.persistFlushDetach(alert); + } + } + + + @Test + public void getTableList() { + ServiceResult<AlertRO> before = testInstance.getTableList(-1, -1, null, null, null); + int newAddedValuesCount = 10; + insertDataObjects(newAddedValuesCount); + + ServiceResult<AlertRO> result = testInstance.getTableList(-1, -1, null, null, null); + + assertEquals(before.getCount() + newAddedValuesCount, result.getCount().intValue()); + } + + + @Test + public void convertToRo() { + DBAlert alert = TestDBUtils.createDBAlert("test"); + AlertRO alertRO = testInstance.convertToRo(alert); + + assertEquals(alert.getUsername(), alertRO.getUsername()); + assertEquals(alert.getAlertLevel(), alertRO.getAlertLevel()); + assertEquals(alert.getAlertStatus(), alertRO.getAlertStatus()); + assertEquals(alert.getAlertStatusDesc(), alertRO.getAlertStatusDesc()); + assertEquals(alert.getMailTo(), alertRO.getMailTo()); + assertEquals(alert.getProperties().size(), alertRO.getAlertDetails().size()); + } +} \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..652e735f1144f5085fd2e26bef362ef412877468 --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceIntegrationTest.java @@ -0,0 +1,463 @@ +package eu.europa.ec.edelivery.smp.services.ui; + +import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; +import eu.europa.ec.edelivery.smp.exceptions.CertificateNotTrustedException; +import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; +import eu.europa.ec.edelivery.smp.services.AbstractServiceIntegrationTest; +import eu.europa.ec.edelivery.smp.services.CRLVerifierService; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.testutil.X509CertificateTestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import javax.security.auth.x500.X500Principal; +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.*; +import java.util.*; + +import static org.junit.Assert.*; + + +@RunWith(SpringJUnit4ClassRunner.class) +public class UITruststoreServiceIntegrationTest extends AbstractServiceIntegrationTest { + + public static final String CERTIFICATE_POLICY_ANY = "2.5.29.32.0"; + public static final String CERTIFICATE_POLICY_QCP_NATURAL = "0.4.0.194112.1.0"; + public static final String CERTIFICATE_POLICY_QCP_LEGAL = "0.4.0.194112.1.1"; + public static final String CERTIFICATE_POLICY_QCP_NATURAL_QSCD = "0.4.0.194112.1.2"; + public static final String CERTIFICATE_POLICY_QCP_LEGAL_QSCD = "0.4.0.194112.1.3"; + + public static final String S_SUBJECT_PEPPOL = "CN=POP000004,OU=PEPPOL TEST AP,O=European Commission,C=BE"; + public static final String S_SUBJECT_PEPPOL_EXPANDED = "serialNumber=12345,emailAddress=test@mail.com,CN=POP000004,OU=PEPPOL TEST AP,O=European Commission,street=My Street,C=BE"; + public static final String S_SUBJECT_PEPPOL_NOT_TRUSTED = "CN=POP000005,OU=PEPPOL TEST AP,O=European Commission,C=BE"; + + public static final String S_SUBJECT_TEST = "CN=SMP test,O=DIGIT,C=BE"; + + + Path resourceDirectory = Paths.get("src", "test", "resources", "truststore"); + Path targetDirectory = Paths.get("target", "truststore"); + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + @Autowired + protected UITruststoreService testInstance; + + @Autowired + protected ConfigurationService configurationService; + + @Autowired + protected CRLVerifierService crlVerifierService; + + @Before + public void setup() throws IOException { + configurationService = Mockito.spy(configurationService); + crlVerifierService = Mockito.spy(crlVerifierService); + + ReflectionTestUtils.setField(testInstance, "crlVerifierService", crlVerifierService); + + ReflectionTestUtils.setField(testInstance, "configurationService", configurationService); + + File truststoreFile = new File(targetDirectory.toFile(), "smp-truststore.jks"); + Mockito.doReturn("test123").when(configurationService).getTruststoreCredentialToken(); + Mockito.doReturn(truststoreFile).when(configurationService).getTruststoreFile(); + Mockito.doReturn(targetDirectory.toFile()).when(configurationService).getConfigurationFolder(); + Mockito.doReturn(true).when(configurationService).forceCRLValidation(); + resetKeystore(); + + testInstance.refreshData(); + } + + public void resetKeystore() throws IOException { + FileUtils.deleteDirectory(targetDirectory.toFile()); + FileUtils.copyDirectory(resourceDirectory.toFile(), targetDirectory.toFile()); + } + + @Test + public void testGetKeystoreEntriesList() { + List<String> lst = testInstance.getNormalizedTrustedList(); + assertEquals(2, lst.size()); + assertEquals(S_SUBJECT_PEPPOL, lst.get(0)); + assertEquals(S_SUBJECT_TEST, lst.get(1)); + } + + @Test + public void testSubjectValid() { + // given when + // then + assertTrue(testInstance.isSubjectOnTrustedList(S_SUBJECT_PEPPOL)); + } + + @Test + public void testSubjectValidExpanded() { + // given when + // then + assertTrue(testInstance.isSubjectOnTrustedList(S_SUBJECT_PEPPOL_EXPANDED)); + } + + @Test + public void testSubjectNotTrusted() { + // given when + // then + assertFalse(testInstance.isSubjectOnTrustedList(S_SUBJECT_PEPPOL_NOT_TRUSTED)); + } + + @Test + public void testAddCertificate() throws Exception { + // given + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + String alias = UUID.randomUUID().toString(); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); + int iSize = testInstance.getNormalizedTrustedList().size(); + assertFalse(testInstance.isSubjectOnTrustedList(certSubject)); + // when + testInstance.addCertificate(alias, certificate); + + // then + assertEquals(iSize + 1, testInstance.getNormalizedTrustedList().size()); + assertTrue(testInstance.isSubjectOnTrustedList(certSubject)); + } + + @Test + public void testAddCertificateRDN() throws Exception { + // given + String certSubject = "GIVENNAME=John+SERIALNUMBER=1+CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + String alias = UUID.randomUUID().toString(); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); + String val = certificate.getSubjectX500Principal().getName(X500Principal.RFC2253); + int iSize = testInstance.getNormalizedTrustedList().size(); + assertFalse(testInstance.isSubjectOnTrustedList(certSubject)); + // when + testInstance.addCertificate(alias, certificate); + + // then + assertEquals(iSize + 1, testInstance.getNormalizedTrustedList().size()); + assertTrue(testInstance.isSubjectOnTrustedList(certSubject)); + } + + @Test + public void testDeleteCertificate() throws Exception { + // given + List<CertificateRO> list = testInstance.getCertificateROEntriesList(); + int iSize = list.size(); + assertTrue(list.size() > 0); + CertificateRO certificateRO = list.get(0); + assertTrue(testInstance.isSubjectOnTrustedList(certificateRO.getSubject())); + // when + testInstance.deleteCertificate(certificateRO.getAlias()); + + // then + assertEquals(iSize - 1, testInstance.getNormalizedTrustedList().size()); + assertFalse(testInstance.isSubjectOnTrustedList(certificateRO.getSubject())); + } + + @Test + public void testIsTruststoreChanged() throws Exception { + // given + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + String alias = UUID.randomUUID().toString(); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); + testInstance.addCertificate(alias, certificate); + assertTrue(testInstance.isSubjectOnTrustedList(certSubject)); + // when rollback truststore + resetKeystore(); + // then it should detect file change and refresh certificates + assertFalse(testInstance.isSubjectOnTrustedList(certSubject)); + } + + + @Test + public void testGetCertificateDataPEMAndFullValidationExpired() throws IOException { + // given + + byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/SMPtest.crt")); + + // when + CertificateRO cer = testInstance.getCertificateData(buff, true); + + //then + assertEquals("CN=SMP test,O=DIGIT,C=BE:0000000000000003", cer.getCertificateId()); + assertEquals("CN=Intermediate CA,O=DIGIT,C=BE", cer.getIssuer()); + assertEquals("1.2.840.113549.1.9.1=#160c736d7040746573742e636f6d,CN=SMP test,O=DIGIT,C=BE", cer.getSubject()); + assertEquals("3", cer.getSerialNumber()); + assertNotNull(cer.getValidFrom()); + assertNotNull(cer.getValidTo()); + assertTrue(cer.getValidFrom().before(cer.getValidTo())); + assertEquals("Certificate is expired!", cer.getInvalidReason()); + } + + @Test + public void testGetCertificateDataPEMWithHeader() throws IOException, CertificateException { + // given + byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/pem-with-header.crt")); + + // when + CertificateRO cer = testInstance.getCertificateData(buff); + + //then + assertEquals("CN=alice,O=www.freelan.org,C=FR:0000000000000001", cer.getCertificateId()); + assertEquals("1.2.840.113549.1.9.1=#1613636f6e7461637440667265656c616e2e6f7267,CN=Freelan Sample Certificate Authority,OU=freelan,O=www.freelan.org,L=Strasbourg,ST=Alsace,C=FR", cer.getIssuer()); + assertEquals("1.2.840.113549.1.9.1=#1613636f6e7461637440667265656c616e2e6f7267,CN=alice,OU=freelan,O=www.freelan.org,ST=Alsace,C=FR", cer.getSubject()); + assertEquals("1", cer.getSerialNumber()); + assertNotNull(cer.getValidFrom()); + assertNotNull(cer.getValidTo()); + assertTrue(cer.getValidFrom().before(cer.getValidTo())); + } + + @Test + public void testGetCertificateDataSMime() throws IOException, CertificateException { + // given + byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/certificates/cert-smime.pem")); + + // when + CertificateRO cer = testInstance.getCertificateData(buff); + + //then + assertEquals("CN=edelivery_sml,O=European Commission,C=BE:3cfe6b37e4702512c01e71f9b9175464", cer.getCertificateId()); + assertEquals("CN=PEPPOL SERVICE METADATA PUBLISHER TEST CA - G2,OU=FOR TEST ONLY,O=OpenPEPPOL AISBL,C=BE", cer.getIssuer()); + assertEquals("C=BE,O=European Commission,OU=PEPPOL TEST SMP,CN=edelivery_sml", cer.getSubject()); + assertEquals("3cfe6b37e4702512c01e71f9b9175464", cer.getSerialNumber()); + assertNotNull(cer.getValidFrom()); + assertNotNull(cer.getValidTo()); + assertTrue(cer.getValidFrom().before(cer.getValidTo())); + } + + @Test + public void testGetCertificateDataDER() throws IOException, CertificateException { + // given + byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/NewPeppolAP.crt")); + + // when + CertificateRO cer = testInstance.getCertificateData(buff); + + //then + assertEquals("CN=POP000004,O=European Commission,C=BE:474980c51478cf62761667461aef5e8e", cer.getCertificateId()); + assertEquals("CN=PEPPOL ACCESS POINT TEST CA - G2,OU=FOR TEST ONLY,O=OpenPEPPOL AISBL,C=BE", cer.getIssuer()); + assertEquals("C=BE,O=European Commission,OU=PEPPOL TEST AP,CN=POP000004", cer.getSubject()); + assertEquals("474980c51478cf62761667461aef5e8e", cer.getSerialNumber()); + assertNotNull(cer.getValidFrom()); + assertNotNull(cer.getValidTo()); + assertTrue(cer.getValidFrom().before(cer.getValidTo())); + } + + @Test + public void testCheckFullCertificateValidityNotYetValid() throws Exception { + // given + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + Calendar from = Calendar.getInstance(); + Calendar to = Calendar.getInstance(); + to.add(Calendar.DAY_OF_YEAR, 2); + from.add(Calendar.DAY_OF_YEAR, 1); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( + "10af", certSubject, certSubject, from.getTime(), to.getTime(), Collections.emptyList()); + + //then + expectedEx.expect(CertificateNotYetValidException.class); + // when + testInstance.checkFullCertificateValidity(certificate); + } + + @Test + public void testCheckFullCertificateValidityExpired() throws Exception { + // given + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + Calendar from = Calendar.getInstance(); + Calendar to = Calendar.getInstance(); + to.add(Calendar.DAY_OF_YEAR, -1); + from.add(Calendar.DAY_OF_YEAR, -2); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( + "10af", certSubject, certSubject, from.getTime(), to.getTime(), Collections.emptyList()); + + //then + expectedEx.expect(CertificateExpiredException.class); + // when + testInstance.checkFullCertificateValidity(certificate); + } + + @Test + public void testCheckFullCertificateNotTrusted() throws Exception { + // given + String crlUrl = "https://localhost/crl"; + String revokedSerialFromList = "0011"; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); + + Mockito.doReturn(crl).when(crlVerifierService).getCRLByURL(crlUrl); + + Calendar from = Calendar.getInstance(); + Calendar to = Calendar.getInstance(); + to.add(Calendar.DAY_OF_YEAR, 1); + from.add(Calendar.DAY_OF_YEAR, -2); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( + revokedSerialFromList, S_SUBJECT_PEPPOL_NOT_TRUSTED, S_SUBJECT_PEPPOL_NOT_TRUSTED, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); + //then + expectedEx.expect(CertificateNotTrustedException.class); + // when + testInstance.checkFullCertificateValidity(certificate); + } + + + @Test + public void testCheckFullCertificateValidityRevoked() throws Exception { + // given + String crlUrl = "https://localhost/crl"; + String revokedSerialFromList = "0011"; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); + + Mockito.doReturn(crl).when(crlVerifierService).downloadCRL(ArgumentMatchers.eq(crlUrl), ArgumentMatchers.anyBoolean()); + + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + Calendar from = Calendar.getInstance(); + Calendar to = Calendar.getInstance(); + to.add(Calendar.DAY_OF_YEAR, 1); + from.add(Calendar.DAY_OF_YEAR, -2); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( + revokedSerialFromList, certSubject, certSubject, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); + // add as trusted certificate + testInstance.addCertificate(UUID.randomUUID().toString(), certificate); + + + //then + expectedEx.expect(CertificateRevokedException.class); + // when + testInstance.checkFullCertificateValidity(certificate); + } + + @Test + public void testCheckFullCertificateValidityNotForceCRL() throws Exception { + // given + String crlUrl = "https://localhost/crl"; + String revokedSerialFromList = "0011"; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); + Mockito.doReturn(false).when(configurationService).forceCRLValidation(); + Mockito.doThrow(new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Error occurred while downloading CRL:" + crlUrl, "")).when(crlVerifierService).downloadURL(crlUrl); + + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + Calendar from = Calendar.getInstance(); + Calendar to = Calendar.getInstance(); + to.add(Calendar.DAY_OF_YEAR, 1); + from.add(Calendar.DAY_OF_YEAR, -2); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( + revokedSerialFromList, certSubject, certSubject, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); + // add as trusted certificate + testInstance.addCertificate(UUID.randomUUID().toString(), certificate); + + + //then should be thrown CertificateRevokedException but is not + // when + testInstance.checkFullCertificateValidity(certificate); + } + + @Test + public void testCheckFullCertificateValidityOK() throws Exception { + // given + String crlUrl = "https://localhost/crl"; + String serialNotInList = "20011FF"; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); + + Mockito.doReturn(crl).when(crlVerifierService).downloadCRL(crlUrl, true); + + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + Calendar from = Calendar.getInstance(); + Calendar to = Calendar.getInstance(); + to.add(Calendar.DAY_OF_YEAR, 1); + from.add(Calendar.DAY_OF_YEAR, -2); + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( + serialNotInList, certSubject, certSubject, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); + // add as trusted certificate + testInstance.addCertificate(UUID.randomUUID().toString(), certificate); + + // when + testInstance.checkFullCertificateValidity(certificate); + + // then + //no errors should be thrown + } + + @Test + public void testCreateAliasForCert() throws Exception { + // given + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); + // when + String alias = testInstance.createAliasFromCert(certificate, null); + + // then + assertEquals("SMP Test", alias); + } + + + @Test + public void testCreateAliasFoMultiValuerCert() throws Exception { + // given + String certSubject = "GIVENNAME=John+SERIALNUMBER=1+CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); + // when + String alias = testInstance.createAliasFromCert(certificate, null); + + // then + assertEquals("SMP Test", alias); + } + + @Test + public void testValidateCertificatePolicyLegacyMatchOk() throws Exception { + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject, BigInteger.TEN, Arrays.asList(CERTIFICATE_POLICY_QCP_NATURAL)); + Mockito.doReturn(Arrays.asList(CERTIFICATE_POLICY_QCP_LEGAL, CERTIFICATE_POLICY_QCP_NATURAL)).when(configurationService).getAllowedCertificatePolicies(); + testInstance.validateCertificatePolicyMatchLegacy(certificate); + } + + @Test + public void testValidateCertificatePolicyLegacyMatchEmpty() throws Exception { + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject, BigInteger.TEN, null); + Mockito.doReturn(Arrays.asList(CERTIFICATE_POLICY_QCP_LEGAL, CERTIFICATE_POLICY_QCP_NATURAL)).when(configurationService).getAllowedCertificatePolicies(); + + CertificateException result = assertThrows(CertificateException.class, + () -> testInstance.validateCertificatePolicyMatchLegacy(certificate)); + MatcherAssert.assertThat(result.getMessage(), CoreMatchers.startsWith("Certificate has empty CertificatePolicy extension. Certificate:")); + } + + @Test + public void testValidateCertificatePolicyLegacyMatchMismatch() throws Exception { + String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject, BigInteger.TEN, Arrays.asList(CERTIFICATE_POLICY_QCP_LEGAL_QSCD)); + Mockito.doReturn(Arrays.asList(CERTIFICATE_POLICY_QCP_LEGAL, CERTIFICATE_POLICY_QCP_NATURAL)).when(configurationService).getAllowedCertificatePolicies(); + + CertificateException result = assertThrows(CertificateException.class, + () -> testInstance.validateCertificatePolicyMatchLegacy(certificate)); + MatcherAssert.assertThat(result.getMessage(), CoreMatchers.startsWith("Certificate policy verification failed.")); + } + + @Test + public void validateCertificateNotUsed() throws CertificateException { + String certId = "cn=test" + UUID.randomUUID().toString() + ",o=test,c=eu:123456"; + CertificateRO certificateRO = new CertificateRO(); + certificateRO.setCertificateId(certId); + // when + testInstance.validateCertificateNotUsed(certificateRO); + //then no error is thrown because + + } + +} \ No newline at end of file diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceTest.java index 2a30e0fbc7e482484f2d33eba3a737499a97a5f8..d0f5cf9fd4c2085ce5bb146b597573cb8fee8271 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreServiceTest.java @@ -1,414 +1,204 @@ package eu.europa.ec.edelivery.smp.services.ui; +import eu.europa.ec.edelivery.smp.data.dao.UserDao; +import eu.europa.ec.edelivery.smp.data.model.DBUser; import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; import eu.europa.ec.edelivery.smp.exceptions.CertificateNotTrustedException; -import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; -import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; -import eu.europa.ec.edelivery.smp.services.AbstractServiceIntegrationTest; import eu.europa.ec.edelivery.smp.services.CRLVerifierService; import eu.europa.ec.edelivery.smp.services.ConfigurationService; import eu.europa.ec.edelivery.smp.testutil.X509CertificateTestUtils; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.core.convert.ConversionService; import javax.security.auth.x500.X500Principal; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.Security; import java.security.cert.*; -import java.util.Calendar; -import java.util.Collections; -import java.util.List; +import java.util.Optional; import java.util.UUID; +import java.util.regex.Pattern; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +public class UITruststoreServiceTest { + ConfigurationService configurationService = Mockito.mock(ConfigurationService.class); + CRLVerifierService crlVerifierService = Mockito.mock(CRLVerifierService.class); + ConversionService conversionService = Mockito.mock(ConversionService.class); + UserDao userDao = Mockito.mock(UserDao.class); -@RunWith(SpringJUnit4ClassRunner.class) -public class UITruststoreServiceTest extends AbstractServiceIntegrationTest { - - public static final String S_SUBJECT_PEPPOL = "CN=POP000004,OU=PEPPOL TEST AP,O=European Commission,C=BE"; - public static final String S_SUBJECT_PEPPOL_EXPANDED = "serialNumber=12345,emailAddress=test@mail.com,CN=POP000004,OU=PEPPOL TEST AP,O=European Commission,street=My Street,C=BE"; - public static final String S_SUBJECT_PEPPOL_NOT_TRUSTED = "CN=POP000005,OU=PEPPOL TEST AP,O=European Commission,C=BE"; - - public static final String S_SUBJECT_TEST = "CN=SMP test,O=DIGIT,C=BE"; - - - Path resourceDirectory = Paths.get("src", "test", "resources", "truststore"); - Path targetDirectory = Paths.get("target", "truststore"); - - @Rule - public ExpectedException expectedEx = ExpectedException.none(); - - @Autowired - protected UITruststoreService testInstance; - - @Autowired - protected ConfigurationService configurationService; - - @Autowired - protected CRLVerifierService crlVerifierService; + UITruststoreService testInstance = spy(new UITruststoreService(configurationService, crlVerifierService, conversionService, userDao)); @Before - public void setup() throws IOException { - configurationService = Mockito.spy(configurationService); - crlVerifierService = Mockito.spy(crlVerifierService); - - ReflectionTestUtils.setField(testInstance, "crlVerifierService", crlVerifierService); - - ReflectionTestUtils.setField(testInstance, "configurationService", configurationService); - - File truststoreFile = new File(targetDirectory.toFile(), "smp-truststore.jks"); - Mockito.doReturn("test123").when(configurationService).getTruststoreCredentialToken(); - Mockito.doReturn(truststoreFile).when(configurationService).getTruststoreFile(); - Mockito.doReturn(targetDirectory.toFile()).when(configurationService).getConfigurationFolder(); - Mockito.doReturn(true).when(configurationService).forceCRLValidation(); - resetKeystore(); - - testInstance.refreshData(); - } - - public void resetKeystore() throws IOException { - FileUtils.deleteDirectory(targetDirectory.toFile()); - FileUtils.copyDirectory(resourceDirectory.toFile(), targetDirectory.toFile()); + public void setup() { + Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1); } @Test - public void testGetKeystoreEntriesList() { - List<String> lst = testInstance.getNormalizedTrustedList(); - assertEquals(2, lst.size()); - assertEquals(S_SUBJECT_PEPPOL, lst.get(0)); - assertEquals(S_SUBJECT_TEST, lst.get(1)); - } - - @Test - public void testSubjectValid() { - // given when - // then - assertTrue(testInstance.isSubjectOnTrustedList(S_SUBJECT_PEPPOL)); + public void validateCertificateNotUsedOk() throws CertificateException { + String certId = "cn=test" + UUID.randomUUID().toString() + ",o=test,c=eu:123456"; + CertificateRO certificateRO = new CertificateRO(); + certificateRO.setCertificateId(certId); + doReturn(Optional.empty()).when(userDao).findUserByCertificateId(ArgumentMatchers.anyString()); + // when + testInstance.validateCertificateNotUsed(certificateRO); + //then no error is thrown because + ArgumentCaptor<String> certIdCaptor = ArgumentCaptor.forClass(String.class); + verify(userDao, times(1)) + .findUserByCertificateId(certIdCaptor.capture()); + assertEquals(certId, certIdCaptor.getValue()); } @Test - public void testSubjectValidExpanded() { - // given when - // then - assertTrue(testInstance.isSubjectOnTrustedList(S_SUBJECT_PEPPOL_EXPANDED)); + public void validateCertificateNotUsedIsUsed() { + String certId = "cn=test" + UUID.randomUUID().toString() + ",o=test,c=eu:123456"; + CertificateRO certificateRO = new CertificateRO(); + certificateRO.setCertificateId(certId); + doReturn(Optional.of(new DBUser())).when(userDao).findUserByCertificateId(ArgumentMatchers.anyString()); + // when + CertificateException result = assertThrows(CertificateException.class, () -> testInstance.validateCertificateNotUsed(certificateRO)); + assertEquals("Certificate: [" + certId + "] is already used!", result.getMessage()); } @Test - public void testSubjectNotTrusted() { - // given when - // then - assertFalse(testInstance.isSubjectOnTrustedList(S_SUBJECT_PEPPOL_NOT_TRUSTED)); - } + public void validateNewCertificateOk() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doNothing().when(testInstance).checkFullCertificateValidity(cert); + doNothing().when(testInstance).validateCertificateNotUsed(certData); - @Test - public void testAddCertificate() throws Exception { - // given - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - String alias = UUID.randomUUID().toString(); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); - int iSize = testInstance.getNormalizedTrustedList().size(); - assertFalse(testInstance.isSubjectOnTrustedList(certSubject)); - // when - testInstance.addCertificate(alias, certificate); + testInstance.validateCertificate(cert, certData); - // then - assertEquals(iSize + 1, testInstance.getNormalizedTrustedList().size()); - assertTrue(testInstance.isSubjectOnTrustedList(certSubject)); + assertFalse(certData.isInvalid()); + assertNull(certData.getInvalidReason()); } @Test - public void testAddCertificateRDN() throws Exception { - // given - String certSubject = "GIVENNAME=John+SERIALNUMBER=1+CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - String alias = UUID.randomUUID().toString(); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); - String val = certificate.getSubjectX500Principal().getName(X500Principal.RFC2253); - int iSize = testInstance.getNormalizedTrustedList().size(); - assertFalse(testInstance.isSubjectOnTrustedList(certSubject)); - // when - testInstance.addCertificate(alias, certificate); + public void validateNewCertificateCertificateExpiredException() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doThrow(new CertificateExpiredException("Expired")).when(testInstance).checkFullCertificateValidity(cert); - // then - assertEquals(iSize + 1, testInstance.getNormalizedTrustedList().size()); - assertTrue(testInstance.isSubjectOnTrustedList(certSubject)); - } + testInstance.validateCertificate(cert, certData); - @Test - public void testDeleteCertificate() throws Exception { - // given - List<CertificateRO> list = testInstance.getCertificateROEntriesList(); - int iSize = list.size(); - assertTrue(list.size() > 0); - CertificateRO certificateRO = list.get(0); - assertTrue(testInstance.isSubjectOnTrustedList(certificateRO.getSubject())); - // when - testInstance.deleteCertificate(certificateRO.getAlias()); - - // then - assertEquals(iSize - 1, testInstance.getNormalizedTrustedList().size()); - assertFalse(testInstance.isSubjectOnTrustedList(certificateRO.getSubject())); + assertTrue(certData.isInvalid()); + assertEquals("Certificate is expired!", certData.getInvalidReason()); } @Test - public void testIsTruststoreChanged() throws Exception { - // given - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - String alias = UUID.randomUUID().toString(); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); - testInstance.addCertificate(alias, certificate); - assertTrue(testInstance.isSubjectOnTrustedList(certSubject)); - // when rollback truststore - resetKeystore(); - // then it should detect file change and refresh certificates - assertFalse(testInstance.isSubjectOnTrustedList(certSubject)); - } + public void validateNewCertificateCertificateCertificateNotYetValidException() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doThrow(new CertificateNotYetValidException("Error")).when(testInstance).checkFullCertificateValidity(cert); + testInstance.validateCertificate(cert, certData); - @Test - public void testGetCertificateDataPEM() throws IOException, CertificateException { - // given - byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/SMPtest.crt")); - - // when - CertificateRO cer = testInstance.getCertificateData(buff); - - //then - assertEquals("CN=SMP test,O=DIGIT,C=BE:0000000000000003", cer.getCertificateId()); - assertEquals("CN=Intermediate CA,O=DIGIT,C=BE", cer.getIssuer()); - assertEquals("1.2.840.113549.1.9.1=#160c736d7040746573742e636f6d,CN=SMP test,O=DIGIT,C=BE", cer.getSubject()); - assertEquals("3", cer.getSerialNumber()); - assertNotNull(cer.getValidFrom()); - assertNotNull(cer.getValidTo()); - assertTrue(cer.getValidFrom().before(cer.getValidTo())); + assertTrue(certData.isInvalid()); + assertEquals("Certificate is not yet valid!", certData.getInvalidReason()); } @Test - public void testGetCertificateDataPEMWithHeader() throws IOException, CertificateException { - // given - byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/pem-with-header.crt")); + public void validateNewCertificateCertificateCertificateRevokedException() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doThrow(Mockito.mock(CertificateRevokedException.class)).when(testInstance).checkFullCertificateValidity(cert); - // when - CertificateRO cer = testInstance.getCertificateData(buff); - - //then - assertEquals("CN=alice,O=www.freelan.org,C=FR:0000000000000001", cer.getCertificateId()); - assertEquals("1.2.840.113549.1.9.1=#1613636f6e7461637440667265656c616e2e6f7267,CN=Freelan Sample Certificate Authority,OU=freelan,O=www.freelan.org,L=Strasbourg,ST=Alsace,C=FR", cer.getIssuer()); - assertEquals("1.2.840.113549.1.9.1=#1613636f6e7461637440667265656c616e2e6f7267,CN=alice,OU=freelan,O=www.freelan.org,ST=Alsace,C=FR", cer.getSubject()); - assertEquals("1", cer.getSerialNumber()); - assertNotNull(cer.getValidFrom()); - assertNotNull(cer.getValidTo()); - assertTrue(cer.getValidFrom().before(cer.getValidTo())); - } - - @Test - public void testGetCertificateDataSMime() throws IOException, CertificateException { - // given - byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/certificates/cert-smime.pem")); + testInstance.validateCertificate(cert, certData); - // when - CertificateRO cer = testInstance.getCertificateData(buff); - - //then - assertEquals("CN=edelivery_sml,O=European Commission,C=BE:3cfe6b37e4702512c01e71f9b9175464", cer.getCertificateId()); - assertEquals("CN=PEPPOL SERVICE METADATA PUBLISHER TEST CA - G2,OU=FOR TEST ONLY,O=OpenPEPPOL AISBL,C=BE", cer.getIssuer()); - assertEquals("C=BE,O=European Commission,OU=PEPPOL TEST SMP,CN=edelivery_sml", cer.getSubject()); - assertEquals("3cfe6b37e4702512c01e71f9b9175464", cer.getSerialNumber()); - assertNotNull(cer.getValidFrom()); - assertNotNull(cer.getValidTo()); - assertTrue(cer.getValidFrom().before(cer.getValidTo())); + assertTrue(certData.isInvalid()); + assertEquals("Certificate is revoked!", certData.getInvalidReason()); } @Test - public void testGetCertificateDataDER() throws IOException, CertificateException { - // given - byte[] buff = IOUtils.toByteArray(UIUserServiceIntegrationTest.class.getResourceAsStream("/truststore/NewPeppolAP.crt")); + public void validateNewCertificateCertificateCertificateNotTrustedException() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doThrow(Mockito.mock(CertificateNotTrustedException.class)).when(testInstance).checkFullCertificateValidity(cert); - // when - CertificateRO cer = testInstance.getCertificateData(buff); - - //then - assertEquals("CN=POP000004,O=European Commission,C=BE:474980c51478cf62761667461aef5e8e", cer.getCertificateId()); - assertEquals("CN=PEPPOL ACCESS POINT TEST CA - G2,OU=FOR TEST ONLY,O=OpenPEPPOL AISBL,C=BE", cer.getIssuer()); - assertEquals("C=BE,O=European Commission,OU=PEPPOL TEST AP,CN=POP000004", cer.getSubject()); - assertEquals("474980c51478cf62761667461aef5e8e", cer.getSerialNumber()); - assertNotNull(cer.getValidFrom()); - assertNotNull(cer.getValidTo()); - assertTrue(cer.getValidFrom().before(cer.getValidTo())); - } + testInstance.validateCertificate(cert, certData); - @Test - public void testCheckFullCertificateValidityNotYetValid() throws Exception { - // given - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - Calendar from = Calendar.getInstance(); - Calendar to = Calendar.getInstance(); - to.add(Calendar.DAY_OF_YEAR, 2); - from.add(Calendar.DAY_OF_YEAR, 1); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( - "10af", certSubject, certSubject, from.getTime(), to.getTime(), Collections.emptyList()); - - //then - expectedEx.expect(CertificateNotYetValidException.class); - // when - testInstance.checkFullCertificateValidity(certificate); + assertTrue(certData.isInvalid()); + assertEquals("Certificate is not trusted!", certData.getInvalidReason()); } @Test - public void testCheckFullCertificateValidityExpired() throws Exception { - // given - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - Calendar from = Calendar.getInstance(); - Calendar to = Calendar.getInstance(); - to.add(Calendar.DAY_OF_YEAR, -1); - from.add(Calendar.DAY_OF_YEAR, -2); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( - "10af", certSubject, certSubject, from.getTime(), to.getTime(), Collections.emptyList()); - - //then - expectedEx.expect(CertificateExpiredException.class); - // when - testInstance.checkFullCertificateValidity(certificate); + public void validateNewCertificateCertPathValidatorException() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doThrow(new CertificateException(Mockito.mock(CertPathValidatorException.class))).when(testInstance).checkFullCertificateValidity(cert); + + testInstance.validateCertificate(cert, certData); + + assertTrue(certData.isInvalid()); + assertEquals("Certificate is not trusted! Invalid certificate policy path!", certData.getInvalidReason()); } @Test - public void testCheckFullCertificateNotTrusted() throws Exception { - // given - String crlUrl = "https://localhost/crl"; - String revokedSerialFromList = "0011"; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); - - Mockito.doReturn(crl).when(crlVerifierService).getCRLByURL(crlUrl); - - Calendar from = Calendar.getInstance(); - Calendar to = Calendar.getInstance(); - to.add(Calendar.DAY_OF_YEAR, 1); - from.add(Calendar.DAY_OF_YEAR, -2); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( - revokedSerialFromList, S_SUBJECT_PEPPOL_NOT_TRUSTED, S_SUBJECT_PEPPOL_NOT_TRUSTED, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); - //then - expectedEx.expect(CertificateNotTrustedException.class); - // when - testInstance.checkFullCertificateValidity(certificate); - } + public void validateNewCertificateCertificateException() throws CertificateException { + String errorMessage = "Error Message"; + X509Certificate cert = Mockito.mock(X509Certificate.class); + CertificateRO certData = new CertificateRO(); + doThrow(new CertificateException(errorMessage)).when(testInstance).checkFullCertificateValidity(cert); + testInstance.validateCertificate(cert, certData); - @Test - public void testCheckFullCertificateValidityRevoked() throws Exception { - // given - String crlUrl = "https://localhost/crl"; - String revokedSerialFromList = "0011"; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); - - Mockito.doReturn(crl).when(crlVerifierService).downloadCRL(ArgumentMatchers.eq(crlUrl), ArgumentMatchers.anyBoolean()); - - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - Calendar from = Calendar.getInstance(); - Calendar to = Calendar.getInstance(); - to.add(Calendar.DAY_OF_YEAR, 1); - from.add(Calendar.DAY_OF_YEAR, -2); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( - revokedSerialFromList, certSubject, certSubject, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); - // add as trusted certificate - testInstance.addCertificate(UUID.randomUUID().toString(), certificate); - - - //then - expectedEx.expect(CertificateRevokedException.class); - // when - testInstance.checkFullCertificateValidity(certificate); + assertTrue(certData.isInvalid()); + assertEquals(errorMessage, certData.getInvalidReason()); } + @Test - public void testCheckFullCertificateValidityNotForceCRL() throws Exception { - // given - String crlUrl = "https://localhost/crl"; - String revokedSerialFromList = "0011"; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); - Mockito.doReturn(false).when(configurationService).forceCRLValidation(); - Mockito.doThrow(new SMPRuntimeException(ErrorCode.CERTIFICATE_ERROR, "Error occurred while downloading CRL:" + crlUrl, "")).when(crlVerifierService).downloadURL(crlUrl); - - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - Calendar from = Calendar.getInstance(); - Calendar to = Calendar.getInstance(); - to.add(Calendar.DAY_OF_YEAR, 1); - from.add(Calendar.DAY_OF_YEAR, -2); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( - revokedSerialFromList, certSubject, certSubject, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); - // add as trusted certificate - testInstance.addCertificate(UUID.randomUUID().toString(), certificate); - - - //then should be thrown CertificateRevokedException but is not - // when - testInstance.checkFullCertificateValidity(certificate); + public void validateCertificateSubjectExpressionLegacyIfNullSkip() throws CertificateException { + X509Certificate cert = Mockito.mock(X509Certificate.class); + doReturn(null).when(configurationService).getCertificateSubjectRegularExpression(); + testInstance.validateCertificateSubjectExpressionLegacy(cert); } @Test - public void testCheckFullCertificateValidityOK() throws Exception { - // given - String crlUrl = "https://localhost/crl"; - String serialNotInList = "20011FF"; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509CRL crl = (X509CRL) cf.generateCRL(getClass().getResourceAsStream("/certificates/smp-crl-test.crl")); - - Mockito.doReturn(crl).when(crlVerifierService).downloadCRL(crlUrl, true); - - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - Calendar from = Calendar.getInstance(); - Calendar to = Calendar.getInstance(); - to.add(Calendar.DAY_OF_YEAR, 1); - from.add(Calendar.DAY_OF_YEAR, -2); - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest( - serialNotInList, certSubject, certSubject, from.getTime(), to.getTime(), Collections.singletonList(crlUrl)); - // add as trusted certificate - testInstance.addCertificate(UUID.randomUUID().toString(), certificate); + public void validateCertificateSubjectExpressionLegacyValidatedNotMatch() throws Exception { + String regularExpression = ".*CN=SomethingNotExists.*"; + String subject = "CN=Something,O=test,C=EU"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(subject); + doReturn(Pattern.compile(regularExpression)).when(configurationService).getCertificateSubjectRegularExpression(); + CertificateException resultException = assertThrows(CertificateException.class, () -> testInstance.validateCertificateSubjectExpressionLegacy(certificate)); - // when - testInstance.checkFullCertificateValidity(certificate); - - // then - //no errors should be thrown + assertEquals("Certificate subject [" + +certificate.getSubjectX500Principal().getName(X500Principal.RFC2253) + +"] does not match the regular expression configured ["+regularExpression+"]", + resultException.getMessage()); } @Test - public void testCreateAliasForCert() throws Exception { - // given - String certSubject = "CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); - // when - String alias = testInstance.createAliasFromCert(certificate, null); + public void validateCertificateSubjectExpressionLegacyValidatedMatch() throws Exception { + String regularExpression = ".*CN=Something.*"; + String subject = "CN=Something,O=test,C=EU"; + X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(subject); + doReturn(Pattern.compile(regularExpression)).when(configurationService).getCertificateSubjectRegularExpression(); - // then - assertEquals("SMP Test", alias); + testInstance.validateCertificateSubjectExpressionLegacy(certificate); + // no error is thrown } - @Test - public void testCreateAliasFoMultiValuerCert() throws Exception { - // given - String certSubject = "GIVENNAME=John+SERIALNUMBER=1+CN=SMP Test,OU=eDelivery,O=DIGITAL,C=BE"; - X509Certificate certificate = X509CertificateTestUtils.createX509CertificateForTest(certSubject); - // when - String alias = testInstance.createAliasFromCert(certificate, null); - - // then - assertEquals("SMP Test", alias); + public void loadTruststoreDoNotThrowError(){ + // test for null file + KeyStore result = testInstance.loadTruststore(null); + assertNull(result); + // test for file not exists + result = testInstance.loadTruststore(new File(UUID.randomUUID().toString())); + assertNull(result); + // test for file credentials not exist + Path resourceDirectory = Paths.get("src", "test", "resources", "truststore","smp-truststore.jks"); + assertTrue(resourceDirectory.toFile().exists()); + doReturn(null).when(configurationService).getTruststoreCredentialToken(); + result = testInstance.loadTruststore(resourceDirectory.toFile()); + assertNull(result); } - } \ No newline at end of file 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 c6d61ec30e63f9ae66cf8021cb8664d32a8f0b5a..2827d35091e562a75263cf7061de2c789fae2d4d 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 @@ -2,27 +2,35 @@ package eu.europa.ec.edelivery.smp.services.ui; import eu.europa.ec.edelivery.smp.config.ConversionTestConfig; +import eu.europa.ec.edelivery.smp.data.dao.ServiceGroupDao; import eu.europa.ec.edelivery.smp.data.model.DBCertificate; +import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup; import eu.europa.ec.edelivery.smp.data.model.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.ServiceResult; -import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import eu.europa.ec.edelivery.smp.data.ui.*; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.services.AbstractServiceIntegrationTest; +import eu.europa.ec.edelivery.smp.testutil.TestConstants; import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; +import eu.europa.ec.edelivery.smp.testutil.TestROUtils; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; +import java.math.BigInteger; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.*; +import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*; import static org.junit.Assert.*; @@ -41,6 +49,10 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest protected UIUserService testInstance; + @Autowired + protected ServiceGroupDao serviceGroupDao; + + protected void insertDataObjects(int size) { for (int i = 0; i < size; i++) { DBUser d = TestDBUtils.createDBUserByUsername("user" + i); @@ -103,6 +115,8 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest user.setRole("ROLE"); user.setStatus(EntityROStatus.NEW.getStatusNumber()); + + //when testInstance.updateUserList(Collections.singletonList(user), null); @@ -296,4 +310,179 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest assertNotNull(result.getAccessTokenGeneratedOn()); } + @Test + public void testUpdateUserPasswordNotMatchReqExpression() { + long authorizedUserId = 1L; + long userToUpdateId = 1L; + String authorizedPassword = "testPass"; + String newPassword = "newPass"; + + SMPRuntimeException result = assertThrows(SMPRuntimeException.class, + () -> testInstance.updateUserPassword(authorizedUserId, userToUpdateId, authorizedPassword, newPassword)); + + MatcherAssert.assertThat(result.getMessage(), CoreMatchers.containsString("Invalid request [PasswordChange].")); + } + + @Test + public void testUpdateUserPasswordUserNotExists() { + + long authorizedUserId = 1L; + long userToUpdateId = 1L; + String authorizedPassword = "oldPass"; + String newPassword = "TTTTtttt1111$$$$$"; + + SMPRuntimeException result = assertThrows(SMPRuntimeException.class, + () -> testInstance.updateUserPassword(authorizedUserId, userToUpdateId, authorizedPassword, newPassword)); + + MatcherAssert.assertThat(result.getMessage(), CoreMatchers.containsString("Invalid request [UserId]. Error: Can not find user id!")); + } + + @Test + public void testUpdateUserPasswordUserNotAuthorized() { + String userPassword = UUID.randomUUID().toString(); + DBUser user = new DBUser(); + user.setPassword(BCrypt.hashpw(userPassword, BCrypt.gensalt())); + user.setUsername(UUID.randomUUID().toString()); + user.setEmailAddress(UUID.randomUUID().toString()); + user.setRole("ROLE"); + userDao.persistFlushDetach(user); + + long authorizedUserId = user.getId(); + long userToUpdateId = 1L; + String authorizedPassword = "oldPass"; + String newPassword = "TTTTtttt1111$$$$$"; + + BadCredentialsException result = assertThrows(BadCredentialsException.class, + () -> testInstance.updateUserPassword(authorizedUserId, userToUpdateId, authorizedPassword, newPassword)); + + MatcherAssert.assertThat(result.getMessage(), CoreMatchers.containsString("Password change failed; Invalid current password!")); + } + + @Test + public void testUpdateUserPasswordOK() { + String userPassword = UUID.randomUUID().toString(); + DBUser user = new DBUser(); + user.setPassword(BCrypt.hashpw(userPassword, BCrypt.gensalt())); + user.setUsername(UUID.randomUUID().toString()); + user.setEmailAddress(UUID.randomUUID().toString()); + user.setRole("ROLE"); + userDao.persistFlushDetach(user); + + long authorizedUserId = user.getId(); + long userToUpdateId = user.getId(); + String authorizedPassword = userPassword; + String newPassword = "TTTTtttt1111$$$$$"; + + testInstance.updateUserPassword(authorizedUserId, userToUpdateId, authorizedPassword, newPassword); + } + + @Test + public void testUpdateUserdataOK() { + String userPassword = UUID.randomUUID().toString(); + DBUser user = new DBUser(); + user.setPassword(BCrypt.hashpw(userPassword, BCrypt.gensalt())); + user.setUsername(UUID.randomUUID().toString()); + user.setEmailAddress(UUID.randomUUID().toString()); + user.setRole("ROLE"); + userDao.persistFlushDetach(user); + + UserRO userRO = new UserRO(); + userRO.setEmailAddress(UUID.randomUUID().toString()); + userRO.setUsername(UUID.randomUUID().toString()); + userRO.setAccessTokenId(UUID.randomUUID().toString()); + userRO.setRole(UUID.randomUUID().toString()); + + testInstance.updateUserdata(user.getId(), userRO); + + DBUser changedUser = userDao.findUser(user.getId()).get(); + // fields must not change + assertEquals(user.getUsername(), changedUser.getUsername()); + assertEquals(user.getAccessToken(), changedUser.getAccessToken()); + assertEquals(user.getRole(), changedUser.getRole()); + // changed + assertEquals(userRO.getEmailAddress(), changedUser.getEmailAddress()); + } + + @Test + public void testUpdateUserdataCertificateOK() throws Exception { + String certSubject = "CN=" + UUID.randomUUID().toString() + ",O=eDelivery,C=EU"; + String userPassword = UUID.randomUUID().toString(); + DBUser user = new DBUser(); + user.setPassword(BCrypt.hashpw(userPassword, BCrypt.gensalt())); + user.setUsername(UUID.randomUUID().toString()); + user.setEmailAddress(UUID.randomUUID().toString()); + user.setRole("ROLE"); + userDao.persistFlushDetach(user); + + CertificateRO certificateRO = TestROUtils.createCertificateRO(certSubject, BigInteger.TEN); + UserRO userRO = new UserRO(); + userRO.setCertificate(certificateRO); + + testInstance.updateUserdata(user.getId(), userRO); + + + DBUser changedUser = userDao.findUser(user.getId()).get(); + // fields must not change + assertNotNull(changedUser.getCertificate()); + assertNotNull(changedUser.getCertificate().getPemEncoding()); + assertNotNull(certificateRO.getCertificateId(), changedUser.getCertificate().getCertificateId()); + assertNotNull(certificateRO.getSubject(), changedUser.getCertificate().getSubject()); + assertNotNull(certificateRO.getIssuer(), changedUser.getCertificate().getIssuer()); + assertNotNull(certificateRO.getSerialNumber(), changedUser.getCertificate().getSerialNumber()); + } + + + @Test + public void testUpdateUserdataCertificateWithExistingCertificateOK() throws Exception { + String certSubject = "CN=" + UUID.randomUUID().toString() + ",O=eDelivery,C=EU"; + DBUser user = TestDBUtils.createDBUserByCertificate(TestConstants.USER_CERT_2); + userDao.persistFlushDetach(user); + + CertificateRO certificateRO = TestROUtils.createCertificateRO(certSubject, BigInteger.TEN); + UserRO userRO = new UserRO(); + userRO.setCertificate(certificateRO); + + testInstance.updateUserdata(user.getId(), userRO); + + + DBUser changedUser = userDao.findUser(user.getId()).get(); + // fields must not change + assertNotNull(changedUser.getCertificate()); + assertNotNull(changedUser.getCertificate().getPemEncoding()); + assertNotNull(certificateRO.getCertificateId(), changedUser.getCertificate().getCertificateId()); + assertNotNull(certificateRO.getSubject(), changedUser.getCertificate().getSubject()); + assertNotNull(certificateRO.getIssuer(), changedUser.getCertificate().getIssuer()); + assertNotNull(certificateRO.getSerialNumber(), changedUser.getCertificate().getSerialNumber()); + } + + @Test + public void testValidateDeleteRequest() throws Exception { + String username1 = "test-user-delete-01"; + String username2 = "test-user-delete-02"; + + DBUser user1 = TestDBUtils.createDBUser(username1); + DBUser user2 = TestDBUtils.createDBUser(username2); + userDao.persistFlushDetach(user1); + userDao.persistFlushDetach(user2); + + DBDomain d = new DBDomain(); + d.setDomainCode(TEST_DOMAIN_CODE_1); + d.setSmlSubdomain(TEST_SML_SUBDOMAIN_CODE_1); + domainDao.persistFlushDetach(d); + + DBServiceGroup sg = TestDBUtils.createDBServiceGroup(TEST_SG_ID_1, TEST_SG_SCHEMA_1); + sg.getUsers().add(user2); + sg.addDomain(d); + + serviceGroupDao.persistFlushDetach(sg); + DeleteEntityValidation validation = new DeleteEntityValidation(); + validation.getListIds().add(user1.getId()+""); + validation.getListIds().add(user2.getId()+""); + + DeleteEntityValidation result = testInstance.validateDeleteRequest(validation); + + assertEquals(1, result.getListDeleteNotPermitedIds().size()); + assertEquals(user2.getId()+"", result.getListDeleteNotPermitedIds().get(0)); + assertEquals(2, result.getListIds().size()); + } } 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 da792681aa0f5fb8f3f7498a0bdef495ebaa0336..a10bc9d5895cd2a28b7a8ab3a1319c457145e179 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 @@ -1,5 +1,6 @@ package eu.europa.ec.edelivery.smp.testutil; +import com.sun.org.apache.bcel.internal.generic.ARETURN; import eu.europa.ec.edelivery.smp.data.model.*; import eu.europa.ec.edelivery.smp.data.ui.enums.AlertLevelEnum; import eu.europa.ec.edelivery.smp.data.ui.enums.AlertStatusEnum; @@ -24,6 +25,27 @@ public class TestDBUtils { return domain; } + public static DBAlert createDBAlert(String username) { + return createDBAlert(username, "mail-subject", "mail.to@test.eu",AlertLevelEnum.MEDIUM, AlertTypeEnum.CREDENTIAL_IMMINENT_EXPIRATION); + } + + public static DBAlert createDBAlert(String username, String mailSubject, + String mailTo, + AlertLevelEnum level, + AlertTypeEnum alertType) { + DBAlert alert = new DBAlert(); + alert.setMailSubject(mailSubject); + alert.setMailTo(mailTo); + alert.setUsername(username); + alert.setReportingTime(OffsetDateTime.now()); + alert.setAlertType(alertType); + alert.setAlertLevel(level); + alert.setAlertStatus(AlertStatusEnum.PROCESS); + alert.addProperty("prop1", "propValue1"); + alert.addProperty("prop2", "propValue2"); + return alert; + } + public static DBDomain createDBDomain() { return createDBDomain(TestConstants.TEST_DOMAIN_CODE_1); } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java index f107f79b3875c7ef63ba42b37b311c32a2c849e0..c9158a142956c2335970373e4cbfcbc699cefc06 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java @@ -1,13 +1,13 @@ package eu.europa.ec.edelivery.smp.testutil; +import eu.europa.ec.edelivery.smp.conversion.X509CertificateToCertificateROConverter; import eu.europa.ec.edelivery.smp.data.model.DBDomain; -import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupDomainRO; -import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO; -import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupRO; -import eu.europa.ec.edelivery.smp.data.ui.ServiceMetadataRO; +import eu.europa.ec.edelivery.smp.data.ui.*; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; import java.io.IOException; +import java.math.BigInteger; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.UUID; @@ -17,18 +17,20 @@ import static eu.europa.ec.edelivery.smp.testutil.TestConstants.SIMPLE_EXTENSION public class TestROUtils { + public static final X509CertificateToCertificateROConverter CERT_CONVERTER = new X509CertificateToCertificateROConverter(); - public static ServiceMetadataRO createServiceMetadataDomain(DBDomain domain, ServiceGroupRO sgo, String docid, String docSch){ + + public static ServiceMetadataRO createServiceMetadataDomain(DBDomain domain, ServiceGroupRO sgo, String docid, String docSch) { ServiceMetadataRO sgdmd = new ServiceMetadataRO(); sgdmd.setDomainCode(domain.getDomainCode()); sgdmd.setSmlSubdomain(domain.getSmlSubdomain()); sgdmd.setDocumentIdentifier(docid); sgdmd.setDocumentIdentifierScheme(docSch); - sgdmd.setXmlContent(generateServiceMetadata(sgo.getParticipantIdentifier(), sgo.getParticipantScheme(), docid,docSch )); + sgdmd.setXmlContent(generateServiceMetadata(sgo.getParticipantIdentifier(), sgo.getParticipantScheme(), docid, docSch)); return sgdmd; } - public static ServiceGroupDomainRO createServiceGroupDomain(DBDomain domain){ + public static ServiceGroupDomainRO createServiceGroupDomain(DBDomain domain) { ServiceGroupDomainRO sgd = new ServiceGroupDomainRO(); sgd.setDomainId(domain.getId()); @@ -41,8 +43,8 @@ public class TestROUtils { return createROServiceGroup(TestConstants.TEST_SG_ID_1, TestConstants.TEST_SG_SCHEMA_1); } - public static ServiceGroupRO createROServiceGroupForDomains(DBDomain ... domains) { - ServiceGroupRO sgo = createROServiceGroup(TestConstants.TEST_SG_ID_1, TestConstants.TEST_SG_SCHEMA_1); + public static ServiceGroupRO createROServiceGroupForDomains(DBDomain... domains) { + ServiceGroupRO sgo = createROServiceGroup(TestConstants.TEST_SG_ID_1, TestConstants.TEST_SG_SCHEMA_1); Arrays.asList(domains).forEach(domain -> { ServiceGroupDomainRO sgd = createServiceGroupDomain(domain); sgo.getServiceGroupDomains().add(sgd); @@ -50,8 +52,8 @@ public class TestROUtils { return sgo; } - public static ServiceGroupRO createROServiceGroupForDomains(String id, String sch, DBDomain ... domains) { - ServiceGroupRO sgo = createROServiceGroup(id, sch); + public static ServiceGroupRO createROServiceGroupForDomains(String id, String sch, DBDomain... domains) { + ServiceGroupRO sgo = createROServiceGroup(id, sch); Arrays.asList(domains).forEach(domain -> { ServiceGroupDomainRO sgd = createServiceGroupDomain(domain); sgo.getServiceGroupDomains().add(sgd); @@ -75,12 +77,12 @@ public class TestROUtils { return grp; } - public static String generateExtension(){ + public static String generateExtension() { return String.format(SIMPLE_EXTENSION_XML, UUID.randomUUID().toString()); } - public static String generateServiceMetadata(String partId, String partSch, String docId, String docSch){ - return String.format(SIMPLE_DOCUMENT_XML, partSch, partId,docSch, docId, UUID.randomUUID().toString() ); + public static String generateServiceMetadata(String partId, String partSch, String docId, String docSch) { + return String.format(SIMPLE_DOCUMENT_XML, partSch, partId, docSch, docId, UUID.randomUUID().toString()); } public static ServiceGroupValidationRO getValidExtension() throws IOException { @@ -116,4 +118,10 @@ public class TestROUtils { sg.setExtension(extension); return sg; } + + + public static CertificateRO createCertificateRO(String certSubject, BigInteger serial) throws Exception { + X509Certificate cert = X509CertificateTestUtils.createX509CertificateForTest(certSubject, serial, null); + return CERT_CONVERTER.convert(cert); + } } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/X509CertificateTestUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/X509CertificateTestUtils.java index 570354e4e8e17e74b447a22d7c9205ffc57d25bc..f02a0a01ab44bb3bf332bc77491fb983c53923a1 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/X509CertificateTestUtils.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/X509CertificateTestUtils.java @@ -1,5 +1,6 @@ package eu.europa.ec.edelivery.smp.testutil; +import eu.europa.ec.edelivery.security.utils.X509CertificateUtils; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.cert.X509v3CertificateBuilder; @@ -14,6 +15,7 @@ import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.time.OffsetDateTime; import java.util.*; import java.util.stream.Collectors; @@ -77,40 +79,18 @@ public class X509CertificateTestUtils { return certs; } - /** - * Method generates certificate chain - * @param subjects - * @param certificatePoliciesOids - * @param startDate - * @param expiryDate - * @return - * @throws Exception - */ - public static X509Certificate[] createCertificateChain(String[] subjects, List<List<String>> certificatePoliciesOids, Date startDate, Date expiryDate) throws Exception { - String issuer = null; - PrivateKey issuerKey = null; - long iSerial = 10000; - X509Certificate[] certs = new X509Certificate[subjects.length]; - - int index = subjects.length; - for (String sbj: subjects){ - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - keyGen.initialize(1024); - KeyPair key = keyGen.generateKeyPair(); - - X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(new X500Name(issuer ==null? sbj:issuer), - BigInteger.valueOf(iSerial++), startDate, expiryDate, new X500Name(sbj), - SubjectPublicKeyInfo.getInstance(key.getPublic().getEncoded())); - - ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WITHRSA") - .setProvider("BC").build(issuerKey ==null?key.getPrivate():issuerKey); - certs[--index] = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(sigGen)); - issuer= sbj; - issuerKey = key.getPrivate(); + public static X509Certificate createX509CertificateForTest( String subject, BigInteger serial, List<String> listOfPolicyOIDs) throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair key = keyGen.generateKeyPair(); + KeyUsage usage = new KeyUsage(244); + X509Certificate cert = X509CertificateUtils.createCertificate(serial, + key.getPublic(), subject, OffsetDateTime.now().minusDays(1L), + OffsetDateTime.now().plusYears(5L), (String)null, + key.getPrivate(), false, -1, usage, "SHA256withRSA",listOfPolicyOIDs); - } - return certs; + return cert; } } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java index 71d0246229078ec2673e97dbb09d6306e6d68c03..8e800808121683a9a8fb73bffcdf00610a748e69 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java @@ -123,6 +123,25 @@ public class PropertyUtilsTest { } } + @Test + public void testSubjectRegExpLength() { + SMPRuntimeException result = assertThrows(SMPRuntimeException.class, () -> + PropertyUtils.isValidProperty(ALERT_USER_LOGIN_FAILURE_MAIL_SUBJECT, + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", ROOT_FOLDER)); + + assertEquals("Configuration error: Subject must have less than 256 character!", result.getMessage()); + } + + + @Test + public void testSubjectRegExpValue() { + SMPRuntimeException result = assertThrows(SMPRuntimeException.class, () -> + PropertyUtils.isValidProperty(ALERT_USER_SUSPENDED_LEVEL, + "value", ROOT_FOLDER)); + + assertEquals("Configuration error: Allowed values are: LOW, MEDIUM, HIGH!", result.getMessage()); + } + @Test @Parameters(method = "testTypeValues") diff --git a/smp-server-library/src/test/resources/cleanup-database.sql b/smp-server-library/src/test/resources/cleanup-database.sql index 95d7afdd477308e9e2462ef85d657703bc4a5053..0a97f57a19778de9207a2d279c68a6f9e2704937 100755 --- a/smp-server-library/src/test/resources/cleanup-database.sql +++ b/smp-server-library/src/test/resources/cleanup-database.sql @@ -9,6 +9,7 @@ DELETE FROM SMP_SERVICE_GROUP_AUD ; DELETE FROM SMP_DOMAIN_AUD; DELETE FROM SMP_CERTIFICATE_AUD ; DELETE FROM SMP_USER_AUD; +DELETE FROM SMP_ALERT_PROPERTY_AUD; DELETE FROM SMP_ALERT_AUD; DELETE FROM SMP_REV_INFO; @@ -23,6 +24,7 @@ DELETE FROM SMP_SERVICE_GROUP; DELETE FROM SMP_DOMAIN; DELETE FROM SMP_CERTIFICATE; DELETE FROM SMP_USER; +DELETE FROM SMP_ALERT_PROPERTY; DELETE FROM SMP_ALERT; DELETE FROM SMP_OWNERSHIP; 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 b789020d40624ff61dd40a443cbb0972ca5a4661..91b5e02d363feb580b93b62aa4f2f166cf843c9b 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 @@ -1,7 +1,7 @@ insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (1, 'peppol_user@test-mail.eu','peppol_user', '$2a$10$.pqNZZ4fRDdNbLhNlnEYg.1/d4yAGpLDgeXpJFI0sw7.WtyKphFzu','peppol_user', '$2a$10$.pqNZZ4fRDdNbLhNlnEYg.1/d4yAGpLDgeXpJFI0sw7.WtyKphFzu', 'SMP_ADMIN', 1, NOW(), NOW()); insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (2, 'the_admin@test-mail.eu','the_admin', '','the_admin', '', 'SMP_ADMIN', 1, NOW(), NOW()); -insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (3, 'AdminSMP1TEST@test-mail.eu','AdminSMP1TEST', '$2a$06$u6Hym7Zrbsf4gEIeAsJRceK.Kg7tei3kDypwucQQdky0lXOLCkrCO','AdminSMP1TEST', '$2a$06$u6Hym7Zrbsf4gEIeAsJRceK.Kg7tei3kDypwucQQdky0lXOLCkrCO', 'SMP_ADMIN', 1, NOW(), NOW()); -insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (4, 'AdminSMP2TEST@test-mail.eu','AdminSMP2TEST', '$2a$10$h8Q3Kjbs6ZrGkU6ditjNueINlJOMDJ/g/OKiqFZy32WmdhLjV5TAi','AdminSMP2TEST', '$2a$10$h8Q3Kjbs6ZrGkU6ditjNueINlJOMDJ/g/OKiqFZy32WmdhLjV5TAi', 'SMP_ADMIN', 1, NOW(), NOW()); +insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (3, 'AdminSMP1TEST@test-mail.eu','AdminSMP1TEST', '$2a$06$u6Hym7Zrbsf4gEIeAsJRceK.Kg7tei3kDypwucQQdky0lXOLCkrCO','LvglqPCs', '$2a$10$zaFAFqFIfLUZx15ZDPMvDeWBtsZLaAkrY3Vmya5e3/yCCkFq/FJCu', 'SMP_ADMIN', 1, NOW(), NOW()); +insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (4, 'AdminSMP2TEST@test-mail.eu','AdminSMP2TEST', '$2a$10$h8Q3Kjbs6ZrGkU6ditjNueINlJOMDJ/g/OKiqFZy32WmdhLjV5TAi','VIhnrCJK', '$2a$10$BtInQBIycY2BSN28PD7TxO9ipAR3lhxUT2FLeShptGmjt6HaLpR7O', 'SMP_ADMIN', 1, NOW(), NOW()); insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (5, 'test@test-mail.eu','test', '','test', '', 'SMP_ADMIN', 1, NOW(), NOW()); insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (6, 'test1@test-mail.eu','test1', '$2a$06$toKXJgjqQINZdjQqSao3NeWz2n1S64PFPhVU1e8gIHh4xdbwzy1Uy','test1', '$2a$06$toKXJgjqQINZdjQqSao3NeWz2n1S64PFPhVU1e8gIHh4xdbwzy1Uy', 'SMP_ADMIN', 1, NOW(), NOW()); insert into SMP_USER (ID, EMAIL, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (7, 'system@test-mail.eu','system', '$2a$06$FDmjewn/do3C219uysNm9.XG8mIn.ubHnMydAzC8lsv61HsRpOR36','system', '$2a$06$FDmjewn/do3C219uysNm9.XG8mIn.ubHnMydAzC8lsv61HsRpOR36', 'SYSTEM_ADMIN', 1, NOW(), NOW()); diff --git a/smp-soapui-tests/groovy/oracle-4.1_integration_test_data.sql b/smp-soapui-tests/groovy/oracle-4.1_integration_test_data.sql index 465a0284734abf30168887fc01a2c8f1ee88dae3..dbad27545bc58a4a4ef620529aa870caded1c048 100644 --- a/smp-soapui-tests/groovy/oracle-4.1_integration_test_data.sql +++ b/smp-soapui-tests/groovy/oracle-4.1_integration_test_data.sql @@ -24,11 +24,10 @@ DELETE FROM SMP_OWNERSHIP; set define off; - insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'peppol_user', '$2a$10$.pqNZZ4fRDdNbLhNlnEYg.1/d4yAGpLDgeXpJFI0sw7.WtyKphFzu','peppol_user', '$2a$10$.pqNZZ4fRDdNbLhNlnEYg.1/d4yAGpLDgeXpJFI0sw7.WtyKphFzu', 'SMP_ADMIN', 1, sysdate, sysdate); insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'the_admin', '','the_admin', '', 'SMP_ADMIN', 1, sysdate, sysdate); -insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'AdminSMP1TEST', '$2a$06$u6Hym7Zrbsf4gEIeAsJRceK.Kg7tei3kDypwucQQdky0lXOLCkrCO','AdminSMP1TEST', '$2a$06$u6Hym7Zrbsf4gEIeAsJRceK.Kg7tei3kDypwucQQdky0lXOLCkrCO', 'SMP_ADMIN', 1, sysdate, sysdate); -insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'AdminSMP2TEST', '$2a$10$h8Q3Kjbs6ZrGkU6ditjNueINlJOMDJ/g/OKiqFZy32WmdhLjV5TAi','AdminSMP2TEST', '$2a$10$h8Q3Kjbs6ZrGkU6ditjNueINlJOMDJ/g/OKiqFZy32WmdhLjV5TAi', 'SMP_ADMIN', 1, sysdate, sysdate); +insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'AdminSMP1TEST', '$2a$06$u6Hym7Zrbsf4gEIeAsJRceK.Kg7tei3kDypwucQQdky0lXOLCkrCO','LvglqPCs', '$2a$10$zaFAFqFIfLUZx15ZDPMvDeWBtsZLaAkrY3Vmya5e3/yCCkFq/FJCu', 'SMP_ADMIN', 1, sysdate, sysdate); +insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'AdminSMP2TEST', '$2a$10$h8Q3Kjbs6ZrGkU6ditjNueINlJOMDJ/g/OKiqFZy32WmdhLjV5TAi','VIhnrCJK', '$2a$10$BtInQBIycY2BSN28PD7TxO9ipAR3lhxUT2FLeShptGmjt6HaLpR7O', 'SMP_ADMIN', 1, sysdate, sysdate); insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'test', '','test', '', 'SMP_ADMIN', 1, sysdate, sysdate); insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'test1', '$2a$06$toKXJgjqQINZdjQqSao3NeWz2n1S64PFPhVU1e8gIHh4xdbwzy1Uy','test1', '$2a$06$toKXJgjqQINZdjQqSao3NeWz2n1S64PFPhVU1e8gIHh4xdbwzy1Uy', 'SMP_ADMIN', 1, sysdate, sysdate); insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (SMP_USER_SEQ.nextval, 'system', '$2a$06$FDmjewn/do3C219uysNm9.XG8mIn.ubHnMydAzC8lsv61HsRpOR36','system', '$2a$06$FDmjewn/do3C219uysNm9.XG8mIn.ubHnMydAzC8lsv61HsRpOR36', 'SYSTEM_ADMIN', 1, sysdate, sysdate); diff --git a/smp-soapui-tests/pom.xml b/smp-soapui-tests/pom.xml index ff1030e00c3392117afa11de95df9bce83e6bb54..dbe99490dfefb24f827f5b380efaf14d016c395e 100644 --- a/smp-soapui-tests/pom.xml +++ b/smp-soapui-tests/pom.xml @@ -9,7 +9,7 @@ <artifactId>smp-soapui-tests</artifactId> <packaging>jar</packaging> <name>smp-soapui-tests</name> - <description>Interation tests suit for SMP</description> + <description>Interaction tests suit for SMP</description> <properties> <!-- Only selected modules are deployed --> @@ -43,7 +43,7 @@ </property> <property> <name>soapui.logroot</name> - <value>${sonar.jacoco.codeCoveragePath}/soapui/logs/</value> + <value>${project.build.directory}/soapui/logs/</value> </property> <property> <name>http.nonProxyHosts</name> @@ -128,7 +128,7 @@ <version>${jacoco.maven.plugin.version}</version> <configuration> - <classDumpDir>${project.build.outputDirectory}../target/</classDumpDir> + <classDumpDir>${project.build.outputDirectory}</classDumpDir> </configuration> <executions> <execution> 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 c0b1e1a1435e6e7339ac4770f8f6f065589134ef..2e92befd3711ba0768e459579c185dbb3ca82d69 100644 --- a/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml +++ b/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml @@ -187,7 +187,7 @@ </con:parameters><con:parameterOrder><con:entry>ParticipantIdentifierScheme</con:entry><con:entry>ParticipantIdentifier</con:entry></con:parameterOrder></con:request><con:request name="Simple Request with reference params" id="803032c1-cc3a-49c0-98d8-ac48dc1ebdc5" mediaType="application/json"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting></con:settings><con:endpoint>${#Project#url}</con:endpoint><con:request/><con:credentials><con:authType>No Authorization</con:authType></con:credentials><con:jmsConfig JMSDeliveryMode="PERSISTENT"/><con:jmsPropertyConfig/><con:parameters> <con:entry key="ParticipantIdentifierScheme" value="${Put ServiceGroup#ParticipantIdentifierScheme}"/> <con:entry key="ParticipantIdentifier" value="${Put ServiceGroup#ParticipantIdentifier}"/> -</con:parameters><con:parameterOrder><con:entry>ParticipantIdentifierScheme</con:entry><con:entry>ParticipantIdentifier</con:entry></con:parameterOrder></con:request></con:method><con:method name="PUT ServiceGroup" id="57d76dab-afb8-4f0e-9a71-3c95150e3ceb" method="PUT"><con:settings/><con:parameters/><con:representation type="REQUEST"><con:mediaType>text/xml</con:mediaType><con:params/><con:element xmlns:ns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05">ns:ServiceGroup</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/xml;charset=UTF-8</con:mediaType><con:status>401 400 500 404</con:status><con:params/><con:element xmlns:ec="ec:services:SMP:1.0">ec:ErrorResponse</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/html</con:mediaType><con:status>401</con:status><con:params/><con:element xmlns:ec="ec:services:SMP:1.0">ec:ErrorResponse</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/html;charset=utf-8</con:mediaType><con:status>403 401</con:status><con:params/><con:element>html</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/xml</con:mediaType><con:status>400 500</con:status><con:params/><con:element xmlns:ec="ec:services:SMP:1.0">ec:ErrorResponse</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType xsi:nil="true"/><con:status>400</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType xsi:nil="true"/><con:status>400</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType xsi:nil="true"/><con:status>400</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/html; charset=utf-8</con:mediaType><con:status>503</con:status><con:params/><con:element>html</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:request name="Simple Request" id="803032c1-cc3a-49c0-98d8-ac48dc1ebdc5" mediaType="text/xml" postQueryString="false"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting></con:settings><con:endpoint>${#Project#url}</con:endpoint><con:request><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?> +</con:parameters><con:parameterOrder><con:entry>ParticipantIdentifierScheme</con:entry><con:entry>ParticipantIdentifier</con:entry></con:parameterOrder></con:request></con:method><con:method name="PUT ServiceGroup" id="57d76dab-afb8-4f0e-9a71-3c95150e3ceb" method="PUT"><con:settings/><con:parameters/><con:representation type="REQUEST"><con:mediaType>text/xml</con:mediaType><con:params/><con:element xmlns:ns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05">ns:ServiceGroup</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/xml;charset=UTF-8</con:mediaType><con:status>401 400 500 404</con:status><con:params/><con:element xmlns:ec="ec:services:SMP:1.0">ec:ErrorResponse</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/html</con:mediaType><con:status>401</con:status><con:params/><con:element xmlns:ec="ec:services:SMP:1.0">ec:ErrorResponse</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/html;charset=utf-8</con:mediaType><con:status>403 401 404</con:status><con:params/><con:element>html</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/xml</con:mediaType><con:status>400 500</con:status><con:params/><con:element xmlns:ec="ec:services:SMP:1.0">ec:ErrorResponse</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType xsi:nil="true"/><con:status>400</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType xsi:nil="true"/><con:status>400</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType xsi:nil="true"/><con:status>400</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="FAULT"><con:mediaType>text/html; charset=utf-8</con:mediaType><con:status>503</con:status><con:params/><con:element>html</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>200</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>201</con:status><con:params/><con:element>data</con:element></con:representation><con:representation type="RESPONSE"><con:mediaType xsi:nil="true"/><con:status>0</con:status><con:params/><con:element>data</con:element></con:representation><con:request name="Simple Request" id="803032c1-cc3a-49c0-98d8-ac48dc1ebdc5" mediaType="text/xml" postQueryString="false"><con:settings><con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting></con:settings><con:endpoint>${#Project#url}</con:endpoint><con:request><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?> <ServiceGroup xmlns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05"> <ParticipantIdentifier scheme="${=request.getProperty('ParticipantIdentifierScheme').getValue()}">${=request.getProperty('ParticipantIdentifier').getValue()}</ParticipantIdentifier> <ServiceMetadataReferenceCollection/> @@ -8402,4 +8402,4 @@ test.finalize();</scriptText></con:configuration></con:assertion><con:assertion </con:parameters></con:restRequest></con:config></con:testStep><con:tearDownScript>// Run clean test steps. testRunner.testCase.testSteps['Delete ServiceMetadata'].run(testRunner, context); testRunner.testCase.testSteps['Delete ServiceGroup'].run(testRunner, context); -//ExcelReporting.reportTestCase(testRunner, log)</con:tearDownScript><con:properties/><con:reportParameters/><con:breakPoints><con:testStepId>cda74952-fe52-42df-8643-8a59932a76f9</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints><con:breakPoints><con:testStepId>e84b7e54-b24e-491a-95b2-a12ff29eb5cc</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints><con:breakPoints><con:testStepId>46350f0e-d28d-4ed8-9a45-06d697f21192</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints><con:breakPoints><con:testStepId>3e085bb5-d0a1-4264-8e84-74bc84466c0e</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints></con:testCase><con:properties/><con:reportParameters/></con:testSuite><con:testSuite id="f2e20764-c52e-404a-87d9-65ac638ff0a9" name="DEV_BAMBOO_CANDIDATES"><con:settings/><con:runType>SEQUENTIAL</con:runType><con:properties/></con:testSuite><con:requirements/><con:properties><con:property><con:name>url</con:name><con:value>http://localhost:8982/smp</con:value></con:property><con:property><con:name>reportFilePath</con:name><con:value>C:\\ec\\soapui\\reports\\SMP_TEST_4.xlsx</con:value></con:property><con:property><con:name>updateReport</con:name><con:value>false</con:value></con:property><con:property><con:name>urlExt</con:name><con:value>https://edeltest5.westeurope.cloudapp.azure.com:8443/smp</con:value></con:property><con:property><con:name>defaultParticipantIdentifierScheme</con:name><con:value>ehealth-actorid-qns</con:value></con:property><con:property><con:name>defaultParticipantIdentifier</con:name><con:value>0088:7770010100777:test</con:value></con:property><con:property><con:name>defaultDocTypeIdentifierScheme</con:name><con:value>busdox-docid-qns</con:value></con:property><con:property><con:name>defaultDocTypeIdentifier</con:name><con:value>urn:oasis:names:specification:ubl:schema:xsd:Invoice-12::Invoice##urn:www.cenbii.eu:transaction:biicoretrdm010:ver1.0:#urn:www.peppol.eu:bis:peppol4a:ver1.0::2.0</con:value></con:property><con:property><con:name>secondDefaultParticipantIdentifierScheme</con:name><con:value>iso6523-actorid-upis</con:value></con:property><con:property><con:name>secondDefaultParticipantIdentifier</con:name><con:value>0088:777002abzz777:test</con:value></con:property><con:property><con:name>secondDefaultDocTypeIdentifierScheme</con:name><con:value>busdox-docid-qns</con:value></con:property><con:property><con:name>secondDefaultDocTypeIdentifier</con:name><con:value>urn:oasis:names:specification:ubl:schema:xsd:Invoice-001::Invoice##UBL-2.0</con:value></con:property><con:property><con:name>defaultDomainName</con:name><con:value>domain</con:value></con:property><con:property><con:name>testWithMultipleDomain</con:name><con:value>false</con:value></con:property><con:property><con:name>testDB</con:name><con:value>false</con:value></con:property><con:property><con:name>jdbc.url</con:name><con:value>jdbc:oracle:thin:@localhost:51521/xe</con:value></con:property><con:property><con:name>jdbc.driver</con:name><con:value>oracle.jdbc.OracleDriver</con:value></con:property><con:property><con:name>dbUser</con:name><con:value>user</con:value></con:property><con:property><con:name>dbPassword</con:name><con:value>password</con:value></con:property><con:property><con:name>adminSmp1Test</con:name><con:value>AdminSMP1TEST</con:value></con:property><con:property><con:name>adminSmp1TestPassword</con:name><con:value>adminsmp1test</con:value></con:property><con:property><con:name>adminSmp2Test</con:name><con:value>AdminSMP2TEST</con:value></con:property><con:property><con:name>adminSmp2TestPassword</con:name><con:value>adminsmp2test</con:value></con:property><con:property><con:name>ebCoreISO6523ParticipantIdentifierScheme</con:name><con:value>urn:oasis:names:tc:ebcore:partyid-type:iso6523:0088</con:value></con:property><con:property><con:name>ebCoreUnregisteredParticipantIdentifierScheme</con:name><con:value>urn:oasis:names:tc:ebcore:partyid-type:unregistered</con:value></con:property><con:property><con:name>ebCoreUnregisteredWithCatalogParticipantIdentifierScheme</con:name><con:value>urn:oasis:names:tc:ebcore:partyid-type:unregistered:domain</con:value></con:property><con:property><con:name>ebCoreISO6523ParticipantIdentifier</con:name><con:value>7770010100777</con:value></con:property></con:properties><con:wssContainer/><con:databaseConnectionContainer/><con:oAuth2ProfileContainer/><con:oAuth1ProfileContainer/><con:reporting><con:xmlTemplates/><con:parameters/></con:reporting><con:sensitiveInformation/></con:soapui-project> \ No newline at end of file +//ExcelReporting.reportTestCase(testRunner, log)</con:tearDownScript><con:properties/><con:reportParameters/><con:breakPoints><con:testStepId>cda74952-fe52-42df-8643-8a59932a76f9</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints><con:breakPoints><con:testStepId>e84b7e54-b24e-491a-95b2-a12ff29eb5cc</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints><con:breakPoints><con:testStepId>46350f0e-d28d-4ed8-9a45-06d697f21192</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints><con:breakPoints><con:testStepId>3e085bb5-d0a1-4264-8e84-74bc84466c0e</con:testStepId><con:status>NONE</con:status><con:properties/></con:breakPoints></con:testCase><con:properties/><con:reportParameters/></con:testSuite><con:testSuite id="f2e20764-c52e-404a-87d9-65ac638ff0a9" name="DEV_BAMBOO_CANDIDATES"><con:settings/><con:runType>SEQUENTIAL</con:runType><con:properties/></con:testSuite><con:requirements/><con:properties><con:property><con:name>url</con:name><con:value>http://localhost:8080/smp</con:value></con:property><con:property><con:name>reportFilePath</con:name><con:value>C:\\ec\\soapui\\reports\\SMP_TEST_4.xlsx</con:value></con:property><con:property><con:name>updateReport</con:name><con:value>false</con:value></con:property><con:property><con:name>urlExt</con:name><con:value>https://edeltest5.westeurope.cloudapp.azure.com:8443/smp</con:value></con:property><con:property><con:name>defaultParticipantIdentifierScheme</con:name><con:value>ehealth-actorid-qns</con:value></con:property><con:property><con:name>defaultParticipantIdentifier</con:name><con:value>0088:7770010100777:test</con:value></con:property><con:property><con:name>defaultDocTypeIdentifierScheme</con:name><con:value>busdox-docid-qns</con:value></con:property><con:property><con:name>defaultDocTypeIdentifier</con:name><con:value>urn:oasis:names:specification:ubl:schema:xsd:Invoice-12::Invoice##urn:www.cenbii.eu:transaction:biicoretrdm010:ver1.0:#urn:www.peppol.eu:bis:peppol4a:ver1.0::2.0</con:value></con:property><con:property><con:name>secondDefaultParticipantIdentifierScheme</con:name><con:value>iso6523-actorid-upis</con:value></con:property><con:property><con:name>secondDefaultParticipantIdentifier</con:name><con:value>0088:777002abzz777:test</con:value></con:property><con:property><con:name>secondDefaultDocTypeIdentifierScheme</con:name><con:value>busdox-docid-qns</con:value></con:property><con:property><con:name>secondDefaultDocTypeIdentifier</con:name><con:value>urn:oasis:names:specification:ubl:schema:xsd:Invoice-001::Invoice##UBL-2.0</con:value></con:property><con:property><con:name>defaultDomainName</con:name><con:value>domain</con:value></con:property><con:property><con:name>testWithMultipleDomain</con:name><con:value>false</con:value></con:property><con:property><con:name>testDB</con:name><con:value>false</con:value></con:property><con:property><con:name>jdbc.url</con:name><con:value>jdbc:oracle:thin:@localhost:51521/xe</con:value></con:property><con:property><con:name>jdbc.driver</con:name><con:value>oracle.jdbc.OracleDriver</con:value></con:property><con:property><con:name>dbUser</con:name><con:value>user</con:value></con:property><con:property><con:name>dbPassword</con:name><con:value>password</con:value></con:property><con:property><con:name>adminSmp1Test</con:name><con:value>LvglqPCs</con:value></con:property><con:property><con:name>adminSmp1TestPassword</con:name><con:value>>siz.7#D)a;Pi/v.</con:value></con:property><con:property><con:name>adminSmp2Test</con:name><con:value>VIhnrCJK</con:value></con:property><con:property><con:name>adminSmp2TestPassword</con:name><con:value>Gp[JdVPUt],+j-o|</con:value></con:property><con:property><con:name>ebCoreISO6523ParticipantIdentifierScheme</con:name><con:value>urn:oasis:names:tc:ebcore:partyid-type:iso6523:0088</con:value></con:property><con:property><con:name>ebCoreUnregisteredParticipantIdentifierScheme</con:name><con:value>urn:oasis:names:tc:ebcore:partyid-type:unregistered</con:value></con:property><con:property><con:name>ebCoreUnregisteredWithCatalogParticipantIdentifierScheme</con:name><con:value>urn:oasis:names:tc:ebcore:partyid-type:unregistered:domain</con:value></con:property><con:property><con:name>ebCoreISO6523ParticipantIdentifier</con:name><con:value>7770010100777</con:value></con:property></con:properties><con:wssContainer/><con:databaseConnectionContainer/><con:oAuth2ProfileContainer/><con:oAuth1ProfileContainer/><con:reporting><con:xmlTemplates/><con:parameters/></con:reporting><con:sensitiveInformation/></con:soapui-project> \ No newline at end of file diff --git a/smp-ui-tests/src/main/java/utils/enums/SMPMessages.java b/smp-ui-tests/src/main/java/utils/enums/SMPMessages.java index ff8ab31f60e136eded819f09e4f89b86a295f13b..89da050ed737a5991f63c9d57b1723eb7e1d2eef 100644 --- a/smp-ui-tests/src/main/java/utils/enums/SMPMessages.java +++ b/smp-ui-tests/src/main/java/utils/enums/SMPMessages.java @@ -68,7 +68,7 @@ public class SMPMessages { public static final String MSG_21 = "Unable to login. SMP is not running."; - public static final String USERNAME_VALIDATION_MESSAGE = "Username can only contain alphanumeric characters (letters A-Z, numbers 0-9) and must have from 4 to 32 characters!"; + public static final String USERNAME_VALIDATION_MESSAGE = "Username is case insensitive and can only contain alphanumeric characters (letters a-zA-Z, numbers 0-9) and must have from 4 to 32 characters!"; public static final String PASS_POLICY_MESSAGE = "Password should follow all of these rules:\n" + "- Minimum length: 8 characters\n" + "- Maximum length: 32 characters\n" + 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 f62e036dfd5fe9540a5a88a6ce6fc00e7188c94d..7304a576d7668a9de43c826a146fff692d0d6437 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,7 +14,6 @@ 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; /** @@ -51,7 +50,9 @@ 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.stream().map(auth->auth.getAuthority()).toArray()); + LOG.debug("Set session idle timeout [{}] for user [{}] with roles [{}]", + idleTimeout, event.getAuthentication().getName(), + authorities.stream().map(GrantedAuthority::getAuthority).toArray()); session.setMaxInactiveInterval(idleTimeout); } else { LOG.warn("Could not get ServletRequestAttributes attributes for authentication [{}]", event.getAuthentication()); @@ -61,9 +62,9 @@ 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_WS_SYSTEM_ADMIN.getAuthority()) - || StringUtils.equalsIgnoreCase(grantedAuthority.getAuthority(), SMPAuthority.S_AUTHORITY_WS_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 admin role [{}]", hasAdminRole); LOG.debug("configurationService [{}]", configurationService); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java index fc4fa5f2d242767a639ea4433e5b29446528e6ea..bc13a886c32428c532f11ead77adb4ffc6efbb70 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java @@ -1,11 +1,9 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.security.PreAuthenticatedCertificatePrincipal; -import eu.europa.ec.edelivery.security.cert.CertificateValidator; import eu.europa.ec.edelivery.smp.data.dao.UserDao; 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.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.ui.enums.AlertSuspensionMomentEnum; import eu.europa.ec.edelivery.smp.data.ui.enums.CredentialTypeEnum; @@ -29,7 +27,6 @@ import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.stereotype.Component; -import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.CertificateRevokedException; import java.security.cert.X509Certificate; @@ -55,7 +52,9 @@ import static java.util.Locale.US; @Component public class SMPAuthenticationProvider implements AuthenticationProvider { - public static final String LOGIN_FAILED_MESSAGE = "Login failed; Invalid userID or password"; + protected static final BadCredentialsException BAD_CREDENTIALS_EXCEPTION = new BadCredentialsException("Login failed; Invalid userID or password"); + protected static final BadCredentialsException SUSPENDED_CREDENTIALS_EXCEPTION = new BadCredentialsException("The user is suspended. Please try again later or contact your administrator."); + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPAuthenticationProvider.class); /** @@ -99,9 +98,9 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { LOG.warn("Unknown or null PreAuthenticatedAuthenticationToken principal type: " + principal); } } else if (authenticationToken instanceof UsernamePasswordAuthenticationToken) { - LOG.info("try to authentication Token: [{}] with user:[{}]" , authenticationToken.getClass(), authenticationToken.getPrincipal()); - if (CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER.equalsIgnoreCase((String)authenticationToken.getPrincipal()) - || CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equalsIgnoreCase((String)authenticationToken.getPrincipal())){ + LOG.info("try to authentication Token: [{}] with user:[{}]", authenticationToken.getClass(), authenticationToken.getPrincipal()); + if (CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER.equalsIgnoreCase((String) authenticationToken.getPrincipal()) + || CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equalsIgnoreCase((String) authenticationToken.getPrincipal())) { LOG.debug("Ignore CAS authentication and leave it to cas authentication module"); return null; } @@ -127,29 +126,31 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { */ public Authentication authenticateByCertificateToken(PreAuthenticatedCertificatePrincipal principal) { LOG.info("authenticateByCertificateToken:" + principal.getName()); - KeyStore truststore = truststoreService.getTrustStore(); - DBUser user; X509Certificate x509Certificate = principal.getCertificate(); String userToken = principal.getName(); + long startTime = Calendar.getInstance().getTimeInMillis(); + - if (truststore != null && x509Certificate != null) { - CertificateValidator certificateValidator = new CertificateValidator( - null, truststore, null); + if (x509Certificate != null) { try { - certificateValidator.validateCertificate(x509Certificate); + truststoreService.validateCertificateWithTruststore(x509Certificate); } catch (CertificateException e) { - throw new BadCredentialsException("Certificate is not trusted!"); + String message = "Certificate is not trusted!"; + LOG.securityWarn(SMPMessageCode.SEC_USER_CERT_INVALID, userToken, message + + " The cert chain is not in truststore or either subject regexp or allowed cert policies does not match"); + throw new BadCredentialsException(message); } } try { Optional<DBUser> oUsr = mUserDao.findUserByCertificateId(userToken, true); - if (!oUsr.isPresent()) { + if (!oUsr.isPresent() || !oUsr.get().isActive() ) { LOG.securityWarn(SMPMessageCode.SEC_USER_NOT_EXISTS, userToken); //https://www.owasp.org/index.php/Authentication_Cheat_Sheet // Do not reveal the status of an existing account. Not to use UsernameNotFoundException - throw new BadCredentialsException(LOGIN_FAILED_MESSAGE); + delayResponse(startTime); + throw BAD_CREDENTIALS_EXCEPTION; } user = oUsr.get(); } catch (AuthenticationException ex) { @@ -163,6 +164,7 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { DBCertificate certificate = user.getCertificate(); // check if certificate is valid Date currentDate = Calendar.getInstance().getTime(); + // this is legacy code because some setups does not have truststore configured // validate dates if (principal.getNotBefore() == null) { String msg = "Invalid certificate configuration: 'Not Before' value is missing!"; @@ -181,6 +183,7 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { LOG.securityWarn(SMPMessageCode.SEC_USER_CERT_INVALID, userToken, msg); throw new AuthenticationServiceException(msg); } + // check if issuer or subject are in trusted list if (!(truststoreService.isSubjectOnTrustedList(principal.getSubjectOriginalDN()) || truststoreService.isSubjectOnTrustedList(principal.getIssuerDN()))) { @@ -189,6 +192,8 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { LOG.securityWarn(SMPMessageCode.SEC_USER_CERT_INVALID, userToken, msg); throw new AuthenticationServiceException(msg); } + + validateCertificatePolicyMatchLegacy(userToken, principal.getPolicyOids()); // Check crl list String url = certificate.getCrlUrl(); if (!StringUtils.isBlank(url)) { @@ -216,8 +221,39 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { } + /** + * Method validates if the certificate contains one of allowed Certificate policy. At the moment it does not validates + * the whole chain. Because in some configuration cases does not use the truststore + * + * @param certificateId + * @throws CertificateException + */ + protected void validateCertificatePolicyMatchLegacy(String certificateId, List<String> certPolicyList) throws AuthenticationServiceException { + + // allowed list + List<String> allowedCertificatePolicyOIDList = configurationService.getAllowedCertificatePolicies(); + if (allowedCertificatePolicyOIDList == null || allowedCertificatePolicyOIDList.isEmpty()) { + LOG.debug("Certificate policy is not configured. Skip Certificate policy validation!"); + return; + } + // certificate list + if (certPolicyList.isEmpty()) { + String excMessage = String.format("Certificate [%] does not have CertificatePolicy extension.", certificateId); + throw new AuthenticationServiceException(excMessage); + } + + Optional<String> result = certPolicyList.stream().filter(allowedCertificatePolicyOIDList::contains).findFirst(); + if (result.isPresent()) { + LOG.debug("Certificate [{}] is trusted with certificate policy [{}]",certificateId, result.get()); + return; + } + String excMessage = String.format("Certificate policy verification failed. Certificate [%s] does not contain any of the mandatory policy: [%s]", certificateId, allowedCertificatePolicyOIDList); + throw new AuthenticationServiceException(excMessage); + } + + public void delayResponse(long startTime) { - int delayInMS = configurationService.getAccessTokenLoginFailDelayInMilliSeconds() - (int) (Calendar.getInstance().getTimeInMillis() - startTime); + int delayInMS = configurationService.getAccessTokenLoginFailDelayInMilliSeconds() - (int) (Calendar.getInstance().getTimeInMillis() - startTime); if (delayInMS > 0) { try { LOG.debug("Delay response for [{}] ms to mask password/username login failures!", delayInMS); @@ -234,9 +270,9 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { * * @param user */ - public void validateIfTokenIsSuspended(DBUser user) { + public void validateIfTokenIsSuspended(DBUser user, long startTime) { if (user.getSequentialTokenLoginFailureCount() == null - || user.getSequentialTokenLoginFailureCount() < 0) { + || user.getSequentialTokenLoginFailureCount() < 1) { LOG.trace("User has no previous failed attempts"); return; } @@ -247,14 +283,17 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { } if (user.getLastTokenFailedLoginAttempt() == null) { - LOG.warn("Access token [{}] has failed attempts [{}] but null last Failed login attempt!", user.getUsername(), user.getLastFailedLoginAttempt()); + LOG.warn("Access token [{}] for user [{}] has failed attempts [{}] but null last Failed login attempt!", + user.getAccessTokenIdentifier(), user.getUsername(), user.getLastFailedLoginAttempt()); return; } // check if the last failed attempt is already expired. If yes just clear the attempts - if (configurationService.getAccessTokenLoginSuspensionTimeInSeconds() != null && configurationService.getAccessTokenLoginSuspensionTimeInSeconds() > 0 - && ChronoUnit.SECONDS.between(OffsetDateTime.now(), user.getLastTokenFailedLoginAttempt()) > configurationService.getAccessTokenLoginSuspensionTimeInSeconds()) { - LOG.warn("User [{}] suspension is expired! Clear failed login attempts and last failed login attempt", user.getUsername()); + if (configurationService.getAccessTokenLoginSuspensionTimeInSeconds() != null + && configurationService.getAccessTokenLoginSuspensionTimeInSeconds() > 0 + && ChronoUnit.SECONDS.between(user.getLastTokenFailedLoginAttempt(), OffsetDateTime.now()) > configurationService.getAccessTokenLoginSuspensionTimeInSeconds()) { + LOG.info("User token [{}] for user [{}] suspension is expired! Clear failed login attempts and last failed login attempt", + user.getAccessTokenIdentifier(), user.getUsername()); user.setLastTokenFailedLoginAttempt(null); user.setSequentialTokenLoginFailureCount(0); mUserDao.update(user); @@ -262,20 +301,21 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { } if (user.getSequentialTokenLoginFailureCount() < configurationService.getAccessTokenLoginMaxAttempts()) { - LOG.warn("User [{}] failed login attempt [{}]! did not reach the max failed attempts [{}]", user.getUsername(), user.getSequentialTokenLoginFailureCount(), configurationService.getAccessTokenLoginMaxAttempts()); + LOG.debug("User token [{}] for user [{}] failed login attempt [{}] did not reach the max failed attempts [{}]", + user.getAccessTokenIdentifier(), user.getUsername(), user.getSequentialTokenLoginFailureCount(), configurationService.getAccessTokenLoginMaxAttempts()); return; } - if (configurationService.getAlertBeforeUserSuspendedAlertMoment() == AlertSuspensionMomentEnum.AT_LOGON) { - alertService.alertCredentialsSuspended(user, CredentialTypeEnum.ACCESS_TOKEN); - } + LOG.securityWarn(SMPMessageCode.SEC_USER_SUSPENDED, user.getUsername()); - throw new BadCredentialsException("The user is suspended. Please try again later or contact your administrator."); + delayResponse(startTime); + loginAttemptForAccessTokenFailed(user, false, startTime); } public Authentication authenticateByUsernameToken(UsernamePasswordAuthenticationToken auth) throws AuthenticationException { String authenticationTokenId = auth.getName(); + LOG.debug("Got authentication token:" + authenticationTokenId); String authenticationTokenValue = auth.getCredentials().toString(); long startTime = Calendar.getInstance().getTimeInMillis(); @@ -288,36 +328,38 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { //https://www.owasp.org/index.php/Authentication_Cheat_Sheet // Do not reveal the status of an existing account. Not to use UsernameNotFoundException delayResponse(startTime); - throw new BadCredentialsException(LOGIN_FAILED_MESSAGE); + throw BAD_CREDENTIALS_EXCEPTION; } user = oUsr.get(); } catch (AuthenticationException ex) { LOG.securityWarn(SMPMessageCode.SEC_USER_NOT_AUTHENTICATED, authenticationTokenId, ExceptionUtils.getRootCause(ex), ex); - throw ex; + delayResponse(startTime); + throw BAD_CREDENTIALS_EXCEPTION; } catch (RuntimeException ex) { LOG.securityWarn(SMPMessageCode.SEC_USER_NOT_AUTHENTICATED, authenticationTokenId, ExceptionUtils.getRootCause(ex), ex); - throw new AuthenticationServiceException("Internal server error occurred while user authentication!"); + delayResponse(startTime); + throw BAD_CREDENTIALS_EXCEPTION; } - validateIfTokenIsSuspended(user); + validateIfTokenIsSuspended(user, startTime); try { if (!BCrypt.checkpw(authenticationTokenValue, user.getAccessToken())) { - loginAttemptForAccessTokenFailed(user, startTime); + loginAttemptForAccessTokenFailed(user, true, startTime); } user.setSequentialTokenLoginFailureCount(0); user.setLastTokenFailedLoginAttempt(null); mUserDao.update(user); } catch (java.lang.IllegalArgumentException ex) { - // password is not hashed; + // password is not hashed + loginAttemptForAccessTokenFailed(user, true, startTime); LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, ex, authenticationTokenId); - throw new BadCredentialsException(LOGIN_FAILED_MESSAGE); } - // the webservice authentication with corresponding web-service authority; + // the webservice authentication with corresponding web-service authority SMPAuthority authority = SMPAuthority.getAuthorityByRoleName("WS_" + user.getRole()); // the webservice authentication does not support session set the session secret is null! - SMPUserDetails userDetails = new SMPUserDetails(user, null, Collections.singletonList(authority)); + SMPUserDetails userDetails = new SMPUserDetails(user, null, Collections.singletonList(authority)); SMPAuthenticationToken smpAuthenticationToken = new SMPAuthenticationToken(authenticationTokenId, authenticationTokenValue, @@ -328,33 +370,36 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { return smpAuthenticationToken; } - public void loginAttemptForAccessTokenFailed(DBUser user, long startTime) { + public void loginAttemptForAccessTokenFailed(DBUser user, boolean notYetSuspended, long startTime) { user.setSequentialTokenLoginFailureCount(user.getSequentialTokenLoginFailureCount() != null ? user.getSequentialTokenLoginFailureCount() + 1 : 1); user.setLastTokenFailedLoginAttempt(OffsetDateTime.now()); mUserDao.update(user); - LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, user.getAccessTokenIdentifier()); - - user.setSequentialLoginFailureCount(user.getSequentialLoginFailureCount() != null ? user.getSequentialLoginFailureCount() + 1 : 1); - user.setLastFailedLoginAttempt(OffsetDateTime.now()); - mUserDao.update(user); - LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, user.getUsername()); - if (user.getSequentialTokenLoginFailureCount() >= configurationService.getAccessTokenLoginMaxAttempts()) { + LOG.securityWarn(SMPMessageCode.SEC_INVALID_TOKEN, user.getUsername(), user.getAccessTokenIdentifier()); + boolean isCredentialSuspended =user.getSequentialTokenLoginFailureCount() >= configurationService.getAccessTokenLoginMaxAttempts(); + if (isCredentialSuspended) { LOG.info("User access token [{}] failed sequential attempt exceeded the max allowed attempts [{}]!", user.getAccessToken(), configurationService.getAccessTokenLoginMaxAttempts()); - alertService.alertCredentialsSuspended(user, CredentialTypeEnum.ACCESS_TOKEN); + if (notYetSuspended || + configurationService.getAlertBeforeUserSuspendedAlertMoment() == AlertSuspensionMomentEnum.AT_LOGON) { + alertService.alertCredentialsSuspended(user, CredentialTypeEnum.ACCESS_TOKEN); + } } else { alertService.alertCredentialVerificationFailed(user, CredentialTypeEnum.ACCESS_TOKEN); } delayResponse(startTime); - throw new BadCredentialsException(LOGIN_FAILED_MESSAGE); + if (isCredentialSuspended) { + throw SUSPENDED_CREDENTIALS_EXCEPTION; + } else { + throw BAD_CREDENTIALS_EXCEPTION; + } } @Override public boolean supports(Class<?> auth) { - LOG.info("Support authentication: " + auth); + LOG.info("Support authentication: [{}].", auth); boolean supportAuthentication = auth.equals(UsernamePasswordAuthenticationToken.class) || auth.equals(PreAuthenticatedAuthenticationToken.class); if (!supportAuthentication) { - LOG.warn("SMP does not support authentication type: " + auth); + LOG.warn("SMP does not support authentication type: [{}].", auth); } return supportAuthentication; } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java index 8409a4fceb51dfac90492da0e805f26329457e09..0594f2c6ebd4ce2839fcd3e7ebff0aef73843c7a 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java @@ -18,7 +18,6 @@ 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.AuthenticationProvider; -import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -42,6 +41,9 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPAuthenticationProviderForUI.class); + private static final BadCredentialsException BAD_CREDENTIALS_EXCEPTION = new BadCredentialsException("Login failed; Invalid userID or password"); + private static final BadCredentialsException SUSPENDED_CREDENTIALS_EXCEPTION = new BadCredentialsException("The user is suspended. Please try again later or contact your administrator."); + final UserDao mUserDao; final ConversionService conversionService; final CRLVerifierService crlVerifierService; @@ -89,24 +91,25 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { DBUser user; try { Optional<DBUser> oUsr = mUserDao.findUserByUsername(username); - if (!oUsr.isPresent()) { + if (!oUsr.isPresent() || !oUsr.get().isActive() ){ LOG.debug("User with username does not exists [{}], continue with next authentication provider"); LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, "Username does not exits", username); delayResponse(startTime); - throw new BadCredentialsException("Login failed; Invalid userID or password"); + throw BAD_CREDENTIALS_EXCEPTION; } user = oUsr.get(); } catch (AuthenticationException ex) { LOG.securityWarn(SMPMessageCode.SEC_USER_NOT_AUTHENTICATED, username, ExceptionUtils.getRootCause(ex), ex); - throw ex; + delayResponse(startTime); + throw BAD_CREDENTIALS_EXCEPTION; } catch (RuntimeException ex) { LOG.securityWarn(SMPMessageCode.SEC_USER_NOT_AUTHENTICATED, username, ExceptionUtils.getRootCause(ex), ex); - throw new AuthenticationServiceException("Internal server error occurred while user authentication!"); - + delayResponse(startTime); + throw BAD_CREDENTIALS_EXCEPTION; } - validateIfUserAccountIsSuspended(user); + validateIfUserAccountIsSuspended(user, startTime); SMPAuthority authority = SMPAuthority.getAuthorityByRoleName(user.getRole()); // the webservice authentication does not support session set the session secret is null! @@ -119,7 +122,8 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { userDetails); try { if (!BCrypt.checkpw(userCredentialToken, user.getPassword())) { - loginAttemptForUserFailed(user, startTime); + LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, username); + loginAttemptForUserFailedAndThrowError(user, true, startTime); } user.setSequentialLoginFailureCount(0); user.setLastFailedLoginAttempt(null); @@ -127,12 +131,13 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { } catch (IllegalArgumentException ex) { // password is not hashed; LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, ex, username); - throw new BadCredentialsException("Login failed; Invalid userID or password"); + loginAttemptForUserFailedAndThrowError(user, true, startTime); } LOG.securityInfo(SMPMessageCode.SEC_USER_AUTHENTICATED, username, role); return smpAuthenticationToken; } + public void delayResponse(long startTime) { int delayInMS = configurationService.getLoginFailDelayInMilliSeconds() - (int) (Calendar.getInstance().getTimeInMillis() - startTime); if (delayInMS > 0) { @@ -146,19 +151,29 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { } } - public void loginAttemptForUserFailed(DBUser user, long startTime) { + public void loginAttemptForUserFailedAndThrowError(DBUser user, boolean notYetSuspended, long startTime) { user.setSequentialLoginFailureCount(user.getSequentialLoginFailureCount() != null ? user.getSequentialLoginFailureCount() + 1 : 1); user.setLastFailedLoginAttempt(OffsetDateTime.now()); mUserDao.update(user); LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, user.getUsername()); - if (user.getSequentialLoginFailureCount() >= configurationService.getLoginMaxAttempts()) { + boolean isUserSuspended = user.getSequentialLoginFailureCount() >= configurationService.getLoginMaxAttempts(); + if (isUserSuspended) { LOG.info("User [{}] failed sequential attempt exceeded the max allowed attempts [{}]!", user.getUsername(), configurationService.getLoginMaxAttempts()); - alertService.alertCredentialsSuspended(user, CredentialTypeEnum.USERNAME_PASSWORD); + // at notYetSuspended alert is sent for all settings AT_LOGON, WHEN_BLOCKED + if (notYetSuspended || + configurationService.getAlertBeforeUserSuspendedAlertMoment() == AlertSuspensionMomentEnum.AT_LOGON) { + alertService.alertCredentialsSuspended(user, CredentialTypeEnum.USERNAME_PASSWORD); + } } else { + // always invoke the method. The method handles the smp.alert.user.login_failure.enabled alertService.alertCredentialVerificationFailed(user, CredentialTypeEnum.USERNAME_PASSWORD); } delayResponse(startTime); - throw new BadCredentialsException("Login failed; Invalid userID or password"); + if (isUserSuspended) { + throw SUSPENDED_CREDENTIALS_EXCEPTION; + } else { + throw BAD_CREDENTIALS_EXCEPTION; + } } /** @@ -166,7 +181,7 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { * * @param user */ - public void validateIfUserAccountIsSuspended(DBUser user) { + public void validateIfUserAccountIsSuspended(DBUser user, long startTime) { if (user.getSequentialLoginFailureCount() == null || user.getSequentialLoginFailureCount() < 0) { LOG.trace("User has no previous failed attempts"); @@ -184,7 +199,7 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { } // check if the last failed attempt is already expired. If yes just clear the attempts if (configurationService.getLoginSuspensionTimeInSeconds() != null && configurationService.getLoginSuspensionTimeInSeconds() > 0 - && ChronoUnit.SECONDS.between(OffsetDateTime.now(), user.getLastFailedLoginAttempt()) > configurationService.getLoginSuspensionTimeInSeconds()) { + && ChronoUnit.SECONDS.between(user.getLastFailedLoginAttempt(), OffsetDateTime.now()) > configurationService.getLoginSuspensionTimeInSeconds()) { LOG.warn("User [{}] suspension is expired! Clear failed login attempts and last failed login attempt", user.getUsername()); user.setLastFailedLoginAttempt(null); user.setSequentialLoginFailureCount(0); @@ -193,14 +208,11 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { } if (user.getSequentialLoginFailureCount() < configurationService.getLoginMaxAttempts()) { - LOG.warn("User [{}] failed login attempt [{}]! did not reach the max failed attempts [{}]", user.getUsername(), user.getSequentialLoginFailureCount(), configurationService.getLoginMaxAttempts()); + LOG.debug("User [{}] failed login attempt [{}]! did not reach the max failed attempts [{}]", user.getUsername(), user.getSequentialLoginFailureCount(), configurationService.getLoginMaxAttempts()); return; } - if (configurationService.getAlertBeforeUserSuspendedAlertMoment() == AlertSuspensionMomentEnum.AT_LOGON) { - alertService.alertCredentialsSuspended(user, CredentialTypeEnum.USERNAME_PASSWORD); - } LOG.securityWarn(SMPMessageCode.SEC_USER_SUSPENDED, user.getUsername()); - throw new BadCredentialsException("The user is suspended. Please try again later or contact your administrator."); + loginAttemptForUserFailedAndThrowError(user, false, startTime); } @Override 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 a741c84c38465af80148be40d6ece5f0ad19b400..0ae7d40ec469cad952a3e7896ccc10705a20893d 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 @@ -124,6 +124,7 @@ public class SMPAuthorizationService { public UserRO getLoggedUserData() { SMPUserDetails userDetails = getAndValidateUserDetails(); + // refresh data from database! DBUser dbUser = userDao.find(userDetails.getUser().getId()); if (dbUser == null || !dbUser.isActive()) { @@ -132,7 +133,9 @@ public class SMPAuthorizationService { userDetails.getUser().getUsername()); return null; } - return getUserData(dbUser); + UserRO userRO = getUserData(dbUser); + userRO.setCasAuthenticated(userDetails.isCasAuthenticated()); + return userRO; } public UserRO getUserData(DBUser user) { diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidator.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidator.java index 2aeb2ee1f93fd259bed39ae1b17b4cb9e939c92f..1a5d96924f14556a316a9d45f545488c86fc4a56 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidator.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidator.java @@ -25,6 +25,7 @@ public class SMPCas20ServiceTicketValidator extends Cas20ServiceTicketValidator this.urlSuffix = urlSuffix; } + @Override protected String getUrlSuffix() { if (StringUtils.isBlank(urlSuffix)){ LOG.warn("Cas20 ServiceTicketValidator url suffix is not configured. Use default value: [{}]", super.getUrlSuffix()); @@ -33,6 +34,7 @@ public class SMPCas20ServiceTicketValidator extends Cas20ServiceTicketValidator return urlSuffix; } + @Override protected void customParseResponse(final String response, final Assertion assertion) throws TicketValidationException { LOG.debug("Got CAS response: [{}] and test it with assertion [{}]",response,assertion ); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java index dd1275b4abc926a0455126fbd6700273bd42937e..f198922647130ca716a4365db642cd8ba9583c22 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java @@ -68,15 +68,15 @@ public class SMPCasConfigurer { * @return */ @Bean - public CasAuthenticationEntryPoint casAuthenticationEntryPoint(@Nullable @Qualifier(SMP_CAS_PROPERTIES_BEAN) ServiceProperties serviceProperties, ConfigurationService configService) { + public CasAuthenticationEntryPoint casAuthenticationEntryPoint(@Nullable @Qualifier(SMP_CAS_PROPERTIES_BEAN) ServiceProperties serviceProperties) { - if (!configService.isSSOEnabledForUserAuthentication()) { - LOG.debug("Bean CasAuthenticationEntryPoint is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); + if (!configurationService.isSSOEnabledForUserAuthentication()) { + LOG.debug("Bean [{}] is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); return null; } - String casUrl = configService.getCasURL().toString(); - String casLoginPath = configService.getCasURLPathLogin(); + String casUrl = configurationService.getCasURL().toString(); + String casLoginPath = configurationService.getCasURLPathLogin(); String casUrlLogin = StringUtils.removeEnd(casUrl, "/") + StringUtils.prependIfMissing(casLoginPath, "/"); CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint(); @@ -87,21 +87,20 @@ public class SMPCasConfigurer { } @Bean - public SMPCas20ServiceTicketValidator ecasServiceTicketValidator(ConfigurationService configService) { - if (!configService.isSSOEnabledForUserAuthentication()) { - LOG.debug("Bean SMPCas20ServiceTicketValidator is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); + public SMPCas20ServiceTicketValidator ecasServiceTicketValidator() { + if (!configurationService.isSSOEnabledForUserAuthentication()) { + LOG.debug("Bean [{}] is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); return null; } - if (configService.getCasURL() == null) { - LOG.error("Bean SMPCas20ServiceTicketValidator is not created! Missing Service parameter [{}]!", SSO_CAS_URL.getProperty()); + if (configurationService.getCasURL() == null) { + LOG.error("Bean [{}] is not created! Missing Service parameter [{}]!", SMP_CAS_PROPERTIES_BEAN, SSO_CAS_URL.getProperty()); return null; } - String casUrl = configService.getCasURL().toString(); - String casTokenValidationSuffix = configService.getCasURLTokenValidation(); - LOG.debug("Create Bean SMPCas20ServiceTicketValidator with cas URL [{}] and token suffix [{}]!", casUrl,casTokenValidationSuffix ); + String casUrl = configurationService.getCasURL().toString(); + String casTokenValidationSuffix = configurationService.getCasURLTokenValidation(); SMPCas20ServiceTicketValidator validator = new SMPCas20ServiceTicketValidator(casUrl, casTokenValidationSuffix); - validator.setCustomParameters(getCustomParameters(configService)); + validator.setCustomParameters(getCustomParameters()); validator.setRenew(false); return validator; } @@ -109,15 +108,14 @@ public class SMPCasConfigurer { /** * Generate properties for SMPCas20ServiceTicketValidator * - * @param configService * @return CAS properties */ - public Map<String, String> getCustomParameters(ConfigurationService configService) { + public Map<String, String> getCustomParameters() { Map<String, String> map = new HashMap<>(); // always return details map.put("userDetails", "true"); - map.putAll(configService.getCasTokenValidationParams()); - List<String> groupList = configService.getCasURLTokenValidationGroups(); + map.putAll(configurationService.getCasTokenValidationParams()); + List<String> groupList = configurationService.getCasURLTokenValidationGroups(); if (!groupList.isEmpty()) { map.put("groups", String.join(",", groupList)); } @@ -133,10 +131,9 @@ public class SMPCasConfigurer { public CasAuthenticationProvider casAuthenticationProvider( @Nullable @Qualifier(SMP_CAS_PROPERTIES_BEAN) ServiceProperties serviceProperties, @Nullable SMPCas20ServiceTicketValidator serviceTicketValidator, - @Nullable SMPCasUserService smpCasUserService, - ConfigurationService configService) { + @Nullable SMPCasUserService smpCasUserService) { - if (!configService.isSSOEnabledForUserAuthentication()) { + if (!configurationService.isSSOEnabledForUserAuthentication()) { LOG.debug("Bean [CasAuthenticationProvider:{}] is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); return null; } @@ -169,7 +166,7 @@ public class SMPCasConfigurer { //filter.setFilterProcessesUrl(SMP_SECURITY_PATH_CAS_AUTHENTICATE); filter.setServiceProperties(casServiceProperties); filter.setAuthenticationManager(authenticationManager); - LOG.info("Created CAS Filter: " + filter.getClass().getSimpleName() + "with the properties: " + casServiceProperties.getArtifactParameter()); + LOG.info("Created CAS Filter: [{}] with the properties: [{}]", filter.getClass().getSimpleName() , casServiceProperties.getArtifactParameter()); return filter; } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java index ce251bae57ff529fc9e8a2b17dfe0f5e4376919e..aa924b40453866777d737ae84a892b627f3c893e 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java @@ -14,7 +14,6 @@ package eu.europa.ec.edelivery.smp.controllers; import eu.europa.ec.edelivery.smp.conversion.CaseSensitivityNormalizer; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.conversion.ServiceGroupConverter; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; @@ -23,7 +22,6 @@ import eu.europa.ec.edelivery.smp.services.PayloadValidatorService; import eu.europa.ec.edelivery.smp.services.ServiceGroupService; import eu.europa.ec.edelivery.smp.services.ServiceMetadataService; import eu.europa.ec.edelivery.smp.validation.ServiceGroupValidator; -import eu.europa.ec.smp.api.Identifiers; import eu.europa.ec.smp.api.exceptions.XmlInvalidAgainstSchemaException; import eu.europa.ec.smp.api.validators.BdxSmpOasisValidator; import org.apache.commons.lang3.StringUtils; @@ -31,7 +29,6 @@ import org.oasis_open.docs.bdxr.ns.smp._2016._05.DocumentIdentifier; import org.oasis_open.docs.bdxr.ns.smp._2016._05.ParticipantIdentifierType; import org.oasis_open.docs.bdxr.ns.smp._2016._05.ServiceGroup; import org.oasis_open.docs.bdxr.ns.smp._2016._05.ServiceMetadataReferenceType; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.security.core.context.SecurityContextHolder; @@ -42,10 +39,9 @@ import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayInputStream; import java.util.List; -import static eu.europa.ec.edelivery.smp.controllers.WebConstans.HTTP_PARAM_DOMAIN; -import static eu.europa.ec.edelivery.smp.controllers.WebConstans.HTTP_PARAM_OWNER; +import static eu.europa.ec.edelivery.smp.controllers.WebConstants.HTTP_PARAM_DOMAIN; +import static eu.europa.ec.edelivery.smp.controllers.WebConstants.HTTP_PARAM_OWNER; import static eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority.*; -import static eu.europa.ec.smp.api.Identifiers.asParticipantId; import static org.springframework.http.ResponseEntity.created; import static org.springframework.http.ResponseEntity.ok; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java index e0ae31ec5f150e5f65a422e2e26f7c1ebf5b30e3..e8823d80852b30f5187f3f57c445bca5b5d8b8af 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java @@ -24,7 +24,6 @@ import eu.europa.ec.edelivery.smp.validation.ServiceMetadataValidator; import eu.europa.ec.smp.api.exceptions.XmlInvalidAgainstSchemaException; import org.apache.commons.lang3.StringUtils; import org.oasis_open.docs.bdxr.ns.smp._2016._05.ParticipantIdentifierType; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; @@ -37,9 +36,8 @@ import javax.xml.transform.TransformerException; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; -import static eu.europa.ec.edelivery.smp.controllers.WebConstans.HTTP_PARAM_DOMAIN; +import static eu.europa.ec.edelivery.smp.controllers.WebConstants.HTTP_PARAM_DOMAIN; import static eu.europa.ec.smp.api.Identifiers.asDocumentId; -import static eu.europa.ec.smp.api.Identifiers.asParticipantId; import static org.springframework.http.ResponseEntity.created; import static org.springframework.http.ResponseEntity.ok; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/SmpUrlBuilder.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/SmpUrlBuilder.java index 54ad42fc4406c161c8757c587fc9f70753d00d78..7ea947e29c81143ac949d4a3923f48f7352772b7 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/SmpUrlBuilder.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/SmpUrlBuilder.java @@ -73,16 +73,21 @@ public class SmpUrlBuilder { LOG.debug("Build SMP url for participant identifier: [{}] and document identifier [{}].", participantId, docId); HttpServletRequest req = getCurrentRequest(); HttpForwardedHeaders fh = new HttpForwardedHeaders(req); - LOG.info("Generate response uri for forwarded headers: " + fh.toString()); - UriComponentsBuilder uriBuilder = getSMPUrlBuilder(); - // + LOG.debug("Generate response uri with headers data: [{}]" + fh.toString()); + UriComponentsBuilder uriBuilder = getSMPUrlBuilder();// if (fh.getHost()!=null) { + LOG.debug("Set response uri for forwarded headers: [{}]", fh.toString()); uriBuilder = uriBuilder.host(fh.getHost()); String port = fh.getNonDefaultPort(); if (!StringUtils.isBlank(port)) { uriBuilder = uriBuilder.port(port); + } else if (!StringUtils.isBlank(fh.getPort())){ + LOG.debug("Set port to null because it is default port: [{}]", fh.toString()); + uriBuilder = uriBuilder.port(null); } uriBuilder = uriBuilder.scheme(fh.getProto()); + } else { + LOG.debug("Ignore settings header because host is null!"); } String path = uriBuilder diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/WebConstans.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/WebConstants.java similarity index 86% rename from smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/WebConstans.java rename to smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/WebConstants.java index 6efab566c3a1df31ddfe0ec7e916f29aad8c57da..fff71cc8b792f2c78e966166f47ddc67f5d8ce32 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/WebConstans.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/WebConstants.java @@ -1,6 +1,6 @@ package eu.europa.ec.edelivery.smp.controllers; -public class WebConstans { +public class WebConstants { public static final String HTTP_PARAM_DOMAIN="Domain"; public static final String HTTP_PARAM_OWNER="ServiceGroup-Owner"; 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 index 83995035837ce67dffd86298852d378dac7a875f..8afe8219b95b07bc9c94033bba1cae8278ea2534 100644 --- 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 @@ -20,6 +20,7 @@ public class ResourceConstants { 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"; public static final String CONTEXT_PATH_PUBLIC_SECURITY_AUTHENTICATION = CONTEXT_PATH_PUBLIC_SECURITY + "/authentication"; + public static final String CONTEXT_PATH_PUBLIC_SECURITY_USER = CONTEXT_PATH_PUBLIC_SECURITY + "/user"; //internal public static final String CONTEXT_PATH_INTERNAL_ALERT = CONTEXT_PATH_INTERNAL + "alert"; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java index e366a5508956d39bfd80db4c3214b6889f40b91b..f4b17af39678478adcfab6e67965722436cd059c 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResource.java @@ -1,6 +1,7 @@ package eu.europa.ec.edelivery.smp.ui.external; +import eu.europa.ec.edelivery.smp.auth.enums.SMPUserAuthenticationTypes; 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; @@ -14,6 +15,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Collections; +import java.util.List; import java.util.TimeZone; /** @@ -52,8 +55,12 @@ public class ApplicationResource { public SmpInfoRO getApplicationInfo() { SmpInfoRO info = new SmpInfoRO(); info.setVersion(getDisplayVersion()); - info.addAuthTypes(configurationService.getUIAuthenticationTypes()); - if (configurationService.getUIAuthenticationTypes().contains("SSO")){ + List<String> authTypes = configurationService.getUIAuthenticationTypes(); + // set default password + authTypes = authTypes ==null || authTypes.isEmpty()? + Collections.singletonList(SMPUserAuthenticationTypes.PASSWORD.name()):authTypes; + info.addAuthTypes(authTypes); + if (authTypes.contains(SMPUserAuthenticationTypes.SSO.name())){ info.setSsoAuthenticationLabel(configurationService.getCasUILabel()); info.setSsoAuthenticationURI(configurationService.getCasSMPLoginRelativePath()); } 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 index a7d8e4f4433d8dcc6e72c0e0fe150762864476ce..4a280322f9fd54f668df61e6d2be42e7e8ae601e 100644 --- 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 @@ -2,6 +2,7 @@ package eu.europa.ec.edelivery.smp.ui.external; import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationService; import eu.europa.ec.edelivery.smp.auth.SMPAuthorizationService; +import eu.europa.ec.edelivery.smp.auth.SMPUserDetails; 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.PasswordChangeRO; @@ -9,7 +10,9 @@ import eu.europa.ec.edelivery.smp.data.ui.UserRO; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.services.ui.UIUserService; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.web.authentication.session.SessionAuthenticationException; import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; @@ -40,11 +43,16 @@ public class UserResource { @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") @PostMapping(path = "/{user-id}/generate-access-token", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) - public AccessTokenRO generateAccessToken(@PathVariable("user-id") String userId, @RequestBody String password) { + public AccessTokenRO generateAccessToken(@PathVariable("user-id") String userId, @RequestBody(required = false) String password) { Long entityId = decryptEntityId(userId); + SMPUserDetails currentUser = SessionSecurityUtils.getSessionUserDetails(); LOG.info("Generated access token for user:[{}] with id:[{}] ", userId, entityId); + if (currentUser == null) { + throw new SessionAuthenticationException("User session expired!"); + } - return uiUserService.generateAccessTokenForUser(entityId, entityId, password); + // no need to validate password if CAS authenticated + return uiUserService.generateAccessTokenForUser(entityId, entityId, password, !currentUser.isCasAuthenticated()); } @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") @@ -52,6 +60,7 @@ public class UserResource { public boolean changePassword(@PathVariable("user-id") String userId, @RequestBody PasswordChangeRO newPassword, HttpServletRequest request, HttpServletResponse response) { Long entityId = decryptEntityId(userId); LOG.info("Validating the password of the currently logged in user:[{}] with id:[{}] ", userId, entityId); + // when user changing password the current password must be verified even if cas authenticated DBUser result = uiUserService.updateUserPassword(entityId, entityId, newPassword.getCurrentPassword(), newPassword.getNewPassword()); if (result!=null) { LOG.info("Password successfully changed. Logout the user, to be able to login with the new password!"); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResource.java index ebaa92d146f1649675c97a1f4f14885da497237d..ca4181ab79c63ced9541904b43a978cdd6100f4a 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResource.java @@ -12,6 +12,7 @@ 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.security.access.annotation.Secured; +import org.springframework.security.web.authentication.session.SessionAuthenticationException; import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; @@ -89,12 +90,18 @@ public class UserAdminResource { public AccessTokenRO generateAccessTokenForUser( @PathVariable("user-id") String userId, @PathVariable("update-user-id") String regenerateForUserId, - @RequestBody String password) { + @RequestBody(required = false) String password) { Long authorizedUserId = decryptEntityId(userId); Long changeUserId = decryptEntityId(regenerateForUserId); LOG.info("Generated access token for user:[{}] with id:[{}] by the system user with id [{}]", userId, regenerateForUserId, authorizedUserId); - return uiUserService.generateAccessTokenForUser(authorizedUserId, changeUserId, password); + SMPUserDetails currentUser = SessionSecurityUtils.getSessionUserDetails(); + if (currentUser == null) { + throw new SessionAuthenticationException("User session expired!"); + } + + // no need to validate password if cas authenticated + return uiUserService.generateAccessTokenForUser(authorizedUserId, changeUserId, password,!currentUser.isCasAuthenticated()); } @@ -102,11 +109,17 @@ public class UserAdminResource { @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN}) public UserRO changePassword(@PathVariable("user-id") String userId, @PathVariable("update-user-id") String regenerateForUserId, - @RequestBody PasswordChangeRO newPassword, HttpServletRequest request, HttpServletResponse response) { + @RequestBody PasswordChangeRO newPassword) { Long authorizedUserId = decryptEntityId(userId); Long changeUserId = decryptEntityId(regenerateForUserId); LOG.info("change the password of the currently logged in user:[{}] with id:[{}] ", changeUserId, regenerateForUserId); - DBUser user = uiUserService.updateUserPassword(authorizedUserId, changeUserId, newPassword.getCurrentPassword(), newPassword.getNewPassword()); + + SMPUserDetails currentUser = SessionSecurityUtils.getSessionUserDetails(); + if (currentUser == null) { + throw new SessionAuthenticationException("User session expired!"); + } + + DBUser user = uiUserService.updateUserPassword(authorizedUserId, changeUserId, newPassword.getCurrentPassword(), newPassword.getNewPassword(),!currentUser.isCasAuthenticated()); return authorizationService.sanitize(uiUserService.convertToRo(user)); } diff --git a/smp-webapp/src/main/resources/logback.xml b/smp-webapp/src/main/resources/logback.xml index d338d52eeca409b8384ce43953e81fc820c1b715..b0ce159870d58fa0918239ffad2f7c58bd94c873 100644 --- a/smp-webapp/src/main/resources/logback.xml +++ b/smp-webapp/src/main/resources/logback.xml @@ -4,7 +4,6 @@ <!-- pattern definition --> <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> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> @@ -27,19 +26,15 @@ <pattern>${encoderPattern}</pattern> </encoder> </appender> - - - <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <Target>System.out</Target> <encoder> <pattern>${consolePattern}</pattern> </encoder> </appender> - - <logger name="eu.europa.ec.edelivery" level="DEBUG" /> - <logger name="eu.europa.ec.smp" level="DEBUG" /> - <logger name="org.springframework.security.cas" level="DEBUG" /> + <logger name="eu.europa.ec.edelivery" level="INFO" /> + <logger name="eu.europa.ec.smp" level="INFO" /> + <logger name="org.springframework.security.cas" level="INFO" /> <root level="WARN"> <appender-ref ref="file"/> <appender-ref ref="stdout"/> diff --git a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl index ee77db259de28bbc31303b424859b013d978ac0c..506236cb393949f81649646b41f4af20a0413f7d 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl @@ -1,5 +1,5 @@ -- ------------------------------------------------------------------------ --- This file was generated by hibernate for SMP version 4.2-SNAPSHOT. +-- This file was generated by hibernate for SMP version 4.2-RC2-SNAPSHOT. -- ------------------------------------------------------------------------ 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 80a64d4b43df605143a1469c5ac0c17e6106c3ca..ceba8f2fef596b0ef41e89aedc4bad9ee8a5b01b 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl @@ -1,5 +1,5 @@ -- ------------------------------------------------------------------------ --- This file was generated by hibernate for SMP version 4.2-SNAPSHOT. +-- This file was generated by hibernate for SMP version 4.2-RC2-SNAPSHOT. -- ------------------------------------------------------------------------ diff --git a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl index 9aa22f1a31cf7b2b9f273a1c9c3cc20ff108da25..62558376a3cf541bf5ecf151faf5a20872faa197 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl @@ -1,5 +1,5 @@ -- ------------------------------------------------------------------------ --- This file was generated by hibernate for SMP version 4.2-SNAPSHOT. +-- This file was generated by hibernate for SMP version 4.2-RC2-SNAPSHOT. -- ------------------------------------------------------------------------ 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 431ec449c9984e6294894e6abe3c86c281ba0f6f..7e52f574397fd7a1d465eb63a529adb305fc48aa 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl @@ -1,5 +1,5 @@ -- ------------------------------------------------------------------------ --- This file was generated by hibernate for SMP version 4.2-SNAPSHOT. +-- This file was generated by hibernate for SMP version 4.2-RC2-SNAPSHOT. -- ------------------------------------------------------------------------ create sequence SMP_ALERT_PROP_SEQ start with 1 increment by 1; diff --git a/smp-webapp/src/main/smp-setup/logback.xml b/smp-webapp/src/main/smp-setup/smp-logback.xml similarity index 99% rename from smp-webapp/src/main/smp-setup/logback.xml rename to smp-webapp/src/main/smp-setup/smp-logback.xml index 41767c18636663607bc909d8f94d1cf538ef4fd1..855023009253b9545f7b82bc4b2df7f9d81fcefd 100644 --- a/smp-webapp/src/main/smp-setup/logback.xml +++ b/smp-webapp/src/main/smp-setup/smp-logback.xml @@ -1,10 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> - <configuration> <!-- pattern definition --> <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> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> @@ -27,16 +25,12 @@ <pattern>${encoderPattern}</pattern> </encoder> </appender> - - - <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <Target>System.out</Target> <encoder> <pattern>${consolePattern}</pattern> </encoder> </appender> - <logger name="eu.europa.ec.edelivery.smp" level="INFO" /> <root level="WARN"> <appender-ref ref="file"/> diff --git a/smp-webapp/src/main/smp-setup/smp.config.properties b/smp-webapp/src/main/smp-setup/smp.config.properties index cb75c54d50ba30908c92ac8a0db57b17c6e2be1c..f02019390bbf30bb7d01e4e9ee22c924c6c56827 100644 --- a/smp-webapp/src/main/smp-setup/smp.config.properties +++ b/smp-webapp/src/main/smp-setup/smp.config.properties @@ -50,7 +50,7 @@ jdbc.password=secret123 # smp log folder # log.folder=../logs/ -# custom log4j configuration file +# custom logback configuration file # log.configuration.file=smp-logback.xml # ********************************* diff --git a/smp-webapp/src/main/webapp/WEB-INF/weblogic.xml b/smp-webapp/src/main/webapp/WEB-INF/weblogic.xml index 0dde753ac4f73403ed19b264bdbe2dd8bcf66e1d..15a5a759f4ef63a85713655cc1d6898277c6e1cc 100644 --- a/smp-webapp/src/main/webapp/WEB-INF/weblogic.xml +++ b/smp-webapp/src/main/webapp/WEB-INF/weblogic.xml @@ -15,7 +15,7 @@ <weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app http://www.bea.com/ns/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app"> <container-descriptor> <prefer-application-packages> - + <package-name>org.bouncycastle.*</package-name> <!-- used by Hibernate and JPA --> <package-name>javax.persistence.*</package-name> <package-name>javassist.*</package-name> diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUITest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUITest.java new file mode 100644 index 0000000000000000000000000000000000000000..e168753dede78f77ac2cbdf43205877e1c0d3fa4 --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUITest.java @@ -0,0 +1,51 @@ +package eu.europa.ec.edelivery.smp.auth; + +import eu.europa.ec.edelivery.smp.data.dao.UserDao; +import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.services.AlertService; +import eu.europa.ec.edelivery.smp.services.CRLVerifierService; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.core.convert.ConversionService; + +import java.time.OffsetDateTime; +import java.util.Calendar; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.doReturn; + +public class SMPAuthenticationProviderForUITest { + + UserDao mockUserDao = Mockito.mock(UserDao.class); + ConversionService mockConversionService = Mockito.mock(ConversionService.class); + CRLVerifierService mockCrlVerifierService = Mockito.mock(CRLVerifierService.class); + UITruststoreService mockTruststoreService = Mockito.mock(UITruststoreService.class); + ConfigurationService mockConfigurationService = Mockito.mock(ConfigurationService.class); + AlertService mocAlertService = Mockito.mock(AlertService.class); + SMPAuthenticationProviderForUI testInstance = new SMPAuthenticationProviderForUI(mockUserDao, + mockConversionService, + mockCrlVerifierService, + mocAlertService, + mockTruststoreService, + mockConfigurationService); + + @Test + public void testValidateIfTokenIsSuspendedReset(){ + int starFailCount = 5; + DBUser user = new DBUser(); + user.setUsername("TestToken"); + int suspensionSeconds =100; + + user.setLastFailedLoginAttempt(OffsetDateTime.now().minusSeconds(suspensionSeconds+10)); + user.setSequentialLoginFailureCount(starFailCount); + doReturn(suspensionSeconds).when(mockConfigurationService).getLoginSuspensionTimeInSeconds(); + doReturn(starFailCount).when(mockConfigurationService).getLoginMaxAttempts(); + + testInstance.validateIfUserAccountIsSuspended(user, Calendar.getInstance().getTimeInMillis()); + + assertEquals(0, (int)user.getSequentialLoginFailureCount()); + assertEquals(null, user.getLastFailedLoginAttempt()); + } +} \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java index 5069c6ec3ebf0cd165131c07b5e0185f91b34130..8e2a7bdbf67f299f29abcd061370197ad3dacdf7 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java @@ -2,6 +2,7 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.data.ui.enums.CredentialTypeEnum; import eu.europa.ec.edelivery.smp.services.AlertService; import eu.europa.ec.edelivery.smp.services.CRLVerifierService; import eu.europa.ec.edelivery.smp.services.ConfigurationService; @@ -15,12 +16,13 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.bcrypt.BCrypt; +import java.time.OffsetDateTime; import java.util.Calendar; import java.util.Optional; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.*; /** * @author Joze Rihtarsic @@ -52,8 +54,8 @@ public class SMPAuthenticationProviderTest { user.setAccessTokenIdentifier("User"); user.setAccessToken(BCrypt.hashpw("InvalidPassword", BCrypt.gensalt())); user.setRole("MY_ROLE"); - doReturn(500).when(mockConfigurationService).getAccessTokenLoginFailDelayInMilliSeconds(); - doReturn(count+1).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + doReturn(1000).when(mockConfigurationService).getAccessTokenLoginFailDelayInMilliSeconds(); + doReturn(count+5).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); doReturn(Optional.of(user)).when(mockUserDao).findUserByIdentifier(any()); @@ -84,4 +86,55 @@ public class SMPAuthenticationProviderTest { Matchers.lessThan(50L)); } + @Test + public void testLoginAttemptForAccessTokenFailed(){ + int starFailCount = 2; + DBUser user = new DBUser(); + user.setSequentialTokenLoginFailureCount(starFailCount); + long starTime =Calendar.getInstance().getTimeInMillis(); + doReturn(100).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + // when + BadCredentialsException error = assertThrows(BadCredentialsException.class, + () -> testInstance.loginAttemptForAccessTokenFailed(user,true, starTime)); + + assertEquals(SMPAuthenticationProvider.BAD_CREDENTIALS_EXCEPTION, error); + assertEquals(starFailCount+1,(int)user.getSequentialTokenLoginFailureCount()); + verify(mocAlertService, times(1)).alertCredentialVerificationFailed(user, CredentialTypeEnum.ACCESS_TOKEN); + + } + + @Test + public void testLoginAttemptForAccessTokenSuspended(){ + int starFailCount = 5; + DBUser user = new DBUser(); + user.setSequentialTokenLoginFailureCount(starFailCount); + long starTime =Calendar.getInstance().getTimeInMillis(); + doReturn(5).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + // when + BadCredentialsException error = assertThrows(BadCredentialsException.class, + () -> testInstance.loginAttemptForAccessTokenFailed(user,true,starTime)); + + assertEquals(SMPAuthenticationProvider.SUSPENDED_CREDENTIALS_EXCEPTION, error); + assertEquals(starFailCount+1,(int)user.getSequentialTokenLoginFailureCount()); + verify(mocAlertService, times(1)).alertCredentialsSuspended(user, CredentialTypeEnum.ACCESS_TOKEN); + } + + @Test + public void testValidateIfTokenIsSuspendedReset(){ + int starFailCount = 5; + DBUser user = new DBUser(); + user.setUsername("TestToken"); + int suspensionSeconds =100; + + user.setLastTokenFailedLoginAttempt(OffsetDateTime.now().minusSeconds(suspensionSeconds+10)); + user.setSequentialTokenLoginFailureCount(starFailCount); + doReturn(suspensionSeconds).when(mockConfigurationService).getAccessTokenLoginSuspensionTimeInSeconds(); + doReturn(starFailCount).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + + testInstance.validateIfTokenIsSuspended(user, Calendar.getInstance().getTimeInMillis()); + + assertEquals(0, (int)user.getSequentialTokenLoginFailureCount()); + assertEquals(null, user.getLastTokenFailedLoginAttempt()); + } + } \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidatorTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ab69b5bcb9434e6b3264e4eab5192f0a44e6cd5c --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCas20ServiceTicketValidatorTest.java @@ -0,0 +1,28 @@ +package eu.europa.ec.edelivery.smp.auth.cas; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SMPCas20ServiceTicketValidatorTest { + + @Test + public void testGetUrlSuffix() { + String casUrl = "https://cas-server.local/cas"; + String casSuffix = "urlSuffix"; + + SMPCas20ServiceTicketValidator testInstance = new SMPCas20ServiceTicketValidator(casUrl, casSuffix); + + assertEquals(casSuffix, testInstance.getUrlSuffix()); + } + + @Test + public void testGetUrlSuffixDefault() { + String casUrl = "https://cas-server.local/cas"; + String casSuffix = null; + + SMPCas20ServiceTicketValidator testInstance = new SMPCas20ServiceTicketValidator(casUrl, casSuffix); + + assertEquals("serviceValidate", testInstance.getUrlSuffix()); + } +} \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurerTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2e24400c71601aab8091e1d0afbac03a8fd1227e --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurerTest.java @@ -0,0 +1,103 @@ +package eu.europa.ec.edelivery.smp.auth.cas; + +import eu.europa.ec.edelivery.smp.controllers.SmpUrlBuilder; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.security.cas.ServiceProperties; +import org.springframework.security.cas.authentication.CasAuthenticationProvider; +import org.springframework.security.cas.web.CasAuthenticationEntryPoint; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class SMPCasConfigurerTest { + ConfigurationService mockConfigService = Mockito.mock(ConfigurationService.class); + SmpUrlBuilder mockSmpUrlBuilder = Mockito.mock(SmpUrlBuilder.class); + + SMPCasConfigurer testInstance = new SMPCasConfigurer(mockSmpUrlBuilder, mockConfigService); + + @Test + public void serviceProperties() throws MalformedURLException { + String callbackString = "http://callback.local/smp"; + URL callBackURL = new URL(callbackString); + doReturn(callBackURL).when(mockConfigService).getCasCallbackUrl(); + ServiceProperties serviceProperties = testInstance.serviceProperties(); + + assertNotNull(serviceProperties); + assertEquals(callbackString, serviceProperties.getService()); + assertEquals(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER, serviceProperties.getArtifactParameter()); + assertEquals(true, serviceProperties.isAuthenticateAllArtifacts()); + } + + @Test + public void casAuthenticationEntryPoint() throws MalformedURLException { + String casUrl = "http://cas-server.local/cas"; + String casLoginPath = "login"; + doReturn(true).when(mockConfigService).isSSOEnabledForUserAuthentication(); + doReturn(new URL(casUrl)).when(mockConfigService).getCasURL(); + doReturn(casLoginPath).when(mockConfigService).getCasURLPathLogin(); + ServiceProperties serviceProperties = testInstance.serviceProperties(); + + CasAuthenticationEntryPoint result = testInstance.casAuthenticationEntryPoint(serviceProperties); + assertNotNull(serviceProperties); + assertEquals(casUrl+"/"+casLoginPath,result.getLoginUrl() ); + assertEquals(serviceProperties,result.getServiceProperties() ); + } + + @Test + public void ecasServiceTicketValidator() throws MalformedURLException { + String casUrl = "http://cas-server.local/cas"; + String tokenValidator = "laxValidate"; + + doReturn(true).when(mockConfigService).isSSOEnabledForUserAuthentication(); + doReturn(new URL(casUrl)).when(mockConfigService).getCasURL(); + doReturn(tokenValidator).when(mockConfigService).getCasURLTokenValidation(); + + SMPCas20ServiceTicketValidator result = testInstance.ecasServiceTicketValidator(); + assertNotNull(result); + assertEquals(tokenValidator, result.getUrlSuffix()); + } + + @Test + public void getCustomParameters() { + Map<String, String> testMap = new HashMap<>(); + testMap.put("key1","val1"); + testMap.put("key2","val2"); + List<String> groups = Arrays.asList("list1","list2"); + doReturn(testMap).when(mockConfigService).getCasTokenValidationParams(); + doReturn(groups).when(mockConfigService).getCasURLTokenValidationGroups(); + + Map<String, String> result = testInstance.getCustomParameters(); + + assertEquals(4, result.size()); + assertEquals("true", result.get("userDetails")); + assertEquals(String.join(",", groups), result.get("groups")); + assertEquals("val1", result.get("key1")); + assertEquals("val2", result.get("key2")); + } + + @Test + public void casAuthenticationProvider() { + ServiceProperties serviceProperties = mock(ServiceProperties.class); + SMPCas20ServiceTicketValidator smpCas20ServiceTicketValidator = mock(SMPCas20ServiceTicketValidator.class); + SMPCasUserService smpCasUserService = mock(SMPCasUserService.class); + + doReturn(true).when(mockConfigService).isSSOEnabledForUserAuthentication(); + + + CasAuthenticationProvider provider = testInstance.casAuthenticationProvider(serviceProperties, smpCas20ServiceTicketValidator, smpCasUserService); + + assertNotNull(provider); + + } + +} \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java index 1f52cfbf24d3b7c8a66f043f7e4090700d27bac0..a95441d5031397723e5f3201b26300c942d4882d 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java @@ -1,6 +1,7 @@ package eu.europa.ec.edelivery.smp.test.testutils; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpSession; @@ -31,17 +32,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @since 4.2 */ public class MockMvcUtils { - static ObjectMapper mapper = new ObjectMapper(); - - private static final String SYS_ADMIN_USERNAME = "sys_admin"; - private static final String SYS_ADMIN_PASSWD = "test123"; - private static final String SMP_ADMIN_USERNAME = "smp_admin"; - private static final String SMP_ADMIN_PASSWD = "test123"; - private static final String SG_USER_USERNAME = "sg_admin"; - private static final String SG_USER_PASSWD = "test123"; - - private static final String SG_USER2_USERNAME = "test_user_hashed_pass"; - private static final String SG_USER2_PASSWD = "test123"; + static ObjectMapper mapper = new ObjectMapper(){{ + registerModule(new JavaTimeModule()); + }}; + + public static final String SYS_ADMIN_USERNAME = "sys_admin"; + public static final String SYS_ADMIN_PASSWD = "test123"; + public static final String SMP_ADMIN_USERNAME = "smp_admin"; + public static final String SMP_ADMIN_PASSWD = "test123"; + public static final String SG_USER_USERNAME = "sg_admin"; + public static final String SG_USER_PASSWD = "test123"; + + public static final String SG_USER2_USERNAME = "test_user_hashed_pass"; + public static final String SG_USER2_PASSWD = "test123"; public static RequestPostProcessor getHttpBasicSystemAdminCredentials() { diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java index e129019134c04339d15fdce0168f8e217d209f6e..c6f8871d400a62c634ac6b2538445e081903fcc0 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java @@ -1,10 +1,8 @@ package eu.europa.ec.edelivery.smp.ui.external; import com.fasterxml.jackson.databind.ObjectMapper; -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 com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import eu.europa.ec.edelivery.smp.data.ui.*; import eu.europa.ec.edelivery.smp.test.SmpTestWebAppConfig; import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import org.junit.Before; @@ -12,7 +10,6 @@ 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.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.junit4.SpringRunner; @@ -22,11 +19,10 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.context.WebApplicationContext; import javax.ws.rs.core.MediaType; -import java.util.Arrays; import java.util.UUID; import static eu.europa.ec.edelivery.smp.test.testutils.MockMvcUtils.*; -import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_INTERNAL_USER; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_SECURITY_USER; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD; @@ -57,28 +53,10 @@ public class UserResourceIntegrationTest { @Before public void setup() { + mapper.registerModule(new JavaTimeModule()); mvc = initializeMockMvc(webAppContext); } - @Test - public void getUserList() throws Exception { - MockHttpSession session = loginWithSystemAdmin(mvc); - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf())) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - // then - assertNotNull(res); - assertEquals(10, res.getServiceEntities().size()); - res.getServiceEntities().forEach(sgMap -> { - UserRO sgro = mapper.convertValue(sgMap, UserRO.class); - assertNotNull(sgro.getUserId()); - assertNotNull(sgro.getUsername()); - assertNotNull(sgro.getRole()); - }); - } - @Test public void testUpdateCurrentUserOK() throws Exception { // login @@ -124,134 +102,55 @@ public class UserResourceIntegrationTest { } @Test - public void testUpdateUserList() throws Exception { - // given when - MockHttpSession session = loginWithSystemAdmin(mvc); - - SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor csrf = csrf(); - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf)) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - assertNotNull(res); - assertFalse(res.getServiceEntities().isEmpty()); - UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); - // then - userRO.setActive(!userRO.isActive()); - userRO.setEmailAddress("test@mail.com"); - userRO.setPassword(UUID.randomUUID().toString()); - if (userRO.getCertificate() == null) { - userRO.setCertificate(new CertificateRO()); - } - userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isOk()); - } - - @Test - public void testUpdateUserListWrongAuthentication() throws Exception { - // given when - MockHttpSession session = loginWithSystemAdmin(mvc); - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf())) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - assertNotNull(res); - assertFalse(res.getServiceEntities().isEmpty()); - UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); - // then - userRO.setActive(!userRO.isActive()); - userRO.setEmailAddress("test@mail.com"); - userRO.setPassword(UUID.randomUUID().toString()); - if (userRO.getCertificate() == null) { - userRO.setCertificate(new CertificateRO()); - } - userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - // anonymous - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isUnauthorized()); - - MockHttpSession sessionSMPAdmin = loginWithSMPAdmin(mvc); - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .session(sessionSMPAdmin) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isUnauthorized()); - - MockHttpSession sessionSGAdmin = loginWithServiceGroupUser(mvc); - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .session(sessionSGAdmin) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isUnauthorized()); - } - - @Test - public void testValidateDeleteUserOK() throws Exception { + public void generateAccessTokenForUser() throws Exception { + MockHttpSession session = loginWithServiceGroupUser2(mvc); + UserRO userRO = getLoggedUserData(mvc, session); + assertNotNull(userRO); - // login - MockHttpSession session = loginWithSystemAdmin(mvc); - // get list - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) + MvcResult result = mvc.perform(post(PATH_PUBLIC + "/" + userRO.getUserId()+"/generate-access-token") .with(csrf()) - .session(session)) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - assertNotNull(res); - assertFalse(res.getServiceEntities().isEmpty()); - UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + .session(session) + .contentType(MediaType.TEXT_PLAIN) + .content(SG_USER2_PASSWD) + ).andExpect(status().isOk()).andReturn(); - MvcResult resultDelete = mvc.perform(post(CONTEXT_PATH_INTERNAL_USER + "/validate-delete") + MvcResult resultUser = mvc.perform(get(CONTEXT_PATH_PUBLIC_SECURITY_USER ) .with(csrf()) .session(session) - .contentType(MediaType.APPLICATION_JSON) - .content("[\"" + userRO.getUserId() + "\"]")) - .andExpect(status().isOk()).andReturn(); - - DeleteEntityValidation dev = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + ).andExpect(status().isOk()).andReturn(); - assertFalse(dev.getListIds().isEmpty()); - assertTrue(dev.getListDeleteNotPermitedIds().isEmpty()); - assertEquals(userRO.getUserId(), dev.getListIds().get(0)); + UserRO updateUserData = mapper.readValue(resultUser.getResponse().getContentAsString(), UserRO.class); + AccessTokenRO resAccessToken = mapper.readValue(result.getResponse().getContentAsString(), AccessTokenRO.class); + assertNotNull(resAccessToken); + assertNotEquals(userRO.getAccessTokenId(), resAccessToken.getIdentifier()); + assertNotEquals(userRO.getAccessTokenExpireOn(), resAccessToken.getExpireOn()); + assertEquals(updateUserData.getAccessTokenId(), resAccessToken.getIdentifier()); + assertEquals(updateUserData.getAccessTokenExpireOn(), resAccessToken.getExpireOn()); } @Test - public void testValidateDeleteLoggedUserNotOK() throws Exception { + public void changePassword() throws Exception { + String newPassword = "TESTtest1234!@#$"; - // login - MockHttpSession session = loginWithSystemAdmin(mvc); - // get list - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .with(csrf()) - .session(session)) - .andExpect(status().isOk()).andReturn(); + MockHttpSession session = loginWithServiceGroupUser2(mvc); UserRO userRO = getLoggedUserData(mvc, session); + assertNotNull(userRO); + PasswordChangeRO newPass = new PasswordChangeRO(); + newPass.setUsername(SG_USER2_USERNAME); + newPass.setCurrentPassword(SG_USER2_PASSWD); + newPass.setNewPassword(newPassword); + assertNotEquals(newPassword, SG_USER2_PASSWD); - // note system credential has id 3! - MvcResult resultDelete = mvc.perform(post(CONTEXT_PATH_INTERNAL_USER + "/validate-delete") + mvc.perform(put(PATH_PUBLIC + "/" + userRO.getUserId()+"/change-password") .with(csrf()) .session(session) - .contentType(org.springframework.http.MediaType.APPLICATION_JSON) - .content("[\"" + userRO.getUserId() + "\"]")) - .andExpect(status().isOk()) - .andReturn(); - - DeleteEntityValidation res = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(newPass)) + ).andExpect(status().isOk()).andReturn(); - assertTrue(res.getListIds().isEmpty()); - assertEquals("Could not delete logged user!", res.getStringMessage()); + // test to login with new password + MockHttpSession sessionNew = loginWithCredentials(mvc, SG_USER2_USERNAME, newPassword); + assertNotNull(sessionNew); } } \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResourceIntegrationTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResourceIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9c79954aecde3fa9015cbfda8cca9b0a5a230b6f --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResourceIntegrationTest.java @@ -0,0 +1,272 @@ +package eu.europa.ec.edelivery.smp.ui.internal; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import eu.europa.ec.edelivery.smp.data.ui.*; +import eu.europa.ec.edelivery.smp.test.SmpTestWebAppConfig; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.apache.commons.lang3.StringUtils; +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.MockHttpSession; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.web.context.WebApplicationContext; + +import javax.ws.rs.core.MediaType; +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; + +import static eu.europa.ec.edelivery.smp.test.testutils.MockMvcUtils.*; +import static org.junit.Assert.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebAppConfiguration +@ContextConfiguration(classes = {SmpTestWebAppConfig.class}) +@Sql(scripts = { + "classpath:/cleanup-database.sql", + "classpath:/webapp_integration_test_data.sql"}, + executionPhase = BEFORE_TEST_METHOD) +public class UserAdminResourceIntegrationTest { + + private static final String PATH_INTERNAL = ResourceConstants.CONTEXT_PATH_INTERNAL_USER; + + @Autowired + private WebApplicationContext webAppContext; + + private MockMvc mvc; + + ObjectMapper mapper = new ObjectMapper(); + + @Before + public void setup() { + mapper.registerModule(new JavaTimeModule()); + mvc = initializeMockMvc(webAppContext); + } + + @Test + public void getUsers() throws Exception { + MockHttpSession session = loginWithSystemAdmin(mvc); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .session(session) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + // then + assertNotNull(res); + assertEquals(10, res.getServiceEntities().size()); + res.getServiceEntities().forEach(sgMap -> { + UserRO sgro = mapper.convertValue(sgMap, UserRO.class); + assertNotNull(sgro.getUserId()); + assertNotNull(sgro.getUsername()); + assertNotNull(sgro.getRole()); + }); + } + + @Test + public void testUpdateUserList() throws Exception { + // given when + MockHttpSession session = loginWithSystemAdmin(mvc); + + SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor csrf = csrf(); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .session(session) + .with(csrf)) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + assertNotNull(res); + assertFalse(res.getServiceEntities().isEmpty()); + UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + // then + userRO.setActive(!userRO.isActive()); + userRO.setEmailAddress("test@mail.com"); + userRO.setPassword(UUID.randomUUID().toString()); + if (userRO.getCertificate() == null) { + userRO.setCertificate(new CertificateRO()); + } + userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); + + mvc.perform(put(PATH_INTERNAL) + .session(session) + .with(csrf) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isOk()); + } + + @Test + public void testUpdateUserListWrongAuthentication() throws Exception { + // given when + MockHttpSession session = loginWithSystemAdmin(mvc); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .session(session) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + assertNotNull(res); + assertFalse(res.getServiceEntities().isEmpty()); + UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + // then + userRO.setActive(!userRO.isActive()); + userRO.setEmailAddress("test@mail.com"); + userRO.setPassword(UUID.randomUUID().toString()); + if (userRO.getCertificate() == null) { + userRO.setCertificate(new CertificateRO()); + } + userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); + // anonymous + mvc.perform(put(PATH_INTERNAL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isUnauthorized()); + + MockHttpSession sessionSMPAdmin = loginWithSMPAdmin(mvc); + mvc.perform(put(PATH_INTERNAL) + .session(sessionSMPAdmin) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isUnauthorized()); + + MockHttpSession sessionSGAdmin = loginWithServiceGroupUser(mvc); + mvc.perform(put(PATH_INTERNAL) + .session(sessionSGAdmin) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isUnauthorized()); + } + + @Test + public void testValidateDeleteUserOK() throws Exception { + + // login + MockHttpSession session = loginWithSystemAdmin(mvc); + // get list + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .with(csrf()) + .session(session)) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + assertNotNull(res); + assertFalse(res.getServiceEntities().isEmpty()); + UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + + MvcResult resultDelete = mvc.perform(post(PATH_INTERNAL + "/validate-delete") + .with(csrf()) + .session(session) + .contentType(MediaType.APPLICATION_JSON) + .content("[\"" + userRO.getUserId() + "\"]")) + .andExpect(status().isOk()).andReturn(); + + DeleteEntityValidation dev = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + + assertFalse(dev.getListIds().isEmpty()); + assertTrue(dev.getListDeleteNotPermitedIds().isEmpty()); + assertEquals(userRO.getUserId(), dev.getListIds().get(0)); + } + + @Test + public void testValidateDeleteLoggedUserNotOK() throws Exception { + + // login + MockHttpSession session = loginWithSystemAdmin(mvc); + // get list + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .with(csrf()) + .session(session)) + .andExpect(status().isOk()).andReturn(); + UserRO userRO = getLoggedUserData(mvc, session); + + // note system credential has id 3! + MvcResult resultDelete = mvc.perform(post(PATH_INTERNAL + "/validate-delete") + .with(csrf()) + .session(session) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .content("[\"" + userRO.getUserId() + "\"]")) + .andExpect(status().isOk()) + .andReturn(); + + DeleteEntityValidation res = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + + assertTrue(res.getListIds().isEmpty()); + assertEquals("Could not delete logged user!", res.getStringMessage()); + } + + + @Test + public void generateAccessTokenForUser() throws Exception { + MockHttpSession sessionAdmin = loginWithSystemAdmin(mvc); + UserRO userROAdmin = getLoggedUserData(mvc, sessionAdmin); + + MvcResult resultUsers = mvc.perform(get(PATH_INTERNAL) + .session(sessionAdmin) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(resultUsers.getResponse().getContentAsString(), ServiceResult.class); + Map userROToUpdate = (Map) res.getServiceEntities().stream() + .filter(userMap -> + StringUtils.equals(SG_USER2_USERNAME, (String) ((Map) userMap).get("username"))).findFirst().get(); + + MvcResult result = mvc.perform(post(PATH_INTERNAL + "/" + userROAdmin.getUserId() + "/generate-access-token-for/" + userROToUpdate.get("userId")) + .with(csrf()) + .session(sessionAdmin) + .content(SYS_ADMIN_PASSWD) + ).andExpect(status().isOk()).andReturn(); + + + AccessTokenRO resAccessToken = mapper.readValue(result.getResponse().getContentAsString(), AccessTokenRO.class); + assertNotNull(resAccessToken); + assertNotNull(resAccessToken.getIdentifier()); + assertNotNull(resAccessToken.getValue()); + + } + + @Test + public void changePasswordForUser() throws Exception { + MockHttpSession sessionAdmin = loginWithSystemAdmin(mvc); + UserRO userROAdmin = getLoggedUserData(mvc, sessionAdmin); + + MvcResult resultUsers = mvc.perform(get(PATH_INTERNAL) + .session(sessionAdmin) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(resultUsers.getResponse().getContentAsString(), ServiceResult.class); + Map userROToUpdate = (Map) res.getServiceEntities().stream() + .filter(userMap -> + StringUtils.equals(SG_USER2_USERNAME, (String) ((Map) userMap).get("username"))).findFirst().get(); + String newPassword = "TESTtest1234!@#$"; + + + PasswordChangeRO newPass = new PasswordChangeRO(); + newPass.setUsername(SG_USER2_USERNAME); + newPass.setCurrentPassword(SYS_ADMIN_PASSWD); + newPass.setNewPassword(newPassword); + assertNotEquals(newPassword, SG_USER2_PASSWD); + + mvc.perform(put(PATH_INTERNAL + "/" + userROAdmin.getUserId() + "//change-password-for/" + userROToUpdate.get("userId")) + .with(csrf()) + .session(sessionAdmin) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(newPass)) + ).andExpect(status().isOk()).andReturn(); + + // test to login with new password + MockHttpSession sessionNew = loginWithCredentials(mvc, SG_USER2_USERNAME, newPassword); + assertNotNull(sessionNew); + } + +} \ No newline at end of file diff --git a/smp-webapp/src/test/resources/webapp_integration_test_data.sql b/smp-webapp/src/test/resources/webapp_integration_test_data.sql index 69dffb3513d99c700803269af63cf90d080ba7f9..165ae31d0ab1dbc3ad585b69545cdad6a0692d96 100644 --- a/smp-webapp/src/test/resources/webapp_integration_test_data.sql +++ b/smp-webapp/src/test/resources/webapp_integration_test_data.sql @@ -23,7 +23,7 @@ insert into SMP_CONFIGURATION (PROPERTY, VALUE, CREATED_ON, LAST_UPDATED_ON) VAL insert into SMP_CONFIGURATION (PROPERTY, VALUE, CREATED_ON, LAST_UPDATED_ON) VALUES ('bdmsl.integration.enabled', 'false',CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); insert into SMP_CONFIGURATION (PROPERTY, VALUE, CREATED_ON, LAST_UPDATED_ON) VALUES ('smp.http.forwarded.headers.enabled', 'true',CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); insert into SMP_CONFIGURATION (PROPERTY, VALUE, CREATED_ON, LAST_UPDATED_ON) VALUES ('smp.automation.authentication.external.tls.clientCert.enabled', 'true',CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); -insert into SMP_CONFIGURATION (PROPERTY, VALUE, CREATED_ON, LAST_UPDATED_ON) VALUES ('identifiersBehaviour.scheme.mandatory', 'false',CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); +insert into SMP_CONFIGURATION (PROPERTY, VALUE, CREATED_ON, LAST_UPDATED_ON) VALUES ('identifiersBehaviour.scheme.mandatory', 'false', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); insert into SMP_USER (ID, USERNAME, PASSWORD, ACCESS_TOKEN_ID, ACCESS_TOKEN, ROLE, ACTIVE, CREATED_ON, LAST_UPDATED_ON) values (1, 'smp_admin', '$2a$06$AXSSUDJlpzzq/gPZb7eIBeb8Mi0.PTKqDjzujZH.bWPwj5.ePEInW','pat_smp_admin', '$2a$10$bP44Ij/mE6U6OUo/QrKCvOb7ouSClKnyE0Ak6t58BLob9OTI534IO', 'SMP_ADMIN', 1,CURRENT_TIMESTAMP(),CURRENT_TIMESTAMP()); diff --git a/smp-wls-deploy/pom.xml b/smp-wls-deploy/pom.xml index a634b415d69437ca3e43616b78ee4c10a2829393..6c1212061dc7882a79c7b127f0621e9743375d4f 100644 --- a/smp-wls-deploy/pom.xml +++ b/smp-wls-deploy/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>eu.europa.ec.edelivery</groupId> <artifactId>smp-modules</artifactId> - <version>4.2-SNAPSHOT</version> + <version>4.2-RC2-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>smp-wls-deploy</artifactId>