From 9ef4c7fffd75ed63c49713405083c17e643ff80a Mon Sep 17 00:00:00 2001 From: Graser Leon Date: Fri, 5 Oct 2018 14:38:33 +0000 Subject: [PATCH] KEYCLOAK-8377 Role Attributes --- .../idm/RoleRepresentation.java | 20 ++++ .../models/cache/infinispan/RoleAdapter.java | 69 ++++++++++- .../cache/infinispan/entities/CachedRole.java | 11 +- .../org/keycloak/models/jpa/RoleAdapter.java | 81 ++++++++++++- .../jpa/entities/RoleAttributeEntity.java | 108 ++++++++++++++++++ .../models/jpa/entities/RoleEntity.java | 19 +++ .../META-INF/jpa-changelog-4.6.0.xml | 24 +++- .../main/resources/META-INF/persistence.xml | 1 + .../models/utils/ModelToRepresentation.java | 12 ++ .../models/utils/RepresentationToModel.java | 5 + .../java/org/keycloak/models/RoleModel.java | 14 +++ .../admin/ResourceSetService.java | 2 - .../authorization/admin/ScopeService.java | 2 - .../admin/ClientRoleMappingsResource.java | 9 +- ...entScopeEvaluateScopeMappingsResource.java | 4 +- .../admin/RoleContainerResource.java | 2 +- .../resources/admin/RoleMapperResource.java | 10 +- .../resources/admin/RoleResource.java | 20 +++- .../admin/ScopeMappedClientResource.java | 4 +- .../resources/admin/ScopeMappedResource.java | 12 +- .../testsuite/admin/RoleByIdResourceTest.java | 40 +++++++ .../testsuite/admin/UserStorageRestTest.java | 12 +- .../keycloak/testsuite/admin/UserTest.java | 2 +- .../testsuite/admin/group/GroupTest.java | 2 +- .../exportimport/ExportImportTest.java | 4 + .../base/src/test/resources/testrealm.json | 10 ++ .../theme/base/admin/resources/js/app.js | 39 +++++++ .../admin/resources/js/controllers/clients.js | 30 +++++ .../admin/resources/js/controllers/realm.js | 30 +++++ .../partials/client-role-attributes.html | 45 ++++++++ .../resources/partials/role-attributes.html | 41 +++++++ .../templates/kc-tabs-client-role.html | 1 + .../resources/templates/kc-tabs-role.html | 1 + 33 files changed, 641 insertions(+), 45 deletions(-) create mode 100644 model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleAttributeEntity.java create mode 100755 themes/src/main/resources/theme/base/admin/resources/partials/client-role-attributes.html create mode 100755 themes/src/main/resources/theme/base/admin/resources/partials/role-attributes.html diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java index 8f361b3ccdf..3467b5be23d 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java @@ -17,6 +17,8 @@ package org.keycloak.representations.idm; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -35,6 +37,7 @@ public class RoleRepresentation { protected Composites composites; private Boolean clientRole; private String containerId; + protected Map> attributes; public static class Composites { protected Set realm; @@ -138,4 +141,21 @@ public class RoleRepresentation { public void setContainerId(String containerId) { this.containerId = containerId; } + + public Map> getAttributes() { + return attributes; + } + + public void setAttributes(Map> attributes) { + this.attributes = attributes; + } + + public RoleRepresentation singleAttribute(String name, String value) { + if (attributes == null) { + attributes = new HashMap<>(); + } + + attributes.put(name, Arrays.asList(value)); + return this; + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java index 6a6ac8659b5..51620a1a0ef 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java @@ -25,8 +25,13 @@ import org.keycloak.models.cache.infinispan.entities.CachedRealmRole; import org.keycloak.models.cache.infinispan.entities.CachedRole; import org.keycloak.models.utils.KeycloakModelUtils; +import java.util.Collection; +import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Supplier; /** * @author Bill Burke @@ -39,22 +44,25 @@ public class RoleAdapter implements RoleModel { protected RealmCacheSession cacheSession; protected RealmModel realm; protected Set composites; + private final Supplier modelSupplier; public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel realm) { this.cached = cached; this.cacheSession = session; this.realm = realm; + this.modelSupplier = this::getRoleModel; } protected void getDelegateForUpdate() { if (updated == null) { cacheSession.registerRoleInvalidation(cached.getId(), cached.getName(), getContainerId()); - updated = cacheSession.getRealmDelegate().getRoleById(cached.getId(), realm); + updated = modelSupplier.get(); if (updated == null) throw new IllegalStateException("Not found in database"); } } protected boolean invalidated; + public void invalidate() { invalidated = true; } @@ -68,8 +76,6 @@ public class RoleAdapter implements RoleModel { } - - @Override public String getName() { if (isUpdated()) return updated.getName(); @@ -144,7 +150,7 @@ public class RoleAdapter implements RoleModel { @Override public String getContainerId() { if (isClientRole()) { - CachedClientRole appRole = (CachedClientRole)cached; + CachedClientRole appRole = (CachedClientRole) cached; return appRole.getClientId(); } else { return realm.getId(); @@ -157,7 +163,7 @@ public class RoleAdapter implements RoleModel { if (cached instanceof CachedRealmRole) { return realm; } else { - CachedClientRole appRole = (CachedClientRole)cached; + CachedClientRole appRole = (CachedClientRole) cached; return realm.getClientById(appRole.getClientId()); } } @@ -167,6 +173,59 @@ public class RoleAdapter implements RoleModel { return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>()); } + @Override + public void setSingleAttribute(String name, String value) { + getDelegateForUpdate(); + updated.setSingleAttribute(name, value); + } + + @Override + public void setAttribute(String name, Collection values) { + getDelegateForUpdate(); + updated.setAttribute(name, values); + } + + @Override + public void removeAttribute(String name) { + getDelegateForUpdate(); + updated.removeAttribute(name); + } + + @Override + public String getFirstAttribute(String name) { + if (updated != null) { + return updated.getFirstAttribute(name); + } + + return cached.getAttributes(modelSupplier).getFirst(name); + } + + @Override + public List getAttribute(String name) { + if (updated != null) { + return updated.getAttribute(name); + } + + List result = cached.getAttributes(modelSupplier).get(name); + if (result == null) { + result = Collections.emptyList(); + } + return result; + } + + @Override + public Map> getAttributes() { + if (updated != null) { + return updated.getAttributes(); + } + + return cached.getAttributes(modelSupplier); + } + + private RoleModel getRoleModel() { + return cacheSession.getRealmDelegate().getRoleById(cached.getId(), realm); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java index 5ee29bcbbd0..47bfa725d8c 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java @@ -17,11 +17,15 @@ package org.keycloak.models.cache.infinispan.entities; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.cache.infinispan.DefaultLazyLoader; +import org.keycloak.models.cache.infinispan.LazyLoader; import java.util.HashSet; import java.util.Set; +import java.util.function.Supplier; /** * @author Bill Burke @@ -34,6 +38,7 @@ public class CachedRole extends AbstractRevisioned implements InRealm { final protected String description; final protected boolean composite; final protected Set composites = new HashSet(); + private final LazyLoader> attributes; public CachedRole(Long revision, RoleModel model, RealmModel realm) { super(revision, model.getId()); @@ -46,7 +51,7 @@ public class CachedRole extends AbstractRevisioned implements InRealm { composites.add(child.getId()); } } - + attributes = new DefaultLazyLoader<>(roleModel -> new MultivaluedHashMap<>(roleModel.getAttributes()), MultivaluedHashMap::new); } public String getName() { @@ -68,4 +73,8 @@ public class CachedRole extends AbstractRevisioned implements InRealm { public Set getComposites() { return composites; } + + public MultivaluedHashMap getAttributes(Supplier roleModel) { + return attributes.get(roleModel); + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java index 27b81ba9f28..eb10c37a78d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java @@ -21,11 +21,19 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.jpa.entities.RoleAttributeEntity; import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -116,6 +124,77 @@ public class RoleAdapter implements RoleModel, JpaModel { return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>()); } + private void persistAttributeValue(String name, String value) { + RoleAttributeEntity attr = new RoleAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setName(name); + attr.setValue(value); + attr.setRole(role); + em.persist(attr); + role.getAttributes().add(attr); + } + + @Override + public void setSingleAttribute(String name, String value) { + setAttribute(name, Collections.singletonList(value)); + } + + @Override + public void setAttribute(String name, Collection values) { + removeAttribute(name); + + for (String value : values) { + persistAttributeValue(name, value); + } + } + + @Override + public void removeAttribute(String name) { + Collection attributes = role.getAttributes(); + if (attributes == null) { + return; + } + + Query query = em.createNamedQuery("deleteRoleAttributesByNameAndUser"); + query.setParameter("name", name); + query.setParameter("roleId", role.getId()); + query.executeUpdate(); + + attributes.removeIf(attribute -> attribute.getName().equals(name)); + } + + @Override + public String getFirstAttribute(String name) { + for (RoleAttributeEntity attribute : role.getAttributes()) { + if (attribute.getName().equals(name)) { + return attribute.getValue(); + } + } + + return null; + } + + @Override + public List getAttribute(String name) { + List attributes = new ArrayList<>(); + for (RoleAttributeEntity attribute : role.getAttributes()) { + if (attribute.getName().equals(name)) { + attributes.add(attribute.getValue()); + } + } + return attributes; + } + + @Override + public Map> getAttributes() { + Map> map = new HashMap<>(); + for (RoleAttributeEntity attribute : role.getAttributes()) { + map.computeIfAbsent(attribute.getName(), name -> new ArrayList<>()).add(attribute.getValue()); + } + + return map; + } + @Override public boolean isClientRole() { return role.isClientRole(); @@ -154,7 +233,7 @@ public class RoleAdapter implements RoleModel, JpaModel { public static RoleEntity toRoleEntity(RoleModel model, EntityManager em) { if (model instanceof RoleAdapter) { - return ((RoleAdapter)model).getEntity(); + return ((RoleAdapter) model).getEntity(); } return em.getReference(RoleEntity.class, model.getId()); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleAttributeEntity.java new file mode 100644 index 00000000000..59b88a4494c --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleAttributeEntity.java @@ -0,0 +1,108 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed 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. + */ + +package org.keycloak.models.jpa.entities; + +import org.hibernate.annotations.Nationalized; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Leon Graser + */ +@NamedQueries({ + @NamedQuery(name = "deleteRoleAttributesByNameAndUser", query = "delete from RoleAttributeEntity attr where attr.role.id = :roleId and attr.name = :name"), +}) +@Table(name = "ROLE_ATTRIBUTE") +@Entity +public class RoleAttributeEntity { + + @Id + @Column(name = "ID", length = 36) + @Access(AccessType.PROPERTY) + protected String id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ROLE_ID") + protected RoleEntity role; + + @Column(name = "NAME") + protected String name; + + @Nationalized + @Column(name = "VALUE") + protected String value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public RoleEntity getRole() { + return role; + } + + public void setRole(RoleEntity role) { + this.role = role; + } + + @Override + public boolean equals(Object o) { + boolean result = false; + + if (o instanceof RoleAttributeEntity) { + RoleAttributeEntity otherRole = (RoleAttributeEntity) o; + result = id.equals(otherRole.id); + } + + return result; + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java index 526d55931ec..d1ac58c2006 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java @@ -17,10 +17,14 @@ package org.keycloak.models.jpa.entities; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.Nationalized; import javax.persistence.Access; import javax.persistence.AccessType; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -31,9 +35,13 @@ import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -93,6 +101,11 @@ public class RoleEntity { @JoinTable(name = "COMPOSITE_ROLE", joinColumns = @JoinColumn(name = "COMPOSITE"), inverseJoinColumns = @JoinColumn(name = "CHILD_ROLE")) private Set compositeRoles = new HashSet<>(); + @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="role") + @Fetch(FetchMode.SELECT) + @BatchSize(size = 20) + protected List attributes = new ArrayList<>(); + public String getId() { return id; } @@ -109,7 +122,13 @@ public class RoleEntity { this.realmId = realmId; } + public Collection getAttributes() { + return attributes; + } + public void setAttributes(List attributes) { + this.attributes = attributes; + } public String getName() { return name; diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml index c902a8e7909..64ac38f8da8 100644 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml @@ -23,11 +23,31 @@ NAME LIKE 'group.resource.%' - + + + + + + + + + + + + + + + + + + + + + - + diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml index 76aae9dc30f..d888203d4c2 100755 --- a/model/jpa/src/main/resources/META-INF/persistence.xml +++ b/model/jpa/src/main/resources/META-INF/persistence.xml @@ -31,6 +31,7 @@ org.keycloak.models.jpa.entities.UserFederationProviderEntity org.keycloak.models.jpa.entities.UserFederationMapperEntity org.keycloak.models.jpa.entities.RoleEntity + org.keycloak.models.jpa.entities.RoleAttributeEntity org.keycloak.models.jpa.entities.FederatedIdentityEntity org.keycloak.models.jpa.entities.MigrationModelEntity org.keycloak.models.jpa.entities.UserEntity diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 64d3f2151e3..0697c3874aa 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -223,6 +223,18 @@ public class ModelToRepresentation { } public static RoleRepresentation toRepresentation(RoleModel role) { + RoleRepresentation rep = new RoleRepresentation(); + rep.setId(role.getId()); + rep.setName(role.getName()); + rep.setDescription(role.getDescription()); + rep.setComposite(role.isComposite()); + rep.setClientRole(role.isClientRole()); + rep.setContainerId(role.getContainerId()); + rep.setAttributes(role.getAttributes()); + return rep; + } + + public static RoleRepresentation toBriefRepresentation(RoleModel role) { RoleRepresentation rep = new RoleRepresentation(); rep.setId(role.getId()); rep.setName(role.getName()); diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 4081661a7ea..db661793d99 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1033,6 +1033,11 @@ public class RepresentationToModel { public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) { RoleModel role = roleRep.getId() != null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName()); if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription()); + if (roleRep.getAttributes() != null) { + for (Map.Entry> attribute : roleRep.getAttributes().entrySet()) { + role.setAttribute(attribute.getKey(), attribute.getValue()); + } + } } private static void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) { diff --git a/server-spi/src/main/java/org/keycloak/models/RoleModel.java b/server-spi/src/main/java/org/keycloak/models/RoleModel.java index 119539c2e5a..d93c94c2b1d 100755 --- a/server-spi/src/main/java/org/keycloak/models/RoleModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RoleModel.java @@ -17,6 +17,9 @@ package org.keycloak.models; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -50,4 +53,15 @@ public interface RoleModel { boolean hasRole(RoleModel role); + void setSingleAttribute(String name, String value); + + void setAttribute(String name, Collection values); + + void removeAttribute(String name); + + String getFirstAttribute(String name); + + List getAttribute(String name); + + Map> getAttributes(); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index 78dff9eb2f9..39764e51351 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -41,10 +41,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriInfo; import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.OAuthErrorException; diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index 5e4dd6f4cd4..f87baceb776 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -45,11 +45,9 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriInfo; import java.util.Arrays; import java.util.HashMap; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java index ae7d5195b12..bb658a2aaf7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientRoleMappingsResource.java @@ -31,7 +31,6 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.services.ErrorResponseException; -import org.keycloak.services.ForbiddenException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -98,7 +97,7 @@ public class ClientRoleMappingsResource { Set mappings = user.getClientRoleMappings(client); List mapRep = new ArrayList(); for (RoleModel roleModel : mappings) { - mapRep.add(ModelToRepresentation.toRepresentation(roleModel)); + mapRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return mapRep; } @@ -121,7 +120,7 @@ public class ClientRoleMappingsResource { Set roles = client.getRoles(); List mapRep = new ArrayList(); for (RoleModel roleModel : roles) { - if (user.hasRole(roleModel)) mapRep.add(ModelToRepresentation.toRepresentation(roleModel)); + if (user.hasRole(roleModel)) mapRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return mapRep; } @@ -154,7 +153,7 @@ public class ClientRoleMappingsResource { List mappings = new ArrayList(); for (RoleModel roleModel : roles) { - mappings.add(ModelToRepresentation.toRepresentation(roleModel)); + mappings.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return mappings; } @@ -202,7 +201,7 @@ public class ClientRoleMappingsResource { } auth.roles().requireMapRole(roleModel); user.deleteRoleMapping(roleModel); - roles.add(ModelToRepresentation.toRepresentation(roleModel)); + roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } } else { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateScopeMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateScopeMappingsResource.java index 8e514b405a8..cbdf053cd55 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateScopeMappingsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateScopeMappingsResource.java @@ -76,7 +76,7 @@ public class ClientScopeEvaluateScopeMappingsResource { public List getGrantedScopeMappings() { return getGrantedRoles().stream().map((RoleModel role) -> { - return ModelToRepresentation.toRepresentation(role); + return ModelToRepresentation.toBriefRepresentation(role); }).collect(Collectors.toList()); } @@ -101,7 +101,7 @@ public class ClientScopeEvaluateScopeMappingsResource { }).map((RoleModel role) -> { - return ModelToRepresentation.toRepresentation(role); + return ModelToRepresentation.toBriefRepresentation(role); }).collect(Collectors.toList()); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index 8ad922b7a06..cebd1682168 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -94,7 +94,7 @@ public class RoleContainerResource extends RoleResource { Set roleModels = roleContainer.getRoles(); List roles = new ArrayList(); for (RoleModel roleModel : roleModels) { - roles.add(ModelToRepresentation.toRepresentation(roleModel)); + roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return roles; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java index dccad2b4c16..26feaab9df3 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleMapperResource.java @@ -119,7 +119,7 @@ public class RoleMapperResource { if (realmMappings.size() > 0) { List realmRep = new ArrayList(); for (RoleModel roleModel : realmMappings) { - realmRep.add(ModelToRepresentation.toRepresentation(roleModel)); + realmRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } all.setRealmMappings(realmRep); } @@ -136,7 +136,7 @@ public class RoleMapperResource { List roles = new ArrayList(); mappings.setMappings(roles); for (RoleModel role : roleMappings) { - roles.add(ModelToRepresentation.toRepresentation(role)); + roles.add(ModelToRepresentation.toBriefRepresentation(role)); } appMappings.put(client.getClientId(), mappings); all.setClientMappings(appMappings); @@ -161,7 +161,7 @@ public class RoleMapperResource { Set realmMappings = roleMapper.getRealmRoleMappings(); List realmMappingsRep = new ArrayList(); for (RoleModel roleModel : realmMappings) { - realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel)); + realmMappingsRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return realmMappingsRep; } @@ -184,7 +184,7 @@ public class RoleMapperResource { List realmMappingsRep = new ArrayList(); for (RoleModel roleModel : roles) { if (roleMapper.hasRole(roleModel)) { - realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel)); + realmMappingsRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } } return realmMappingsRep; @@ -253,7 +253,7 @@ public class RoleMapperResource { for (RoleModel roleModel : roleModels) { auth.roles().requireMapRole(roleModel); roleMapper.deleteRoleMapping(roleModel); - roles.add(ModelToRepresentation.toRepresentation(roleModel)); + roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } } else { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java index 3af52c1ab26..c05bfef9e63 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java @@ -31,6 +31,7 @@ import javax.ws.rs.core.UriInfo; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -58,6 +59,19 @@ public abstract class RoleResource { protected void updateRole(RoleRepresentation rep, RoleModel role) { role.setName(rep.getName()); role.setDescription(rep.getDescription()); + + if (rep.getAttributes() != null) { + Set attrsToRemove = new HashSet<>(role.getAttributes().keySet()); + attrsToRemove.removeAll(rep.getAttributes().keySet()); + + for (Map.Entry> attr : rep.getAttributes().entrySet()) { + role.setAttribute(attr.getKey(), attr.getValue()); + } + + for (String attr : attrsToRemove) { + role.removeAttribute(attr); + } + } } protected void addComposites(AdminPermissionEvaluator auth, AdminEventBuilder adminEvent, UriInfo uriInfo, List roles, RoleModel role) { @@ -84,7 +98,7 @@ public abstract class RoleResource { Set composites = new HashSet(role.getComposites().size()); for (RoleModel composite : role.getComposites()) { - composites.add(ModelToRepresentation.toRepresentation(composite)); + composites.add(ModelToRepresentation.toBriefRepresentation(composite)); } return composites; } @@ -95,7 +109,7 @@ public abstract class RoleResource { Set composites = new HashSet(role.getComposites().size()); for (RoleModel composite : role.getComposites()) { if (composite.getContainer() instanceof RealmModel) - composites.add(ModelToRepresentation.toRepresentation(composite)); + composites.add(ModelToRepresentation.toBriefRepresentation(composite)); } return composites; } @@ -106,7 +120,7 @@ public abstract class RoleResource { Set composites = new HashSet(role.getComposites().size()); for (RoleModel composite : role.getComposites()) { if (composite.getContainer().equals(app)) - composites.add(ModelToRepresentation.toRepresentation(composite)); + composites.add(ModelToRepresentation.toBriefRepresentation(composite)); } return composites; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java index 4f7b5dcd6f3..1f2c4550903 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java @@ -87,7 +87,7 @@ public class ScopeMappedClientResource { Set mappings = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer); //scopedClient.getClientScopeMappings(client); List mapRep = new ArrayList(); for (RoleModel roleModel : mappings) { - mapRep.add(ModelToRepresentation.toRepresentation(roleModel)); + mapRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return mapRep; } @@ -165,7 +165,7 @@ public class ScopeMappedClientResource { for (RoleModel roleModel : roleModels) { scopeContainer.deleteScopeMapping(roleModel); - roles.add(ModelToRepresentation.toRepresentation(roleModel)); + roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } } else { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java index 286e22b5b5a..dbf95451bd9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java @@ -98,7 +98,7 @@ public class ScopeMappedResource { if (realmMappings.size() > 0) { List realmRep = new ArrayList(); for (RoleModel roleModel : realmMappings) { - realmRep.add(ModelToRepresentation.toRepresentation(roleModel)); + realmRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } all.setRealmMappings(realmRep); } @@ -115,7 +115,7 @@ public class ScopeMappedResource { List roles = new ArrayList(); mappings.setMappings(roles); for (RoleModel role : roleMappings) { - roles.add(ModelToRepresentation.toRepresentation(role)); + roles.add(ModelToRepresentation.toBriefRepresentation(role)); } clientMappings.put(client.getClientId(), mappings); all.setClientMappings(clientMappings); @@ -144,7 +144,7 @@ public class ScopeMappedResource { Set realmMappings = scopeContainer.getRealmScopeMappings(); List realmMappingsRep = new ArrayList(); for (RoleModel roleModel : realmMappings) { - realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel)); + realmMappingsRep.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return realmMappingsRep; } @@ -174,7 +174,7 @@ public class ScopeMappedResource { for (RoleModel roleModel : roles) { if (client.hasScope(roleModel)) continue; if (!auth.roles().canMapClientScope(roleModel)) continue; - available.add(ModelToRepresentation.toRepresentation(roleModel)); + available.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return available; } @@ -206,7 +206,7 @@ public class ScopeMappedResource { public static List getComposite(ScopeContainerModel client, Set roles) { List composite = new ArrayList(); for (RoleModel roleModel : roles) { - if (client.hasScope(roleModel)) composite.add(ModelToRepresentation.toRepresentation(roleModel)); + if (client.hasScope(roleModel)) composite.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } return composite; } @@ -258,7 +258,7 @@ public class ScopeMappedResource { for (RoleModel roleModel : roleModels) { scopeContainer.deleteScopeMapping(roleModel); - roles.add(ModelToRepresentation.toRepresentation(roleModel)); + roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); } } else { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/RoleByIdResourceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/RoleByIdResourceTest.java index ee4326a39d4..42122521b44 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/RoleByIdResourceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/RoleByIdResourceTest.java @@ -30,6 +30,7 @@ import org.keycloak.testsuite.util.RoleBuilder; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -151,4 +152,43 @@ public class RoleByIdResourceTest extends AbstractAdminTest { } + @Test + public void attributes() { + for (String id : ids.values()) { + RoleRepresentation role = resource.getRole(id); + assertNotNull(role.getAttributes()); + assertTrue(role.getAttributes().isEmpty()); + + // update the role with attributes + Map> attributes = new HashMap<>(); + List attributeValues = new ArrayList<>(); + attributeValues.add("value1"); + attributes.put("key1", attributeValues); + attributeValues = new ArrayList<>(); + attributeValues.add("value2.1"); + attributeValues.add("value2.2"); + attributes.put("key2", attributeValues); + role.setAttributes(attributes); + + resource.updateRole(id, role); + role = resource.getRole(id); + assertNotNull(role); + Map> roleAttributes = role.getAttributes(); + assertNotNull(roleAttributes); + + assertEquals(attributes, roleAttributes); + + + // delete an attribute + attributes.remove("key2"); + role.setAttributes(attributes); + resource.updateRole(id, role); + role = resource.getRole(id); + assertNotNull(role); + roleAttributes = role.getAttributes(); + assertNotNull(roleAttributes); + + assertEquals(attributes, roleAttributes); + } + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java index d7f494f6c15..35a7623dc3f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserStorageRestTest.java @@ -388,8 +388,8 @@ public class UserStorageRestTest extends AbstractAdminTest { String id2 = createUserFederationProvider(dummyRep2); // Assert provider instances available - assertFederationProvider(userFederation().get(id1).toRepresentation(), id1, id1, "dummy", 2, 1000, 500, 123); - assertFederationProvider(userFederation().get(id2).toRepresentation(), id2, "dn1", "dummy", 1, -1, -1, -1, "prop1", "prop1Val", "prop2", "true"); + assertFederationProvider(userFederation().get(id1).toBriefRepresentation(), id1, id1, "dummy", 2, 1000, 500, 123); + assertFederationProvider(userFederation().get(id2).toBriefRepresentation(), id2, "dn1", "dummy", 1, -1, -1, -1, "prop1", "prop1Val", "prop2", "true"); // Assert sorted List providerInstances = userFederation().getProviderInstances(); @@ -411,7 +411,7 @@ public class UserStorageRestTest extends AbstractAdminTest { @Test (expected = NotFoundException.class) public void testLookupNotExistentProvider() { - userFederation().get("not-existent").toRepresentation(); + userFederation().get("not-existent").toBriefRepresentation(); } @@ -433,7 +433,7 @@ public class UserStorageRestTest extends AbstractAdminTest { } // Assert sync didn't happen - Assert.assertEquals(-1, userFederation().get(id1).toRepresentation().getLastSync()); + Assert.assertEquals(-1, userFederation().get(id1).toBriefRepresentation().getLastSync()); // Sync and assert it happened SynchronizationResultRepresentation syncResult = userFederation().get(id1).syncUsers("triggerFullSync"); @@ -443,7 +443,7 @@ public class UserStorageRestTest extends AbstractAdminTest { eventRep.put("action", "triggerFullSync"); assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userFederationResourcePath(id1) + "/sync", eventRep, ResourceType.USER_FEDERATION_PROVIDER); - int fullSyncTime = userFederation().get(id1).toRepresentation().getLastSync(); + int fullSyncTime = userFederation().get(id1).toBriefRepresentation().getLastSync(); Assert.assertTrue(fullSyncTime > 0); // Changed sync @@ -454,7 +454,7 @@ public class UserStorageRestTest extends AbstractAdminTest { assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userFederationResourcePath(id1) + "/sync", eventRep, ResourceType.USER_FEDERATION_PROVIDER); Assert.assertEquals("0 imported users, 0 updated users", syncResult.getStatus()); - int changedSyncTime = userFederation().get(id1).toRepresentation().getLastSync(); + int changedSyncTime = userFederation().get(id1).toBriefRepresentation().getLastSync(); Assert.assertTrue(fullSyncTime + 50 <= changedSyncTime); // Cleanup diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java index 266499d9deb..f535aae85db 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -1368,7 +1368,7 @@ public class UserTest extends AbstractAdminTest { // List realm roles assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); - assertNames(roles.realmLevel().listAvailable(), "admin", "customer-user-premium", "realm-composite-role", "sample-realm-role"); + assertNames(roles.realmLevel().listAvailable(), "admin", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role"); assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); // List client roles diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java index ce4539de6d3..085b04d3b53 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java @@ -501,7 +501,7 @@ public class GroupTest extends AbstractGroupTest { // List realm roles assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite"); - assertNames(roles.realmLevel().listAvailable(), "admin", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role", "sample-realm-role"); + assertNames(roles.realmLevel().listAvailable(), "admin", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "user", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role"); assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child"); // List client roles diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java index 1a8589521b4..4a076d021db 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java @@ -274,6 +274,7 @@ public class ExportImportTest extends AbstractKeycloakTest { List components = adminClient.realm("test").components().query(); KeysMetadataRepresentation keyMetadata = adminClient.realm("test").keys().getKeyMetadata(); String sampleRealmRoleId = adminClient.realm("test").roles().get("sample-realm-role").toRepresentation().getId(); + Map> roleAttributes = adminClient.realm("test").roles().get("attribute-role").toRepresentation().getAttributes(); String testAppId = adminClient.realm("test").clients().findByClientId("test-app").get(0).getId(); String sampleClientRoleId = adminClient.realm("test").clients().get(testAppId).roles().get("sample-client-role").toRepresentation().getId(); @@ -309,6 +310,9 @@ public class ExportImportTest extends AbstractKeycloakTest { String importedSampleRealmRoleId = adminClient.realm("test").roles().get("sample-realm-role").toRepresentation().getId(); assertEquals(sampleRealmRoleId, importedSampleRealmRoleId); + Map> importedRoleAttributes = adminClient.realm("test").roles().get("attribute-role").toRepresentation().getAttributes(); + assertEquals(roleAttributes, importedRoleAttributes); + String importedSampleClientRoleId = adminClient.realm("test").clients().get(testAppId).roles().get("sample-client-role").toRepresentation().getId(); assertEquals(sampleClientRoleId, importedSampleClientRoleId); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json index bbed5618872..81239fa2f16 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -385,6 +385,16 @@ "name": "sample-realm-role", "description": "Sample realm role" }, + { + "name": "attribute-role", + "description": "has attributes assigned", + "attributes": { + "hello": [ + "world", + "keycloak" + ] + } + }, { "name": "realm-composite-role", "description": "Realm composite role containing client role", diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index 8afc0a8fc31..984b2611dbe 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -788,6 +788,24 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RoleDetailCtrl' }) + .when('/realms/:realm/roles/:role/role-attributes', { + templateUrl : resourceUrl + '/partials/role-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'RoleDetailCtrl' + }) .when('/realms/:realm/roles/:role/users', { templateUrl : resourceUrl + '/partials/realm-role-users.html', resolve : { @@ -943,6 +961,27 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'ClientRoleDetailCtrl' }) + .when('/realms/:realm/clients/:client/roles/:role/role-attributes', { + templateUrl : resourceUrl + '/partials/client-role-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function(ClientRoleLoader) { + return ClientRoleLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientRoleDetailCtrl' + }) .when('/realms/:realm/clients/:client/mappers', { templateUrl : resourceUrl + '/partials/client-mappers.html', resolve : { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index a1c1172ef0e..4feb6aec252 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -685,12 +685,14 @@ module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role, $scope.changed = $scope.create; $scope.save = function() { + convertAttributeValuesToLists(); if ($scope.create) { ClientRole.save({ realm: realm.realm, client : client.id }, $scope.role, function (data, headers) { $scope.changed = false; + convertAttributeValuesToString($scope.role); role = angular.copy($scope.role); ClientRole.get({ realm: realm.realm, client : client.id, role: role.name }, function(role) { @@ -721,6 +723,34 @@ module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role, $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/roles"); }; + $scope.addAttribute = function() { + $scope.role.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.role.attributes[key]; + } + + function convertAttributeValuesToLists() { + var attrs = $scope.role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + var attrVals = attrs[attribute].split("##"); + attrs[attribute] = attrVals; + } + } + } + + function convertAttributeValuesToString(role) { + var attrs = role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + var attrVals = attrs[attribute].join("##"); + attrs[attribute] = attrVals; + } + } + } roleControl($scope, realm, role, roles, clients, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 3164a0f8375..381ecd26efc 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1461,12 +1461,14 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, roles, clients $scope.changed = $scope.create; $scope.save = function() { + convertAttributeValuesToLists(); console.log('save'); if ($scope.create) { Role.save({ realm: realm.realm }, $scope.role, function (data, headers) { $scope.changed = false; + convertAttributeValuesToString($scope.role); role = angular.copy($scope.role); Role.get({ realm: realm.realm, role: role.name }, function(role) { @@ -1488,7 +1490,35 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, roles, clients $location.url("/realms/" + realm.realm + "/roles"); }; + $scope.addAttribute = function() { + $scope.role.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + $scope.removeAttribute = function(key) { + delete $scope.role.attributes[key]; + } + + function convertAttributeValuesToLists() { + var attrs = $scope.role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + var attrVals = attrs[attribute].split("##"); + attrs[attribute] = attrVals; + } + } + } + + function convertAttributeValuesToString(role) { + var attrs = role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + var attrVals = attrs[attribute].join("##"); + attrs[attribute] = attrVals; + console.log("attribute" + attrVals) + } + } + } roleControl($scope, realm, role, roles, clients, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-attributes.html new file mode 100755 index 00000000000..47ef4167e38 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-attributes.html @@ -0,0 +1,45 @@ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
{{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
{{key}}{{:: 'delete' | translate}}
{{:: 'add' | translate}}
+ +
+
+ + +
+
+
+
+ + diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-attributes.html new file mode 100755 index 00000000000..b9cbbd7e877 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-attributes.html @@ -0,0 +1,41 @@ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + +
{{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
{{key}}{{:: 'delete' | translate}}
{{:: 'add' | translate}}
+ +
+
+ + +
+
+
+
+ + diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html index c1455834b5d..312f3ca624a 100755 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-role.html @@ -5,6 +5,7 @@