From c0a2eda7d6311b87584fd5187c496a1e7dba869c Mon Sep 17 00:00:00 2001 From: RIHTARSIC Joze <joze.rihtarsic@ext.ec.europa.eu> Date: Tue, 11 Apr 2023 20:51:35 +0200 Subject: [PATCH] Add domain member managing --- smp-angular/src/app/app.module.ts | 12 ++ .../credential-dialog.component.html | 2 +- .../credential-dialog.component.ts | 2 - smp-angular/src/app/common/global-lookups.ts | 4 +- smp-angular/src/app/smp.constants.ts | 32 +++- .../admin-domain/admin-domain.component.html | 5 +- .../domain-member-panel.component.html | 95 ++++++++++ .../domain-member-panel.component.scss | 20 ++ .../domain-member-panel.component.ts | 178 ++++++++++++++++++ .../member-dialog/member-dialog.component.css | 13 ++ .../member-dialog.component.html | 46 +++++ .../member-dialog/member-dialog.component.ts | 138 ++++++++++++++ .../member-dialog/member-type.enum.ts | 8 + .../member-dialog/search-user-ro.model.ts | 9 + .../domain-member-panel/member-ro.model.ts | 13 ++ .../membership-role.enum.ts | 7 + .../domain-member-panel/membership.service.ts | 71 +++++++ .../domain-member-panel/table-result.model.ts | 8 + .../domain-panel/domain-panel.component.ts | 1 + .../app/system-settings/user/user-ro.model.ts | 1 + .../user-profile/user-profile.component.html | 4 +- .../DBDomainMemberToMemberROConverter.java | 27 +++ .../DBUserToSearchUserROConverter.java | 24 +++ .../smp/data/dao/DomainMemberDao.java | 51 ++++- .../ec/edelivery/smp/data/dao/QueryNames.java | 11 ++ .../ec/edelivery/smp/data/dao/UserDao.java | 27 +++ .../smp/data/model/user/DBDomainMember.java | 38 +++- .../edelivery/smp/data/model/user/DBUser.java | 12 ++ .../ec/edelivery/smp/data/ui/MemberRO.java | 52 +++++ .../edelivery/smp/data/ui/SearchUserRO.java | 32 ++++ .../services/ui/UIDomainPublicService.java | 84 ++++++++- .../smp/services/ui/UIUserService.java | 22 ++- .../smp/data/dao/DomainMemberDaoTest.java | 101 +++++++--- .../ui/UIUserServiceIntegrationTest.java | 3 - .../mysql-4.1_integration_test_data.sql | 4 + .../smp/auth/SMPAuthorizationService.java | 27 ++- .../edelivery/smp/ui/ResourceConstants.java | 1 + .../smp/ui/external/DomainResource.java | 61 +++++- .../smp/ui/external/UserResource.java | 12 ++ .../smp/auth/SMPAuthorizationServiceTest.java | 4 +- 40 files changed, 1188 insertions(+), 74 deletions(-) create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.html create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.scss create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.css create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.html create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-type.enum.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/search-user-ro.model.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-ro.model.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership-role.enum.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership.service.ts create mode 100644 smp-angular/src/app/system-settings/admin-domain/domain-member-panel/table-result.model.ts create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDomainMemberToMemberROConverter.java create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToSearchUserROConverter.java create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SearchUserRO.java diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index a0ac74f8b..dca9f8ac4 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -126,6 +126,14 @@ import { NGX_MAT_MOMENT_FORMATS, NgxMatMomentAdapter, NgxMatMomentModule } from "@angular-material-components/moment-adapter"; +import { + DomainMemberPanelComponent +} from "./system-settings/admin-domain/domain-member-panel/domain-member-panel.component"; +import { + MemberDialogComponent +} from "./system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MembershipService} from "./system-settings/admin-domain/domain-member-panel/membership.service"; @NgModule({ @@ -157,6 +165,7 @@ import { DomainPanelComponent, DomainSmlIntegrationPanelComponent, DomainDetailsDialogComponent, + DomainMemberPanelComponent, DomainResourceTypePanelComponent, DomainSelectorComponent, ExpiredPasswordDialogComponent, @@ -168,6 +177,7 @@ import { KeystoreEditDialogComponent, KeystoreImportDialogComponent, LoginComponent, + MemberDialogComponent, NavTree, NavTreeMenu, ObjectPropertiesDialogComponent, @@ -232,6 +242,7 @@ import { NgxMatMomentModule, ReactiveFormsModule, routing, + MatAutocompleteModule, ], providers: [ AdminDomainService, @@ -244,6 +255,7 @@ import { CertificateService, DatePipe, DomainService, + MembershipService, DownloadService, ExtensionService, GlobalLookups, diff --git a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html index 0ef1bac35..696ae7075 100644 --- a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html +++ b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html @@ -7,7 +7,7 @@ {{message}} </div> - <div *ngIf="!isReadOnly" class="panel" [formGroup]="credentialForm" (ngSubmit)="submitForm()"> + <div *ngIf="!isReadOnly" class="panel" [formGroup]="credentialForm" > <mat-form-field style="width: 100%"> <mat-label>Description</mat-label> diff --git a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts index f86fc02b9..72c80a152 100644 --- a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts @@ -152,10 +152,8 @@ export class CredentialDialogComponent { storeCertificateCredentials() { this.clearAlert(); - this.userService.storeUserCertificateCredential(this.initCredential); this.closeDialog(); - } diff --git a/smp-angular/src/app/common/global-lookups.ts b/smp-angular/src/app/common/global-lookups.ts index a1c3de031..50262cb1b 100644 --- a/smp-angular/src/app/common/global-lookups.ts +++ b/smp-angular/src/app/common/global-lookups.ts @@ -72,12 +72,12 @@ export class GlobalLookups { } public refreshDomainLookupFromPublic() { - let domainUrl = SmpConstants.REST_PUBLIC_DOMAIN_SEARCH; + let domainUrl = SmpConstants.REST_PUBLIC_DOMAIN_MANAGE; this.refreshDomainLookup(domainUrl); } public refreshDomainLookupForLoggedUser() { - let domainUrl = SmpConstants.REST_PUBLIC_DOMAIN_SEARCH; + let domainUrl = SmpConstants.REST_PUBLIC_DOMAIN_MANAGE; // for authenticated admin use internal url which returns more data! if (this.securityService.isCurrentUserSystemAdmin()) { domainUrl = SmpConstants.REST_INTERNAL_DOMAIN_MANAGE_DEPRECATED; diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts index fc490b5fe..fcddea6f7 100644 --- a/smp-angular/src/app/smp.constants.ts +++ b/smp-angular/src/app/smp.constants.ts @@ -10,11 +10,14 @@ export class SmpConstants { public static readonly PATH_ACTION_DELETE = 'delete'; public static readonly PATH_ACTION_UPDATE = 'update'; public static readonly PATH_ACTION_CREATE = 'create'; + + public static readonly PATH_ACTION_ADD = 'add'; public static readonly PATH_ACTION_UPDATE_RESOURCE_TYPES = 'update-resource-types'; public static readonly PATH_ACTION_UPDATE_SML_INTEGRATION = 'update-sml-integration-data'; public static readonly PATH_PARAM_ENC_USER_ID = '{user-id}'; - public static readonly PATH_PARAM_ENC_DOMAIN_ID = '{domainr-id}'; + public static readonly PATH_PARAM_ENC_DOMAIN_ID = '{domain-id}'; + public static readonly PATH_PARAM_ENC_MEMBER_ID = '{member-id}'; public static readonly PATH_PARAM_CERT_ALIAS = '{cert-alias}'; public static readonly PATH_PARAM_ENC_CREDENTIAL_ID = '{credential-id}'; public static readonly PATH_PARAM_ENC_MANAGED_USER_ID = '{managed-user-id}'; @@ -29,14 +32,29 @@ export class SmpConstants { public static readonly REST_PUBLIC = 'public/rest/'; public static readonly REST_INTERNAL = 'internal/rest/'; public static readonly REST_PUBLIC_SEARCH_SERVICE_GROUP = SmpConstants.REST_PUBLIC + 'search'; - public static readonly REST_PUBLIC_DOMAIN_SEARCH = SmpConstants.REST_PUBLIC + 'domain'; + public static readonly REST_PUBLIC_DOMAIN_MANAGE = SmpConstants.REST_PUBLIC + 'domain'; + public static readonly REST_PUBLIC_DOMAIN_MEMBERS = SmpConstants.REST_PUBLIC_DOMAIN_MANAGE + + "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/"+ SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + "/"+ "members"; + + public static readonly REST_PUBLIC_DOMAIN_MEMBERS_ADD = SmpConstants.REST_PUBLIC_DOMAIN_MANAGE + + "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/"+ SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + "/"+ "member"; + + public static readonly REST_PUBLIC_DOMAIN_MEMBERS_DELETE = SmpConstants.REST_PUBLIC_DOMAIN_MANAGE + + "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/"+ SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + "/"+ "member" + +"/" + SmpConstants.PATH_PARAM_ENC_MEMBER_ID +"/" + SmpConstants.PATH_ACTION_DELETE; + + + public static readonly REST_PUBLIC_APPLICATION_INFO = SmpConstants.REST_PUBLIC + 'application/info'; public static readonly REST_PUBLIC_APPLICATION_CONFIG = SmpConstants.REST_PUBLIC + 'application/config'; // user public services public static readonly REST_PUBLIC_USER = SmpConstants.REST_PUBLIC + 'user'; + public static readonly REST_PUBLIC_USER_UPDATE = SmpConstants.REST_PUBLIC_USER + "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/"; public static readonly REST_PUBLIC_USER_GENERATE_ACCESS_TOKEN = SmpConstants.REST_PUBLIC_USER_UPDATE + 'generate-access-token'; public static readonly REST_PUBLIC_USER_CHANGE_PASSWORD = SmpConstants.REST_PUBLIC_USER_UPDATE + 'change-password'; + + public static readonly REST_PUBLIC_USER_SEARCH = SmpConstants.REST_PUBLIC_USER + "/" + SmpConstants.PATH_PARAM_ENC_USER_ID + "/" + 'search' // 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_CERT_VALIDATE = SmpConstants.REST_PUBLIC_TRUSTSTORE + 'validate-certificate'; @@ -66,21 +84,21 @@ export class SmpConstants { '/' + SmpConstants.PATH_PARAM_ENC_USER_ID; public static readonly REST_INTERNAL_DOMAIN_MANAGE_DELETE = SmpConstants.REST_INTERNAL + 'domain' + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' +SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_DELETE; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_DELETE; public static readonly REST_INTERNAL_DOMAIN_MANAGE_UPDATE = SmpConstants.REST_INTERNAL + 'domain' + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' +SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_UPDATE; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_UPDATE; public static readonly REST_INTERNAL_DOMAIN_MANAGE_CREATE = SmpConstants.REST_INTERNAL + 'domain' + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + SmpConstants.PATH_ACTION_CREATE; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + SmpConstants.PATH_ACTION_CREATE; public static readonly REST_INTERNAL_DOMAIN_MANAGE_UPDATE_SML_INTEGRATION = SmpConstants.REST_INTERNAL + 'domain' + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' +SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_UPDATE_SML_INTEGRATION; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_UPDATE_SML_INTEGRATION; public static readonly REST_INTERNAL_DOMAIN_MANAGE_UPDATE_RESOURCE_TYPES = SmpConstants.REST_INTERNAL + 'domain' + - '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' +SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_UPDATE_RESOURCE_TYPES; + '/' + SmpConstants.PATH_PARAM_ENC_USER_ID + '/' + SmpConstants.PATH_PARAM_ENC_DOMAIN_ID + '/' + SmpConstants.PATH_ACTION_UPDATE_RESOURCE_TYPES; public static readonly REST_INTERNAL_EXTENSION_MANAGE = SmpConstants.REST_INTERNAL + 'extension'; public static readonly REST_INTERNAL_PROPERTY_MANAGE = SmpConstants.REST_INTERNAL + 'property'; diff --git a/smp-angular/src/app/system-settings/admin-domain/admin-domain.component.html b/smp-angular/src/app/system-settings/admin-domain/admin-domain.component.html index 6659889fa..46d2558b7 100644 --- a/smp-angular/src/app/system-settings/admin-domain/admin-domain.component.html +++ b/smp-angular/src/app/system-settings/admin-domain/admin-domain.component.html @@ -30,7 +30,9 @@ ></domain-sml-integration-panel> </mat-tab> <mat-tab label="Members"> - Content 3 + <domain-member-panel #domainMemberPanelComponent + [domain]="selected" + ></domain-member-panel> </mat-tab> </mat-tab-group> </data-panel> @@ -46,7 +48,6 @@ <mat-toolbar-row class="mat-elevation-z5"> <button mat-raised-button mat-flat-button color="primary" - onDiscardNew (click)="onCreateDomainClicked()" >Create domain </button> diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.html b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.html new file mode 100644 index 000000000..21ab9c182 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.html @@ -0,0 +1,95 @@ +<form [formGroup]="domainForm" > + <div id="domain-member-panel" class="mat-elevation-z2"> + <mat-toolbar> + <mat-toolbar-row> + <button id="addMemberButton" mat-raised-button (click)="onAddMemberButtonClicked()" color="primary" + [disabled]="inviteMemberDisabled" + > + <mat-icon>people</mat-icon> + <span>Invite member</span> + </button> + <button id="editButton" mat-raised-button (click)="onEditSelectedButtonClicked()" color="primary" + [disabled]="!selectedMember"> + <mat-icon>edit</mat-icon> + <span>Edit selected member</span> + </button> + <button id="deleteButton" mat-raised-button (click)="onDeleteSelectedButtonClicked()" color="primary" + [disabled]="!selectedMember"> + <mat-icon>delete</mat-icon> + <span>Delete selected member</span> + </button> + + + + + </mat-toolbar-row> + </mat-toolbar> + <h3>Domain members</h3> + <div class="domain-member-container mat-elevation-z8"> + <div class="domain-member-loading-shade" + *ngIf="isLoadingResults"> + <mat-spinner *ngIf="isLoadingResults"></mat-spinner> + </div> + + <div class="domain-member-table-container"> + + <mat-form-field id="domain-member-filter" > + <mat-label>Member filter</mat-label> + <input matInput (keyup)="applyMemberFilter($event)" + placeholder="Member filter" + [disabled]="domainNotSelected" + #inputDomainMemberFilter> + </mat-form-field> + + <table class="mat-elevation-z2" mat-table [dataSource]="data"> + + <ng-container matColumnDef="username"> + <th mat-header-cell *matHeaderCellDef>username</th> + <td mat-cell *matCellDef="let row">{{row.username}}</td> + </ng-container> + + <ng-container matColumnDef="fullName"> + <th mat-header-cell *matHeaderCellDef>fullName</th> + <td mat-cell *matCellDef="let row">{{row.fullName}}</td> + </ng-container> + + <ng-container matColumnDef="roleType"> + <th mat-header-cell *matHeaderCellDef>roleType</th> + <td mat-cell *matCellDef="let row">{{row.roleType}}</td> + </ng-container> + + <ng-container matColumnDef="memberOf"> + <th mat-header-cell *matHeaderCellDef>memberOf</th> + <td mat-cell *matCellDef="let row">{{row.memberOf}}</td> + </ng-container> + + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + + + <tr mat-row *matRowDef="let odd = odd; let row; columns: displayedColumns;" + (click)="memberSelected(row)" + [ngClass]="{'datatable-row-selected': row==selectedMember,'datatable-row-odd': odd}" + ></tr> + + <tr class="mat-row" *matNoDataRow> + <td *ngIf="inputDomainMemberFilter.value;else noDataFound" class="mat-cell" colspan="2">No direct members matching the filter + "{{inputDomainMemberFilter.value}}" + </td> + <ng-template #noDataFound> + <td class="mat-cell" colspan="2">No direct members for the domain</td> + </ng-template> + </tr> + </table> + </div> + + <mat-paginator [length]="resultsLength" + (page)="onPageChanged($event)" + [pageSize]="5" + [pageSizeOptions]="[5, 10, 25]" + [disabled]="domainNotSelected" + aria-label="Select pages"></mat-paginator> + </div> + + </div> +</form> diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.scss b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.scss new file mode 100644 index 000000000..e4c6e7393 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.scss @@ -0,0 +1,20 @@ + + + +.domain-member-loading-shade { + position: absolute; + top: 0; + left: 0; + bottom: 56px; + right: 0; + background: rgba(0, 0, 0, 0.15); + z-index: 1; + display: flex; + align-items: center; + justify-content: center; +} + +#domain-member-filter { + width: 100%; + padding-top: 1em; +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.ts new file mode 100644 index 000000000..77ed57a39 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/domain-member-panel.component.ts @@ -0,0 +1,178 @@ +import {Component, EventEmitter, Input, Output, ViewChild,} from '@angular/core'; +import {DomainRo} from "../domain-ro.model"; +import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; +import {AdminDomainService} from "../admin-domain.service"; +import {AlertMessageService} from "../../../common/alert-message/alert-message.service"; +import {MatDialog} from "@angular/material/dialog"; +import {BeforeLeaveGuard} from "../../../window/sidenav/navigation-on-leave-guard"; +import {MatPaginator, PageEvent} from "@angular/material/paginator"; +import {MemberRo} from "./member-ro.model"; +import {finalize} from "rxjs/operators"; +import {TableResult} from "./table-result.model"; +import {MemberDialogComponent} from "./member-dialog/member-dialog.component"; +import {MembershipService} from "./membership.service"; +import {MembershipRoleEnum} from "./membership-role.enum"; +import {MemberTypeEnum} from "./member-dialog/member-type.enum"; + + +@Component({ + selector: 'domain-member-panel', + templateUrl: './domain-member-panel.component.html', + styleUrls: ['./domain-member-panel.component.scss'] +}) +export class DomainMemberPanelComponent implements BeforeLeaveGuard { + @Output() onSaveSmlIntegrationDataEvent: EventEmitter<DomainRo> = new EventEmitter(); + private _domain: DomainRo; + + + domainForm: FormGroup; + + displayedColumns: string[] = ['username', 'fullName', 'roleType', 'memberOf']; + + data: MemberRo[] = []; + + selectedMember: MemberRo; + + filter: any = {}; + + filterName: string; + + resultsLength = 0; + isLoadingResults = false; + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor(private domainService: AdminDomainService, + private membershipService: MembershipService, + private alertService: AlertMessageService, + private dialog: MatDialog, + private formBuilder: FormBuilder) { + + this.domainForm = formBuilder.group({ + 'domainCode': new FormControl({value: '', readonly: true}) + }); + + } + + ngAfterViewInit() { + if (!!this._domain) { + this.loadTableData(); + } + } + + get domain(): DomainRo { + let newDomain = {...this._domain}; + + return newDomain; + } + + @Input() set domain(value: DomainRo) { + this._domain = value; + + if (!!value) { + this.loadTableData(); + } else { + this.isLoadingResults = false; + } + } + + + onPageChanged(page: PageEvent) { + this.loadTableData(); + } + + loadTableData() { + if (!this._domain) { + return; + } + + this.isLoadingResults = true; + this.membershipService.getDomainMembersObservable(this._domain.domainId, this.filter, this.paginator.pageIndex, this.paginator.pageSize) + .pipe( + finalize(() => { + this.isLoadingResults = false; + })) + .subscribe((result: TableResult<MemberRo>) => { + this.data = [...result.serviceEntities]; + this.resultsLength = result.count; + this.isLoadingResults = false; + } + ); + } + + applyMemberFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; + this.filter["filter"] = filterValue.trim().toLowerCase(); + this.refresh(); + } + + get submitButtonEnabled(): boolean { + return this.domainForm.valid && this.domainForm.dirty; + } + + get inviteMemberDisabled(): boolean { + return !this._domain; + } + + public memberSelected(member: MemberRo) { + this.selectedMember = member; + } + + public onAddMemberButtonClicked() { + // add member + this.dialog.open(MemberDialogComponent, { + data: { + domain: this._domain, + member: this.createDomainMember(), + formTitle: "Invite new member to domain" + } + }).afterClosed().subscribe(value => { + this.refresh(); + }); + } + + public refresh() { + if (this.paginator) { + this.paginator.firstPage(); + } + this.loadTableData(); + } + + public createDomainMember(): MemberRo { + return { + memberOf: MemberTypeEnum.DOMAIN, + roleType: MembershipRoleEnum.VIEWER + } as MemberRo + } + + + public onEditSelectedButtonClicked() { + this.dialog.open(MemberDialogComponent, { + data: { + domain: this._domain, + member: this.selectedMember, + formTitle: "Edit member role for domain" + } + }).afterClosed().subscribe(value => { + this.refresh(); + }); + } + public onDeleteSelectedButtonClicked() { + this.membershipService.deleteMemberFromDomain(this._domain.domainId, this.selectedMember).subscribe(value => { + this.refresh(); + });; + } + + isDirty(): boolean { + return this.domainForm.dirty; + } + + get domainNotSelected() { + return !this._domain + } +} + + + + + + diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.css b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.css new file mode 100644 index 000000000..1c224cec9 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.css @@ -0,0 +1,13 @@ +.empty-field-label { + color: gray; +} + + +#custom-file-upload { + display: none; +} + +.custom-file-upload { + display: inline-block; + cursor: pointer; +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.html b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.html new file mode 100644 index 000000000..66c9c2922 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.html @@ -0,0 +1,46 @@ +<h2 mat-dialog-title>{{formTitle}}</h2> +<mat-dialog-content style="width:700px"> + <form [formGroup]="memberForm"> + <b *ngIf="newMode">You're inviting members to the SMP project.</b> + <mat-form-field style="width: 100%"> + <mat-label>Choose User to invite</mat-label> + <input id="member-user" type="text" matInput formControlName="member-user" + [matAutocomplete]="auto" (keyup)="applyUserFilter($event)" + required> + <mat-autocomplete #auto="matAutocomplete"> + <mat-option *ngFor="let user of filteredOptions | async" [value]="user.username"> + {{user.username}} + </mat-option> + </mat-autocomplete> + <mat-hint *ngIf="newMode">Type username or name to locate the user and select user from the list</mat-hint> + </mat-form-field> + + + <mat-form-field style="width:100%"> + <mat-label>Select role for the user</mat-label> + <mat-select placeholder="Role for the member" + formControlName="member-roleType" + name="Role type" + matTooltip="Role type for the member." + id="member-user_id" required> + <mat-option *ngFor="let role of memberRoles" + [value]="role.value"> + {{role.key}} + </mat-option> + </mat-select> + <mat-hint> Chose member role</mat-hint> + </mat-form-field> + </form> +</mat-dialog-content> +<mat-dialog-actions> + <button mat-raised-button color="primary" (click)="closeDialog()"> + <mat-icon>cancel</mat-icon> + <span>Close</span> + </button> + <button id="saveButton" mat-raised-button (click)="onSaveButtonClicked()" color="primary" + [disabled]="!submitButtonEnabled"> + <mat-icon>save</mat-icon> + <span>Save</span> + </button> +</mat-dialog-actions> + diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.ts new file mode 100644 index 000000000..ae0887867 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-dialog.component.ts @@ -0,0 +1,138 @@ +import {Component, Inject, Input, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; +import {MembershipRoleEnum} from "../membership-role.enum"; +import {Observable} from "rxjs"; +import {SearchUserRo} from "./search-user-ro.model"; +import {MembershipService} from "../membership.service"; +import {MemberRo} from "../member-ro.model"; +import {DomainRo} from "../../domain-ro.model"; +import {MemberTypeEnum} from "./member-type.enum"; +import {AlertMessageService} from "../../../../common/alert-message/alert-message.service"; + + +@Component({ + templateUrl: './member-dialog.component.html', + styleUrls: ['./member-dialog.component.css'] +}) +export class MemberDialogComponent implements OnInit { + + formTitle = "Member dialog"; + memberForm: FormGroup; + + message: string; + messageType: string = "alert-error"; + + currentFilter: string; + + _currentMember: MemberRo; + _currentDomain: DomainRo; + + filteredOptions: Observable<SearchUserRo[]>; + + readonly memberRoles = Object.keys(MembershipRoleEnum).map(el => { + return {key: el, value: MembershipRoleEnum[el]} + }); + + + constructor(@Inject(MAT_DIALOG_DATA) public data: any, + private membershipService: MembershipService, + public dialogRef: MatDialogRef<MemberDialogComponent>, + private alertService: AlertMessageService, + private formBuilder: FormBuilder + ) { + dialogRef.disableClose = true;//disable default close operation + this.formTitle = data.formTitle; + this._currentDomain = data.domain; + + this.memberForm = formBuilder.group({ + 'member-user': new FormControl({value: null}), + 'member-fullName': new FormControl({value: null}), + 'member-memberOf': new FormControl({value: null}), + 'member-roleType': new FormControl({value: null}) + }); + this.member = { + ...data.member + }; + this.currentFilter = ""; + } + + get member(): MemberRo { + let member = {...this._currentMember}; + member.username = this.memberForm.get('member-user').value; + member.fullName = this.memberForm.get('member-fullName').value; + member.memberOf = this.memberForm.get('member-memberOf').value; + member.roleType = this.memberForm.get('member-roleType').value; + return member; + } + + get newMode(): boolean { + return !this._currentMember?.memberId; + } + + @Input() set member(value: MemberRo) { + this._currentMember = value; + + if (!!value) { + this.memberForm.controls['member-user'].setValue(value.username); + // control disable enable did not work?? + if (this.newMode) { + this.memberForm.controls['member-user'].enable(); + } else { + this.memberForm.controls['member-user'].disable(); + } + + this.memberForm.controls['member-fullName'].setValue(value.fullName); + this.memberForm.controls['member-memberOf'].setValue(value.memberOf); + this.memberForm.controls['member-roleType'].setValue(value.roleType); + + } else { + this.memberForm.controls['member-user'].setValue(""); + this.memberForm.controls['member-fullName'].setValue(""); + this.memberForm.controls['member-memberOf'].setValue(""); + this.memberForm.controls['member-roleType'].setValue(""); + } + + } + + ngOnInit() { + this.filteredOptions = this.membershipService.getUserLookupObservable(""); + } + + applyUserFilter(event: Event) { + let filterValue = (event.target as HTMLInputElement).value; + if (this.currentFilter == filterValue) { + // ignore update + return; + } + this.currentFilter = filterValue + this.filteredOptions = this.membershipService.getUserLookupObservable(filterValue.trim().toLowerCase()); + } + + clearAlert() { + this.message = null; + this.messageType = null; + } + + + closeDialog() { + this.dialogRef.close() + } + + get submitButtonEnabled(): boolean { + return this.memberForm.valid && this.memberForm.dirty; + } + + public onSaveButtonClicked() { + let member = this.member; + if (member.memberOf == MemberTypeEnum.DOMAIN) { + this.membershipService.addEditMemberToDomain(this._currentDomain.domainId, this.member).subscribe((member: MemberRo) => { + if (!!member) { + this.closeDialog(); + } + }, (error)=> { + this.alertService.error(error.error?.errorDescription) + }); + } + } +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-type.enum.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-type.enum.ts new file mode 100644 index 000000000..734fb7c97 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/member-type.enum.ts @@ -0,0 +1,8 @@ +export enum MemberTypeEnum { + /** + * Resource, group of domain is marked as PUBLIC. + */ + DOMAIN= 'DOMAIN', + GROUP= 'GROUP', + RESOURCE= 'RESOURCE', +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/search-user-ro.model.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/search-user-ro.model.ts new file mode 100644 index 000000000..84ff26cd3 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-dialog/search-user-ro.model.ts @@ -0,0 +1,9 @@ +import {SearchTableEntity} from "../../../../common/search-table/search-table-entity.model"; + +export interface SearchUserRo extends SearchTableEntity { + userId: string, + username: string; + fullName: string; + +} + diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-ro.model.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-ro.model.ts new file mode 100644 index 000000000..d42464599 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/member-ro.model.ts @@ -0,0 +1,13 @@ + +import {MembershipRoleEnum} from "./membership-role.enum"; +import {SearchTableEntity} from "../../../common/search-table/search-table-entity.model"; +import {MemberTypeEnum} from "./member-dialog/member-type.enum"; + +export interface MemberRo extends SearchTableEntity { + + memberId:string; + username:string; + memberOf:MemberTypeEnum; + fullName:string; + roleType:MembershipRoleEnum; +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership-role.enum.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership-role.enum.ts new file mode 100644 index 000000000..8f8bf7c64 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership-role.enum.ts @@ -0,0 +1,7 @@ +export enum MembershipRoleEnum { + /** + * Resource, group of domain is marked as PUBLIC. + */ + VIEWER= 'VIEWER', + ADMIN= 'ADMIN' +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership.service.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership.service.ts new file mode 100644 index 000000000..364f024dc --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/membership.service.ts @@ -0,0 +1,71 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {SearchTableResult} from "../../../common/search-table/search-table-result.model"; +import {User} from "../../../security/user.model"; +import {HttpClient, HttpParams} from "@angular/common/http"; +import {SmpConstants} from "../../../smp.constants"; +import {SecurityService} from "../../../security/security.service"; +import {AlertMessageService} from "../../../common/alert-message/alert-message.service"; +import {MemberRo} from "./member-ro.model"; +import {TableResult} from "./table-result.model"; +import {SearchUserRo} from "./member-dialog/search-user-ro.model"; +import {MembershipRoleEnum} from "./membership-role.enum"; + + +@Injectable() +export class MembershipService { + + + constructor( + private http: HttpClient, + private securityService: SecurityService, + private alertService: AlertMessageService) { + } + + + getDomainMembersObservable(domainID: string, filter: any, page: number, pageSize: number): Observable<SearchTableResult> { + const currentUser: User = this.securityService.getCurrentUser(); + + let params: HttpParams = new HttpParams() + .set('page', page.toString()) + .set('pageSize', pageSize.toString()); + + for (let filterProperty in filter) { + if (filter.hasOwnProperty(filterProperty)) { + // must encode else problem with + sign + params = params.set(filterProperty, encodeURIComponent(filter[filterProperty])); + } + } + + return this.http.get<TableResult<MemberRo>>(SmpConstants.REST_PUBLIC_DOMAIN_MEMBERS + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domainID), {params}); + } + + getUserLookupObservable(filter: string): Observable<SearchUserRo[]> { + const currentUser: User = this.securityService.getCurrentUser(); + let params: HttpParams = new HttpParams() + .set('filter', filter); + return this.http.get<SearchUserRo[]>(SmpConstants.REST_PUBLIC_USER_SEARCH + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId), {params}); + } + + + addEditMemberToDomain(domainId: string, member: MemberRo): Observable<MemberRo> { + const currentUser: User = this.securityService.getCurrentUser(); + + return this.http.put<MemberRo>(SmpConstants.REST_PUBLIC_DOMAIN_MEMBERS_ADD + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domainId),member); + } + + deleteMemberFromDomain( domainId: string, member: MemberRo): Observable<MemberRo> { + const currentUser: User = this.securityService.getCurrentUser(); + + return this.http.delete<MemberRo>(SmpConstants.REST_PUBLIC_DOMAIN_MEMBERS_DELETE + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domainId) + .replace(SmpConstants.PATH_PARAM_ENC_MEMBER_ID, member.memberId)); + } + +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/table-result.model.ts b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/table-result.model.ts new file mode 100644 index 000000000..ae03b2514 --- /dev/null +++ b/smp-angular/src/app/system-settings/admin-domain/domain-member-panel/table-result.model.ts @@ -0,0 +1,8 @@ + + +export interface TableResult<T> { + serviceEntities: T[]; + pageSize: number; + count: number; + filter: any; +} diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.ts b/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.ts index 312422f3d..eaac8abf2 100644 --- a/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.ts +++ b/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.ts @@ -8,6 +8,7 @@ import {CertificateRo} from "../../user/certificate-ro.model"; import {VisibilityEnum} from "../../../common/enums/visibility.enum"; import {ResourceDefinitionRo} from "../../admin-extension/resource-definition-ro.model"; import {BeforeLeaveGuard} from "../../../window/sidenav/navigation-on-leave-guard"; +import {UserRo} from "../../user/user-ro.model"; @Component({ diff --git a/smp-angular/src/app/system-settings/user/user-ro.model.ts b/smp-angular/src/app/system-settings/user/user-ro.model.ts index 03a6a62ec..fbcfe70c9 100644 --- a/smp-angular/src/app/system-settings/user/user-ro.model.ts +++ b/smp-angular/src/app/system-settings/user/user-ro.model.ts @@ -4,6 +4,7 @@ import {CertificateRo} from './certificate-ro.model'; export interface UserRo extends SearchTableEntity { userId?: string username: string; + fullName?: string; emailAddress: string; accessTokenId?: string; passwordExpireOn?: Date; diff --git a/smp-angular/src/app/user-settings/user-profile/user-profile.component.html b/smp-angular/src/app/user-settings/user-profile/user-profile.component.html index 1272873cd..7b00ffa2d 100644 --- a/smp-angular/src/app/user-settings/user-profile/user-profile.component.html +++ b/smp-angular/src/app/user-settings/user-profile/user-profile.component.html @@ -1,5 +1,5 @@ <div id="user-profile-panel"> - <form [formGroup]="userForm" (ngSubmit)="onSaveButtonClicked()"> + <form [formGroup]="userForm" > <data-panel title="Account" text="Account data"> <mat-form-field class="user-profile-pane-field"> @@ -102,7 +102,7 @@ </div> </data-panel> </form> - <form [formGroup]="userCredentialForm" (ngSubmit)="onSaveButtonClicked()"> + <form [formGroup]="userCredentialForm"> <data-panel *ngIf="true" title="Username/password credentials" text="Reset username password for the UI login"> <div style="display: flex;flex-flow: row wrap;"> diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDomainMemberToMemberROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDomainMemberToMemberROConverter.java new file mode 100644 index 000000000..8f1918328 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDomainMemberToMemberROConverter.java @@ -0,0 +1,27 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.user.DBDomainMember; +import eu.europa.ec.edelivery.smp.data.ui.MemberRO; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class DBDomainMemberToMemberROConverter implements Converter<DBDomainMember, MemberRO> { + + @Override + public MemberRO convert(DBDomainMember source) { + MemberRO target = new MemberRO(); + target.setMemberOf("DOMAIN"); + target.setUsername(source.getUser().getUsername()); + target.setFullName(source.getUser().getFullName()); + target.setRoleType(source.getRole()); + target.setMemberId(SessionSecurityUtils.encryptedEntityId(source.getId())); + + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToSearchUserROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToSearchUserROConverter.java new file mode 100644 index 000000000..83dfba2c4 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToSearchUserROConverter.java @@ -0,0 +1,24 @@ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.user.DBUser; +import eu.europa.ec.edelivery.smp.data.ui.SearchUserRO; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class DBUserToSearchUserROConverter implements Converter<DBUser, SearchUserRO> { + + @Override + public SearchUserRO convert(DBUser source) { + SearchUserRO target = new SearchUserRO(); + target.setUsername(source.getUsername()); + target.setFullName(source.getFullName()); + target.setUserId(SessionSecurityUtils.encryptedEntityId(source.getId())); + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDao.java index a9bc27da8..f04fc0c7d 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDao.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDao.java @@ -16,10 +16,8 @@ package eu.europa.ec.edelivery.smp.data.dao; import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.user.DBDomainMember; -import eu.europa.ec.edelivery.smp.data.model.user.DBGroupMember; import eu.europa.ec.edelivery.smp.data.model.user.DBUser; -import eu.europa.ec.edelivery.smp.logging.SMPLogger; -import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Repository; import javax.persistence.TypedQuery; @@ -37,15 +35,16 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*; public class DomainMemberDao extends BaseDao<DBDomainMember> { - public boolean isUserDomainMember(DBUser user, DBDomain domain){ + public boolean isUserDomainMember(DBUser user, DBDomain domain) { return isUserDomainsMember(user.getId(), Collections.singletonList(domain.getId())); } - public boolean isUserDomainsMember(DBUser user, List<DBDomain> domainList){ + + public boolean isUserDomainsMember(DBUser user, List<DBDomain> domainList) { List<Long> domainIds = domainList.stream().map(dbDomain -> dbDomain.getId()).collect(Collectors.toList()); return isUserDomainsMember(user.getId(), domainIds); } - public boolean isUserDomainsMember(Long userId, List<Long> domainIdList){ + public boolean isUserDomainsMember(Long userId, List<Long> domainIdList) { TypedQuery<Long> query = memEManager.createNamedQuery(QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS_COUNT, Long.class); query.setParameter(PARAM_USER_ID, userId); @@ -58,7 +57,45 @@ public class DomainMemberDao extends BaseDao<DBDomainMember> { query.setParameter(PARAM_USER_ID, userId); query.setParameter(PARAM_DOMAIN_IDS, domainsIds); - return query.getResultList().stream().anyMatch(member ->member.getRole() == roleType ); + return query.getResultList().stream().anyMatch(member -> member.getRole() == roleType); + } + + public List<DBDomainMember> getDomainMembers(Long domainId, int iPage, int iPageSize, String filter) { + boolean hasFilter = StringUtils.isNotBlank(filter); + TypedQuery<DBDomainMember> query = memEManager.createNamedQuery(hasFilter ? + QUERY_DOMAIN_MEMBERS_FILTER : QUERY_DOMAIN_MEMBERS, DBDomainMember.class); + + if (iPageSize> -1 && iPage >-1 ) { + query.setFirstResult(iPage * iPageSize); + } + if (iPageSize> 0) { + query.setMaxResults(iPageSize); + } + query.setParameter(PARAM_DOMAIN_ID, domainId); + if (hasFilter) { + query.setParameter(PARAM_USER_FILTER, "%"+StringUtils.trim(filter)+"%"); + } + return query.getResultList(); + } + + public Long getDomainMemberCount(Long domainId, String filter) { + boolean hasFilter = StringUtils.isNotBlank(filter); + TypedQuery<Long> query = memEManager.createNamedQuery(hasFilter ? QUERY_DOMAIN_MEMBERS_FILTER_COUNT : QUERY_DOMAIN_MEMBERS_COUNT, Long.class); + query.setParameter(PARAM_DOMAIN_ID, domainId); + if (hasFilter) { + query.setParameter(PARAM_USER_FILTER, "%"+StringUtils.trim(filter)+"%"); + } + return query.getSingleResult(); + } + + + public DBDomainMember addMemberToDomain(DBDomain domain, DBUser user , MembershipRoleType role){ + DBDomainMember domainMember = new DBDomainMember(); + domainMember.setRole(role); + domainMember.setUser(user); + domainMember.setDomain(domain); + domainMember = merge(domainMember); + return domainMember; } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java index 91adae188..aa93f3db7 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java @@ -25,7 +25,11 @@ public class QueryNames { public static final String QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS_COUNT = "DBDomainMember.getByUserAndDomainsCount"; public static final String QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS = "DBDomainMember.getByUserAndDomains"; + public static final String QUERY_DOMAIN_MEMBERS_COUNT = "DBDomainMember.getByDomainCount"; + public static final String QUERY_DOMAIN_MEMBERS_FILTER_COUNT = "DBDomainMember.getByDomainFilterCount"; + public static final String QUERY_DOMAIN_MEMBERS = "DBDomainMember.getByDomain"; + public static final String QUERY_DOMAIN_MEMBERS_FILTER = "DBDomainMember.getByDomainFilter"; public static final String QUERY_DOMAIN_RESOURCE_DEF_ALL = "DBDomainResourceDef.getAll"; public static final String QUERY_DOMAIN_RESOURCE_DEF_DOMAIN_ALL = "DBDomainResourceDef.getAllForDomain"; @@ -79,6 +83,10 @@ public class QueryNames { public static final String QUERY_USER_BY_CREDENTIAL_NAME_TYPE_TARGET = "DBUser.getUserByCredentialNameTypeTarget"; public static final String QUERY_USER_BY_CI_CREDENTIAL_NAME_TYPE_TARGET = "DBUser.getUserByCaseInsensitiveCredentialNameTypeTarget"; + public static final String QUERY_USER_COUNT = "DBUser.getUsersCount"; + public static final String QUERY_USER_FILTER_COUNT = "DBUser.getUsersByFilterCount"; + public static final String QUERY_USERS = "DBUser.getUsers"; + public static final String QUERY_QUERY_USERS_FILTER = "DBUser.getUsersByFilter"; public static final String PARAM_NAME = "name"; @@ -86,6 +94,9 @@ public class QueryNames { public static final String PARAM_IDENTIFIER = "identifier"; public static final String PARAM_ID = "id"; + public static final String PARAM_USER_FILTER = "user_filter"; + + public static final String PARAM_URL_SEGMENT = "url_segment"; public static final String PARAM_EXTENSION_ID = "extension_id"; 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 8d354e31e..f3f0d68fb 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 @@ -316,4 +316,31 @@ public class UserDao extends BaseDao<DBUser> { } */ } + + public List<DBUser> getFilteredUserList(int iPage, int iPageSize, String filter) { + boolean hasFilter = StringUtils.isNotBlank(filter); + TypedQuery<DBUser> query = memEManager.createNamedQuery(hasFilter ? + QUERY_QUERY_USERS_FILTER : QUERY_USERS, DBUser.class); + + if (iPageSize > -1 && iPage > -1) { + query.setFirstResult(iPage * iPageSize); + } + if (iPageSize > 0) { + query.setMaxResults(iPageSize); + } + + if (hasFilter) { + query.setParameter(PARAM_USER_FILTER, "%" + StringUtils.trim(filter) + "%"); + } + return query.getResultList(); + } + + public Long getFilteredUserListCount(String filter) { + boolean hasFilter = StringUtils.isNotBlank(filter); + TypedQuery<Long> query = memEManager.createNamedQuery(hasFilter ? QUERY_USER_FILTER_COUNT : QUERY_USER_COUNT, Long.class); + if (hasFilter) { + query.setParameter(PARAM_USER_FILTER, "%" + StringUtils.trim(filter) + "%"); + } + return query.getSingleResult(); + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBDomainMember.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBDomainMember.java index f3c844085..9967e2572 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBDomainMember.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBDomainMember.java @@ -4,6 +4,8 @@ import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.BaseEntity; import eu.europa.ec.edelivery.smp.data.model.CommonColumnsLengths; import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hibernate.annotations.GenericGenerator; import org.hibernate.envers.Audited; @@ -22,13 +24,19 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*; @Table(name = "SMP_DOMAIN_MEMBER", indexes = {@Index(name = "SMP_DOM_MEM_IDX", columnList = "FK_DOMAIN_ID, FK_USER_ID", unique = true) }) -@NamedQueries({ - @NamedQuery(name = QUERY_DOMAIN_MEMBER_ALL, query = "SELECT u FROM DBDomainMember u"), - @NamedQuery(name = QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS_COUNT, query = "SELECT count(c) FROM DBDomainMember c " + - "WHERE c.user.id = :user_id and c.domain.id in (:domain_ids)"), - @NamedQuery(name = QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS, query = "SELECT c FROM DBDomainMember c " + - "WHERE c.user.id = :user_id and c.domain.id in (:domain_ids)") -}) +@NamedQuery(name = QUERY_DOMAIN_MEMBER_ALL, query = "SELECT u FROM DBDomainMember u") +@NamedQuery(name = QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS_COUNT, query = "SELECT count(c) FROM DBDomainMember c " + + "WHERE c.user.id = :user_id and c.domain.id in (:domain_ids)") +@NamedQuery(name = QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS, query = "SELECT c FROM DBDomainMember c " + + "WHERE c.user.id = :user_id and c.domain.id in (:domain_ids)") +@NamedQuery(name = QUERY_DOMAIN_MEMBERS_COUNT, query = "SELECT count(c) FROM DBDomainMember c " + + " WHERE c.domain.id = :domain_id") +@NamedQuery(name = QUERY_DOMAIN_MEMBERS, query = "SELECT c FROM DBDomainMember c " + + " WHERE c.domain.id = :domain_id order by c.user.username") +@NamedQuery(name = QUERY_DOMAIN_MEMBERS_FILTER_COUNT, query = "SELECT count(c) FROM DBDomainMember c " + + " WHERE c.domain.id = :domain_id AND (lower(c.user.fullName) like lower(:user_filter) OR lower(c.user.username) like lower(:user_filter))") +@NamedQuery(name = QUERY_DOMAIN_MEMBERS_FILTER, query = "SELECT c FROM DBDomainMember c " + + " WHERE c.domain.id = :domain_id AND (lower(c.user.fullName) like lower(:user_filter) OR lower(c.user.username) like lower(:user_filter)) order by c.user.username") public class DBDomainMember extends BaseEntity { @Id @@ -93,4 +101,20 @@ public class DBDomainMember extends BaseEntity { public void setRole(MembershipRoleType role) { this.role = role; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + DBDomainMember that = (DBDomainMember) o; + + return new EqualsBuilder().appendSuper(super.equals(o)).append(id, that.id).append(domain, that.domain).append(user, that.user).append(role, that.role).isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37).appendSuper(super.hashCode()).append(id).append(role).toHashCode(); + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBUser.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBUser.java index 72598a68c..f9fa64425 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBUser.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBUser.java @@ -25,6 +25,8 @@ import org.hibernate.envers.Audited; import javax.persistence.*; import java.util.Objects; +import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*; + @Entity @Audited @Table(name = "SMP_USER") @@ -38,6 +40,16 @@ import java.util.Objects; " WHERE upper(c.name) = upper(:credential_name) " + " AND c.credentialType = :credential_type " + " AND c.credentialTarget = :credential_target") + + +@NamedQuery(name = QUERY_USER_COUNT, query = "SELECT count(c) FROM DBUser c") +@NamedQuery(name = QUERY_USERS, query = "SELECT c FROM DBUser c order by c.username") +@NamedQuery(name = QUERY_USER_FILTER_COUNT, query = "SELECT count(c) FROM DBUser c " + + " WHERE (lower(c.username) like lower(:user_filter) OR lower(c.fullName) like lower(:user_filter))") +@NamedQuery(name = QUERY_QUERY_USERS_FILTER, query = "SELECT c FROM DBUser c " + + " WHERE (lower(c.username) like lower(:user_filter) OR lower(c.fullName) like lower(:user_filter)) order by c.username") + + //@NamedQueries({ // @NamedQuery(name = "DBUser.getUserByCertificateId", query = "SELECT u FROM DBUser u WHERE u.certificate.certificateId = :certificateId"), diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java new file mode 100644 index 000000000..b90802239 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java @@ -0,0 +1,52 @@ +package eu.europa.ec.edelivery.smp.data.ui; + +import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; + +public class MemberRO { + + String memberId; + String username; + String memberOf; + String fullName; + MembershipRoleType roleType; + + public String getMemberId() { + return memberId; + } + + public void setMemberId(String memberId) { + this.memberId = memberId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getMemberOf() { + return memberOf; + } + + public void setMemberOf(String memberOf) { + this.memberOf = memberOf; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public MembershipRoleType getRoleType() { + return roleType; + } + + public void setRoleType(MembershipRoleType roleType) { + this.roleType = roleType; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SearchUserRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SearchUserRO.java new file mode 100644 index 000000000..2051d4d8a --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SearchUserRO.java @@ -0,0 +1,32 @@ +package eu.europa.ec.edelivery.smp.data.ui; + +public class SearchUserRO { + + String userId; + String username; + String fullName; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java index 7fe169ab2..416f47474 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainPublicService.java @@ -2,13 +2,25 @@ package eu.europa.ec.edelivery.smp.services.ui; import eu.europa.ec.edelivery.smp.data.dao.BaseDao; import eu.europa.ec.edelivery.smp.data.dao.DomainDao; +import eu.europa.ec.edelivery.smp.data.dao.DomainMemberDao; +import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.user.DBDomainMember; +import eu.europa.ec.edelivery.smp.data.model.user.DBUser; import eu.europa.ec.edelivery.smp.data.ui.DomainPublicRO; +import eu.europa.ec.edelivery.smp.data.ui.MemberRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; +import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; /** * Service bean provides only public domain entity data for the Domain. @@ -20,8 +32,19 @@ import org.springframework.stereotype.Service; public class UIDomainPublicService extends UIServiceBase<DBDomain, DomainPublicRO> { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UIDomainPublicService.class); - @Autowired - DomainDao domainDao; + private final DomainDao domainDao; + + private final DomainMemberDao domainMemberDao; + private final UserDao userDao; + private final ConversionService conversionService; + + + public UIDomainPublicService(DomainDao domainDao, DomainMemberDao domainMemberDao,ConversionService conversionService, UserDao userDao) { + this.domainDao = domainDao; + this.domainMemberDao = domainMemberDao; + this.conversionService = conversionService; + this.userDao = userDao; + } @Override protected BaseDao<DBDomain> getDatabaseDao() { @@ -44,4 +67,59 @@ public class UIDomainPublicService extends UIServiceBase<DBDomain, DomainPublicR LOG.debug("Query for public domain data: page: [{}], page size [{}], sort: [{}], filter: [{}].", page, pageSize, sortField, filter); return super.getTableList(page, pageSize, sortField, sortOrder, filter); } + + + @Transactional + public ServiceResult<MemberRO> getDomainMembers(Long domainId, int page, int pageSize, + String filter) { + Long count = domainMemberDao.getDomainMemberCount(domainId, filter); + ServiceResult<MemberRO> result = new ServiceResult<>(); + result.setPage(page); + result.setPageSize(pageSize); + if (count<1) { + result.setCount(0L); + return result; + } + result.setCount(count); + List<DBDomainMember> memberROS = domainMemberDao.getDomainMembers(domainId, page, pageSize, filter); + List<MemberRO> memberList = memberROS.stream().map(member-> conversionService.convert(member, MemberRO.class)).collect(Collectors.toList()); + + result.getServiceEntities().addAll(memberList); + return result; + } + + @Transactional + public MemberRO addMemberToDomain(Long domainId, MemberRO memberRO, Long memberId) { + LOG.info("Add member [{}] to domain [{}]", memberRO.getUsername(), domainId); + DBUser user = userDao.findUserByUsername(memberRO.getUsername()) + .orElseThrow(() -> new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Add/edit membership", "User ["+memberRO.getUsername()+"] does not exists!")); + + DBDomainMember domainMember; + if (memberId !=null) { + domainMember = domainMemberDao.find(memberId); + domainMember.setRole(memberRO.getRoleType()); + } else { + DBDomain domain = domainDao.find(domainId); + if (domainMemberDao.isUserDomainMember(user, domain)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Add membership", "User ["+memberRO.getUsername()+"] is already a member!"); + } + domainMember = domainMemberDao.addMemberToDomain(domain, user,memberRO.getRoleType() ); + } + return conversionService.convert(domainMember, MemberRO.class); + } + + @Transactional + public MemberRO deleteMemberFromDomain(Long domainId, Long memberId) { + LOG.info("Delete member [{}] from domain [{}]", memberId, domainId); + DBDomainMember domainMember = domainMemberDao.find(memberId); + if (domainMember == null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Membership", "Membership does not exists!"); + } + if (!Objects.equals(domainMember.getDomain().getId(),domainId )){ + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Membership", "Membership does not belong to domain!"); + } + + domainMemberDao.remove(domainMember); + return conversionService.convert(domainMember, MemberRO.class); + } } 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 074c9c6eb..f5861488c 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 @@ -204,7 +204,6 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { return result; } - /** * Method updates the user password * @@ -270,6 +269,7 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { return dbCredential.getUser(); } + /** * Method creates Username/passwords credentials for the user with given userId. * The method must be called inside active transactions. @@ -493,6 +493,26 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { return credentialResultRO; } + @Transactional + public ServiceResult<SearchUserRO> searchUsers(int page, int pageSize, String filter) { + Long count = userDao.getFilteredUserListCount(filter); + ServiceResult<SearchUserRO> result = new ServiceResult<>(); + result.setPage(page); + result.setPageSize(pageSize); + ; + if (count < 1) { + result.setCount(0L); + return result; + } + result.setCount(count); + List<DBUser> users = userDao.getFilteredUserList(page, pageSize, filter); + List<SearchUserRO> userList = users.stream().map(usr -> conversionService.convert(usr, SearchUserRO.class)).collect(Collectors.toList()); + + result.getServiceEntities().addAll(userList); + return result; + } + + @Transactional(readOnly = true) public DBUser findUserByUsername(String userName) { return userDao.findUserByUsername(userName).orElseThrow(() -> new SMPRuntimeException(ErrorCode.USER_NOT_EXISTS)); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDaoTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDaoTest.java index be940cffe..b5373da1c 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDaoTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainMemberDaoTest.java @@ -4,15 +4,15 @@ import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.user.DBDomainMember; import eu.europa.ec.edelivery.smp.data.model.user.DBUser; -import eu.europa.ec.edelivery.smp.testutil.TestConstants; -import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.Collections; +import java.util.List; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * @author Joze Rihtarsic @@ -27,16 +27,19 @@ public class DomainMemberDaoTest extends AbstractBaseDao { @Autowired DomainMemberDao testInstance; + @Before + public void prepareDatabase() { + testUtilsDao.clearData(); + testUtilsDao.createUsers(); + testUtilsDao.createDomains(); + } + + @Test public void testIsUserDomainsMember() { - DBUser user = TestDBUtils.createDBUserByUsername(TestConstants.USERNAME_1); - DBDomain domain = TestDBUtils.createDBDomain(); - userDao.persistFlushDetach(user); - domainDao.persistFlushDetach(domain); - DBDomainMember domainMember = new DBDomainMember(); - domainMember.setDomain(domain); - domainMember.setUser(user); - testInstance.persistFlushDetach(domainMember); + DBDomain domain = testUtilsDao.getD1(); + DBUser user = testUtilsDao.getUser1(); + addMemberToDomain(user, domain, MembershipRoleType.ADMIN); // then boolean result = testInstance.isUserDomainsMember(user, Collections.singletonList(domain)); @@ -45,31 +48,77 @@ public class DomainMemberDaoTest extends AbstractBaseDao { @Test public void testIsUserDomainsMemberFalse() { - DBUser user = TestDBUtils.createDBUserByUsername(TestConstants.USERNAME_1); - DBDomain domain = TestDBUtils.createDBDomain(); - userDao.persistFlushDetach(user); - domainDao.persistFlushDetach(domain); + // then - boolean result = testInstance.isUserDomainsMember(user, Collections.singletonList(domain)); + boolean result = testInstance.isUserDomainsMember(testUtilsDao.getUser1(), Collections.singletonList(testUtilsDao.getD1())); assertFalse(result); } @Test public void testIsUserDomainsMemberWithRoleTrue() { - DBUser user = TestDBUtils.createDBUserByUsername(TestConstants.USERNAME_1); - DBDomain domain = TestDBUtils.createDBDomain(); - userDao.persistFlushDetach(user); - domainDao.persistFlushDetach(domain); - DBDomainMember domainMember = new DBDomainMember(); - domainMember.setDomain(domain); - domainMember.setUser(user); - domainMember.setRole(MembershipRoleType.ADMIN); - testInstance.persistFlushDetach(domainMember); + DBDomain domain = testUtilsDao.getD1(); + DBUser user = testUtilsDao.getUser1(); + addMemberToDomain(user, domain, MembershipRoleType.ADMIN); // then boolean result = testInstance.isUserDomainMemberWithRole(user.getId(), Collections.singletonList(domain.getId()), MembershipRoleType.ADMIN); assertTrue(result); result = testInstance.isUserDomainMemberWithRole(user.getId(), Collections.singletonList(domain.getId()), MembershipRoleType.VIEWER); assertFalse(result); } + + @Test + public void testGetDomainMembersEmpty() { + DBDomain domain = testUtilsDao.getD1(); + // then + Long resultCount = testInstance.getDomainMemberCount(domain.getId(), null); + List<DBDomainMember> result = testInstance.getDomainMembers(domain.getId(), 0, 10, null); + assertEquals(0, resultCount.intValue()); + assertEquals(0, result.size()); + } + + @Test + public void testGetDomainMembersOne() { + DBDomain domain = testUtilsDao.getD1(); + DBUser user = testUtilsDao.getUser1(); + addMemberToDomain(user, domain, MembershipRoleType.ADMIN); + // then + Long resultCount = testInstance.getDomainMemberCount(domain.getId(), null); + List<DBDomainMember> result = testInstance.getDomainMembers(domain.getId(), 0, 10, null); + assertEquals(1, resultCount.intValue()); + assertEquals(1, result.size()); + } + + @Test + public void testGetDomainMembersOneFilter() { + DBDomain domain = testUtilsDao.getD1(); + DBUser user = testUtilsDao.getUser1(); + addMemberToDomain(user, domain, MembershipRoleType.ADMIN); + // then filter no match + assertFilter("NotExistsAtAll", 0, domain); + assertFilter(user.getUsername(), 1, domain); + assertFilter(user.getFullName(), 1, domain); + + assertFilter(StringUtils.upperCase(user.getUsername()), 1, domain); + assertFilter(StringUtils.upperCase(user.getFullName()), 1, domain); + assertFilter(StringUtils.lowerCase(user.getUsername()), 1, domain); + assertFilter(StringUtils.lowerCase(user.getFullName()), 1, domain); + assertFilter("", 1, domain); + assertFilter(null, 1, domain); + } + + private void assertFilter(String filter, int expectedCount, DBDomain domain) { + Long resultCount = testInstance.getDomainMemberCount(domain.getId(), filter); + List<DBDomainMember> result = testInstance.getDomainMembers(domain.getId(), 0, 10, filter); + assertEquals(expectedCount, resultCount.intValue()); + assertEquals(expectedCount, result.size()); + } + + private void addMemberToDomain(DBUser user, DBDomain domain, MembershipRoleType role) { + DBDomainMember domainMember = new DBDomainMember(); + domainMember.setDomain(domain); + domainMember.setUser(user); + domainMember.setRole(role); + testInstance.persistFlushDetach(domainMember); + } } 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 5d65fe1a5..0a8f42fbd 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 @@ -22,11 +22,8 @@ import org.springframework.test.context.ContextConfiguration; public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest { @Rule public ExpectedException expectedExeption = ExpectedException.none(); - @Autowired protected UIUserService testInstance; - - @Autowired protected ResourceDao serviceGroupDao; /* 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 7f56ba1ad..0490d286a 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 @@ -109,3 +109,7 @@ insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREA (1, 1, 2, 'ADMIN', NOW(), NOW()), (2, 1, 3, 'ADMIN', NOW(), NOW()), (3, 1, 4, 'ADMIN', NOW(), NOW()); + +insert into SMP_DOMAIN_MEMBER (ID, FK_DOMAIN_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values +(1, 1, 1, 'ADMIN', NOW(), NOW()), +(2, 1, 2, 'VIEWER', NOW(), NOW()); 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 24d983336..2f914ad1b 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 @@ -1,7 +1,9 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.auth.enums.SMPUserAuthenticationTypes; +import eu.europa.ec.edelivery.smp.data.dao.DomainMemberDao; import eu.europa.ec.edelivery.smp.data.dao.UserDao; +import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.user.DBUser; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; @@ -21,9 +23,11 @@ import org.springframework.stereotype.Service; import java.net.URL; import java.time.OffsetDateTime; +import java.util.Collections; import java.util.stream.Collectors; -import static eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority.*; +import static eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN; +import static eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority.S_AUTHORITY_TOKEN_USER; /** * @author Sebastian-Ion TINCU @@ -34,6 +38,7 @@ public class SMPAuthorizationService { private static final String ERR_INVALID_OR_NULL = "Invalid or null authentication for the session!"; private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPAuthorizationService.class); + DomainMemberDao domainMemberDao; private final ServiceGroupService serviceGroupService; private final ConversionService conversionService; private final ConfigurationService configurationService; @@ -42,11 +47,13 @@ public class SMPAuthorizationService { public SMPAuthorizationService(ServiceGroupService serviceGroupService, ConversionService conversionService, ConfigurationService configurationService, - UserDao userDao) { + UserDao userDao, + DomainMemberDao domainMemberDao) { this.serviceGroupService = serviceGroupService; this.conversionService = conversionService; this.configurationService = configurationService; this.userDao = userDao; + this.domainMemberDao = domainMemberDao; } public boolean isSystemAdministrator() { @@ -56,6 +63,18 @@ public class SMPAuthorizationService { return hasSystemRole; } + public boolean isDomainAdministrator(String domainEncId) { + SMPUserDetails userDetails = getAndValidateUserDetails(); + Long domainId; + try { + domainId = SessionSecurityUtils.decryptEntityId(domainEncId); + } catch (SMPRuntimeException | NumberFormatException ex) { + LOG.error("Error occurred while decrypting domain-id:[" + domainEncId + "]", ex); + throw new BadCredentialsException("Login failed; Invalid userID or password"); + } + return domainMemberDao.isUserDomainMemberWithRole(userDetails.getUser().getId(), Collections.singletonList(domainId), MembershipRoleType.ADMIN); + } + public boolean isSMPAdministrator() { SMPUserDetails userDetails = getAndValidateUserDetails(); boolean hasRole = hasSessionUserRole(S_AUTHORITY_TOKEN_USER, userDetails); @@ -79,7 +98,7 @@ public class SMPAuthorizationService { public boolean isAuthorizedForManagingTheServiceMetadataGroup(Long serviceMetadataId) { SMPUserDetails userDetails = getAndValidateUserDetails(); if (hasSessionUserRole(S_AUTHORITY_TOKEN_USER, userDetails)) { - LOG.debug("SMP admin is authorized to manage service metadata: [{}]" + serviceMetadataId); + LOG.debug("SMP admin is authorized to manage service metadata: [{}]" , serviceMetadataId); return true; } @@ -154,7 +173,7 @@ public class SMPAuthorizationService { // set cas authentication data if (configurationService.getUIAuthenticationTypes().contains(SMPUserAuthenticationTypes.SSO.name())) { URL casUrlData = configurationService.getCasUserDataURL(); - userRO.setCasUserDataUrl(casUrlData!=null?casUrlData.toString():null); + userRO.setCasUserDataUrl(casUrlData != null ? casUrlData.toString() : null); } return sanitize(userRO); 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 4db2ba11e..80dfb192e 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 @@ -37,6 +37,7 @@ public class ResourceConstants { // parameters public static final String PARAM_PAGINATION_PAGE="page"; public static final String PARAM_PAGINATION_PAGE_SIZE="pageSize"; + public static final String PARAM_PAGINATION_FILTER="filter"; public static final String PARAM_PAGINATION_ORDER_BY="orderBy"; public static final String PARAM_PAGINATION_ORDER_TYPE="orderType"; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java index fa1beb097..579ad68b9 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/DomainResource.java @@ -1,16 +1,17 @@ package eu.europa.ec.edelivery.smp.ui.external; +import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.ui.DomainPublicRO; +import eu.europa.ec.edelivery.smp.data.ui.MemberRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.services.ui.UIDomainPublicService; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.MimeTypeUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; @@ -26,14 +27,16 @@ public class DomainResource { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DomainResource.class); - private UIDomainPublicService uiDomainService; + private final UIDomainPublicService uiDomainService; + public DomainResource(UIDomainPublicService uiDomainService) { this.uiDomainService = uiDomainService; + } @GetMapping(produces = {MimeTypeUtils.APPLICATION_JSON_VALUE}) - public ServiceResult<DomainPublicRO> geDomainList( + public ServiceResult<DomainPublicRO> getDomainList( @RequestParam(value = PARAM_PAGINATION_PAGE, defaultValue = "0") int page, @RequestParam(value = PARAM_PAGINATION_PAGE_SIZE, defaultValue = "10") int pageSize, @RequestParam(value = PARAM_PAGINATION_ORDER_BY, required = false) String orderBy, @@ -45,4 +48,50 @@ public class DomainResource { return result; } + + @GetMapping(path = "/{user-enc-id}/{domain-enc-id}/members", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId)") + public ServiceResult<MemberRO> getDomainMemberList( + @PathVariable("user-enc-id") String userEncId, + @PathVariable("domain-enc-id") String domainEncId, + @RequestParam(value = PARAM_PAGINATION_PAGE, defaultValue = "0") int page, + @RequestParam(value = PARAM_PAGINATION_PAGE_SIZE, defaultValue = "10") int pageSize, + @RequestParam(value = PARAM_PAGINATION_FILTER, defaultValue = "", required = false) String filter) { + + LOG.info("Search for domain members with filter [{}], paging: [{}/{}], user: {}",filter, page, pageSize, userEncId); + Long domainId = SessionSecurityUtils.decryptEntityId(domainEncId); + return uiDomainService.getDomainMembers(domainId, page, pageSize, filter); + } + + @PutMapping(path = "/{user-enc-id}/{domain-enc-id}/member", produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and (@smpAuthorizationService.systemAdministrator or @smpAuthorizationService.isDomainAdministrator(#domainEncId))") + public MemberRO putDomainMember( + @PathVariable("user-enc-id") String userEncId, + @PathVariable("domain-enc-id") String domainEncId, + @RequestBody MemberRO memberRO) { + + LOG.info("add member to domain"); + Long domainId = SessionSecurityUtils.decryptEntityId(domainEncId); + Long memberId = memberRO.getMemberId() == null?null: SessionSecurityUtils.decryptEntityId(memberRO.getMemberId()); + if (memberRO.getRoleType() == null) { + memberRO.setRoleType(MembershipRoleType.VIEWER); + } + // is user domain admin or system admin + return uiDomainService.addMemberToDomain(domainId, memberRO, memberId); + } + + @DeleteMapping(value = "/{user-enc-id}/{domain-enc-id}/member/{member-enc-id}/delete") + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and (@smpAuthorizationService.systemAdministrator or @smpAuthorizationService.isDomainAdministrator(#domainEncId))") + public MemberRO deleteDomainMember( + @PathVariable("user-enc-id") String userEncId, + @PathVariable("domain-enc-id") String domainEncId, + @PathVariable("member-enc-id") String memberEncId + ) { + LOG.info("Delete member from domain"); + Long domainId = SessionSecurityUtils.decryptEntityId(domainEncId); + Long memberId= SessionSecurityUtils.decryptEntityId(memberEncId); + + // is user domain admin or system admin + return uiDomainService.deleteMemberFromDomain(domainId, memberId); + } } 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 ead3d8315..d60307bd8 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 @@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletResponse; import java.util.List; import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_USER; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.PARAM_PAGINATION_FILTER; import static eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils.decryptEntityId; /** @@ -74,6 +75,17 @@ public class UserResource { return result != null; } + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @GetMapping(path = "/{user-id}/search", produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + public List<SearchUserRO> lookupUsers(@PathVariable("user-id") String userId, + @RequestParam(value = PARAM_PAGINATION_FILTER, defaultValue = "", required = false) String filter ) { + Long entityId = decryptEntityId(userId); + LOG.info("Validating the password of the currently logged in user:[{}] with id:[{}] ", userId, entityId); + + // return first 10 results + return uiUserService.searchUsers(0, 10, filter).getServiceEntities(); + } + /** * Update the details of the currently logged-in user (e.g. update the role, the credentials or add certificate details). * diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java index ba7442b02..0abc6d06e 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationServiceTest.java @@ -1,5 +1,6 @@ package eu.europa.ec.edelivery.smp.auth; +import eu.europa.ec.edelivery.smp.data.dao.DomainMemberDao; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.user.DBUser; import eu.europa.ec.edelivery.smp.data.ui.UserRO; @@ -33,9 +34,10 @@ public class SMPAuthorizationServiceTest { ConversionService conversionService = Mockito.mock(ConversionService.class); ConfigurationService configurationService = Mockito.mock(ConfigurationService.class); UserDao userDao = Mockito.mock(UserDao.class); + DomainMemberDao domainMemberDao = Mockito.mock(DomainMemberDao.class); SMPAuthorizationService testInstance = new SMPAuthorizationService(serviceGroupService, conversionService, - configurationService, userDao); + configurationService, userDao, domainMemberDao); @Before -- GitLab