You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2021/10/05 08:15:05 UTC

[incubator-streampipes] branch STREAMPIPES-426 updated: [STREAMPIPES-43] Add group management

This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-426
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git


The following commit(s) were added to refs/heads/STREAMPIPES-426 by this push:
     new 8b4e495  [STREAMPIPES-43] Add group management
8b4e495 is described below

commit 8b4e4952d6d00c76068e253d4360a65f98f46cb5
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Tue Oct 5 10:14:52 2021 +0200

    [STREAMPIPES-43] Add group management
---
 .../streampipes/model/client/user/Group.java       | 11 ++-
 .../streampipes/model/client/user/Principal.java   | 10 +++
 .../streampipes/rest/impl/UserGroupResource.java   |  2 +-
 ui/src/app/configuration/configuration.module.ts   |  6 +-
 .../edit-group-dialog.component.html               | 55 +++++++++++++
 .../edit-group-dialog.component.scss               | 29 +++++++
 .../edit-group-dialog.component.ts                 | 87 ++++++++++++++++++++
 .../edit-user-dialog.component.html                | 26 +++++-
 .../edit-user-dialog/edit-user-dialog.component.ts | 40 ++++++++--
 .../security-configuration.component.html          |  8 ++
 .../user-group-configuration.component.html        | 77 ++++++++++++++++++
 .../user-group-configuration.component.scss        | 17 ++++
 .../user-group-configuration.component.ts          | 92 ++++++++++++++++++++++
 .../app/core-model/gen/streampipes-model-client.ts | 29 ++++++-
 .../platform-services/apis/user-group.service.ts   | 60 ++++++++++++++
 ui/src/app/platform-services/platform.module.ts    |  4 +-
 16 files changed, 534 insertions(+), 19 deletions(-)

diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java
index 2a2db99..8ee6e90 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Group.java
@@ -18,9 +18,13 @@
 package org.apache.streampipes.model.client.user;
 
 import com.google.gson.annotations.SerializedName;
