From 06d0d9bec072dc81d40ade000744ca98b8a04409 Mon Sep 17 00:00:00 2001
From: RIHTARSIC Joze <joze.rihtarsic@ext.ec.europa.eu>
Date: Mon, 17 Apr 2023 08:10:07 +0200
Subject: [PATCH] Implement resource member administration

---
 smp-angular/src/app/app.module.ts             |   4 +
 smp-angular/src/app/app.routes.ts             |   3 +-
 .../member-dialog/member-dialog.component.ts  |   7 +-
 .../membership-panel.component.ts             |  24 ++-
 .../membership-panel/membership.service.ts    |  48 ++++-
 .../edit/edit-domain/edit-domain.service.ts   |   7 +
 .../edit/edit-group/edit-group.component.ts   |   7 +-
 .../app/edit/edit-group/edit-group.service.ts |  24 ++-
 .../group-resource-panel.component.ts         |   3 +-
 .../edit-resource.component.css               |  19 ++
 .../edit-resource.component.html              | 120 ++++++++++++
 .../edit-resources/edit-resource.component.ts | 181 ++++++++++++++++++
 .../edit-resources/edit-resource.service.ts   |  81 ++++++++
 smp-angular/src/app/smp.constants.ts          |   5 +
 .../DBResourceMemberToMemberROConverter.java  |  26 +++
 .../ec/edelivery/smp/data/dao/DomainDao.java  |  17 ++
 .../smp/data/dao/DomainMemberDao.java         |   2 +-
 .../ec/edelivery/smp/data/dao/GroupDao.java   |  16 +-
 .../smp/data/dao/GroupMemberDao.java          |   2 +-
 .../ec/edelivery/smp/data/dao/QueryNames.java |  27 ++-
 .../smp/data/dao/ResourceMemberDao.java       |  42 ++++
 .../ec/edelivery/smp/data/model/DBDomain.java |  13 ++
 .../ec/edelivery/smp/data/model/DBGroup.java  |  11 +-
 .../smp/data/model/user/DBGroupMember.java    |   2 -
 .../smp/data/model/user/DBResourceMember.java |   8 +
 .../services/ui/UIDomainPublicService.java    |   7 +
 .../smp/services/ui/UIGroupPublicService.java |  11 +-
 .../smp/services/ui/UIResourceService.java    | 109 ++++++++++-
 .../edelivery/smp/data/dao/DomainDaoTest.java |  41 ++++
 .../edelivery/smp/data/dao/GroupDaoTest.java  |  41 +++-
 .../mysql-4.1_integration_test_data.sql       |   8 +-
 .../smp/auth/SMPAuthorizationService.java     |   6 +
 .../edelivery/smp/ui/ResourceConstants.java   |   7 +-
 .../smp/ui/edit/DomainEditController.java     |   4 +-
 .../smp/ui/edit/GroupEditController.java      |  11 +-
 .../smp/ui/edit/ResourceEditController.java   |  71 ++++++-
 36 files changed, 946 insertions(+), 69 deletions(-)
 create mode 100644 smp-angular/src/app/edit/edit-resources/edit-resource.component.css
 create mode 100644 smp-angular/src/app/edit/edit-resources/edit-resource.component.html
 create mode 100644 smp-angular/src/app/edit/edit-resources/edit-resource.component.ts
 create mode 100644 smp-angular/src/app/edit/edit-resources/edit-resource.service.ts
 create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java

diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts
index eeda238ab..a84724a1d 100644
--- a/smp-angular/src/app/app.module.ts
+++ b/smp-angular/src/app/app.module.ts
@@ -150,6 +150,8 @@ import {GroupResourcePanelComponent} from "./edit/edit-group/group-resource-pane
 import {
   ResourceDialogComponent
 } from "./edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component";
+import {EditResourceComponent} from "./edit/edit-resources/edit-resource.component";
+import {EditResourceService} from "./edit/edit-resources/edit-resource.service";
 
 
 @NgModule({
@@ -188,6 +190,7 @@ import {
     DomainSelectorComponent,
     EditDomainComponent,
     EditGroupComponent,
+    EditResourceComponent,
     ExpiredPasswordDialogComponent,
     ExtensionComponent,
     ExtensionPanelComponent,
@@ -285,6 +288,7 @@ import {
     DownloadService,
     EditDomainService,
     EditGroupService,
+    EditResourceService,
     ExtensionService,
     GlobalLookups,
     HttpEventService,
diff --git a/smp-angular/src/app/app.routes.ts b/smp-angular/src/app/app.routes.ts
index 7ff9afaa2..8af043f98 100644
--- a/smp-angular/src/app/app.routes.ts
+++ b/smp-angular/src/app/app.routes.ts
@@ -15,6 +15,7 @@ import {dirtyDeactivateGuard} from "./guards/dirty.guard";
 import {AdminUserComponent} from "./system-settings/admin-users/admin-user.component";
 import {EditDomainComponent} from "./edit/edit-domain/edit-domain.component";
 import {EditGroupComponent} from "./edit/edit-group/edit-group.component";
+import {EditResourceComponent} from "./edit/edit-resources/edit-resource.component";
 
 
 const appRoutes: Routes = [
@@ -28,7 +29,7 @@ const appRoutes: Routes = [
     children: [
       {path: 'edit-domain', component: EditDomainComponent, canDeactivate: [dirtyDeactivateGuard]},
       {path: 'edit-group', component: EditGroupComponent, canDeactivate: [dirtyDeactivateGuard]},
-      {path: 'edit-resource', component: PropertyComponent, canDeactivate: [dirtyDeactivateGuard]}
+      {path: 'edit-resource', component: EditResourceComponent, canDeactivate: [dirtyDeactivateGuard]}
     ]
   },
   {
diff --git a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts
index 1e44d1608..e0f0b7c3f 100644
--- a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts
+++ b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts
@@ -10,6 +10,7 @@ import {DomainRo} from "../../model/domain-ro.model";
 import {MemberTypeEnum} from "../../enums/member-type.enum";
 import {AlertMessageService} from "../../alert-message/alert-message.service";
 import {GroupRo} from "../../model/group-ro.model";
+import {ResourceRo} from "../../model/resource-ro.model";
 
 
 @Component({
@@ -28,6 +29,7 @@ export class MemberDialogComponent implements OnInit {
   _currentMember: MemberRo;
   _currentDomain: DomainRo;
   _currentGroup: GroupRo;
+  _currentResource: ResourceRo;
   membershipType: MemberTypeEnum = MemberTypeEnum.DOMAIN;
 
   filteredOptions: Observable<SearchUserRo[]>;
@@ -46,6 +48,7 @@ export class MemberDialogComponent implements OnInit {
     dialogRef.disableClose = true;//disable default close operation
     this._currentDomain = data.domain;
     this._currentGroup = data.group;
+    this._currentResource = data.resource;
     this.membershipType= data.membershipType;
 
     this.memberForm = formBuilder.group({
@@ -113,7 +116,7 @@ export class MemberDialogComponent implements OnInit {
       case MemberTypeEnum.GROUP:
         return " group ["+this._currentGroup?.groupName+"]"
       case MemberTypeEnum.RESOURCE:
-        return " resource"
+        return " resource ["+this._currentResource?.resourceTypeIdentifier+"]"
     }
     return " target not selected!"
   }
@@ -160,7 +163,7 @@ export class MemberDialogComponent implements OnInit {
       case MemberTypeEnum.GROUP:
         return  this.membershipService.addEditMemberToGroup(this._currentGroup.groupId,this._currentDomain.domainId, this.member)
       case MemberTypeEnum.RESOURCE:
-        return null;
+        return  this.membershipService.addEditMemberToResource(this._currentResource, this._currentGroup,this._currentDomain, this.member)
     }
   }
 }
diff --git a/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts b/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts
index d9af579f8..34c0774ce 100644
--- a/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts
+++ b/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts
@@ -16,6 +16,7 @@ import {GroupRo} from "../../model/group-ro.model";
 import {Observable} from "rxjs";
 import {SearchTableResult} from "../../search-table/search-table-result.model";
 import {ConfirmationDialogComponent} from "../../dialogs/confirmation-dialog/confirmation-dialog.component";
+import {ResourceRo} from "../../model/resource-ro.model";
 
 
 @Component({
@@ -29,6 +30,7 @@ export class MembershipPanelComponent implements BeforeLeaveGuard {
 
   private _domain: DomainRo;
   private _group: GroupRo;
+  private _resource: ResourceRo;
 
 
   displayedColumns: string[] = ['username', 'fullName', 'roleType', 'memberOf'];
@@ -94,7 +96,21 @@ export class MembershipPanelComponent implements BeforeLeaveGuard {
       this.isLoadingResults = false;
     }
   }
+  get resource(): ResourceRo {
+    return this._resource;
+  }
+
+  @Input() set resource(value: ResourceRo) {
+    this._resource = value;
 
+    if (!!value) {
+      if (this.membershipType == MemberTypeEnum.RESOURCE) {
+        this.loadMembershipData();
+      }
+    } else {
+      this.isLoadingResults = false;
+    }
+  }
 
   onPageChanged(page: PageEvent) {
     this.loadMembershipData();
@@ -162,6 +178,7 @@ export class MembershipPanelComponent implements BeforeLeaveGuard {
         membershipType: this.membershipType,
         domain: this._domain,
         group: this._group,
+        resource: this._resource,
         member: member,
       }
     }).afterClosed().subscribe(value => {
@@ -201,7 +218,7 @@ export class MembershipPanelComponent implements BeforeLeaveGuard {
       case MemberTypeEnum.GROUP:
         return !this._group;
       case MemberTypeEnum.RESOURCE:
-        return false;
+        return !this._resource;
     }
   }
 
@@ -212,7 +229,8 @@ export class MembershipPanelComponent implements BeforeLeaveGuard {
       case MemberTypeEnum.GROUP:
         return this.membershipService.getGroupMembersObservable(this._group.groupId, this._domain.domainId, this.filter, this.paginator.pageIndex, this.paginator.pageSize);
       case MemberTypeEnum.RESOURCE:
-        return null;
+        return this.membershipService.getResourceMembersObservable(this._resource, this._group, this._domain, this.filter, this.paginator.pageIndex, this.paginator.pageSize);
+
     }
   }
 
@@ -223,7 +241,7 @@ export class MembershipPanelComponent implements BeforeLeaveGuard {
       case MemberTypeEnum.GROUP:
         return this.membershipService.deleteMemberFromGroup(this._group.groupId, this._domain.domainId, this.selectedMember);
       case MemberTypeEnum.RESOURCE:
-        return null;
+        return this.membershipService.deleteMemberFromResource(this._resource, this._group, this._domain, this.selectedMember);
     }
   }
 }
diff --git a/smp-angular/src/app/common/panels/membership-panel/membership.service.ts b/smp-angular/src/app/common/panels/membership-panel/membership.service.ts
index 795032eaa..b8a021288 100644
--- a/smp-angular/src/app/common/panels/membership-panel/membership.service.ts
+++ b/smp-angular/src/app/common/panels/membership-panel/membership.service.ts
@@ -9,6 +9,9 @@ import {AlertMessageService} from "../../alert-message/alert-message.service";
 import {MemberRo} from "../../model/member-ro.model";
 import {TableResult} from "../../model/table-result.model";
 import {SearchUserRo} from "../../model/search-user-ro.model";
+import {ResourceRo} from "../../model/resource-ro.model";
+import {GroupRo} from "../../model/group-ro.model";
+import {DomainRo} from "../../model/domain-ro.model";
 
 
 @Injectable()
@@ -54,13 +57,32 @@ export class MembershipService {
         params = params.set(filterProperty, encodeURIComponent(filter[filterProperty]));
       }
     }
-
     return this.http.get<TableResult<MemberRo>>(SmpConstants.REST_EDIT_GROUP_MEMBER
       .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
       .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domainId)
       .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, groupId), {params});
   }
 
+  getResourceMembersObservable(resource: ResourceRo, group: GroupRo, domain: DomainRo, 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_EDIT_RESOURCE_MEMBER
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group.groupId)
+      .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource.resourceId), {params});
+  }
+
   getUserLookupObservable(filter: string): Observable<SearchUserRo[]> {
     const currentUser: User = this.securityService.getCurrentUser();
     let params: HttpParams = new HttpParams()
@@ -77,6 +99,7 @@ export class MembershipService {
       .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
       .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domainId), member);
   }
