This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch add-alternate-ids-to-groups-roles in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 7808d7e19faaf5c7b3ff977efe429909d55551fc Author: Dominik Riemer <[email protected]> AuthorDate: Tue Aug 19 22:59:15 2025 +0200 feat: Support alternate ids for groups and roles --- .../environment/model/OAuthConfiguration.java | 12 +++- .../parser/OAuthConfigurationParser.java | 19 +++++- .../streampipes/model/client/user/Group.java | 9 +++ .../apache/streampipes/model/client/user/Role.java | 11 ++++ .../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, 237 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..7fce8f516f 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,6 +24,7 @@ import org.apache.streampipes.model.shared.api.Storable; import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; import java.util.List; @TsModel @@ -35,6 +36,7 @@ public class Role implements Storable { private String label; private boolean defaultRole; private List<String> privilegeIds; + private List<String> alternateIds; // document type should be persisted to CouchDB with Gson serialization, but not via Jackson to the UI @JsonIgnore @@ -49,6 +51,7 @@ public class Role implements Storable { role.label = label; role.defaultRole = true; role.privilegeIds = privilegeIds; + role.alternateIds = new ArrayList<>(); return role; } @@ -103,4 +106,12 @@ public class Role implements Storable { public void setPrivilegeIds(List<String> privilegeIds) { this.privilegeIds = privilegeIds; } + + public List<String> getAlternateIds() { + return alternateIds; + } + + public void setAlternateIds(List<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>