+import org.apache.streampipes.model.shared.annotation.TsModel;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
+@TsModel
 public class Group {
 
   protected @SerializedName("_id") String groupId;
@@ -28,9 +32,10 @@ public class Group {
 
   private String groupName;
 
-  private List<Role> roles;
+  private Set<Role> roles;
 
   public Group() {
+    this.roles = new HashSet<>();
   }
 
   public String getGroupId() {
@@ -57,11 +62,11 @@ public class Group {
     this.groupName = groupName;
   }
 
-  public List<Role> getRoles() {
+  public Set<Role> getRoles() {
     return roles;
   }
 
-  public void setRoles(List<Role> roles) {
+  public void setRoles(Set<Role> roles) {
     this.roles = roles;
   }
 }
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
index a9b9b04..7154e49 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
@@ -42,6 +42,7 @@ public abstract class Principal implements UserDetails {
 	protected List<Element> ownActions;
 
 	protected Set<Role> roles;
+	protected Set<String> groups;
 
 	private PrincipalType principalType;
 
@@ -51,6 +52,7 @@ public abstract class Principal implements UserDetails {
 		this.ownSepas = new ArrayList<>();
 		this.ownSources = new ArrayList<>();
 		this.roles = new HashSet<>();
+		this.groups = new HashSet<>();
 	}
 
 	public List<Element> getOwnSources() {
@@ -167,6 +169,14 @@ public abstract class Principal implements UserDetails {
 		this.principalType = principalType;
 	}
 
+	public Set<String> getGroups() {
+		return groups;
+	}
+
+	public void setGroups(Set<String> groups) {
+		this.groups = groups;
+	}
+
 	@Override
 	public boolean isAccountNonExpired() {
 		return !this.isAccountExpired();
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java
index f02627c..4331fb2 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserGroupResource.java
@@ -24,7 +24,7 @@ import org.apache.streampipes.storage.api.IUserGroupStorage;
 import javax.ws.rs.*;
 import javax.ws.rs.core.Response;
 
-@Path("/v2/users/groups")
+@Path("/v2/usergroups")
 public class UserGroupResource extends AbstractAuthGuardedRestResource {
 
   @GET
diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts
index a9cbc59..f88de28 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -50,6 +50,8 @@ import { SecurityUserConfigComponent } from './security-configuration/security-u
 import { SecurityServiceConfigComponent } from './security-configuration/security-service-configuration/security-service-config.component';
 import { EditUserDialogComponent } from './security-configuration/edit-user-dialog/edit-user-dialog.component';
 import { PlatformServicesModule } from '../platform-services/platform.module';
+import { SecurityUserGroupConfigComponent } from './security-configuration/user-group-configuration/user-group-configuration.component';
+import { EditGroupDialogComponent } from './security-configuration/edit-group-dialog/edit-group-dialog.component';
 
 @NgModule({
   imports: [
@@ -80,12 +82,14 @@ import { PlatformServicesModule } from '../platform-services/platform.module';
     ConsulConfigsNumberComponent,
     DeleteDatalakeIndexComponent,
     EditUserDialogComponent,
+    EditGroupDialogComponent,
     PipelineElementConfigurationComponent,
     SecurityConfigurationComponent,
     SecurityUserConfigComponent,
+    SecurityUserGroupConfigComponent,
     SecurityServiceConfigComponent,
     MessagingConfigurationComponent,
-    DatalakeConfigurationComponent
+    DatalakeConfigurationComponent,
   ],
   providers: [
     ConfigurationService,
diff --git a/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.html b/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.html
new file mode 100644
index 0000000..f16b0fe
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.html
@@ -0,0 +1,55 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content">
+        <div fxFlex="100" fxLayout="column" class="p-15">
+            <form [formGroup]="parentForm" fxFlex="100" fxLayout="column">
+                <div class="general-options-panel" fxLayout="column">
+                    <span class="general-options-header">Basics</span>
+                    <mat-form-field color="accent">
+                        <mat-label>Group Name</mat-label>
+                        <input formControlName="groupName" fxFlex
+                               matInput
+                               required>
+                    </mat-form-field>
+                </div>
+                <div fxLayout="column" class="general-options-panel">
+                    <span class="general-options-header">Roles</span>
+                    <mat-checkbox *ngFor="let role of availableRoles" [value]="role"
+                                  [checked]="group.roles.indexOf(role) > -1" (change)="changeRoleAssignment($event)">
+                        {{role}}
+                    </mat-checkbox>
+                </div>
+            </form>
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions">
+        <div fxLayout="row">
+            <button mat-button mat-raised-button color="accent" (click)="save()" style="margin-right:10px;"
+                    [disabled]="!parentForm.valid || clonedGroup.roles.length == 0"
+                    data-cy="sp-element-edit-user-save">
+                <i class="material-icons">save</i><span>&nbsp;Save</span>
+            </button>
+            <button mat-button mat-raised-button class="mat-basic" (click)="close(false)">
+                Cancel
+            </button>
+        </div>
+    </div>
+</div>
diff --git a/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.scss b/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.scss
new file mode 100644
index 0000000..8dace49
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.scss
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+@import '../../../../scss/sp/sp-dialog.scss';
+
+.form-field .mat-form-field-wrapper {
+  margin-bottom: -1.25em;
+}
+
+
+.form-field .mat-form-field-infix {
+  border-top: 0;
+}
+
diff --git a/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.ts b/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.ts
new file mode 100644
index 0000000..f58a2da
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/edit-group-dialog/edit-group-dialog.component.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
+import { Group, Role } from '../../../core-model/gen/streampipes-model-client';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { UserRole } from '../../../_enums/user-role.enum';
+import { DialogRef } from '../../../core-ui/dialog/base-dialog/dialog-ref';
+import { UserGroupService } from '../../../platform-services/apis/user-group.service';
+import { MatCheckboxChange } from '@angular/material/checkbox';
+
+@Component({
+  selector: 'sp-edit-group-dialog',
+  templateUrl: './edit-group-dialog.component.html',
+  styleUrls: ['./edit-group-dialog.component.scss'],
+  encapsulation: ViewEncapsulation.None
+})
+export class EditGroupDialogComponent implements OnInit {
+
+  @Input()
+  group: Group;
+
+  @Input()
+  editMode: boolean;
+
+  parentForm: FormGroup;
+  availableRoles: string[];
+  clonedGroup: Group;
+
+  constructor(private fb: FormBuilder,
+              private dialogRef: DialogRef<EditGroupDialogComponent>,
+              private userGroupService: UserGroupService) {}
+
+  ngOnInit(): void {
+    this.availableRoles = Object.values(UserRole).filter(value => typeof value === 'string') as string[];
+    this.clonedGroup = Group.fromData(this.group, new Group());
+    this.parentForm = this.fb.group({});
+    this.parentForm.addControl('groupName', new FormControl(this.clonedGroup.groupName, Validators.required));
+
+    this.parentForm.valueChanges.subscribe(v => this.clonedGroup.groupName = v.groupName);
+  }
+
+  close(refresh: boolean) {
+    this.dialogRef.close(refresh);
+  }
+
+  save() {
+    if (this.editMode) {
+      this.userGroupService.updateGroup(this.clonedGroup).subscribe(() => this.close(true));
+    } else {
+      this.userGroupService.createGroup(this.clonedGroup).subscribe(() => this.close(true));
+    }
+  }
+
+  changeRoleAssignment(event: MatCheckboxChange) {
+    if (this.clonedGroup.roles.indexOf(event.source.value as Role) > -1) {
+      this.removeRole(event.source.value);
+    } else {
+      this.addRole(event.source.value);
+    }
+  }
+
+  removeRole(role: string) {
+    this.clonedGroup.roles.splice(this.clonedGroup.roles.indexOf(role as Role), 1);
+  }
+
+  addRole(role: string) {
+    this.clonedGroup.roles.push(role as Role);
+  }
+
+
+}
diff --git a/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html
index 4bdc2a3..e9d5e53 100644
--- a/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html
+++ b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.html
@@ -1,3 +1,21 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
 <div class="sp-dialog-container">
     <div class="sp-dialog-content">
         <div fxFlex="100" fxLayout="column" class="p-15">
@@ -49,10 +67,10 @@
                 </div>
                 <div fxLayout="column" class="general-options-panel">
                     <span class="general-options-header">Groups</span>
-<!--                    <mat-checkbox *ngFor="let role of availableRoles" [value]="role"-->
-<!--                                  [checked]="user.roles.indexOf(role) > -1" (change)="changeRoleAssignment($event)">-->
-<!--                        {{role}}-->
-<!--                    </mat-checkbox>-->
+                    <mat-checkbox *ngFor="let group of availableGroups" [value]="group.groupId"
+                                  [checked]="user.groups.indexOf(group.groupId) > -1" (change)="changeGroupAssignment($event)">
+                        {{group.groupName}}
+                    </mat-checkbox>
                 </div>
                 <div fxLayout="column" class="general-options-panel">
                     <span class="general-options-header">Roles</span>
diff --git a/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts
index c9cb3f9..60b5a0e 100644
--- a/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts
+++ b/ui/src/app/configuration/security-configuration/edit-user-dialog/edit-user-dialog.component.ts
@@ -1,6 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
 import { AfterViewInit, Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
 import { DialogRef } from '../../../core-ui/dialog/base-dialog/dialog-ref';
 import {
+  Group,
   Role,
   ServiceAccount,
   UserAccount
@@ -17,6 +36,7 @@ import {
 import { UserRole } from '../../../_enums/user-role.enum';
 import { MatCheckboxChange } from '@angular/material/checkbox';
 import { UserService } from '../../../platform-services/apis/user.service';
+import { UserGroupService } from '../../../platform-services/apis/user-group.service';
 
 @Component({
   selector: 'sp-edit-user-dialog',
@@ -24,7 +44,7 @@ import { UserService } from '../../../platform-services/apis/user.service';
   styleUrls: ['./edit-user-dialog.component.scss'],
   encapsulation: ViewEncapsulation.None
 })
-export class EditUserDialogComponent implements OnInit, AfterViewInit {
+export class EditUserDialogComponent implements OnInit {
 
   @Input()
   user: any;
@@ -37,17 +57,19 @@ export class EditUserDialogComponent implements OnInit, AfterViewInit {
   clonedUser: UserAccount | ServiceAccount;
 
   availableRoles: string[];
+  availableGroups: Group[] = [];
 
   constructor(private dialogRef: DialogRef<EditUserDialogComponent>,
               private fb: FormBuilder,
-              private userService: UserService) {
-  }
-
-  ngAfterViewInit(): void {
+              private userService: UserService,
+              private userGroupService: UserGroupService) {
   }
 
   ngOnInit(): void {
     this.availableRoles = Object.values(UserRole).filter(value => typeof value === 'string') as string[];
+    this.userGroupService.getAllUserGroups().subscribe(response => {
+      this.availableGroups = response;
+    });
     this.clonedUser = this.user instanceof UserAccount ? UserAccount.fromData(this.user, new UserAccount()) : ServiceAccount.fromData(this.user, new ServiceAccount());
     this.isUserAccount = this.user instanceof UserAccount;
     this.parentForm = this.fb.group({});
@@ -122,6 +144,14 @@ export class EditUserDialogComponent implements OnInit, AfterViewInit {
     this.dialogRef.close(refresh);
   }
 
+  changeGroupAssignment(event: MatCheckboxChange) {
+    if (this.clonedUser.groups.indexOf(event.source.value) > -1) {
+      this.clonedUser.groups.splice(this.clonedUser.groups.indexOf(event.source.value), 1);
+    } else {
+      this.clonedUser.groups.push(event.source.value);
+    }
+  }
+
   changeRoleAssignment(event: MatCheckboxChange) {
     if (this.clonedUser.roles.indexOf(event.source.value as Role) > -1) {
       this.removeRole(event.source.value);
diff --git a/ui/src/app/configuration/security-configuration/security-configuration.component.html b/ui/src/app/configuration/security-configuration/security-configuration.component.html
index a1c9218..5db6583 100644
--- a/ui/src/app/configuration/security-configuration/security-configuration.component.html
+++ b/ui/src/app/configuration/security-configuration/security-configuration.component.html
@@ -33,4 +33,12 @@
         </sp-split-section>
     </div>
     <mat-divider></mat-divider>
+    <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
+        <sp-split-section title="Groups"
+                          subtitle="Manage user groups">
+            <div class="subsection-title">Existing groups</div>
+            <sp-security-user-group-config></sp-security-user-group-config>
+        </sp-split-section>
+    </div>
+    <mat-divider></mat-divider>
 </div>
diff --git a/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.html b/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.html
new file mode 100644
index 0000000..2d1473d
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.html
@@ -0,0 +1,77 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div fxLayout="column">
+    <div>
+        <button mat-button mat-raised-button color="accent" (click)="createGroup()"><i
+                class="material-icons">add</i><span>&nbsp;New User Group</span></button>
+    </div>
+    <div fxFlex="100" fxLayout="column">
+        <table
+                fxFlex="100"
+                mat-table
+                data-cy="security-service-config"
+                [dataSource]="dataSource"
+                style="width: 100%;"
+                matSort>
+
+            <ng-container matColumnDef="groupName">
+                <th mat-header-cell mat-sort-header *matHeaderCellDef>Group name</th>
+                <td mat-cell *matCellDef="let group">
+                    <h4 style="margin-bottom:0px;">{{group.groupName}}</h4>
+                </td>
+            </ng-container>
+
+            <ng-container matColumnDef="edit">
+                <th mat-header-cell *matHeaderCellDef class="text-right">Actions</th>
+                <td mat-cell *matCellDef="let group">
+                    <div fxLayout="row">
+                            <span fxFlex fxFlexOrder="3" fxLayout="row" fxLayoutAlign="end center">
+                                <div class="mr-15">
+                                <button color="accent"
+                                        mat-button
+                                        mat-raised-button
+                                        matTooltip="Edit user"
+                                        matTooltipPosition="above"
+                                        data-cy="service-edit-btn"
+                                        (click)="editGroup(group)">
+                                        <i class="material-icons">edit</i>
+                                        <span>&nbsp;Edit</span>
+                                    </button>
+                                    </div>
+                                <button color="warn"
+                                        mat-button
+                                        mat-raised-button
+                                        matTooltip="Delete service"
+                                        matTooltipPosition="above"
+                                        data-cy="service-delete-btn"
+                                        (click)="deleteGroup(group)">
+                                        <i class="material-icons">delete</i>
+                                        <span>&nbsp;Delete</span>
+                                    </button>
+                                </span>
+                    </div>
+                </td>
+            </ng-container>
+
+            <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+            <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+
+        </table>
+    </div>
+</div>
diff --git a/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.scss b/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.scss
new file mode 100644
index 0000000..13cbc4a
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.scss
@@ -0,0 +1,17 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
diff --git a/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.ts b/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.ts
new file mode 100644
index 0000000..630e48b
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/user-group-configuration/user-group-configuration.component.ts
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { Group } from '../../../core-model/gen/streampipes-model-client';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import { MatTableDataSource } from '@angular/material/table';
+import { UserGroupService } from '../../../platform-services/apis/user-group.service';
+import { PanelType } from '../../../core-ui/dialog/base-dialog/base-dialog.model';
+import { DialogService } from '../../../core-ui/dialog/base-dialog/base-dialog.service';
+import { EditGroupDialogComponent } from '../edit-group-dialog/edit-group-dialog.component';
+
+@Component({
+  selector: 'sp-security-user-group-config',
+  templateUrl: './user-group-configuration.component.html',
+  styleUrls: ['./user-group-configuration.component.scss']
+})
+export class SecurityUserGroupConfigComponent implements OnInit {
+
+
+  @ViewChild(MatPaginator) paginator: MatPaginator;
+  pageSize = 1;
+  @ViewChild(MatSort) sort: MatSort;
+
+  dataSource: MatTableDataSource<Group>;
+
+  displayedColumns: string[] = ['groupName', 'edit'];
+
+  constructor(private userGroupService: UserGroupService,
+              private dialogService: DialogService) {}
+
+  ngOnInit(): void {
+    this.loadAllGroups();
+  }
+
+  createGroup() {
+    const group = new Group();
+    group.roles = [];
+    this.openGroupEditDialog(group, false);
+  }
+
+  loadAllGroups() {
+    this.userGroupService.getAllUserGroups().subscribe(response => {
+      this.dataSource = new MatTableDataSource(response);
+    });
+  }
+
+  deleteGroup(group: Group) {
+    this.userGroupService.deleteGroup(group).subscribe(response => {
+      this.loadAllGroups();
+    });
+  }
+
+  editGroup(group: Group) {
+    this.openGroupEditDialog(group, true);
+  }
+
+  openGroupEditDialog(group: Group, editMode: boolean) {
+    const dialogRef = this.dialogService.open(EditGroupDialogComponent, {
+      panelType: PanelType.SLIDE_IN_PANEL,
+      title: editMode ? 'Edit group ' + group.groupName : 'Add group',
+      width: '50vw',
+      data: {
+        'group': group,
+        'editMode': editMode
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(refresh => {
+      if (refresh) {
+        this.loadAllGroups();
+      }
+    });
+  }
+
+}
diff --git a/ui/src/app/core-model/gen/streampipes-model-client.ts b/ui/src/app/core-model/gen/streampipes-model-client.ts
index 1564e3a..b99c706 100644
--- a/ui/src/app/core-model/gen/streampipes-model-client.ts
+++ b/ui/src/app/core-model/gen/streampipes-model-client.ts
@@ -19,7 +19,7 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2021-10-04 22:00:31.
+// Generated using typescript-generator version 2.27.744 on 2021-10-05 10:08:33.
 
 export class Element {
     elementId: string;
@@ -102,6 +102,25 @@ export interface GrantedAuthority {
     authority: string;
 }
 
+export class Group {
+    groupId: string;
+    groupName: string;
+    rev: string;
+    roles: Role[];
+
+    static fromData(data: Group, target?: Group): Group {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new Group();
+        instance.groupId = data.groupId;
+        instance.rev = data.rev;
+        instance.groupName = data.groupName;
+        instance.roles = __getCopyArrayFn(__identity<Role>())(data.roles);
+        return instance;
+    }
+}
+
 export class MatchingResultMessage {
     description: string;
     matchingSuccessful: boolean;
@@ -134,6 +153,7 @@ export class Principal implements UserDetails {
     authorities: GrantedAuthority[];
     credentialsNonExpired: boolean;
     enabled: boolean;
+    groups: string[];
     ownActions: Element[];
     ownSepas: Element[];
     ownSources: Element[];
@@ -150,12 +170,12 @@ export class Principal implements UserDetails {
         }
         const instance = target || new Principal();
         instance.enabled = data.enabled;
-        instance.password = data.password;
         instance.username = data.username;
-        instance.authorities = __getCopyArrayFn(__identity<GrantedAuthority>())(data.authorities);
-        instance.accountNonLocked = data.accountNonLocked;
+        instance.password = data.password;
         instance.accountNonExpired = data.accountNonExpired;
+        instance.accountNonLocked = data.accountNonLocked;
         instance.credentialsNonExpired = data.credentialsNonExpired;
+        instance.authorities = __getCopyArrayFn(__identity<GrantedAuthority>())(data.authorities);
         instance.principalId = data.principalId;
         instance.rev = data.rev;
         instance.accountEnabled = data.accountEnabled;
@@ -165,6 +185,7 @@ export class Principal implements UserDetails {
         instance.ownSepas = __getCopyArrayFn(Element.fromData)(data.ownSepas);
         instance.ownActions = __getCopyArrayFn(Element.fromData)(data.ownActions);
         instance.roles = __getCopyArrayFn(__identity<Role>())(data.roles);
+        instance.groups = __getCopyArrayFn(__identity<string>())(data.groups);
         instance.principalType = data.principalType;
         return instance;
     }
diff --git a/ui/src/app/platform-services/apis/user-group.service.ts b/ui/src/app/platform-services/apis/user-group.service.ts
new file mode 100644
index 0000000..d39f788
--- /dev/null
+++ b/ui/src/app/platform-services/apis/user-group.service.ts
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { map } from 'rxjs/operators';
+import { Observable } from 'rxjs';
+import { PlatformServicesCommons } from './commons.service';
+import { Group } from '../../core-model/gen/streampipes-model-client';
+
+@Injectable()
+export class UserGroupService {
+
+  constructor(private http: HttpClient,
+              private platformServicesCommons: PlatformServicesCommons) {
+  }
+
+  public getAllUserGroups(): Observable<Group[]> {
+    return this.http.get(`${this.userGroupPath}`)
+        .pipe(map(response => {
+          return (response as any[]).map(p => Group.fromData(p));
+        }));
+  }
+
+  public createGroup(group: Group) {
+    return this.http.post(this.userGroupPath, group);
+  }
+
+  public updateGroup(group: Group) {
+    return this.http.put(`${this.userGroupPath}/${group.groupId}`, group);
+  }
+
+  public deleteGroup(group: Group) {
+    return this.http.delete(`${this.userGroupPath}/${group.groupId}`);
+  }
+
+  public getGroup(groupId: string) {
+    return this.http.get(`${this.userGroupPath}/${groupId}`);
+  }
+
+  private get userGroupPath() {
+    return this.platformServicesCommons.apiBasePath + '/usergroups';
+  }
+
+}
diff --git a/ui/src/app/platform-services/platform.module.ts b/ui/src/app/platform-services/platform.module.ts
index 8a3ed07..e2a8eae 100644
--- a/ui/src/app/platform-services/platform.module.ts
+++ b/ui/src/app/platform-services/platform.module.ts
@@ -29,6 +29,7 @@ import { SemanticTypesService } from './apis/semantic-types.service';
 import { PipelineCanvasMetadataService } from './apis/pipeline-canvas-metadata.service';
 import { PipelineTemplateService } from './apis/pipeline-template.service';
 import { UserService } from './apis/user.service';
+import { UserGroupService } from './apis/user-group.service';
 
 @NgModule({
   imports: [],
@@ -45,7 +46,8 @@ import { UserService } from './apis/user.service';
     PipelineService,
     SemanticTypesService,
     PipelineTemplateService,
-    UserService
+    UserService,
+    UserGroupService
   ],
   entryComponents: []
 })