+
   addEditMemberToGroup(groupId: string, domainId: string, member: MemberRo): Observable<MemberRo> {
     const currentUser: User = this.securityService.getCurrentUser();
 
@@ -86,6 +109,16 @@ export class MembershipService {
       .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, groupId), member);
   }
 
+  addEditMemberToResource(resource: ResourceRo, group: GroupRo, domain: DomainRo, member: MemberRo): Observable<MemberRo> {
+    const currentUser: User = this.securityService.getCurrentUser();
+
+    return this.http.put<MemberRo>(SmpConstants.REST_EDIT_RESOURCE_MEMBER_PUT
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group.groupId)
+      .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource.resourceId), member);
+  }
+
   deleteMemberFromDomain(domainId: string, member: MemberRo): Observable<MemberRo> {
     const currentUser: User = this.securityService.getCurrentUser();
 
@@ -95,7 +128,7 @@ export class MembershipService {
       .replace(SmpConstants.PATH_PARAM_ENC_MEMBER_ID, member.memberId));
   }
 
-  deleteMemberFromGroup(groupId: string,domainId: string,  member: MemberRo): Observable<MemberRo> {
+  deleteMemberFromGroup(groupId: string, domainId: string, member: MemberRo): Observable<MemberRo> {
     const currentUser: User = this.securityService.getCurrentUser();
 
     return this.http.delete<MemberRo>(SmpConstants.REST_EDIT_GROUP_MEMBER_DELETE
@@ -105,4 +138,15 @@ export class MembershipService {
       .replace(SmpConstants.PATH_PARAM_ENC_MEMBER_ID, member.memberId));
   }
 
+  deleteMemberFromResource(resource:ResourceRo, group:GroupRo, domain: DomainRo, member: MemberRo): Observable<MemberRo> {
+    const currentUser: User = this.securityService.getCurrentUser();
+
+    return this.http.delete<MemberRo>(SmpConstants.REST_EDIT_RESOURCE_MEMBER_DELETE
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group.groupId)
+      .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource.resourceId)
+      .replace(SmpConstants.PATH_PARAM_ENC_MEMBER_ID, member.memberId));
+  }
+
 }
diff --git a/smp-angular/src/app/edit/edit-domain/edit-domain.service.ts b/smp-angular/src/app/edit/edit-domain/edit-domain.service.ts
index df96222d9..afd6f447c 100644
--- a/smp-angular/src/app/edit/edit-domain/edit-domain.service.ts
+++ b/smp-angular/src/app/edit/edit-domain/edit-domain.service.ts
@@ -34,6 +34,13 @@ export class EditDomainService {
     return this.getDomainsForUserRoleTypeObservable("group-admin")
   }
 
