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

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new e21cbaea4a feat: Support alternate ids for groups and roles (#3738)
e21cbaea4a is described below

commit e21cbaea4a657dd0b760de28819e21a8bdcd5814
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Aug 20 08:16:37 2025 +0200

    feat: Support alternate ids for groups and roles (#3738)
    
    * feat: Support alternate ids for groups and roles
    
    * Fix model
---
 .../environment/model/OAuthConfiguration.java      | 12 +++-
 .../parser/OAuthConfigurationParser.java           | 19 +++++-
 .../streampipes/model/client/user/Group.java       |  9 +++
 .../apache/streampipes/model/client/user/Role.java | 12 ++++
 .../tasks/ApplyDefaultRolesAndPrivilegesTask.java  | 16 +++--
 .../service/core/oauth2/UserService.java           | 71 +++++++++++++++++++---
 .../src/lib/model/gen/streampipes-model-client.ts  | 10 ++-
 ui/src/app/configuration/configuration.module.ts   |  2 +
 .../alternate-id-configuration.component.html      | 48 +++++++++++++++
 .../alternate-id-configuration.component.ts}       | 39 ++++++------
 .../edit-group-dialog.component.html               |  7 +++
 .../edit-role-dialog.component.html                | 20 ++++--
 .../edit-role-dialog.component.scss                |  1 -
 .../edit-role-dialog/edit-role-dialog.component.ts | 19 ++++--
 .../role-configuration.component.html              |  1 -
 15 files changed, 238 insertions(+), 48 deletions(-)

diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/model/OAuthConfiguration.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/model/OAuthConfiguration.java
index 607bf468b5..65ce2ea3a7 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/model/OAuthConfiguration.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/model/OAuthConfiguration.java
@@ -18,6 +18,8 @@
 
 package org.apache.streampipes.commons.environment.model;
 
+import java.util.Set;
+
 public class OAuthConfiguration {
 
   private String authorizationUri;
@@ -35,7 +37,7 @@ public class OAuthConfiguration {
   private String emailAttributeName;
   private String userIdAttributeName;
   private String roleAttributeName;
-
+  private Set<String> defaultRoles;
 
   public String getRegistrationId() {
     return registrationId;
@@ -156,4 +158,12 @@ public class OAuthConfiguration {
   public void setRegistrationName(String registrationName) {
     this.registrationName = registrationName;
   }
+
+  public Set<String> getDefaultRoles() {
+    return defaultRoles;
+  }
+
+  public void setDefaultRoles(Set<String> defaultRoles) {
+    this.defaultRoles = defaultRoles;
+  }
 }
diff --git 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/parser/OAuthConfigurationParser.java
 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/parser/OAuthConfigurationParser.java
index 3650a06104..98d4004ca6 100644
--- 
a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/parser/OAuthConfigurationParser.java
+++ 
b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/parser/OAuthConfigurationParser.java
@@ -28,6 +28,8 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * The {@code OAuthConfigurationParser} class is responsible for parsing OAuth 
provider configurations
@@ -52,8 +54,16 @@ public class OAuthConfigurationParser {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(OAuthConfigurationParser.class);
 
-
   private static final String OAUTH_PREFIX = "SP_OAUTH_PROVIDER";
+  private static final Set<String> defaultRoles = Set.of(
+      "ROLE_PIPELINE_USER",
+      "ROLE_PIPELINE_ADMIN",
+      "ROLE_CONNECT_ADMIN",
+      "ROLE_DASHBOARD_USER",
+      "ROLE_DASHBOARD_ADMIN",
+      "ROLE_DATA_EXPLORER_USER",
+      "ROLE_DATA_EXPLORER_ADMIN"
+  );
 
   public List<OAuthConfiguration> parse(Map<String, String> env) {
     Map<String, OAuthConfiguration> oAuthConfigurationsMap = new HashMap<>();
@@ -97,6 +107,12 @@ public class OAuthConfigurationParser {
         case "USER_ID_ATTRIBUTE_NAME" -> 
oAuthConfiguration.setUserIdAttributeName(value);
         case "ROLE_ATTRIBUTE_NAME" -> 
oAuthConfiguration.setRoleAttributeName(value);
         case "NAME" -> oAuthConfiguration.setRegistrationName(value);
+        case "DEFAULT_ROLES" -> {
+          var defaultRoles = Arrays.stream(value.split(","))
+              .map(String::trim)
+              .collect(Collectors.toSet());
+          oAuthConfiguration.setDefaultRoles(defaultRoles);
+        }
         default -> LOG.warn(
             "Unknown setting {} for oauth configuration in environment 
variable {}",
             settingName,
@@ -133,6 +149,7 @@ public class OAuthConfigurationParser {
   ) {
     var oAuthConfiguration = 
oAuthConfigurationsMap.computeIfAbsent(registrationId, k -> new 
OAuthConfiguration());
     oAuthConfiguration.setRegistrationId(registrationId);
+    oAuthConfiguration.setDefaultRoles(defaultRoles);
     return oAuthConfiguration;
   }
 }
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 4ce6d272c9..906555ccaf 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
@@ -37,6 +37,7 @@ public class Group implements Storable {
   @JsonIgnore
   private String type = "group";
 
+  private Set<String> alternateIds = new HashSet<>();
   private String groupName;
 
   private Set<String> roles;
@@ -94,4 +95,12 @@ public class Group implements Storable {
   public void setType(String type) {
     this.type = type;
   }
+
+  public Set<String> getAlternateIds() {
+    return alternateIds;
+  }
+
+  public void setAlternateIds(Set<String> alternateIds) {
+    this.alternateIds = alternateIds;
+  }
 }
diff --git 
a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
 
b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
index 84542b9a30..b91bf4a48c 100644
--- 
a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
+++ 
b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Role.java
@@ -24,7 +24,9 @@ import org.apache.streampipes.model.shared.api.Storable;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.gson.annotations.SerializedName;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 @TsModel
 public class Role implements Storable {
@@ -35,6 +37,7 @@ public class Role implements Storable {
   private String label;
   private boolean defaultRole;
   private List<String> privilegeIds;
+  private Set<String> alternateIds;
 
   // document type should be persisted to CouchDB with Gson serialization, but 
not via Jackson to the UI
   @JsonIgnore
@@ -49,6 +52,7 @@ public class Role implements Storable {
     role.label = label;
     role.defaultRole = true;
     role.privilegeIds = privilegeIds;
+    role.alternateIds = new HashSet<>();
     return role;
   }
 
@@ -103,4 +107,12 @@ public class Role implements Storable {
   public void setPrivilegeIds(List<String> privilegeIds) {
     this.privilegeIds = privilegeIds;
   }
+
+  public Set<String> getAlternateIds() {
+    return alternateIds;
+  }
+
+  public void setAlternateIds(Set<String> alternateIds) {
+    this.alternateIds = alternateIds;
+  }
 }
diff --git 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/tasks/ApplyDefaultRolesAndPrivilegesTask.java
 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/tasks/ApplyDefaultRolesAndPrivilegesTask.java
index da26dc9031..f3d346aa6c 100644
--- 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/tasks/ApplyDefaultRolesAndPrivilegesTask.java
+++ 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/tasks/ApplyDefaultRolesAndPrivilegesTask.java
@@ -54,13 +54,17 @@ public class ApplyDefaultRolesAndPrivilegesTask implements 
InstallationTask {
 
   private <T extends Storable> void updateDocs(CRUDStorage<T> storage,
                                                List<T> defaultDocs) {
-    defaultDocs.forEach(role -> {
-      var doc = storage.getElementById(role.getElementId());
-      if (doc != null) {
-        role.setRev(doc.getRev());
-        storage.updateElement(role);
+    defaultDocs.forEach(doc -> {
+      var existingDoc = storage.getElementById(doc.getElementId());
+      if (existingDoc != null) {
+        doc.setRev(existingDoc.getRev());
+        // Preserve alternateIds for Role objects
+        if (doc instanceof Role && existingDoc instanceof Role) {
+          ((Role) doc).setAlternateIds(((Role) existingDoc).getAlternateIds());
+        }
+        storage.updateElement(doc);
       } else {
-        storage.persist(role);
+        storage.persist(doc);
       }
     });
   }
diff --git 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/oauth2/UserService.java
 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/oauth2/UserService.java
index 956a99778d..5f766659c8 100755
--- 
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/oauth2/UserService.java
+++ 
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/oauth2/UserService.java
@@ -21,13 +21,17 @@ package org.apache.streampipes.service.core.oauth2;
 import org.apache.streampipes.commons.environment.Environment;
 import org.apache.streampipes.commons.environment.Environments;
 import org.apache.streampipes.commons.environment.model.OAuthConfiguration;
-import org.apache.streampipes.model.client.user.DefaultRole;
+import org.apache.streampipes.model.client.user.Group;
+import org.apache.streampipes.model.client.user.Role;
 import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.resource.management.UserResourceManager;
 import 
org.apache.streampipes.rest.security.OAuth2AuthenticationProcessingException;
+import org.apache.streampipes.storage.api.CRUDStorage;
 import org.apache.streampipes.storage.api.IUserStorage;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
 
@@ -37,15 +41,24 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 public class UserService {
 
+  private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
+
   private final IUserStorage userStorage;
+  private final CRUDStorage<Role> roleStorage;
+  private final CRUDStorage<Group> groupStorage;
   private final Environment env;
+  private List<Role> allRoles;
+  private List<Group> allGroups;
 
   public UserService() {
     this.userStorage = 
StorageDispatcher.INSTANCE.getNoSqlStore().getUserStorageAPI();
+    this.roleStorage = 
StorageDispatcher.INSTANCE.getNoSqlStore().getRoleStorage();
+    this.groupStorage = 
StorageDispatcher.INSTANCE.getNoSqlStore().getUserGroupStorage();
+    this.allGroups = this.groupStorage.findAll();
+    this.allRoles = this.roleStorage.findAll();
     this.env = Environments.getEnvironment();
   }
 
@@ -78,11 +91,11 @@ public class UserService {
               String.format("Already signed up with another provider %s", 
user.getProvider())
           );
         }
-        applyRoles(user, oAuthConfig, attributes);
+        applyRoles(user, oAuthConfig, attributes, false);
         userStorage.updateUser(user);
       } else {
         user = toUserAccount(registrationId, principalId, email, fullName);
-        applyRoles(user, oAuthConfig, attributes);
+        applyRoles(user, oAuthConfig, attributes, true);
         new UserResourceManager().registerOauthUser(user);
       }
 
@@ -97,7 +110,8 @@ public class UserService {
 
   private void applyRoles(UserAccount user,
                           OAuthConfiguration oAuthConfig,
-                          Map<String, Object> attributes) {
+                          Map<String, Object> attributes,
+                          boolean newUser) {
     if (oAuthConfig.getRoleAttributeName() != null) {
       Object rolesObject = attributes.get(oAuthConfig.getRoleAttributeName());
 
@@ -105,20 +119,57 @@ public class UserService {
         Set<String> roles = extractRoleOrGroup("ROLE", rolesList);
         Set<String> groups = convertGroup(extractRoleOrGroup("GROUP", 
rolesList));
 
+        allRoles.forEach(role -> {
+          if (Objects.nonNull(role.getAlternateIds())) {
+            role.getAlternateIds().forEach(a -> {
+              if (rolesList.contains(a)) {
+                roles.add(role.getElementId());
+              }
+            });
+          }
+        });
+
+        allGroups.forEach(group -> {
+          if (Objects.nonNull(group.getAlternateIds())) {
+            group.getAlternateIds().forEach(a -> {
+              if (rolesList.contains(a)) {
+                groups.add(group.getElementId());
+              }
+            });
+          }
+        });
+
         user.setRoles(roles);
         user.setGroups(groups);
         user.setExternallyManagedRoles(true);
       } else {
-        applyDefaultRole(user);
+        LOG.warn(
+            "Invalid role attribute: {} of type {}",
+            oAuthConfig.getRoleAttributeName(),
+            Objects.nonNull(rolesObject) ? rolesObject.getClass().getName() : 
"null"
+        );
+        applyDefaultRole(user, oAuthConfig.getDefaultRoles(), newUser);
       }
     } else {
-      applyDefaultRole(user);
+      LOG.warn("Applying default roles as no role attribute is configured");
+      applyDefaultRole(user, oAuthConfig.getDefaultRoles(), newUser);
     }
   }
 
-  private void applyDefaultRole(UserAccount user) {
-    
user.setRoles(Stream.of(DefaultRole.ROLE_ADMIN.toString()).collect(Collectors.toSet()));
-    user.setExternallyManagedRoles(false);
+  private void applyDefaultRole(UserAccount user,
+                                Set<String> defaultRoles,
+                                boolean newUser) {
+    if (newUser) {
+      user.setRoles(
+          defaultRoles
+              .stream()
+              .filter(r -> allRoles
+                  .stream()
+                  .anyMatch(role -> role.getElementId().equals(r)))
+              .collect(Collectors.toSet())
+      );
+      user.setExternallyManagedRoles(false);
+    }
   }
 
   private UserAccount toUserAccount(String registrationId,
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
index f6d00886bf..09c378e914 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
@@ -20,11 +20,12 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-08-05 
18:37:35.
+// Generated using typescript-generator version 3.2.1263 on 2025-08-19 
20:00:50.
 
 import { Storable } from './streampipes-model';
 
 export class Group implements Storable {
+    alternateIds: string[];
     elementId: string;
     groupId: string;
     groupName: string;
@@ -36,6 +37,9 @@ export class Group implements Storable {
             return data;
         }
         const instance = target || new Group();
+        instance.alternateIds = __getCopyArrayFn(__identity<string>())(
+            data.alternateIds,
+        );
         instance.elementId = data.elementId;
         instance.groupId = data.groupId;
         instance.groupName = data.groupName;
@@ -189,6 +193,7 @@ export class RawUserApiToken {
 }
 
 export class Role implements Storable {
+    alternateIds: string[];
     defaultRole: boolean;
     elementId: string;
     label: string;
@@ -200,6 +205,9 @@ export class Role implements Storable {
             return data;
         }
         const instance = target || new Role();
+        instance.alternateIds = __getCopyArrayFn(__identity<string>())(
+            data.alternateIds,
+        );
         instance.defaultRole = data.defaultRole;
         instance.elementId = data.elementId;
         instance.label = data.label;
diff --git a/ui/src/app/configuration/configuration.module.ts 
b/ui/src/app/configuration/configuration.module.ts
index 25fe8959aa..14ad6a1fac 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -102,6 +102,7 @@ import { GenericStorageItemsComponent } from 
'./export/export-dialog/generic-sto
 import { TranslateModule } from '@ngx-translate/core';
 import { CertificateConfigurationComponent } from 
'./extensions-service-management/certificate-configuration/certificate-configuration.component';
 import { CertificateDetailsDialogComponent } from 
'./dialog/certificate-details/certificate-details-dialog.component';
+import { AlternateIdConfigurationComponent } from 
'./security-configuration/alternate-id-configuration/alternate-id-configuration.component';
 
 @NgModule({
     imports: [
@@ -259,6 +260,7 @@ import { CertificateDetailsDialogComponent } from 
'./dialog/certificate-details/
         PipelineElementTypeFilter,
         CertificateConfigurationComponent,
         CertificateDetailsDialogComponent,
+        AlternateIdConfigurationComponent,
     ],
     providers: [
         OrderByPipe,
diff --git 
a/ui/src/app/configuration/security-configuration/alternate-id-configuration/alternate-id-configuration.component.html
 
b/ui/src/app/configuration/security-configuration/alternate-id-configuration/alternate-id-configuration.component.html
new file mode 100644
index 0000000000..f02c21b7bb
--- /dev/null
+++ 
b/ui/src/app/configuration/security-configuration/alternate-id-configuration/alternate-id-configuration.component.html
@@ -0,0 +1,48 @@
+<!--
+  ~ 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">
+    @for (id of alternateIds; track $index) {
+        <div fxLayout="row" fxLayoutAlign="start center" class="p-5 border">
+            <span fxFlex>{{ id }}</span>
+            <button
+                mat-icon-button
+                color="accent"
+                (click)="removeAlternateId(id)"
+            >
+                <mat-icon>delete</mat-icon>
+            </button>
+        </div>
+    }
+    <div fxLayout="row" fxLayoutAlign="start center" class="alternate-id-row">
+        <mat-form-field color="accent" fxFlex subscriptSizing="dynamic">
+            <mat-label>New Alternate ID</mat-label>
+            <input
+                matInput
+                [(ngModel)]="newAlternateId"
+                (keyup.enter)="addAlternateId()"
+                placeholder="Enter new alternate ID"
+            />
+        </mat-form-field>
+        <div fxLayoutAlign="start center">
+            <button mat-button class="mat-basic" (click)="addAlternateId()">
+                <mat-icon>add</mat-icon><span>Add</span>
+            </button>
+        </div>
+    </div>
+</div>
diff --git 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
 
b/ui/src/app/configuration/security-configuration/alternate-id-configuration/alternate-id-configuration.component.ts
similarity index 56%
copy from 
ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
copy to 
ui/src/app/configuration/security-configuration/alternate-id-configuration/alternate-id-configuration.component.ts
index 72634367f4..23838e01b2 100644
--- 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
+++ 
b/ui/src/app/configuration/security-configuration/alternate-id-configuration/alternate-id-configuration.component.ts
@@ -1,4 +1,4 @@
-/*!
+/*
  * 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.
@@ -16,24 +16,27 @@
  *
  */
 
-.lists-container {
-    display: flex;
-    justify-content: space-between;
-}
+import { Component, Input, OnInit } from '@angular/core';
 
-.list-section {
-    width: 45%;
-    font-size: 10pt;
-    border: 1px solid var(--color-bg-2);
-}
+@Component({
+    selector: 'sp-alternate-id-configuration',
+    templateUrl: './alternate-id-configuration.component.html',
+    standalone: false,
+})
+export class AlternateIdConfigurationComponent {
+    @Input()
+    alternateIds: string[] = [];
 
-.button-section {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-}
+    newAlternateId: string = '';
+
+    addAlternateId(): void {
+        if (!this.alternateIds) {
+            this.alternateIds = [];
+        }
+        this.alternateIds.push(this.newAlternateId);
+    }
 
-.privilege-header {
-    padding: 10px;
-    font-weight: bold;
+    removeAlternateId(id: string): void {
+        this.alternateIds.splice(this.alternateIds.indexOf(id), 1);
+    }
 }
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
index 53eac39a15..1769401974 100644
--- 
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
@@ -44,6 +44,13 @@
                         {{ role.label }}
                     </mat-checkbox>
                 </div>
+                <div fxLayout="column" class="general-options-panel">
+                    <span class="general-options-header">Alternate IDs</span>
+                    <sp-alternate-id-configuration
+                        [alternateIds]="clonedGroup.alternateIds"
+                    >
+                    </sp-alternate-id-configuration>
+                </div>
             </form>
         </div>
     </div>
diff --git 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.html
 
b/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.html
index b404b3ff57..be0ed9da1a 100644
--- 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.html
+++ 
b/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.html
@@ -52,7 +52,7 @@
                         fxLayoutGap="10px"
                         class="lists-container"
                     >
-                        <div class="list-section">
+                        <div class="list-section" fxFlex="50">
                             <div class="privilege-header">
                                 Available Privileges
                             </div>
@@ -62,7 +62,10 @@
                                         mat-icon-button
                                         color="accent"
                                         (click)="assignPrivilege(privilege)"
-                                        [disabled]="isAssigned(privilege)"
+                                        [disabled]="
+                                            isAssigned(privilege) ||
+                                            clonedRole.defaultRole
+                                        "
                                     >
                                         <mat-icon>add</mat-icon>
                                     </button>
@@ -70,7 +73,7 @@
                                 </div>
                             </div>
                         </div>
-                        <div class="list-section">
+                        <div class="list-section" fxFlex="50">
                             <div class="privilege-header">
                                 Selected Privileges
                             </div>
@@ -79,6 +82,7 @@
                                     <button
                                         mat-icon-button
                                         color="accent"
+                                        [disabled]="clonedRole.defaultRole"
                                         (click)="removePrivilege(privilege)"
                                     >
                                         <mat-icon>remove</mat-icon>
@@ -89,6 +93,14 @@
                         </div>
                     </div>
                 </div>
+
+                <div fxLayout="column" class="general-options-panel">
+                    <span class="general-options-header">Alternate IDs</span>
+                    <sp-alternate-id-configuration
+                        [alternateIds]="clonedRole.alternateIds"
+                    >
+                    </sp-alternate-id-configuration>
+                </div>
             </form>
         </div>
     </div>
@@ -102,7 +114,7 @@
                 (click)="save()"
                 style="margin-right: 10px"
                 [disabled]="
-                    !parentForm.valid || selectedPrivileges.length === 0
+                    parentForm.invalid || selectedPrivileges.length === 0
                 "
                 data-cy="sp-edit-role-save"
             >
diff --git 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
 
b/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
index 72634367f4..ebb3e55286 100644
--- 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
+++ 
b/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.scss
@@ -22,7 +22,6 @@
 }
 
 .list-section {
-    width: 45%;
     font-size: 10pt;
     border: 1px solid var(--color-bg-2);
 }
diff --git 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.ts
 
b/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.ts
index 0032d0a3cb..415649811a 100644
--- 
a/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.ts
+++ 
b/ui/src/app/configuration/security-configuration/edit-role-dialog/edit-role-dialog.component.ts
@@ -74,14 +74,23 @@ export class EditRoleDialogComponent implements OnInit {
         this.parentForm = this.fb.group({});
         this.parentForm.addControl(
             'label',
-            new FormControl(this.clonedRole.label, Validators.required),
+            new FormControl(
+                {
+                    value: this.clonedRole.label,
+                    disabled: this.clonedRole.defaultRole,
+                },
+                Validators.required,
+            ),
         );
         this.parentForm.addControl(
             'elementId',
-            new FormControl(this.clonedRole.elementId, [
-                Validators.required,
-                Validators.pattern(/^ROLE_[A-Z_]+$/),
-            ]),
+            new FormControl(
+                {
+                    value: this.clonedRole.elementId,
+                    disabled: this.clonedRole.defaultRole,
+                },
+                [Validators.required, Validators.pattern(/^ROLE_[A-Z_]+$/)],
+            ),
         );
     }
 
diff --git 
a/ui/src/app/configuration/security-configuration/role-configuration/role-configuration.component.html
 
b/ui/src/app/configuration/security-configuration/role-configuration/role-configuration.component.html
index b5feadd27e..79c00bdd35 100644
--- 
a/ui/src/app/configuration/security-configuration/role-configuration/role-configuration.component.html
+++ 
b/ui/src/app/configuration/security-configuration/role-configuration/role-configuration.component.html
@@ -77,7 +77,6 @@
                                     matTooltip="Edit user"
                                     matTooltipPosition="above"
                                     data-cy="role-edit-btn"
-                                    [disabled]="role.defaultRole"
                                     (click)="editRole(role)"
                                 >
                                     <i class="material-icons">edit</i>

Reply via email to