+  /**
+   * Method fetches all domains where logged user is admin
+   */
+  public getDomainsForResourceAdminUserObservable():Observable<DomainRo[]>  {
+    return this.getDomainsForUserRoleTypeObservable("resource-admin")
+  }
+
   public getDomainsForUserRoleTypeObservable(type: string) :Observable<DomainRo[]> {
     let params: HttpParams = new HttpParams()
       .set(SmpConstants.PATH_QUERY_FILTER_TYPE, type);
diff --git a/smp-angular/src/app/edit/edit-group/edit-group.component.ts b/smp-angular/src/app/edit/edit-group/edit-group.component.ts
index 9e6e3d2a1..d88624fc2 100644
--- a/smp-angular/src/app/edit/edit-group/edit-group.component.ts
+++ b/smp-angular/src/app/edit/edit-group/edit-group.component.ts
@@ -75,7 +75,7 @@ export class EditGroupComponent implements AfterViewInit, BeforeLeaveGuard {
       this.updateGroupList([]);
       return;
     }
-    this.groupService.getDomainGroupsForGroupAdmin(this.selectedDomain)
+    this.groupService.getDomainGroupsForGroupAdminObservable(this.selectedDomain)
       .subscribe((result: GroupRo[]) => {
         this.updateGroupList(result)
       }, (error: any) => {
@@ -107,11 +107,6 @@ export class EditGroupComponent implements AfterViewInit, BeforeLeaveGuard {
     }
   }
 
-  public onGroupSelected(event) {
-    this.selectedGroup = event.value;
-  }
-
-
   isDirty(): boolean {
     return false;
   }
diff --git a/smp-angular/src/app/edit/edit-group/edit-group.service.ts b/smp-angular/src/app/edit/edit-group/edit-group.service.ts
index 4c5d06a1a..82c952e3a 100644
--- a/smp-angular/src/app/edit/edit-group/edit-group.service.ts
+++ b/smp-angular/src/app/edit/edit-group/edit-group.service.ts
@@ -3,7 +3,6 @@ import {Observable} from 'rxjs';
 
 import {HttpClient, HttpParams} from '@angular/common/http';
 import {SecurityService} from "../../security/security.service";
-import {AlertMessageService} from "../../common/alert-message/alert-message.service";
 import {User} from "../../security/user.model";
 import {SmpConstants} from "../../smp.constants";
 import {GroupRo} from "../../common/model/group-ro.model";
@@ -17,13 +16,20 @@ export class EditGroupService {
 
   constructor(
     private http: HttpClient,
-    private securityService: SecurityService,
-    private alertService: AlertMessageService) {
+    private securityService: SecurityService) {
   }
 
-  public getDomainGroupsForGroupAdmin(domain: DomainRo): Observable<GroupRo[]> {
+  public getDomainGroupsForGroupAdminObservable(domain: DomainRo): Observable<GroupRo[]> {
+    return this.getDomainGroupsForUserRoleTypeObservable('group-admin', domain);
+  }
+
+  public getDomainGroupsForResourceAdminObservable(domain: DomainRo): Observable<GroupRo[]> {
+    return this.getDomainGroupsForUserRoleTypeObservable('resource-admin', domain);
+  }
+
+  public getDomainGroupsForUserRoleTypeObservable(userType: string, domain: DomainRo): Observable<GroupRo[]> {
     let params: HttpParams = new HttpParams()
-      .set(SmpConstants.PATH_QUERY_FILTER_TYPE, 'group-admin');
+      .set(SmpConstants.PATH_QUERY_FILTER_TYPE, userType);
 
     const currentUser: User = this.securityService.getCurrentUser();
     return this.http.get<GroupRo[]>(SmpConstants.REST_EDIT_DOMAIN_GROUP
@@ -31,9 +37,15 @@ export class EditGroupService {
       .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId), {params});
   }
 
+
   public getGroupResourcesForGroupAdminObservable(group: GroupRo, domain: DomainRo, filter: any, page: number, pageSize: number): Observable<TableResult<ResourceRo>> {
+    return this.getGroupResourcesForUserTypeObservable('group-admin',group, domain, filter, page, pageSize);
+
+  }
+
+  public getGroupResourcesForUserTypeObservable(userType: string, group: GroupRo, domain: DomainRo, filter: any, page: number, pageSize: number): Observable<TableResult<ResourceRo>> {
     let params: HttpParams = new HttpParams()
-      .set(SmpConstants.PATH_QUERY_FILTER_TYPE, 'resource-admin')
+      .set(SmpConstants.PATH_QUERY_FILTER_TYPE, userType)
       .set('page', page.toString())
       .set('pageSize', pageSize.toString());
 
diff --git a/smp-angular/src/app/edit/edit-group/group-resource-panel/group-resource-panel.component.ts b/smp-angular/src/app/edit/edit-group/group-resource-panel/group-resource-panel.component.ts
index 16f35824c..3b7d1b883 100644
--- a/smp-angular/src/app/edit/edit-group/group-resource-panel/group-resource-panel.component.ts
+++ b/smp-angular/src/app/edit/edit-group/group-resource-panel/group-resource-panel.component.ts
@@ -24,6 +24,7 @@ export class GroupResourcePanelComponent implements BeforeLeaveGuard {
 
   title: string = "Group resources";
   private _group: GroupRo;
+  @Input() resource: ResourceRo;
   @Input() domain: DomainRo;
   @Input() domainResourceDefs: ResourceDefinitionRo[];
   displayedColumns: string[] = ['identifierValue', 'identifierScheme'];
@@ -37,8 +38,6 @@ export class GroupResourcePanelComponent implements BeforeLeaveGuard {
   constructor(private editGroupService: EditGroupService,
               private alertService: AlertMessageService,
               private dialog: MatDialog) {
-
-
   }
 
 
diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.component.css b/smp-angular/src/app/edit/edit-resources/edit-resource.component.css
new file mode 100644
index 000000000..8804b9226
--- /dev/null
+++ b/smp-angular/src/app/edit/edit-resources/edit-resource.component.css
@@ -0,0 +1,19 @@
+
+#admin-resource--panel {
+  display: flex;
+  flex-flow: column;
+  align-items: center;
+  height: 100%;
+  min-height: 600px;
+  padding: 0 2em;
+}
+#resource--filter {
+  width: 100%;
+  padding-top: 1em;
+}
+
+
+#admin-resource--table {
+  width: 100%;
+  padding-top: 1em;
+}
diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.component.html b/smp-angular/src/app/edit/edit-resources/edit-resource.component.html
new file mode 100644
index 000000000..6e77594b7
--- /dev/null
+++ b/smp-angular/src/app/edit/edit-resources/edit-resource.component.html
@@ -0,0 +1,120 @@
+<div id="admin-resource-panel">
+  <data-panel id="admin-resource-data-panel"
+              title="Edit Resource"
+              text="Edit resource administration panel is a tool for resource administrators to administer the resource"
+              [labelColumnContent]="searchGroupPanel">
+
+    <mat-tab-group #domainTabs style="height: 100%">
+      <mat-tab>
+        <ng-template mat-tab-label>
+          <smp-label icon="groups" label="Members"></smp-label>
+        </ng-template>
+        <domain-member-panel #groupMemberPanelComponent
+                             [membershipType]="groupMembershipType"
+                             [domain]="selectedDomain"
+                             [group]="selectedGroup"
+                             [resource]="selectedResource"
+
+        ></domain-member-panel>
+      </mat-tab>
+      <mat-tab>
+        <ng-template mat-tab-label>
+          <smp-label icon="group" label="Resources"></smp-label>
+        </ng-template>
+        <group-resource-panel
+          [domainResourceDefs]="_selectedDomainResourceDef"
+          [domain]="selectedDomain"
+          [group]="selectedGroup"
+          [resource]="selectedResource"
+        ></group-resource-panel>
+      </mat-tab>
+    </mat-tab-group>
+  </data-panel>
+</div>
+
+<ng-template #searchGroupPanel>
+  <mat-form-field style="width:100%">
+    <mat-label>Selected domain</mat-label>
+    <mat-select placeholder="Select domain"
+                matTooltip="Select domain."
+                id="domain_id"
+                [value]="selectedDomain"
+                required>
+      <mat-option *ngFor="let domain of domainList"
+                  [value]="domain"
+      >
+        {{domain.domainCode}}
+      </mat-option>
+
+    </mat-select>
+  </mat-form-field>
+
+  <mat-form-field style="width:100%">
+    <mat-label>Selected Group</mat-label>
+    <mat-select placeholder="Select group"
+                matTooltip="Select group."
+                id="group_id"
+                [value]="selectedGroup"
+                required>
+      <mat-option *ngFor="let group of groupList"
+                  [value]="group">{{group.groupName}}</mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <div class="edit-resource-container mat-elevation-z2">
+    <div class="edit-resource-loading-shade"
+         *ngIf="isLoadingResults">
+      <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
+    </div>
+
+    <div class="edit-resource-table-container">
+
+      <mat-form-field id="edit-resource-filter" style="width: 100%">
+        <mat-label>Resource filter</mat-label>
+        <input matInput (keyup)="applyResourceFilter($event)"
+               placeholder="Resource filter"
+               [disabled]="disabledResourceFilter"
+               #inputDomainMemberFilter>
+      </mat-form-field>
+
+      <table class="mat-elevation-z2" mat-table [dataSource]="data">
+
+        <ng-container matColumnDef="identifierScheme">
+          <th mat-header-cell *matHeaderCellDef>Scheme</th>
+          <td mat-cell *matCellDef="let row">{{row.identifierScheme}}</td>
+        </ng-container>
+
+        <ng-container matColumnDef="identifierValue">
+          <th mat-header-cell *matHeaderCellDef>Identifier</th>
+          <td mat-cell *matCellDef="let row">{{row.identifierValue}}</td>
+        </ng-container>
+
+
+
+        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+        <tr mat-row *matRowDef="let odd = odd; let row; columns: displayedColumns;"
+            (click)="onResourceSelected(row)"
+            [ngClass]="{'datatable-row-selected': row==selectedResource,'datatable-row-odd': odd}"
+        ></tr>
+
+        <tr class="mat-row" *matNoDataRow>
+          <td *ngIf="inputDomainMemberFilter.value;else noDataFound" class="mat-cell" colspan="2">No resources
+            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 class="mat-elevation-z2" [length]="resultsLength"
+                   (page)="onPageChanged($event)"
+                   [pageSize]="5"
+                   [pageSizeOptions]="[5, 10, 25]"
+                   [disabled]="disabledForm"
+                   aria-label="Select pages"></mat-paginator>
+  </div>
+
+</ng-template>
diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.component.ts b/smp-angular/src/app/edit/edit-resources/edit-resource.component.ts
new file mode 100644
index 000000000..f15bdd42d
--- /dev/null
+++ b/smp-angular/src/app/edit/edit-resources/edit-resource.component.ts
@@ -0,0 +1,181 @@
+import {AfterViewInit, Component, Input, ViewChild} from '@angular/core';
+import {BeforeLeaveGuard} from "../../window/sidenav/navigation-on-leave-guard";
+import {MatPaginator} from "@angular/material/paginator";
+import {AlertMessageService} from "../../common/alert-message/alert-message.service";
+import {EditDomainService} from "../edit-domain/edit-domain.service";
+import {DomainRo} from "../../common/model/domain-ro.model";
+import {GroupRo} from "../../common/model/group-ro.model";
+import {MemberTypeEnum} from "../../common/enums/member-type.enum";
+import {ResourceDefinitionRo} from "../../system-settings/admin-extension/resource-definition-ro.model";
+import {EditGroupService} from "../edit-group/edit-group.service";
+import {ResourceRo} from "../../common/model/resource-ro.model";
+import {EditResourceService} from "./edit-resource.service";
+import {group} from "@angular/animations";
+import {TableResult} from "../../common/model/table-result.model";
+
+
+@Component({
+  moduleId: module.id,
+  templateUrl: './edit-resource.component.html',
+  styleUrls: ['./edit-resource.component.css']
+})
+export class EditResourceComponent implements AfterViewInit, BeforeLeaveGuard {
+  groupMembershipType: MemberTypeEnum = MemberTypeEnum.RESOURCE;
+  domainList: DomainRo[] = [];
+  groupList: GroupRo[] = [];
+  resourceList: ResourceRo[] = [];
+
+  _selectedDomain: DomainRo;
+  _selectedGroup: GroupRo;
+
+  _selectedResource: ResourceRo;
+  _selectedDomainResourceDef: ResourceDefinitionRo[];
+
+  displayedColumns: string[] = ['identifierValue', 'identifierScheme'];
+
+  data: ResourceRo[] = [];
+  selected: ResourceRo;
+  filter: any = {};
+  resultsLength = 0;
+  isLoadingResults = false;
+
+  @ViewChild(MatPaginator) paginator: MatPaginator;
+
+  get selectedDomain(): DomainRo {
+    return this._selectedDomain;
+  };
+
+  @Input() set selectedDomain(domain: DomainRo) {
+    this._selectedDomain = domain;
+    if (!!this.selectedDomain) {
+      this.refreshGroups();
+      this.refreshDomainsResourceDefinitions();
+    } else {
+      this.groupList = [];
+      this._selectedDomainResourceDef = [];
+    }
+
+  };
+
+  get selectedGroup(): GroupRo {
+    return this._selectedGroup;
+  };
+
+  @Input() set selectedGroup(resource: GroupRo) {
+    this._selectedGroup = resource;
+    if (!!this._selectedGroup) {
+      this.refreshResources();
+    } else {
+      this.resourceList = [];
+    }
+  };
+  get selectedResource(): ResourceRo {
+    return this._selectedResource;
+  };
+
+  @Input() set selectedResource(resource: ResourceRo) {
+    this._selectedResource = resource;
+  };
+
+  onResourceSelected(resource: ResourceRo){
+    this.selectedResource = resource;
+  }
+
+  constructor(private domainService: EditDomainService,
+              private groupService: EditGroupService,
+              private resourceService: EditResourceService,
+              private alertService: AlertMessageService) {
+
+  }
+
+  ngAfterViewInit() {
+    this.refreshDomains();
+  }
+
+  refreshDomains() {
+    this.domainService.getDomainsForResourceAdminUserObservable()
+      .subscribe((result: DomainRo[]) => {
+        this.updateDomainList(result)
+      }, (error: any) => {
+        this.alertService.error(error.error?.errorDescription)
+      });
+  }
+
+  refreshGroups() {
+    if (!this.selectedDomain) {
+      this.updateGroupList([]);
+      return;
+    }
+    this.groupService.getDomainGroupsForResourceAdminObservable(this.selectedDomain)
+      .subscribe((result: GroupRo[]) => {
+        this.updateGroupList(result)
+      }, (error: any) => {
+        this.alertService.error(error.error?.errorDescription)
+      });
+  }
+
+  refreshResources() {
+    if (!this.selectedGroup) {
+      this.updateResourceList([]);
+      return;
+    }
+
+    this.resourceService.getGroupResourcesForResourceAdminObservable(this.selectedGroup, this.selectedDomain,  this.filter, this.paginator.pageIndex, this.paginator.pageSize)
+      .subscribe((result: TableResult<ResourceRo>) => {
+        console.log("got resources: " + JSON.stringify(result))
+        this.updateResourceList(result.serviceEntities)
+        this.data = [...result.serviceEntities];
+        this.resultsLength = result.count;
+      }, (error: any) => {
+        this.alertService.error(error.error?.errorDescription)
+      });
+
+
+  }
+
+  refreshDomainsResourceDefinitions() {
+    this.domainService.getDomainResourceDefinitionsObservable(this.selectedDomain)
+      .subscribe((result: ResourceDefinitionRo[]) => {
+        this._selectedDomainResourceDef = result
+      }, (error: any) => {
+        this.alertService.error(error.error?.errorDescription)
+      });
+  }
+
+  updateDomainList(list: DomainRo[]) {
+    this.domainList = list;
+    if (!!this.domainList && this.domainList.length > 0) {
+
+      this.selectedDomain = this.domainList[0];
+    }
+  }
+
+  updateGroupList(list: GroupRo[]) {
+    this.groupList = list
+    if (!!this.groupList && this.groupList.length > 0) {
+      this.selectedGroup = this.groupList[0];
+    }
+  }
+
+  updateResourceList(list: ResourceRo[]) {
+    this.resourceList = list
+    if (!!this.resourceList && this.resourceList.length > 0) {
+      this.selectedResource = this.resourceList[0];
+    }
+  }
+
+  applyResourceFilter(event: Event) {
+    const filterValue = (event.target as HTMLInputElement).value;
+    this.filter["filter"] = filterValue.trim().toLowerCase();
+    this.refreshResources();
+  }
+
+  get disabledResourceFilter(): boolean{
+    return !this._selectedGroup;
+  }
+
+  isDirty(): boolean {
+    return false;
+  }
+
+}
diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts b/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts
new file mode 100644
index 000000000..0f6e9c71f
--- /dev/null
+++ b/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts
@@ -0,0 +1,81 @@
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs';
+
+import {HttpClient, HttpParams} from '@angular/common/http';
+import {SecurityService} from "../../security/security.service";
+import {User} from "../../security/user.model";
+import {SmpConstants} from "../../smp.constants";
+import {GroupRo} from "../../common/model/group-ro.model";
+import {ResourceRo} from "../../common/model/resource-ro.model";
+import {TableResult} from "../../common/model/table-result.model";
+import {DomainRo} from "../../common/model/domain-ro.model";
+
+@Injectable()
+export class EditResourceService {
+
+
+  constructor(
+    private http: HttpClient,
+    private securityService: SecurityService) {
+  }
+
+
+  public getGroupResourcesForGroupAdminObservable(group: GroupRo, domain: DomainRo, filter: any, page: number, pageSize: number): Observable<TableResult<ResourceRo>> {
+    return this.getGroupResourcesForUserTypeObservable('group-admin', group, domain, filter, page, pageSize);
+  }
+
+  public getGroupResourcesForResourceAdminObservable(group: GroupRo, domain: DomainRo, filter: any, page: number, pageSize: number): Observable<TableResult<ResourceRo>> {
+    return this.getGroupResourcesForUserTypeObservable('resource-admin', group, domain, filter, page, pageSize);
+  }
+
+  public getGroupResourcesForUserTypeObservable(userType: string, group: GroupRo, domain: DomainRo, filter: any, page: number, pageSize: number): Observable<TableResult<ResourceRo>> {
+
+    let params: HttpParams = new HttpParams()
+      .set(SmpConstants.PATH_QUERY_FILTER_TYPE, userType)
+      .set('page', page.toString())
+      .set('pageSize', pageSize.toString());
+
+    if (!!filter) {
+      for (let filterProperty in filter) {
+        if (filter.hasOwnProperty(filterProperty)) {
+          // must encode else problem with + sign
+          params = params.set(filterProperty, encodeURIComponent(filter[filterProperty]));
+        }
+      }
+    }
+
+
+    const currentUser: User = this.securityService.getCurrentUser();
+    return this.http.get<TableResult<ResourceRo>>(SmpConstants.REST_EDIT_RESOURCE
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId), {params});
+  }
+
+  deleteResourceFromGroup(resource: ResourceRo, group: GroupRo, domain: DomainRo): Observable<ResourceRo> {
+    const currentUser: User = this.securityService.getCurrentUser();
+    return this.http.delete<ResourceRo>(SmpConstants.REST_EDIT_RESOURCE_DELETE
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource.resourceId));
+  }
+
+  createResourceForGroup(resource: ResourceRo, group: GroupRo, domain: DomainRo): Observable<ResourceRo> {
+    const currentUser: User = this.securityService.getCurrentUser();
+    return this.http.put<ResourceRo>(SmpConstants.REST_EDIT_RESOURCE_CREATE
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId), resource);
+  }
+
+  updateResourceForGroup(resource: ResourceRo, group: GroupRo, domain: DomainRo): Observable<ResourceRo> {
+    const currentUser: User = this.securityService.getCurrentUser();
+
+    return this.http.post<ResourceRo>(SmpConstants.REST_EDIT_RESOURCE_UPDATE
+      .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId)
+      .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId)
+      .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId)
+      .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), resource);
+  }
+}
diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts
index b0bc948d0..36da55561 100644
--- a/smp-angular/src/app/smp.constants.ts
+++ b/smp-angular/src/app/smp.constants.ts
@@ -78,6 +78,11 @@ export class SmpConstants {
   public static readonly REST_EDIT_RESOURCE_DELETE = SmpConstants.REST_EDIT_RESOURCE  + '/' + SmpConstants.PATH_PARAM_ENC_RESOURCE_ID
     + '/' + SmpConstants.PATH_ACTION_DELETE;
 
+  public static readonly REST_EDIT_RESOURCE_MEMBER = SmpConstants.REST_EDIT_RESOURCE  + '/' + SmpConstants.PATH_PARAM_ENC_RESOURCE_ID
+    + '/' + SmpConstants.PATH_RESOURCE_TYPE_MEMBER ;
+  public static readonly REST_EDIT_RESOURCE_MEMBER_PUT = SmpConstants.REST_EDIT_RESOURCE_MEMBER  + '/' + SmpConstants.PATH_ACTION_PUT;
+  public static readonly REST_EDIT_RESOURCE_MEMBER_DELETE = SmpConstants.REST_EDIT_RESOURCE_MEMBER + '/' + SmpConstants.PATH_PARAM_ENC_MEMBER_ID
+    + '/' + SmpConstants.PATH_ACTION_DELETE;
 
 
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java
new file mode 100644
index 000000000..53724c367
--- /dev/null
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java
@@ -0,0 +1,26 @@
+package eu.europa.ec.edelivery.smp.conversion;
+
+import eu.europa.ec.edelivery.smp.data.model.user.DBResourceMember;
+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 DBResourceMemberToMemberROConverter implements Converter<DBResourceMember, MemberRO> {
+
+    @Override
+    public MemberRO convert(DBResourceMember source) {
+        MemberRO target = new MemberRO();
+        target.setMemberOf("RESOURCE");
+        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/data/dao/DomainDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainDao.java
index c29053731..99ffa95e7 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DomainDao.java
@@ -142,6 +142,23 @@ public class DomainDao extends BaseDao<DBDomain> {
         query.setParameter(PARAM_MEMBERSHIP_ROLES, toList(roleTypes));
         return query.getResultList();
     }
+
+    public Long getDomainsByUserIdAndResourceRolesCount(Long userId, MembershipRoleType ... roleTypes) {
+
+        TypedQuery<Long> query = memEManager.createNamedQuery(QUERY_DOMAIN_BY_USER_RESOURCE_ROLES_COUNT, Long.class);
+        query.setParameter(PARAM_USER_ID, userId);
+        query.setParameter(PARAM_MEMBERSHIP_ROLES, toList(roleTypes));
+        return query.getSingleResult();
+    }
+
+    public List<DBDomain> getDomainsByUserIdAndResourceRoles(Long userId, MembershipRoleType ... roleTypes) {
+
+        TypedQuery<DBDomain> query = memEManager.createNamedQuery(QUERY_DOMAIN_BY_USER_RESOURCE_ROLES, DBDomain.class);
+        query.setParameter(PARAM_USER_ID, userId);
+        query.setParameter(PARAM_MEMBERSHIP_ROLES, toList(roleTypes));
+        return query.getResultList();
+    }
+
     public List<MembershipRoleType> toList(MembershipRoleType ... roleTypes){
         return Arrays.asList(roleTypes ==null || roleTypes.length==0 ?MembershipRoleType.values(): roleTypes);
     }
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 8d712f356..10d33b668 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
@@ -71,7 +71,7 @@ public class DomainMemberDao extends BaseDao<DBDomainMember> {
     }
 
     public boolean isUserResourceAdministrator(Long userId){
-        return false;
+        return domainDao.getDomainsByUserIdAndResourceRolesCount(userId, MembershipRoleType.ADMIN)>0;
     }
 
     public List<DBDomainMember> getDomainMembers(Long domainId, int iPage, int iPageSize, String filter) {
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java
index 2d92c61c4..d8a761ae8 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java
@@ -135,20 +135,30 @@ public class GroupDao extends BaseDao<DBGroup> {
     }
 
     public List<DBGroup> getGroupsByUserIdAndRoles(Long userId, MembershipRoleType... roleTypes) {
-        TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_BY_USER_ROLES, DBGroup.class);
+        TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_BY_USER_GROUP_ROLES, DBGroup.class);
         query.setParameter(PARAM_USER_ID, userId);
         query.setParameter(PARAM_MEMBERSHIP_ROLES, toList(roleTypes));
         return query.getResultList();
     }
 
-    public List<DBGroup> getGroupsByDomainUserIdAndRoles(Long domainId, Long userId, MembershipRoleType... roleTypes) {
+    public List<DBGroup> getGroupsByDomainUserIdAndGroupRoles(Long domainId, Long userId, MembershipRoleType... roleTypes) {
 
-        TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_BY_DOMAIN_USER_ROLES, DBGroup.class);
+        TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_BY_DOMAIN_USER_GROUP_ROLES, DBGroup.class);
         query.setParameter(PARAM_DOMAIN_ID, domainId);
         query.setParameter(PARAM_USER_ID, userId);
         query.setParameter(PARAM_MEMBERSHIP_ROLES, toList(roleTypes));
         return query.getResultList();
     }
+
+    public List<DBGroup> getGroupsByDomainUserIdAndResourceRoles(Long domainId, Long userId, MembershipRoleType... roleTypes) {
+
+        TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_BY_DOMAIN_USER_RESOURCE_ROLES, DBGroup.class);
+        query.setParameter(PARAM_DOMAIN_ID, domainId);
+        query.setParameter(PARAM_USER_ID, userId);
+        query.setParameter(PARAM_MEMBERSHIP_ROLES, toList(roleTypes));
+        return query.getResultList();
+    }
+
     /**
      * Removes Entity by given domain code
      *
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupMemberDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupMemberDao.java
index ba610172f..42afeb424 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupMemberDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupMemberDao.java
@@ -122,7 +122,7 @@ public class GroupMemberDao extends BaseDao<DBGroupMember> {
     }
 
 
-    public DBGroupMember addMemberToDomain(DBGroup group, DBUser user, MembershipRoleType role) {
+    public DBGroupMember addMemberToGroup(DBGroup group, DBUser user, MembershipRoleType role) {
         DBGroupMember groupMember = new DBGroupMember();
         groupMember.setRole(role);
         groupMember.setUser(user);
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 5c5dd1069..ff1b663df 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
@@ -19,6 +19,9 @@ public class QueryNames {
 
     public static final String QUERY_DOMAIN_BY_USER_GROUP_ROLES_COUNT = "DBDomain.getByUserAndGroupRolesCount";
     public static final String QUERY_DOMAIN_BY_USER_GROUP_ROLES = "DBDomain.getByUserAndGroupRoles";
+
+    public static final String QUERY_DOMAIN_BY_USER_RESOURCE_ROLES_COUNT = "DBDomain.getByUserAndResourceRolesCount";
+    public static final String QUERY_DOMAIN_BY_USER_RESOURCE_ROLES = "DBDomain.getByUserAndResourceRoles";
     public static final String QUERY_EXTENSION_ALL = "DBExtension.getAll";
     public static final String QUERY_EXTENSION_BY_IDENTIFIER = "DBExtension.getByIdentifier";
 
@@ -27,13 +30,15 @@ public class QueryNames {
     public static final String QUERY_GROUP_BY_NAME_DOMAIN = "DBGroup.getByNameDomain";
     public static final String QUERY_GROUP_BY_NAME_DOMAIN_CODE = "DBGroup.getByNameDomainCode";
 
-    public static final String QUERY_GROUP_BY_USER_ROLES = "DBGroup.getByUserAndRoles";
-    public static final String QUERY_GROUP_BY_DOMAIN_USER_ROLES = "DBGroup.getByDomainAndUserAndRoles";
-    public static final String QUERY_GROUP_BY_USER_ROLES_COUNT = "DBGroup.getByUserAndRolesCount";
-    public static final String QUERY_GROUP_MEMBERS_COUNT = "DBGroup.getByGroupCount";
-    public static final String QUERY_GROUP_MEMBERS_FILTER_COUNT = "DBGroup.getByGroupFilterCount";
-    public static final String QUERY_GROUP_MEMBERS = "DBGroup.getByGroup";
-    public static final String QUERY_GROUP_MEMBERS_FILTER = "DBGroup.getByGroupFilter";
+    public static final String QUERY_GROUP_BY_USER_GROUP_ROLES = "DBGroup.getByUserAndRoles";
+    public static final String QUERY_GROUP_BY_DOMAIN_USER_GROUP_ROLES = "DBGroup.getByDomainAndUserAndGroupRoles";
+
+    public static final String QUERY_GROUP_BY_DOMAIN_USER_RESOURCE_ROLES = "DBGroup.getByDomainAndUserAndResourceRoles";
+    public static final String QUERY_GROUP_BY_USER_ROLES_COUNT = "DBGroup.getByUserAndGrouRolesCount";
+    public static final String QUERY_GROUP_MEMBERS_COUNT = "DBGroupMember.getByGroupCount";
+    public static final String QUERY_GROUP_MEMBERS_FILTER_COUNT = "DBGroupMember.getByGroupFilterCount";
+    public static final String QUERY_GROUP_MEMBERS = "DBGroupMember.getByGroup";
+    public static final String QUERY_GROUP_MEMBERS_FILTER = "DBGroupMember.getByGroupFilter";
 
     public static final String QUERY_DOMAIN_MEMBER_ALL = "DBDomainMember.getAll";
     public static final String QUERY_DOMAIN_MEMBER_BY_USER_DOMAINS_COUNT = "DBDomainMember.getByUserAndDomainsCount";
@@ -66,6 +71,11 @@ public class QueryNames {
     public static final String QUERY_RESOURCE_MEMBER_BY_USER_DOMAIN_RESOURCE_ROLE_COUNT = "DBResourceMember.getByUserAndDomainRoleResourceCount";
     public static final String QUERY_RESOURCE_MEMBER_BY_USER_GROUP_RESOURCES_ROLE_COUNT = "DBResourceMember.getByUserAndGroupsResourcesAndRoleCount";
 
+    public static final String QUERY_RESOURCE_MEMBERS_COUNT = "DBResourceMember.getByResourceCount";
+    public static final String QUERY_RESOURCE_MEMBERS_FILTER_COUNT = "DBResourceMember.getByResourceFilterCount";
+    public static final String QUERY_RESOURCE_MEMBERS = "DBResourceMember.getByResource";
+    public static final String QUERY_RESOURCE_MEMBERS_FILTER = "DBResourceMember.getByResourceFilter";
+
     public static final String QUERY_RESOURCE_MEMBER_BY_USER_RESOURCE= "DBResourceMember.getByUserAndResource";
 
     public static final String QUERY_SUBRESOURCE_BY_IDENTIFIER_RESOURCE_SUBRESDEF = "DBSubresource.getByIdentifierAndResourceAndSubresourceDef";
@@ -93,6 +103,8 @@ public class QueryNames {
     public static final String QUERY_GROUP_MEMBER_BY_USER_GROUPS_COUNT = "DBGroupMember.getByUserAndGroupsCount";
     public static final String QUERY_GROUP_MEMBER_BY_USER_DOMAIN_GROUPS_COUNT = "DBGroupMember.getByUserAndDomainGroupsCount";
     public static final String QUERY_GROUP_MEMBER_BY_USER_GROUPS = "DBGroupMember.getByUserAndGroups";
+
+    public static final String QUERY_RESOURCE_MEMBER_BY_USER_RESOURCES = "DBResourceMember.getByUserAndResources";
     public static final String QUERY_GROUP_MEMBER_BY_USER_DOMAIN_GROUPS_ROLE_COUNT = "DBGroupMember.getByUserAndDomainGroupsAndRoleCount";
 
     public static final String QUERY_USER_BY_CI_USERNAME = "DBUser.getUserByUsernameInsensitive";
@@ -122,6 +134,7 @@ public class QueryNames {
     public static final String PARAM_CERTIFICATE_IDENTIFIER = "certificate_identifier";
 
     public static final String PARAM_RESOURCE_ID = "resource_id";
+    public static final String PARAM_RESOURCE_IDS = "resource_ids";
     public static final String PARAM_SUBRESOURCE_ID = "subresource_id";
     // resource identifier value
     public static final String PARAM_RESOURCE_IDENTIFIER = "resource_identifier";
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java
index 73f95e564..76c9267fb 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java
@@ -15,11 +15,14 @@ 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.DBGroup;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBResource;
+import eu.europa.ec.edelivery.smp.data.model.user.DBGroupMember;
 import eu.europa.ec.edelivery.smp.data.model.user.DBResourceMember;
 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;
@@ -105,4 +108,43 @@ public class ResourceMemberDao extends BaseDao<DBResourceMember> {
     }
 
 
+    public List<DBResourceMember> getResourceMembers(Long resourceId, int iPage, int iPageSize, String filter) {
+        boolean hasFilter = StringUtils.isNotBlank(filter);
+        TypedQuery<DBResourceMember> query = memEManager.createNamedQuery(hasFilter ?
+                QUERY_RESOURCE_MEMBERS_FILTER : QUERY_RESOURCE_MEMBERS, DBResourceMember.class);
+
+        if (iPageSize > -1 && iPage > -1) {
+            query.setFirstResult(iPage * iPageSize);
+        }
+        if (iPageSize > 0) {
+            query.setMaxResults(iPageSize);
+        }
+        query.setParameter(PARAM_RESOURCE_ID, resourceId);
+        if (hasFilter) {
+            query.setParameter(PARAM_USER_FILTER, StringUtils.wrapIfMissing(StringUtils.trim(filter),"%" ));
+        }
+        return query.getResultList();
+    }
+
+    public Long getResourceMemberCount(Long groupId, String filter) {
+        boolean hasFilter = StringUtils.isNotBlank(filter);
+        TypedQuery<Long> query = memEManager.createNamedQuery(hasFilter ? QUERY_RESOURCE_MEMBERS_FILTER_COUNT : QUERY_RESOURCE_MEMBERS_COUNT, Long.class);
+        query.setParameter(PARAM_RESOURCE_ID, groupId);
+        if (hasFilter) {
+            query.setParameter(PARAM_USER_FILTER, StringUtils.wrapIfMissing(StringUtils.trim(filter),"%" ));
+        }
+        return query.getSingleResult();
+    }
+
+
+    public DBResourceMember addMemberToResource(DBResource resource, DBUser user, MembershipRoleType role) {
+        DBResourceMember resourceMember = new DBResourceMember();
+        resourceMember.setRole(role);
+        resourceMember.setUser(user);
+        resourceMember.setResource(resource);
+        resourceMember = merge(resourceMember);
+        return resourceMember;
+    }
+
+
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java
index dce74527c..fb791616f 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java
@@ -58,6 +58,19 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*;
         " JOIN DBGroupMember gm ON g.id = gm.group.id " +
         " WHERE gm.role in (:membership_roles) and gm.user.id= :user_id")
 
+@NamedQuery(name = QUERY_DOMAIN_BY_USER_RESOURCE_ROLES_COUNT, query = "SELECT count(d) FROM DBDomain d " +
+        " JOIN DBGroup g ON d.id = g.domain.id " +
+        " JOIN DBResource r ON  g.id = r.group.id " +
+        " JOIN DBResourceMember rm ON r.id = rm.resource.id " +
+        " WHERE rm.role in (:membership_roles) and rm.user.id= :user_id")
+
+
+@NamedQuery(name = QUERY_DOMAIN_BY_USER_RESOURCE_ROLES, query = "SELECT d FROM DBDomain d " +
+        " JOIN DBGroup g ON d.id = g.domain.id " +
+        " JOIN DBResource r ON  g.id = r.group.id " +
+        " JOIN DBResourceMember rm ON r.id = rm.resource.id " +
+        " WHERE rm.role in (:membership_roles) and rm.user.id= :user_id")
+
 @org.hibernate.annotations.Table(appliesTo = "SMP_DOMAIN", comment = "SMP can handle multiple domains. This table contains domain specific data")
 public class DBDomain extends BaseEntity {
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBGroup.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBGroup.java
index ccd8cfd81..321728bc8 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBGroup.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBGroup.java
@@ -45,10 +45,15 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*;
 @NamedQuery(name = QUERY_GROUP_BY_USER_ROLES_COUNT, query = "SELECT count(c) FROM DBGroup c JOIN DBGroupMember dm ON c.id = dm.group.id " +
         " WHERE dm.role in (:membership_roles) and dm.user.id= :user_id")
 
-@NamedQuery(name = QUERY_GROUP_BY_USER_ROLES, query = "SELECT c FROM DBGroup c JOIN DBGroupMember dm ON c.id = dm.group.id " +
-        " WHERE dm.role in (:membership_roles) and dm.user.id= :user_id")
-@NamedQuery(name = QUERY_GROUP_BY_DOMAIN_USER_ROLES, query = "SELECT c FROM DBGroup c JOIN DBGroupMember dm ON c.id = dm.group.id " +
+@NamedQuery(name = QUERY_GROUP_BY_USER_GROUP_ROLES, query = "SELECT c FROM DBGroup c JOIN DBGroupMember gm ON c.id = gm.group.id " +
+        " WHERE gm.role in (:membership_roles) and gm.user.id= :user_id")
+@NamedQuery(name = QUERY_GROUP_BY_DOMAIN_USER_GROUP_ROLES, query = "SELECT c FROM DBGroup c JOIN DBGroupMember dm ON c.id = dm.group.id " +
         " WHERE c.domain.id = :domain_id AND dm.role in (:membership_roles) and dm.user.id= :user_id")
+@NamedQuery(name = QUERY_GROUP_BY_DOMAIN_USER_RESOURCE_ROLES, query = "SELECT c FROM DBGroup c " +
+        " JOIN DBResource r ON c.id = r.group.id " +
+        " JOIN DBResourceMember rm on r.id = rm.resource.id" +
+        " WHERE c.domain.id = :domain_id AND rm.role in (:membership_roles) and rm.user.id= :user_id")
+
 public class DBGroup extends BaseEntity {
 
     @Id
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBGroupMember.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBGroupMember.java
index bf89df0aa..1db9cd3b9 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBGroupMember.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBGroupMember.java
@@ -40,8 +40,6 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*;
         " WHERE c.group.id = :group_id AND (lower(c.user.fullName) like lower(:user_filter) OR lower(c.user.username) like lower(:user_filter))")
 @NamedQuery(name = QUERY_GROUP_MEMBERS_FILTER, query = "SELECT c FROM DBGroupMember c " +
         " WHERE c.group.id = :group_id  AND (lower(c.user.fullName) like lower(:user_filter) OR lower(c.user.username) like lower(:user_filter))  order by c.user.username")
-
-
 @NamedQuery(name = QUERY_GROUP_MEMBER_BY_USER_DOMAIN_GROUPS_ROLE_COUNT, query = "SELECT count(c) FROM DBGroupMember c " +
         " WHERE c.user.id = :user_id AND c.group.domain.id = :domain_id AND c.role= :membership_role ")
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java
index 35f42b7d9..c34140f9f 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java
@@ -34,6 +34,14 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*;
 @NamedQuery(name = QUERY_RESOURCE_MEMBER_BY_USER_GROUP_RESOURCES_ROLE_COUNT, query = "SELECT count(c) FROM DBResourceMember c " +
         " WHERE c.user.id = :user_id AND c.resource.group.id = :group_id AND c.role= :membership_role ")
 
+@NamedQuery(name = QUERY_RESOURCE_MEMBERS_COUNT, query = "SELECT count(c) FROM DBResourceMember c " +
+        " WHERE c.resource.id = :resource_id")
+@NamedQuery(name = QUERY_RESOURCE_MEMBERS, query = "SELECT c FROM DBResourceMember c " +
+        " WHERE c.resource.id = :resource_id order by c.user.username")
+@NamedQuery(name = QUERY_RESOURCE_MEMBERS_FILTER_COUNT, query = "SELECT count(c) FROM DBResourceMember c " +
+        " WHERE c.resource.id = :resource_id AND (lower(c.user.fullName) like lower(:user_filter) OR lower(c.user.username) like lower(:user_filter))")
+@NamedQuery(name = QUERY_RESOURCE_MEMBERS_FILTER, query = "SELECT c FROM DBResourceMember c " +
+        " WHERE c.resource.id = :resource_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 DBResourceMember extends BaseEntity {
 
     @Id
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 70aea05df..ad78a9ac6 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
@@ -85,6 +85,13 @@ public class UIDomainPublicService extends UIServiceBase<DBDomain, DomainPublicR
                 .collect(Collectors.toList());
     }
 
+    @Transactional
+    public List<DomainRO> getAllDomainsForResourceAdminUser(Long userId) {
+        List<DBDomain> domains = domainDao.getDomainsByUserIdAndResourceRoles(userId, MembershipRoleType.ADMIN);
+        return domains.stream().map(domain -> conversionService.convert(domain, DomainRO.class))
+                .collect(Collectors.toList());
+    }
+
     @Transactional
     public ServiceResult<MemberRO> getDomainMembers(Long domainId, int page, int pageSize,
                                                    String filter) {
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIGroupPublicService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIGroupPublicService.java
index e9137971f..53c37efd1 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIGroupPublicService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIGroupPublicService.java
@@ -86,17 +86,16 @@ public class UIGroupPublicService extends UIServiceBase<DBGroup, GroupRO> {
     }
 
     @Transactional
-    public List<GroupRO> getAllGroupsForDomainAndUserAndRole(Long domainId, Long userId, MembershipRoleType role) {
-        List<DBGroup> domainGroups = groupDao.getGroupsByDomainUserIdAndRoles(domainId, userId, role);
+    public List<GroupRO> getAllGroupsForDomainAndUserAndGroupRole(Long domainId, Long userId, MembershipRoleType role) {
+        List<DBGroup> domainGroups = groupDao.getGroupsByDomainUserIdAndGroupRoles(domainId, userId, role);
 
         return domainGroups.stream().map(domain -> conversionService.convert(domain, GroupRO.class))
                 .collect(Collectors.toList());
     }
 
     @Transactional
-    public List<GroupRO> getAllGroupsForUser(Long userId, MembershipRoleType role) {
-        List<DBGroup> domainGroups = groupDao.getGroupsByUserIdAndRoles(userId, role);
-
+    public List<GroupRO> getAllGroupsForDomainAndUserAndResourceRole(Long domainId, Long userId, MembershipRoleType role) {
+        List<DBGroup> domainGroups = groupDao.getGroupsByDomainUserIdAndResourceRoles(domainId, userId, role);
         return domainGroups.stream().map(domain -> conversionService.convert(domain, GroupRO.class))
                 .collect(Collectors.toList());
     }
@@ -199,7 +198,7 @@ public class UIGroupPublicService extends UIServiceBase<DBGroup, GroupRO> {
             if (groupMemberDao.isUserGroupMember(user, Collections.singletonList(group))) {
                 throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Add membership", "User [" + memberRO.getUsername() + "] is already a member!");
             }
-            member = groupMemberDao.addMemberToDomain(group, user, memberRO.getRoleType());
+            member = groupMemberDao.addMemberToGroup(group, user, memberRO.getRoleType());
         }
         return conversionService.convert(member, MemberRO.class);
     }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java
index ae8367b9e..e5e05492f 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java
@@ -1,15 +1,17 @@
 package eu.europa.ec.edelivery.smp.services.ui;
 
-import eu.europa.ec.edelivery.smp.data.dao.DomainResourceDefDao;
-import eu.europa.ec.edelivery.smp.data.dao.GroupDao;
-import eu.europa.ec.edelivery.smp.data.dao.ResourceDao;
-import eu.europa.ec.edelivery.smp.data.dao.ResourceDefDao;
+import eu.europa.ec.edelivery.smp.data.dao.*;
+import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType;
 import eu.europa.ec.edelivery.smp.data.model.DBDomainResourceDef;
 import eu.europa.ec.edelivery.smp.data.model.DBGroup;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBResource;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBResourceFilter;
 import eu.europa.ec.edelivery.smp.data.model.ext.DBResourceDef;
+import eu.europa.ec.edelivery.smp.data.model.user.DBGroupMember;
+import eu.europa.ec.edelivery.smp.data.model.user.DBResourceMember;
+import eu.europa.ec.edelivery.smp.data.model.user.DBUser;
+import eu.europa.ec.edelivery.smp.data.ui.MemberRO;
 import eu.europa.ec.edelivery.smp.data.ui.ResourceRO;
 import eu.europa.ec.edelivery.smp.data.ui.ServiceResult;
 import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
@@ -22,6 +24,7 @@ import org.springframework.core.convert.ConversionService;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -44,16 +47,20 @@ public class UIResourceService {
 
     private final ResourceDao resourceDao;
     private final GroupDao groupDao;
+    private final ResourceMemberDao resourceMemberDao;
+    private final UserDao userDao;
     private final ResourceDefDao resourceDefDao;
     private final DomainResourceDefDao domainResourceDefDao;
     private final ConversionService conversionService;
     private final SmlConnector smlConnector;
 
-    public UIResourceService(ResourceDao resourceDao, ResourceDefDao resourceDefDao, DomainResourceDefDao domainResourceDefDao, GroupDao groupDao, ConversionService conversionService, SmlConnector smlConnector) {
+    public UIResourceService(ResourceDao resourceDao, ResourceMemberDao resourceMemberDao, ResourceDefDao resourceDefDao, DomainResourceDefDao domainResourceDefDao,  UserDao userDao, GroupDao groupDao, ConversionService conversionService, SmlConnector smlConnector) {
         this.resourceDao = resourceDao;
+        this.resourceMemberDao = resourceMemberDao;
         this.resourceDefDao = resourceDefDao;
         this.domainResourceDefDao = domainResourceDefDao;
         this.groupDao = groupDao;
+        this.userDao = userDao;
         this.conversionService = conversionService;
         this.smlConnector = smlConnector;
     }
@@ -89,6 +96,43 @@ public class UIResourceService {
         return result;
     }
 
+
+    @Transactional
+    public ServiceResult<ResourceRO> getResourcesForUserAndGroup(Long userId, MembershipRoleType role,  Long groupId, int page, int pageSize, String filterValue) {
+
+        DBGroup group = groupDao.find(groupId);
+        if (group == null) {
+            throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_LIST, "Group does not exist!");
+        }
+        DBUser user = userDao.find(userId);
+        if (user == null) {
+            throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_LIST, "User does not exist!");
+        }
+
+        DBResourceFilter filter = DBResourceFilter.createBuilder()
+                .user(user)
+                .membershipRoleType(role)
+                .group(group)
+                .identifierFilter(StringUtils.trimToNull(filterValue))
+                .build();
+
+        Long count = resourceDao.getResourcesForFilterCount(filter);
+
+        ServiceResult<ResourceRO> result = new ServiceResult<>();
+        result.setPage(page);
+        result.setPageSize(pageSize);
+        if (count < 1) {
+            result.setCount(0L);
+            return result;
+        }
+        result.setCount(count);
+        List<DBResource> resources = resourceDao.getResourcesForFilter(page, pageSize, filter);
+        List<ResourceRO> resourceROS = resources.stream().map(resource -> conversionService.convert(resource, ResourceRO.class)).collect(Collectors.toList());
+        resourceDao.getResourcesForFilter(page, pageSize, filter);
+        result.getServiceEntities().addAll(resourceROS);
+        return result;
+    }
+
     @Transactional
     public ResourceRO deleteResourceFromGroup(Long resourceId, Long groupId,  Long domainId) {
         DBResource resource = resourceDao.find(resourceId);
@@ -172,6 +216,61 @@ public class UIResourceService {
         return conversionService.convert(resource, ResourceRO.class);
     }
 
+    @Transactional
+    public ServiceResult<MemberRO> getResourceMembers(Long resourceId, int page, int pageSize,
+                                                   String filter) {
+        Long count = resourceMemberDao.getResourceMemberCount(resourceId, filter);
+        ServiceResult<MemberRO> result = new ServiceResult<>();
+        result.setPage(page);
+        result.setPageSize(pageSize);
+        if (count < 1) {
+            result.setCount(0L);
+            return result;
+        }
+        result.setCount(count);
+        List<DBResourceMember> memberROS = resourceMemberDao.getResourceMembers(resourceId, 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 addMemberToResource(Long resourceId, MemberRO memberRO, Long memberId) {
+        LOG.info("Add member [{}] to resource [{}]", memberRO.getUsername(), resourceId);
+        DBUser user = userDao.findUserByUsername(memberRO.getUsername())
+                .orElseThrow(() -> new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Add/edit membership", "User [" + memberRO.getUsername() + "] does not exists!"));
+
+        DBResourceMember member;
+        if (memberId != null) {
+            member = resourceMemberDao.find(memberId);
+            member.setRole(memberRO.getRoleType());
+        } else {
+            DBResource resource = resourceDao.find(resourceId);
+            if (resourceMemberDao.isUserResourceMember(user, resource)) {
+                throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Add membership", "User [" + memberRO.getUsername() + "] is already a member!");
+            }
+            member = resourceMemberDao.addMemberToResource(resource, user, memberRO.getRoleType());
+        }
+        return conversionService.convert(member, MemberRO.class);
+    }
+
+    @Transactional
+    public MemberRO deleteMemberFromResource(Long resourceId, Long memberId) {
+        LOG.info("Delete member [{}] from resource [{}]", memberId, resourceId);
+        DBResourceMember resourceMember = resourceMemberDao.find(memberId);
+        if (resourceMember == null) {
+            throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Membership", "Membership does not exists!");
+        }
+        if (!Objects.equals(resourceMember.getResource().getId(), resourceId)) {
+            throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Membership", "Membership does not belong to resource!");
+        }
+
+        resourceMemberDao.remove(resourceMember);
+        return conversionService.convert(resourceMember, MemberRO.class);
+    }
+
+
     public DBDocument createDocumentForResourceDef(DBResourceDef resourceDef) {
         DBDocument document = new DBDocument();
         document.setCurrentVersion(1);
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoTest.java
index b1fa8c48f..9d433f3f1 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DomainDaoTest.java
@@ -28,6 +28,7 @@ public class DomainDaoTest extends AbstractBaseDao {
         testUtilsDao.clearData();
         testUtilsDao.creatDomainMemberships();
         testUtilsDao.createGroupMemberships();
+        testUtilsDao.createResourceMemberships();
 
     }
     @Test
@@ -111,4 +112,44 @@ public class DomainDaoTest extends AbstractBaseDao {
         result = testInstance.getDomainsByUserIdAndGroupRoles(testUtilsDao.getUser1().getId(), MembershipRoleType.VIEWER,  MembershipRoleType.ADMIN);
         assertEquals(2, result.size());
     }
+
+    @Test
+    public void getDomainsByUserIdAndResourceRolesCount() {
+        // one for domain 1
+        Long cnt = testInstance.getDomainsByUserIdAndResourceRolesCount(testUtilsDao.getUser1().getId(), MembershipRoleType.ADMIN);
+        assertEquals(1, cnt.intValue());
+
+        // one for domain 2
+        cnt = testInstance.getDomainsByUserIdAndResourceRolesCount(testUtilsDao.getUser1().getId(), MembershipRoleType.VIEWER);
+        assertEquals(1, cnt.intValue());
+
+        // all
+        cnt = testInstance.getDomainsByUserIdAndResourceRolesCount(testUtilsDao.getUser1().getId());
+        assertEquals(2, cnt.intValue());
+
+        // all
+        cnt = testInstance.getDomainsByUserIdAndResourceRolesCount(testUtilsDao.getUser1().getId(),  MembershipRoleType.VIEWER,  MembershipRoleType.ADMIN);
+        assertEquals(2, cnt.intValue());
+    }
+    @Test
+    public void getDomainsByUserIdAndResourceRoles() {
+        // one for domain 1
+        List<DBDomain> result = testInstance.getDomainsByUserIdAndResourceRoles(testUtilsDao.getUser1().getId(), MembershipRoleType.ADMIN);
+        assertEquals(1, result.size());
+        assertEquals(testUtilsDao.getD1(), result.get(0));
+
+        // one for domain 2
+        result = testInstance.getDomainsByUserIdAndResourceRoles(testUtilsDao.getUser1().getId(), MembershipRoleType.VIEWER);
+        assertEquals(1, result.size());
+        assertEquals(testUtilsDao.getD2(), result.get(0));
+
+        result = testInstance.getDomainsByUserIdAndResourceRoles(testUtilsDao.getUser2().getId(), MembershipRoleType.VIEWER);
+        assertEquals(0, result.size());
+
+        result = testInstance.getDomainsByUserIdAndResourceRoles(testUtilsDao.getUser1().getId());
+        assertEquals(2, result.size());
+
+        result = testInstance.getDomainsByUserIdAndResourceRoles(testUtilsDao.getUser1().getId(), MembershipRoleType.VIEWER,  MembershipRoleType.ADMIN);
+        assertEquals(2, result.size());
+    }
 }
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/GroupDaoTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/GroupDaoTest.java
index 0f6c6cd95..6eeb20442 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/GroupDaoTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/GroupDaoTest.java
@@ -29,6 +29,7 @@ public class GroupDaoTest extends AbstractBaseDao {
         // setup initial data!
         testUtilsDao.clearData();
         testUtilsDao.createGroupMemberships();
+        testUtilsDao.createResourceMemberships();
         testInstance.clearPersistenceContext();
     }
 
@@ -97,30 +98,30 @@ public class GroupDaoTest extends AbstractBaseDao {
     }
 
     @Test
-    public void getGroupsByDomainUserIdAndRolesExists() {
+    public void getGroupsByDomainUserIdAndGroupRolesExists() {
 
-        List<DBGroup> groups = testInstance.getGroupsByDomainUserIdAndRoles(
+        List<DBGroup> groups = testInstance.getGroupsByDomainUserIdAndGroupRoles(
                 testUtilsDao.getD1().getId(),
                 testUtilsDao.getUser1().getId(),
                 MembershipRoleType.ADMIN);
 
         assertEquals(1, groups.size());
 
-        groups = testInstance.getGroupsByDomainUserIdAndRoles(
+        groups = testInstance.getGroupsByDomainUserIdAndGroupRoles(
                 testUtilsDao.getD1().getId(),
                 testUtilsDao.getUser2().getId(),
                 MembershipRoleType.ADMIN);
 
         assertEquals(0, groups.size());
 
-        groups = testInstance.getGroupsByDomainUserIdAndRoles(
+        groups = testInstance.getGroupsByDomainUserIdAndGroupRoles(
                 testUtilsDao.getD1().getId(),
                 testUtilsDao.getUser1().getId(),
                 MembershipRoleType.VIEWER);
 
         assertEquals(0, groups.size());
 
-        groups = testInstance.getGroupsByDomainUserIdAndRoles(
+        groups = testInstance.getGroupsByDomainUserIdAndGroupRoles(
                 testUtilsDao.getD2().getId(),
                 testUtilsDao.getUser1().getId(),
                 MembershipRoleType.VIEWER);
@@ -128,5 +129,35 @@ public class GroupDaoTest extends AbstractBaseDao {
         assertEquals(1, groups.size());
     }
 
+    @Test
+    public void getGroupsByDomainUserIdAndResourceRoles() {
+
+        List<DBGroup> groups = testInstance.getGroupsByDomainUserIdAndResourceRoles(
+                testUtilsDao.getD1().getId(),
+                testUtilsDao.getUser1().getId(),
+                MembershipRoleType.ADMIN);
+
+        assertEquals(1, groups.size());
+
+        groups = testInstance.getGroupsByDomainUserIdAndResourceRoles(
+                testUtilsDao.getD1().getId(),
+                testUtilsDao.getUser2().getId(),
+                MembershipRoleType.ADMIN);
+
+        assertEquals(0, groups.size());
 
+        groups = testInstance.getGroupsByDomainUserIdAndResourceRoles(
+                testUtilsDao.getD1().getId(),
+                testUtilsDao.getUser1().getId(),
+                MembershipRoleType.VIEWER);
+
+        assertEquals(0, groups.size());
+
+        groups = testInstance.getGroupsByDomainUserIdAndResourceRoles(
+                testUtilsDao.getD2().getId(),
+                testUtilsDao.getUser1().getId(),
+                MembershipRoleType.VIEWER);
+
+        assertEquals(1, groups.size());
+    }
 }
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 159a3053e..6236e0929 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
@@ -95,7 +95,12 @@ insert into SMP_SUBRESOURCE (ID, FK_RESOURCE_ID,FK_SUREDEF_ID, FK_DOCUMENT_ID, I
 (2, 1, 1, 2, 'service-value2', 'service-schema2', NOW(),  NOW());
 
 insert into SMP_RESOURCE_MEMBER (ID, FK_RESOURCE_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values
-(1, 1, 2, 'ADMIN', NOW(),  NOW());
+(1, 1, 2, 'ADMIN', NOW(),  NOW()),
+(2, 2, 1, 'ADMIN', NOW(),  NOW()),
+(3, 3, 2, 'ADMIN', NOW(),  NOW()),
+(4, 4, 2, 'ADMIN', NOW(),  NOW()),
+(5, 5, 2, 'ADMIN', NOW(),  NOW())
+;
 
 insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values
 (1, 1, 2, 'ADMIN', NOW(),  NOW()),
@@ -105,3 +110,4 @@ insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREA
 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 45853178c..4f149a260 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
@@ -88,6 +88,12 @@ public class SMPAuthorizationService {
         return groupMemberDao.isUserGroupMemberWithRole(userDetails.getUser().getId(), Collections.singletonList(groupId), MembershipRoleType.ADMIN);
     }
 
+    public boolean isResourceAdministrator(String resourceEncId) {
+        SMPUserDetails userDetails = getAndValidateUserDetails();
+        Long resourceId  = getIdFromEncryptedString(resourceEncId, false);
+        return resourceMemberDao.isUserResourceMemberWithRole(userDetails.getUser().getId(), resourceId, MembershipRoleType.ADMIN);
+    }
+
     public boolean isAnyDomainAdministrator() {
         SMPUserDetails userDetails = getAndValidateUserDetails();
         return domainMemberDao.isUserAnyDomainAdministrator(userDetails.getUser().getId());
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 50acffc30..68e7a50d2 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
@@ -76,14 +76,17 @@ public class ResourceConstants {
             + "{" + PATH_PARAM_ENC_MEMBER_ID + "}" + "/" +  PATH_ACTION_DELETE;
     public static final String CONTEXT_PATH_EDIT_RESOURCE = CONTEXT_PATH_EDIT_GROUP + "/" +  "{" + PATH_PARAM_ENC_GROUP_ID + "}"
             + "/"+ PATH_RESOURCE_TYPE_RESOURCE;
-
     public static final String SUB_CONTEXT_PATH_EDIT_RESOURCE_CREATE =  PATH_ACTION_CREATE;
-
     public static final String SUB_CONTEXT_PATH_EDIT_RESOURCE_DELETE = "{" + PATH_PARAM_ENC_RESOURCE_ID + "}"
             + "/"+ PATH_ACTION_DELETE;
     public static final String SUB_CONTEXT_PATH_EDIT_RESOURCE_UPDATE = "{" + PATH_PARAM_ENC_RESOURCE_ID + "}"
             + "/"+ PATH_ACTION_UPDATE;
 
+    public static final String SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER =  "{" + PATH_PARAM_ENC_RESOURCE_ID + "}" + "/" +  PATH_RESOURCE_TYPE_MEMBER;
+    public static final String SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER_PUT =  SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER+ "/" +  PATH_ACTION_PUT;
+    public static final String SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER_DELETE = SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER + "/"
+            + "{" + PATH_PARAM_ENC_MEMBER_ID + "}" + "/" +  PATH_ACTION_DELETE;
+
     // public
     public static final String CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT = CONTEXT_PATH_PUBLIC + "search";
     public static final String CONTEXT_PATH_PUBLIC_DOMAIN = CONTEXT_PATH_PUBLIC + "domain";
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DomainEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DomainEditController.java
index c5d6303b0..b14900c43 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DomainEditController.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DomainEditController.java
@@ -57,7 +57,9 @@ public class DomainEditController {
         if (StringUtils.equals(forRole, "group-admin")) {
             return uiDomainService.getAllDomainsForGroupAdminUser(userId);
         }
-
+        if (StringUtils.equals(forRole, "resource-admin")) {
+            return uiDomainService.getAllDomainsForResourceAdminUser(userId);
+        }
         if (StringUtils.isBlank(forRole) || StringUtils.equals(forRole, "domain-admin")) {
             return uiDomainService.getAllDomainsForDomainAdminUser(userId);
         }
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java
index e2d8af16c..94d03743f 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java
@@ -66,13 +66,18 @@ public class GroupEditController {
             return uiGroupPublicService.getAllGroupsForDomain(domainId);
         }
         if (StringUtils.equalsIgnoreCase("group-admin", forRole)) {
-            return uiGroupPublicService.getAllGroupsForDomainAndUserAndRole(domainId, userId, MembershipRoleType.ADMIN);
+            return uiGroupPublicService.getAllGroupsForDomainAndUserAndGroupRole(domainId, userId, MembershipRoleType.ADMIN);
         }
+
+        if (StringUtils.equalsIgnoreCase("resource-admin", forRole)) {
+            return uiGroupPublicService.getAllGroupsForDomainAndUserAndResourceRole(domainId, userId, MembershipRoleType.ADMIN);
+        }
+
         if (StringUtils.equalsIgnoreCase("group-viewer", forRole)) {
-            return uiGroupPublicService.getAllGroupsForDomainAndUserAndRole(domainId, userId, MembershipRoleType.VIEWER);
+            return uiGroupPublicService.getAllGroupsForDomainAndUserAndGroupRole(domainId, userId, MembershipRoleType.VIEWER);
         }
         if (StringUtils.equalsIgnoreCase("all-roles", forRole)) {
-            return uiGroupPublicService.getAllGroupsForDomainAndUserAndRole(domainId, userId, null);
+            return uiGroupPublicService.getAllGroupsForDomainAndUserAndGroupRole(domainId, userId, null);
         }
         throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "getGroupsForDomain", "Unknown parameter type [" + forRole + "]!");
     }
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java
index 5071a29f6..e45893362 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java
@@ -1,6 +1,8 @@
 package eu.europa.ec.edelivery.smp.ui.edit;
 
 
+import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType;
+import eu.europa.ec.edelivery.smp.data.ui.MemberRO;
 import eu.europa.ec.edelivery.smp.data.ui.ResourceRO;
 import eu.europa.ec.edelivery.smp.data.ui.ServiceResult;
 import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
@@ -59,20 +61,20 @@ public class ResourceEditController {
                                                           @RequestParam(value = PARAM_PAGINATION_FILTER, defaultValue = "", required = false) String filter) {
         logAdminAccess("getResourcesForGroup and type: " + forRole);
         Long groupId = SessionSecurityUtils.decryptEntityId(groupEncId);
+        Long userId = SessionSecurityUtils.decryptEntityId(userEncId);
 
         if (StringUtils.isBlank(forRole)) {
             return uiResourceService.getGroupResources(groupId, page, pageSize, filter);
         }
 
-        if (StringUtils.equalsIgnoreCase("resource-admin", forRole)) {
+        if (StringUtils.equalsIgnoreCase("group-admin", forRole)) {
             return uiResourceService.getGroupResources(groupId, page, pageSize, filter);
-        }  /*
-        if (StringUtils.equalsIgnoreCase("resource-viewer", forRole)) {
-            return uiGroupPublicService.getAllGroupsForDomainAndUserAndRole(domainId, userId, MembershipRoleType.VIEWER);
         }
-        if (StringUtils.equalsIgnoreCase("all-roles", forRole)) {
-            return uiGroupPublicService.getAllGroupsForDomainAndUserAndRole(domainId, userId, null);
-        }*/
+
+        if (StringUtils.equalsIgnoreCase("resource-admin", forRole)) {
+            return uiResourceService.getResourcesForUserAndGroup(userId, MembershipRoleType.ADMIN, groupId, page, pageSize, filter);
+        }
+
         throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "ResourcesForGroups", "Unknown parameter type [" + forRole + "]!");
     }
 
@@ -115,6 +117,61 @@ public class ResourceEditController {
         return uiResourceService.updateResourceForGroup(resourceRO, resourceId, groupId, domainId);
     }
 
+
+    @GetMapping(path = SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+    @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and" +
+            " (@smpAuthorizationService.isGroupAdministrator(#groupEncId) or @smpAuthorizationService.isResourceAdministrator(#resourceEncId))")
+    public ServiceResult<MemberRO> getGroupMemberList(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId,
+                                                      @PathVariable(PATH_PARAM_ENC_DOMAIN_ID) String domainEncId,
+                                                      @PathVariable(PATH_PARAM_ENC_GROUP_ID) String groupEncId,
+                                                      @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId,
+                                                      @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 group members with filter  [{}], paging: [{}/{}], user: {}", filter, page, pageSize, userEncId);
+        Long groupId = SessionSecurityUtils.decryptEntityId(groupEncId);
+        Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId);
+        return uiResourceService.getResourceMembers(resourceId, page, pageSize, filter);
+    }
+
+    @PutMapping(path = SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER_PUT, produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE)
+    @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and @smpAuthorizationService.isGroupAdministrator(#groupEncId)")
+    public MemberRO putGroupMember(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId,
+                                   @PathVariable(PATH_PARAM_ENC_DOMAIN_ID) String domainEncId,
+                                   @PathVariable(PATH_PARAM_ENC_GROUP_ID) String groupEncId,
+                                   @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId,
+                                   @RequestBody MemberRO memberRO) {
+
+        LOG.info("add member to group");
+        Long groupId = SessionSecurityUtils.decryptEntityId(groupEncId);
+        Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId);
+        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 uiResourceService.addMemberToResource(resourceId, memberRO, memberId);
+    }
+
+    @DeleteMapping(value = SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER_DELETE)
+    @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and @smpAuthorizationService.isGroupAdministrator(#groupEncId)")
+    public MemberRO deleteDomainMember(
+            @PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId,
+            @PathVariable(PATH_PARAM_ENC_DOMAIN_ID) String domainEncId,
+            @PathVariable(PATH_PARAM_ENC_GROUP_ID) String groupEncId,
+            @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId,
+            @PathVariable(PATH_PARAM_ENC_MEMBER_ID) String memberEncId
+    ) {
+        LOG.info("Delete member from group");
+        Long groupId = SessionSecurityUtils.decryptEntityId(groupEncId);
+        Long memberId = SessionSecurityUtils.decryptEntityId(memberEncId);
+        Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId);
+
+        // is user domain admin or system admin
+        return uiResourceService.deleteMemberFromResource(resourceId, memberId);
+    }
+
     protected void logAdminAccess(String action) {
         LOG.info(SMPLogger.SECURITY_MARKER, "Admin Domain action [{}] by user [{}], ", action, SessionSecurityUtils.getSessionUserDetails());
     }
-- 
GitLab