KEYCLOAK-14977 create MapRoleProvider

This commit is contained in:
vramik 2020-09-02 13:11:13 +02:00 committed by Hynek Mlnařík
parent b494b8bb44
commit 785f2e78bc
31 changed files with 1277 additions and 189 deletions

View file

@ -71,4 +71,4 @@ jobs:
- name: Build testsuite
run: mvn clean install -B -DskipTests -f testsuite/pom.xml
- name: Run base tests - undertow
run: mvn clean install -B -f testsuite/integration-arquillian/tests/base/pom.xml -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map | misc/log/trimmer.sh; exit ${PIPESTATUS[0]}
run: mvn clean install -B -f testsuite/integration-arquillian/tests/base/pom.xml -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map | misc/log/trimmer.sh; exit ${PIPESTATUS[0]}

View file

@ -25,7 +25,6 @@ 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.HashSet;
import java.util.List;
import java.util.Map;
@ -182,7 +181,7 @@ public class RoleAdapter implements RoleModel {
}
@Override
public void setAttribute(String name, Collection<String> values) {
public void setAttribute(String name, List<String> values) {
getDelegateForUpdate();
updated.setAttribute(name, values);
}

View file

@ -28,7 +28,6 @@ import org.keycloak.models.jpa.entities.ClientAttributeEntity;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.ClientScopeClientMappingEntity;
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RoleUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -37,8 +36,7 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -46,6 +44,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@ -66,6 +65,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
this.entity = entity;
}
@Override
public ClientEntity getEntity() {
return entity;
}
@ -148,7 +148,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
@Override
public Set<String> getWebOrigins() {
Set<String> result = new HashSet<String>();
Set<String> result = new HashSet<>();
result.addAll(entity.getWebOrigins());
return result;
}
@ -172,7 +172,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
@Override
public Set<String> getRedirectUris() {
Set<String> result = new HashSet<String>();
Set<String> result = new HashSet<>();
result.addAll(entity.getRedirectUris());
return result;
}
@ -244,21 +244,19 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
@Override
public Stream<RoleModel> getScopeMappingsStream() {
return getEntity().getScopeMapping().stream()
.map(RoleEntity::getId)
return entity.getScopeMappingIds().stream()
.map(realm::getRoleById)
.filter(Objects::nonNull);
}
@Override
public void addScopeMapping(RoleModel role) {
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
getEntity().getScopeMapping().add(roleEntity);
entity.getScopeMappingIds().add(role.getId());
}
@Override
public void deleteScopeMapping(RoleModel role) {
getEntity().getScopeMapping().remove(RoleAdapter.toRoleEntity(role, em));
entity.getScopeMappingIds().remove(role.getId());
}
@Override
@ -684,69 +682,51 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
@Override
public Stream<String> getDefaultRolesStream() {
Collection<RoleEntity> entities = entity.getDefaultRoles();
if (entities == null) return Stream.empty();
return entities.stream().map(RoleEntity::getName);
return entity.getDefaultRolesIds().stream().map(this::getRoleNameById);
}
private String getRoleNameById(String id) {
RoleModel roleById = session.roles().getRoleById(realm, id);
if (roleById == null) {
return null;
}
return roleById.getName();
}
@Override
public void addDefaultRole(String name) {
if (entity.getDefaultRolesIds().add(getOrAddRoleId(name))) {
em.flush();
}
}
private String getOrAddRoleId(String name) {
RoleModel role = getRole(name);
if (role == null) {
role = addRole(name);
}
Collection<RoleEntity> entities = entity.getDefaultRoles();
for (RoleEntity entity : entities) {
if (entity.getId().equals(role.getId())) {
return;
}
}
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
entities.add(roleEntity);
return role.getId();
}
@Override
public void updateDefaultRoles(String... defaultRoles) {
Collection<RoleEntity> entities = entity.getDefaultRoles();
Set<String> already = new HashSet<String>();
List<RoleEntity> remove = new ArrayList<>();
for (RoleEntity rel : entities) {
if (!contains(rel.getName(), defaultRoles)) {
remove.add(rel);
} else {
already.add(rel.getName());
}
}
for (RoleEntity entity : remove) {
entities.remove(entity);
}
em.flush();
for (String roleName : defaultRoles) {
if (!already.contains(roleName)) {
addDefaultRole(roleName);
}
}
Set<String> newDefaultRolesIds = Arrays.stream(defaultRoles)
.map(this::getOrAddRoleId)
.collect(Collectors.toSet());
entity.getDefaultRolesIds().retainAll(newDefaultRolesIds);
entity.getDefaultRolesIds().addAll(newDefaultRolesIds);
em.flush();
}
@Override
public void removeDefaultRoles(String... defaultRoles) {
Collection<RoleEntity> entities = entity.getDefaultRoles();
List<RoleEntity> remove = new ArrayList<RoleEntity>();
for (RoleEntity rel : entities) {
if (contains(rel.getName(), defaultRoles)) {
remove.add(rel);
}
}
for (RoleEntity entity : remove) {
entities.remove(entity);
}
Arrays.stream(defaultRoles)
.map(this::getRole)
.filter(Objects::nonNull)
.forEach(role -> entity.getDefaultRolesIds().remove(role.getId()));
em.flush();
}
@Override
public int getNodeReRegistrationTimeout() {
return entity.getNodeReRegistrationTimeout();

View file

@ -216,21 +216,19 @@ public class ClientScopeAdapter implements ClientScopeModel, JpaModel<ClientScop
@Override
public Stream<RoleModel> getScopeMappingsStream() {
return getEntity().getScopeMapping().stream()
.map(RoleEntity::getId)
return entity.getScopeMappingIds().stream()
.map(realm::getRoleById)
.filter(Objects::nonNull);
}
@Override
public void addScopeMapping(RoleModel role) {
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
getEntity().getScopeMapping().add(roleEntity);
entity.getScopeMappingIds().add(role.getId());
}
@Override
public void deleteScopeMapping(RoleModel role) {
getEntity().getScopeMapping().remove(RoleAdapter.toRoleEntity(role, em));
entity.getScopeMappingIds().remove(role.getId());
}
@Override

View file

@ -163,7 +163,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro
adapter.removeClientScope(a.getId());
}
removeRoles(adapter);
session.roles().removeRoles(adapter);
adapter.getTopLevelGroupsStream().forEach(adapter::removeGroup);
@ -738,8 +738,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro
if (client == null) return false;
session.users().preRemove(realm, client);
removeRoles(client);
session.roles().removeRoles(client);
ClientEntity clientEntity = em.find(ClientEntity.class, id, LockModeType.PESSIMISTIC_WRITE);

View file

@ -368,10 +368,12 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override
public void grantToAllUsers(RealmModel realm, RoleModel role) {
int num = em.createNamedQuery("grantRoleToAllUsers")
if (realm.equals(role.isClientRole() ? ((ClientModel)role.getContainer()).getRealm() : (RealmModel)role.getContainer())) {
int num = em.createNamedQuery("grantRoleToAllUsers")
.setParameter("realmId", realm.getId())
.setParameter("roleId", role.getId())
.executeUpdate();
}
}
@Override

View file

@ -33,6 +33,7 @@ import javax.persistence.LockModeType;
import javax.persistence.TypedQuery;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Objects.nonNull;
@ -705,74 +706,47 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
return realm.getRequiredCredentials().stream().map(this::toRequiredCredentialModel);
}
@Override
public Stream<String> getDefaultRolesStream() {
Collection<RoleEntity> entities = realm.getDefaultRoles();
if (entities == null || entities.isEmpty()) return Stream.empty();
return entities.stream().map(RoleEntity::getName);
return realm.getDefaultRolesIds().stream().map(this::getRoleNameById);
}
private String getRoleNameById(String id) {
return getRoleById(id).getName();
}
@Override
public void addDefaultRole(String name) {
if (realm.getDefaultRolesIds().add(getOrAddRoleId(name))) {
em.flush();
}
}
private String getOrAddRoleId(String name) {
RoleModel role = getRole(name);
if (role == null) {
role = addRole(name);
}
Collection<RoleEntity> entities = realm.getDefaultRoles();
for (RoleEntity entity : entities) {
if (entity.getId().equals(role.getId())) {
return;
}
}
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
entities.add(roleEntity);
em.flush();
}
public static boolean contains(String str, String[] array) {
for (String s : array) {
if (str.equals(s)) return true;
}
return false;
return role.getId();
}
@Override
public void updateDefaultRoles(String[] defaultRoles) {
Collection<RoleEntity> entities = realm.getDefaultRoles();
Set<String> already = new HashSet<String>();
List<RoleEntity> remove = new ArrayList<RoleEntity>();
for (RoleEntity rel : entities) {
if (!contains(rel.getName(), defaultRoles)) {
remove.add(rel);
} else {
already.add(rel.getName());
}
}
for (RoleEntity entity : remove) {
entities.remove(entity);
}
em.flush();
for (String roleName : defaultRoles) {
if (!already.contains(roleName)) {
addDefaultRole(roleName);
}
}
Set<String> newDefaultRolesIds = Arrays.stream(defaultRoles)
.map(this::getOrAddRoleId)
.collect(Collectors.toSet());
realm.getDefaultRolesIds().retainAll(newDefaultRolesIds);
realm.getDefaultRolesIds().addAll(newDefaultRolesIds);
em.flush();
}
@Override
public void removeDefaultRoles(String... defaultRoles) {
Collection<RoleEntity> entities = realm.getDefaultRoles();
List<RoleEntity> remove = new ArrayList<RoleEntity>();
for (RoleEntity rel : entities) {
if (contains(rel.getName(), defaultRoles)) {
remove.add(rel);
}
}
for (RoleEntity entity : remove) {
entities.remove(entity);
}
Arrays.stream(defaultRoles)
.map(this::getRole)
.filter(Objects::nonNull)
.map(RoleModel::getId)
.forEach(realm.getDefaultRolesIds()::remove);
em.flush();
}

View file

@ -28,7 +28,6 @@ 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;
@ -54,6 +53,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
this.session = session;
}
@Override
public RoleEntity getEntity() {
return role;
}
@ -94,7 +94,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
@Override
public void addCompositeRole(RoleModel role) {
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
RoleEntity entity = toRoleEntity(role);
for (RoleEntity composite : getEntity().getCompositeRoles()) {
if (composite.equals(entity)) return;
}
@ -103,13 +103,14 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
@Override
public void removeCompositeRole(RoleModel role) {
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
RoleEntity entity = toRoleEntity(role);
getEntity().getCompositeRoles().remove(entity);
}
@Override
public Stream<RoleModel> getCompositesStream() {
return getEntity().getCompositeRoles().stream().map(c -> new RoleAdapter(session, realm, em, c));
Stream<RoleModel> composites = getEntity().getCompositeRoles().stream().map(c -> new RoleAdapter(session, realm, em, c));
return composites.filter(Objects::nonNull);
}
@Override
@ -133,7 +134,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
}
@Override
public void setAttribute(String name, Collection<String> values) {
public void setAttribute(String name, List<String> values) {
removeAttribute(name);
for (String value : values) {
@ -143,10 +144,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
@Override
public void removeAttribute(String name) {
Collection<RoleAttributeEntity> attributes = role.getAttributes();
if (attributes == null) {
return;
}
List<RoleAttributeEntity> attributes = role.getAttributes();
Query query = em.createNamedQuery("deleteRoleAttributesByNameAndUser");
query.setParameter("name", name);
@ -156,11 +154,6 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
attributes.removeIf(attribute -> attribute.getName().equals(name));
}
@Override
public String getFirstAttribute(String name) {
return getAttributeStream(name).findFirst().orElse(null);
}
@Override
public Stream<String> getAttributeStream(String name) {
return role.getAttributes().stream()
@ -185,19 +178,13 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
@Override
public String getContainerId() {
if (isClientRole()) return role.getClientId();
else return realm.getId();
return isClientRole() ? role.getClientId() : role.getRealmId();
}
@Override
public RoleContainerModel getContainer() {
if (role.isClientRole()) {
return realm.getClientById(role.getClientId());
} else {
return realm;
}
return isClientRole() ? realm.getClientById(role.getClientId()) : realm;
}
@Override
@ -214,7 +201,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
return getId().hashCode();
}
public static RoleEntity toRoleEntity(RoleModel model, EntityManager em) {
private RoleEntity toRoleEntity(RoleModel model) {
if (model instanceof RoleAdapter) {
return ((RoleAdapter) model).getEntity();
}

View file

@ -29,7 +29,6 @@ import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.NamedQueries;
@ -154,13 +153,15 @@ public class ClientEntity {
@Column(name="NODE_REREG_TIMEOUT")
private int nodeReRegistrationTimeout;
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
Collection<RoleEntity> defaultRoles;
@ElementCollection
@Column(name="ROLE_ID")
@CollectionTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")})
private Set<String> defaultRolesIds;
@OneToMany(fetch = FetchType.LAZY)
@JoinTable(name="SCOPE_MAPPING", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
protected Set<RoleEntity> scopeMapping;
@ElementCollection
@Column(name="ROLE_ID")
@CollectionTable(name="SCOPE_MAPPING", joinColumns = { @JoinColumn(name="CLIENT_ID")})
private Set<String> scopeMappingIds;
@ElementCollection
@MapKeyColumn(name="NAME")
@ -375,15 +376,15 @@ public class ClientEntity {
this.managementUrl = managementUrl;
}
public Collection<RoleEntity> getDefaultRoles() {
if (defaultRoles == null) {
defaultRoles = new LinkedList<>();
public Set<String> getDefaultRolesIds() {
if (defaultRolesIds == null) {
defaultRolesIds = new HashSet<>();
}
return defaultRoles;
return defaultRolesIds;
}
public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
this.defaultRoles = defaultRoles;
public void setDefaultRolesIds(Set<String> defaultRolesIds) {
this.defaultRolesIds = defaultRolesIds;
}
public boolean isBearerOnly() {
@ -453,15 +454,15 @@ public class ClientEntity {
this.registeredNodes = registeredNodes;
}
public Set<RoleEntity> getScopeMapping() {
if (scopeMapping == null) {
scopeMapping = new HashSet<>();
public Set<String> getScopeMappingIds() {
if (scopeMappingIds == null) {
scopeMappingIds = new HashSet<>();
}
return scopeMapping;
return scopeMappingIds;
}
public void setScopeMapping(Set<RoleEntity> scopeMapping) {
this.scopeMapping = scopeMapping;
public void setScopeMapping(Set<String> scopeMappingIds) {
this.scopeMappingIds = scopeMappingIds;
}
@Override

View file

@ -26,7 +26,9 @@ import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
@ -69,9 +71,10 @@ public class ClientScopeEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "clientScope")
protected Collection<ClientScopeAttributeEntity> attributes;
@OneToMany(fetch = FetchType.LAZY)
@JoinTable(name="CLIENT_SCOPE_ROLE_MAPPING", joinColumns = { @JoinColumn(name="SCOPE_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
protected Set<RoleEntity> scopeMapping = new HashSet<>();
@ElementCollection
@Column(name="ROLE_ID")
@CollectionTable(name="CLIENT_SCOPE_ROLE_MAPPING", joinColumns = { @JoinColumn(name="SCOPE_ID")})
private Set<String> scopeMappingIds = new HashSet<>();
public RealmEntity getRealm() {
return realm;
@ -135,12 +138,12 @@ public class ClientScopeEntity {
this.attributes = attributes;
}
public Set<RoleEntity> getScopeMapping() {
return scopeMapping;
public Set<String> getScopeMappingIds() {
return scopeMappingIds;
}
public void setScopeMapping(Set<RoleEntity> scopeMapping) {
this.scopeMapping = scopeMapping;
public void setScopeMappingIds(Set<String> scopeMappingIds) {
this.scopeMappingIds = scopeMappingIds;
}
@Override

View file

@ -157,9 +157,10 @@ public class RealmEntity {
@CollectionTable(name="REALM_SMTP_CONFIG", joinColumns={ @JoinColumn(name="REALM_ID") })
protected Map<String, String> smtpConfig;
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
protected Collection<RoleEntity> defaultRoles;
@ElementCollection
@Column(name="ROLE_ID")
@CollectionTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")})
protected Set<String> defaultRolesIds;
@ElementCollection
@Column(name="GROUP_ID")
@ -454,15 +455,15 @@ public class RealmEntity {
this.smtpConfig = smtpConfig;
}
public Collection<RoleEntity> getDefaultRoles() {
if (defaultRoles == null) {
defaultRoles = new LinkedList<>();
public Set<String> getDefaultRolesIds() {
if (defaultRolesIds == null) {
defaultRolesIds = new HashSet<>();
}
return defaultRoles;
return defaultRolesIds;
}
public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
this.defaultRoles = defaultRoles;
public void setDefaultRolesIds(Set<String> defaultRolesIds) {
this.defaultRolesIds = defaultRolesIds;
}
public Set<String> getDefaultGroupIds() {

View file

@ -38,7 +38,6 @@ import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -105,7 +104,7 @@ public class RoleEntity {
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="role")
@Fetch(FetchMode.SELECT)
@BatchSize(size = 20)
protected Collection<RoleAttributeEntity> attributes;
protected List<RoleAttributeEntity> attributes;
public String getId() {
return id;
@ -123,14 +122,14 @@ public class RoleEntity {
this.realmId = realmId;
}
public Collection<RoleAttributeEntity> getAttributes() {
public List<RoleAttributeEntity> getAttributes() {
if (attributes == null) {
attributes = new LinkedList<>();
}
return attributes;
}
public void setAttributes(Collection<RoleAttributeEntity> attributes) {
public void setAttributes(List<RoleAttributeEntity> attributes) {
this.attributes = attributes;
}

View file

@ -42,7 +42,7 @@ import java.io.Serializable;
@NamedQuery(name="deleteUserRoleMappingsByRealmAndLink", query="delete from UserRoleMappingEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteUserRoleMappingsByRole", query="delete from UserRoleMappingEntity m where m.roleId = :roleId"),
@NamedQuery(name="deleteUserRoleMappingsByUser", query="delete from UserRoleMappingEntity m where m.user = :user"),
@NamedQuery(name="grantRoleToAllUsers", query="insert into UserRoleMappingEntity (roleId, user) select role.id, user from RoleEntity role, UserEntity user where role.id = :roleId AND role.realm.id = :realmId AND user.realmId = :realmId")
@NamedQuery(name="grantRoleToAllUsers", query="insert into UserRoleMappingEntity (roleId, user) select :roleId, user from UserEntity user where user.realmId = :realmId")
})
@Table(name="USER_ROLE_MAPPING")

View file

@ -19,6 +19,11 @@
<changeSet author="keycloak" id="map-remove-ri">
<dropForeignKeyConstraint baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP"/>
<dropForeignKeyConstraint baseTableName="REALM_DEFAULT_ROLES" constraintName="FK_H4WPD7W4HSOOLNI3H0SW7BTJE"/>
<dropForeignKeyConstraint baseTableName="CLIENT_SCOPE_ROLE_MAPPING" constraintName="FK_CL_SCOPE_RM_ROLE"/>
<dropForeignKeyConstraint baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE"/>
<dropForeignKeyConstraint baseTableName="CLIENT_DEFAULT_ROLES" constraintName="FK_8AELWNIBJI49AVXSRTUF6XJOW"/>
<dropForeignKeyConstraint baseTableName="SCOPE_MAPPING" constraintName="FK_P3RH9GRKU11KQFRS4FLTT7RNQ"/>
</changeSet>
</databaseChangeLog>

View file

@ -0,0 +1,201 @@
/*
* Copyright 2020 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.map.common;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Spliterator.IMMUTABLE;
/**
*
* @author hmlnarik
*/
public class StreamUtils {
public static final class Pair<T1, T2> {
private final T1 k;
private final T2 v;
public Pair(T1 k, T2 v) {
this.k = k;
this.v = v;
}
public T1 getK() {
return k;
}
public T2 getV() {
return v;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.k);
hash = 97 * hash + Objects.hashCode(this.v);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pair<?, ?> other = (Pair<?, ?>) obj;
if ( ! Objects.equals(this.k, other.k)) {
return false;
}
if ( ! Objects.equals(this.v, other.v)) {
return false;
}
return true;
}
@Override
public String toString() {
return "[" + k + ", " + v + "]";
}
}
public static abstract class AbstractToPairSpliterator<K, V, M> implements Spliterator<Pair<K, V>> {
protected final Iterator<K> streamIterator;
protected final M mapper;
protected Iterator<V> flatMapIterator;
protected K currentKey;
public AbstractToPairSpliterator(Stream<K> stream, M mapper) {
this.streamIterator = stream.iterator();
this.mapper = mapper;
}
protected abstract void nextKey();
@Override
public boolean tryAdvance(Consumer<? super Pair<K, V>> action) {
if (flatMapIterator != null && flatMapIterator.hasNext()) {
action.accept(new Pair<>(currentKey, flatMapIterator.next()));
return true;
}
nextKey();
if (flatMapIterator != null && flatMapIterator.hasNext()) {
action.accept(new Pair<>(currentKey, flatMapIterator.next()));
return true;
}
return false;
}
@Override
public Spliterator<Pair<K, V>> trySplit() {
return null;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
@Override
public int characteristics() {
return IMMUTABLE;
}
}
private static class ToPairSpliterator<K,V> extends AbstractToPairSpliterator<K, V, Function<? super K, Stream<V>>> {
public ToPairSpliterator(Stream<K> stream, Function<? super K, Stream<V>> mapper) {
super(stream, mapper);
}
@Override
protected void nextKey() {
this.flatMapIterator = null;
while (this.flatMapIterator == null && streamIterator.hasNext()) {
currentKey = streamIterator.next();
final Stream<V> vStream = mapper.apply(currentKey);
this.flatMapIterator = vStream == null ? null : vStream.iterator();
}
}
}
private static class IterableToPairSpliterator<K,V> extends AbstractToPairSpliterator<K, V, Function<? super K, ? extends Iterable<V>>> {
public IterableToPairSpliterator(Stream<K> stream, Function<? super K, ? extends Iterable<V>> mapper) {
super(stream, mapper);
}
@Override
protected void nextKey() {
this.flatMapIterator = null;
while (this.flatMapIterator == null && streamIterator.hasNext()) {
currentKey = streamIterator.next();
final Iterable<V> vStream = mapper.apply(currentKey);
this.flatMapIterator = vStream == null ? null : vStream.iterator();
}
}
}
/**
* Creates a stream of pairs that join two streams. For each element <i>k</i> from the {@code stream}
* and each element <i>v</i> obtained from the stream returned by the {@code mapper} for <i>k</i>, generates
* a stream of pairs <i>(k, v)</i>.
* <p>
* Effectively performs equivalent of a {@code LEFT INNER JOIN} SQL operation on streams.
*
* @param <K>
* @param <V>
* @param stream
* @param mapper
* @return
*/
public static <K, V> Stream<Pair<K,V>> leftInnerJoinStream(Stream<K> stream, Function<? super K, Stream<V>> mapper) {
return StreamSupport.stream(() -> new ToPairSpliterator<>(stream, mapper), IMMUTABLE, false);
}
/**
* Creates a stream of pairs that join two streams. For each element <i>k</i> from the {@code stream}
* and each element <i>v</i> obtained from the {@code Iterable} returned by the {@code mapper} for <i>k</i>, generates
* a stream of pairs <i>(k, v)</i>.
* <p>
* Effectively performs equivalent of a {@code LEFT INNER JOIN} SQL operation on streams.
*
* @param <K>
* @param <V>
* @param stream
* @param mapper
* @return
*/
public static <K, V> Stream<Pair<K,V>> leftInnerJoinIterable(Stream<K> stream, Function<? super K, ? extends Iterable<V>> mapper) {
return StreamSupport.stream(() -> new IterableToPairSpliterator<>(stream, mapper), IMMUTABLE, false);
}
}

View file

@ -0,0 +1,150 @@
/*
* Copyright 2020 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.map.role;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.keycloak.models.map.common.AbstractEntity;
public abstract class AbstractRoleEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String name;
private String description;
private boolean clientRole;
private String clientId;
private Set<String> compositeRoles = new HashSet<>();
private Map<String, List<String>> attributes = new HashMap<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected AbstractRoleEntity() {
this.id = null;
this.realmId = null;
}
public AbstractRoleEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> values) {
this.updated |= ! Objects.equals(this.attributes.put(name, values), values);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= ! Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public boolean isClientRole() {
return clientRole;
}
public void setClientRole(boolean clientRole) {
this.updated |= ! Objects.equals(this.clientRole, clientRole);
this.clientRole = clientRole;
}
public boolean isComposite() {
return ! (compositeRoles == null || compositeRoles.isEmpty());
}
public Set<String> getCompositeRoles() {
return compositeRoles;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= ! Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public void setCompositeRoles(Set<String> compositeRoles) {
this.updated |= ! Objects.equals(this.compositeRoles, compositeRoles);
this.compositeRoles.clear();
this.compositeRoles.addAll(compositeRoles);
}
public void addCompositeRole(String roleId) {
this.updated |= this.compositeRoles.add(roleId);
}
public void removeCompositeRole(String roleId) {
this.updated |= this.compositeRoles.remove(roleId);
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2020 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.map.role;
import java.util.Objects;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.common.AbstractEntity;
public abstract class AbstractRoleModel<E extends AbstractEntity> implements RoleModel {
protected final KeycloakSession session;
protected final RealmModel realm;
protected final E entity;
public AbstractRoleModel(KeycloakSession session, RealmModel realm, E entity) {
Objects.requireNonNull(entity, "entity");
Objects.requireNonNull(realm, "realm");
this.session = session;
this.realm = realm;
this.entity = entity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof RoleModel)) return false;
RoleModel that = (RoleModel) o;
return Objects.equals(that.getId(), getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright 2020 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.map.role;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RoleModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.utils.KeycloakModelUtils;
public class MapRoleAdapter extends AbstractRoleModel<MapRoleEntity> implements RoleModel {
private static final Logger LOG = Logger.getLogger(MapRoleAdapter.class);
public MapRoleAdapter(KeycloakSession session, RealmModel realm, MapRoleEntity entity) {
super(session, realm, entity);
}
@Override
public String getName() {
return entity.getName();
}
@Override
public String getDescription() {
return entity.getDescription();
}
@Override
public void setDescription(String description) {
entity.setDescription(description);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public void setName(String name) {
entity.setName(name);
}
@Override
public boolean isComposite() {
return ! entity.getCompositeRoles().isEmpty();
}
@Override
public Stream<RoleModel> getCompositesStream() {
LOG.tracef("%% %s(%s).getCompositesStream():%d - %s", entity.getName(), entity.getId().toString(), entity.getCompositeRoles().size(), getShortStackTrace());
return entity.getCompositeRoles().stream()
.map(uuid -> session.roles().getRoleById(realm, uuid))
.filter(Objects::nonNull);
}
@Override
public void addCompositeRole(RoleModel role) {
LOG.tracef("%s(%s).addCompositeRole(%s(%s))%s", entity.getName(), entity.getId().toString(), role.getName(), role.getId(), getShortStackTrace());
entity.addCompositeRole(role.getId());
}
@Override
public void removeCompositeRole(RoleModel role) {
LOG.tracef("%s(%s).removeCompositeRole(%s(%s))%s", entity.getName(), entity.getId().toString(), role.getName(), role.getId(), getShortStackTrace());
entity.removeCompositeRole(role.getId());
}
@Override
public boolean isClientRole() {
return entity.isClientRole();
}
@Override
public String getContainerId() {
return isClientRole() ? entity.getClientId() : entity.getRealmId();
}
@Override
public RoleContainerModel getContainer() {
return isClientRole() ? session.clients().getClientById(realm, entity.getClientId()) : realm;
}
@Override
public void setAttribute(String name, List<String> values) {
entity.setAttribute(name, values);
}
@Override
public void setSingleAttribute(String name, String value) {
setAttribute(name, Collections.singletonList(value));
}
@Override
public void removeAttribute(String name) {
entity.removeAttribute(name);
}
@Override
public Map<String, List<String>> getAttributes() {
return entity.getAttributes();
}
@Override
public boolean hasRole(RoleModel role) {
return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>());
}
@Override
public Stream<String> getAttributeStream(String name) {
return getAttributes().getOrDefault(name, Collections.EMPTY_LIST).stream();
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2020 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.map.role;
import java.util.UUID;
public class MapRoleEntity extends AbstractRoleEntity<UUID> {
protected MapRoleEntity() {
super();
}
public MapRoleEntity(UUID id, String realmId) {
super(id, realmId);
}
}

View file

@ -0,0 +1,371 @@
/*
* Copyright 2020 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.map.role;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.common.Serialization;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.map.storage.MapStorage;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleProvider;
import org.keycloak.models.map.common.StreamUtils;
public class MapRoleProvider implements RoleProvider {
private static final Logger LOG = Logger.getLogger(MapRoleProvider.class);
private static final Predicate<MapRoleEntity> ALWAYS_FALSE = role -> { return false; };
private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapRoleEntity> tx;
private final MapStorage<UUID, MapRoleEntity> roleStore;
private static final Comparator<MapRoleEntity> COMPARE_BY_NAME = new Comparator<MapRoleEntity>() {
@Override
public int compare(MapRoleEntity o1, MapRoleEntity o2) {
String r1 = o1 == null ? null : o1.getName();
String r2 = o2 == null ? null : o2.getName();
return r1 == r2 ? 0
: r1 == null ? -1
: r2 == null ? 1
: r1.compareTo(r2);
}
};
public MapRoleProvider(KeycloakSession session, MapStorage<UUID, MapRoleEntity> roleStore) {
this.session = session;
this.roleStore = roleStore;
this.tx = new MapKeycloakTransaction<>(roleStore);
session.getTransactionManager().enlist(tx);
}
private Function<MapRoleEntity, RoleModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapRoleAdapter(session, realm, registerEntityForChanges(origEntity));
}
private MapRoleEntity registerEntityForChanges(MapRoleEntity origEntity) {
final MapRoleEntity res = Serialization.from(origEntity);
tx.putIfChanged(origEntity.getId(), res, MapRoleEntity::isUpdated);
return res;
}
private Predicate<MapRoleEntity> entityRealmFilter(RealmModel realm) {
if (realm == null || realm.getId() == null) {
return MapRoleProvider.ALWAYS_FALSE;
}
String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId());
}
private Predicate<MapRoleEntity> entityClientFilter(ClientModel client) {
if (client == null || client.getId() == null) {
return MapRoleProvider.ALWAYS_FALSE;
}
String clientId = client.getId();
return entity -> entity.isClientRole() &&
Objects.equals(clientId, entity.getClientId());
}
private Stream<MapRoleEntity> getNotRemovedUpdatedRolesStream(RealmModel realm) {
Stream<MapRoleEntity> updatedAndNotRemovedRolesStream = roleStore.entrySet().stream()
.map(tx::getUpdated) // If the role has been removed, tx.get will return null, otherwise it will return me.getValue()
.filter(Objects::nonNull);
return Stream.concat(tx.createdValuesStream(roleStore.keySet()), updatedAndNotRemovedRolesStream)
.filter(entityRealmFilter(realm));
}
@Override
public RoleModel addRealmRole(RealmModel realm, String id, String name) {
if (getRealmRole(realm, name) != null) {
throw new ModelDuplicateException("Role exists: " + id);
}
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
LOG.tracef("addRealmRole(%s, %s, %s)%s", realm.getName(), id, name, getShortStackTrace());
MapRoleEntity entity = new MapRoleEntity(entityId, realm.getId());
entity.setName(name);
entity.setRealmId(realm.getId());
if (tx.get(entity.getId(), roleStore::get) != null) {
throw new ModelDuplicateException("Role exists: " + id);
}
tx.putIfAbsent(entity.getId(), entity);
return entityToAdapterFunc(realm).apply(entity);
}
@Override
public Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max) {
Stream<RoleModel> s = getRealmRolesStream(realm);
if (first != null && first >= 0) {
s = s.skip(first);
}
if (max != null && max >= 0) {
s = s.limit(max);
}
return s;
}
@Override
public Stream<RoleModel> getRealmRolesStream(RealmModel realm) {
return getNotRemovedUpdatedRolesStream(realm)
.filter(this::isRealmRole)
.sorted(COMPARE_BY_NAME)
.map(entityToAdapterFunc(realm));
}
private boolean isRealmRole(MapRoleEntity role) {
return ! role.isClientRole();
}
@Override
public RoleModel addClientRole(ClientModel client, String id, String name) {
if (getClientRole(client, name) != null) {
throw new ModelDuplicateException("Role exists: " + id);
}
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
LOG.tracef("addClientRole(%s, %s, %s)%s", client.getClientId(), id, name, getShortStackTrace());
MapRoleEntity entity = new MapRoleEntity(entityId, client.getRealm().getId());
entity.setName(name);
entity.setClientRole(true);
entity.setClientId(client.getId());
if (tx.get(entity.getId(), roleStore::get) != null) {
throw new ModelDuplicateException("Role exists: " + id);
}
tx.putIfAbsent(entity.getId(), entity);
return entityToAdapterFunc(client.getRealm()).apply(entity);
}
@Override
public Stream<RoleModel> getClientRolesStream(ClientModel client, Integer first, Integer max) {
Stream<RoleModel> s = getClientRolesStream(client);
if (first != null && first > 0) {
s = s.skip(first);
}
if (max != null && max >= 0) {
s = s.limit(max);
}
return s;
}
@Override
public Stream<RoleModel> getClientRolesStream(ClientModel client) {
return getNotRemovedUpdatedRolesStream(client.getRealm())
.filter(entityClientFilter(client))
.sorted(COMPARE_BY_NAME)
.map(entityToAdapterFunc(client.getRealm()));
}
@Override
public boolean removeRole(RoleModel role) {
LOG.tracef("removeRole(%s(%s))%s", role.getName(), role.getId(), getShortStackTrace());
RealmModel realm = role.isClientRole() ? ((ClientModel)role.getContainer()).getRealm() : (RealmModel)role.getContainer();
session.users().preRemove(realm, role);
RoleContainerModel container = role.getContainer();
if (container.getDefaultRolesStream().anyMatch(r -> Objects.equals(r, role.getName()))) {
container.removeDefaultRoles(role.getName());
}
//remove role from realm-roles composites
try (Stream<MapRoleEntity> baseStream = getNotRemovedUpdatedRolesStream(realm)
.filter(this::isRealmRole)
.filter(MapRoleEntity::isComposite)) {
StreamUtils.leftInnerJoinIterable(baseStream, MapRoleEntity::getCompositeRoles)
.filter(pair -> role.getId().equals(pair.getV()))
.collect(Collectors.toSet())
.forEach(pair -> {
MapRoleEntity origEntity = pair.getK();
registerEntityForChanges(origEntity);
origEntity.removeCompositeRole(role.getId());
});
}
//remove role from client-roles composites
session.clients().getClientsStream(realm).forEach(client -> {
client.deleteScopeMapping(role);
try (Stream<MapRoleEntity> baseStream = getNotRemovedUpdatedRolesStream(client.getRealm())
.filter(entityClientFilter(client))
.filter(MapRoleEntity::isComposite)) {
StreamUtils.leftInnerJoinIterable(baseStream, MapRoleEntity::getCompositeRoles)
.filter(pair -> role.getId().equals(pair.getV()))
.collect(Collectors.toSet())
.forEach(pair -> {
MapRoleEntity origEntity = pair.getK();
registerEntityForChanges(origEntity);
origEntity.removeCompositeRole(role.getId());
});
}
});
session.groups().preRemove(realm, role);
// TODO: Sending an event should be extracted to store layer
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
@Override
public RoleModel getRole() {
return role;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
// TODO: ^^^^^^^ Up to here
tx.remove(UUID.fromString(role.getId()));
return true;
}
@Override
public void removeRoles(RealmModel realm) {
getRealmRolesStream(realm).forEach(this::removeRole);
}
@Override
public void removeRoles(ClientModel client) {
getClientRolesStream(client).forEach(this::removeRole);
}
@Override
public RoleModel getRealmRole(RealmModel realm, String name) {
if (name == null) {
return null;
}
LOG.tracef("getRealmRole(%s, %s)%s", realm.getName(), name, getShortStackTrace());
String roleNameLower = name.toLowerCase();
String roleId = getNotRemovedUpdatedRolesStream(realm)
.filter(entity -> entity.getName()!= null && Objects.equals(entity.getName().toLowerCase(), roleNameLower))
.map(entityToAdapterFunc(realm))
.map(RoleModel::getId)
.findFirst()
.orElse(null);
//we need to go via session.roles() not to bypass cache
return roleId == null ? null : session.roles().getRoleById(realm, roleId);
}
@Override
public RoleModel getClientRole(ClientModel client, String name) {
if (name == null) {
return null;
}
LOG.tracef("getClientRole(%s, %s)%s", client.getClientId(), name, getShortStackTrace());
String roleNameLower = name.toLowerCase();
String roleId = getNotRemovedUpdatedRolesStream(client.getRealm())
.filter(entityClientFilter(client))
.filter(entity -> entity.getName()!= null && Objects.equals(entity.getName().toLowerCase(), roleNameLower))
.map(entityToAdapterFunc(client.getRealm()))
.map(RoleModel::getId)
.findFirst()
.orElse(null);
//we need to go via session.roles() not to bypass cache
return roleId == null ? null : session.roles().getRoleById(client.getRealm(), roleId);
}
@Override
public RoleModel getRoleById(RealmModel realm, String id) {
if (id == null) {
return null;
}
LOG.tracef("getRoleById(%s, %s)%s", realm.getName(), id, getShortStackTrace());
MapRoleEntity entity = tx.get(UUID.fromString(id), roleStore::get);
return (entity == null || ! entityRealmFilter(realm).test(entity))
? null
: entityToAdapterFunc(realm).apply(entity);
}
@Override
public Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) {
if (search == null) {
return Stream.empty();
}
String searchLower = search.toLowerCase();
Stream<MapRoleEntity> s = getNotRemovedUpdatedRolesStream(realm)
.filter(entity ->
(entity.getName() != null && entity.getName().toLowerCase().contains(searchLower)) ||
(entity.getDescription() != null && entity.getDescription().toLowerCase().contains(searchLower))
)
.sorted(COMPARE_BY_NAME);
if (first != null && first > 0) {
s = s.skip(first);
}
if (max != null && max >= 0) {
s = s.limit(max);
}
return s.map(entityToAdapterFunc(realm));
}
@Override
public Stream<RoleModel> searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) {
if (search == null) {
return Stream.empty();
}
String searchLower = search.toLowerCase();
Stream<MapRoleEntity> s = getNotRemovedUpdatedRolesStream(client.getRealm())
.filter(entityClientFilter(client))
.filter(entity ->
(entity.getName() != null && entity.getName().toLowerCase().contains(searchLower)) ||
(entity.getDescription() != null && entity.getDescription().toLowerCase().contains(searchLower))
)
.sorted(COMPARE_BY_NAME);
if (first != null && first > 0) {
s = s.skip(first);
}
if (max != null && max >= 0) {
s = s.limit(max);
}
return s.map(entityToAdapterFunc(client.getRealm()));
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2020 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.map.role;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RoleProvider;
import org.keycloak.models.RoleProviderFactory;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
public class MapRoleProviderFactory extends AbstractMapProviderFactory<RoleProvider> implements RoleProviderFactory {
private MapStorage<UUID, MapRoleEntity> store;
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("roles", UUID.class, MapRoleEntity.class);
}
@Override
public RoleProvider create(KeycloakSession session) {
return new MapRoleProvider(session, store);
}
}

View file

@ -0,0 +1,18 @@
#
# Copyright 2020 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.
#
org.keycloak.models.map.role.MapRoleProviderFactory

View file

@ -0,0 +1,83 @@
/*
* Copyright 2020 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.map.common;
import org.keycloak.models.map.common.StreamUtils.Pair;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author hmlnarik
*/
public class StreamUtilsTest {
@Test
public void testLeftInnerJoinStream() {
Stream<Integer> a = Stream.of(0,1,2,3,4);
Stream[] b = new Stream[] {
Stream.of(1,2),
Stream.of(1,2),
null,
null,
Stream.of(5, 6, 7),
};
try (Stream<Pair<Integer, Integer>> res = StreamUtils.leftInnerJoinStream(a, n -> b[n])) {
final List<Pair<Integer, Integer>> l = res.collect(Collectors.toList());
assertEquals(l, Arrays.asList(
new Pair<>(0, 1),
new Pair<>(0, 2),
new Pair<>(1, 1),
new Pair<>(1, 2),
new Pair<>(4, 5),
new Pair<>(4, 6),
new Pair<>(4, 7)
));
}
}
@Test
public void testLeftInnerJoinIterable() {
Stream<Integer> a = Stream.of(0,1,2,3,4);
Iterable[] b = new Iterable[] {
Arrays.asList(1,2),
Arrays.asList(1,2),
null,
null,
Arrays.asList(5, 6, 7),
};
try (Stream<Pair<Integer, Integer>> res = StreamUtils.leftInnerJoinIterable(a, n -> b[n])) {
final List<Pair<Integer, Integer>> l = res.collect(Collectors.toList());
assertEquals(l, Arrays.asList(
new Pair<>(0, 1),
new Pair<>(0, 2),
new Pair<>(1, 1),
new Pair<>(1, 2),
new Pair<>(4, 5),
new Pair<>(4, 6),
new Pair<>(4, 7)
));
}
}
}

View file

@ -70,15 +70,36 @@ public interface RoleContainerModel {
Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max);
/**
* Returns all the default role names of this object.
* @return List of the default role names of this object. Never returns {@code null}.
* @deprecated use the stream variant instead
*/
@Deprecated
default List<String> getDefaultRoles() {
return getDefaultRolesStream().collect(Collectors.toList());
}
/**
* Returns all default role names of this object as a stream.
* @return stream of default role names of this object. Never returns {@code null}.
*/
Stream<String> getDefaultRolesStream();
/**
* Adds a role with given name to default roles of this object. If the role
* doesn't exist a new role is created.
* @param name of the role to be (created and ) added
*/
void addDefaultRole(String name);
/**
* Updates default roles of this object. It removes all default roles which
* are not specified by {@code defaultRoles} and adds all which weren't
* present in original default roles. In other words it's the same as calling
* {@code Set.retainAll} and {@code Set.addAll}.
* @param defaultRoles Array of realm roles to be updated
*/
default void updateDefaultRoles(String... defaultRoles) {
List<String> defaultRolesArray = Arrays.asList(defaultRoles);
Collection<String> entities = getDefaultRolesStream().collect(Collectors.toList());
@ -100,6 +121,10 @@ public interface RoleContainerModel {
}
}
/**
* Removes default roles from this object according to {@code defaultRoles}.
* @param defaultRoles Role names to be removed from default roles of this object.
*/
void removeDefaultRoles(String... defaultRoles);
}

View file

@ -17,7 +17,6 @@
package org.keycloak.models;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -62,11 +61,13 @@ public interface RoleModel {
void setSingleAttribute(String name, String value);
void setAttribute(String name, Collection<String> values);
void setAttribute(String name, List<String> values);
void removeAttribute(String name);
String getFirstAttribute(String name);
default String getFirstAttribute(String name) {
return getAttributeStream(name).findFirst().orElse(null);
}
@Deprecated
default List<String> getAttribute(String name) {

View file

@ -102,7 +102,9 @@ public interface RoleProvider extends Provider, RoleLookupProvider {
* @param name String name of the role.
* @return Model of the created role.
*/
RoleModel addClientRole(ClientModel client, String name);
default RoleModel addClientRole(ClientModel client, String name) {
return addClientRole(client, null, name);
}
/**
* Adds a client role with given internal ID and {@code name} to the given client.

View file

@ -24,5 +24,12 @@ import org.keycloak.models.RoleModel;
* @version $Revision: 1 $
*/
public interface UserBulkUpdateProvider {
/**
* Grants the given role to all users from particular realm. The role has to
* belong to the realm.
* @param realm Realm
* @param role Role to be granted
*/
void grantToAllUsers(RealmModel realm, RoleModel role);
}

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.testsuite.federation;
import java.util.Collection;
import java.util.Map;
import java.util.List;
import java.util.stream.Stream;
@ -180,7 +179,7 @@ public class HardcodedRoleStorageProvider implements RoleStorageProvider {
}
@Override
public void setAttribute(String name, Collection<String> values) {
public void setAttribute(String name, List<String> values) {
throw new ReadOnlyException("role is read only");
}

View file

@ -65,7 +65,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.ImpersonationDisabledTest.IMPERSONATION_DISABLED;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
@ -839,7 +842,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
newRealm.setId("anotherRealm");
newRealm.setEnabled(true);
realmClient.realms().create(newRealm);
ClientRepresentation newClient = new ClientRepresentation();
newClient.setName("newClient");
@ -851,12 +854,17 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
newClient.setEnabled(true);
Response response = realmClient.realm("anotherRealm").clients().create(newClient);
Assert.assertEquals(403, response.getStatus());
response.close();
realmClient.close();
//creating new client to refresh token
realmClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(),
"master", "admin", "admin", "fullScopedClient", "618268aa-51e6-4e64-93c4-3c0bc65b8171");
assertThat(realmClient.realms().findAll().stream().map(RealmRepresentation::getRealm).collect(Collectors.toSet()),
hasItem("anotherRealm"));
response = realmClient.realm("anotherRealm").clients().create(newClient);
Assert.assertEquals(201, response.getStatus());
response.close();
} finally {
adminClient.realm("anotherRealm").remove();
realmClient.close();
@ -896,6 +904,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
newClient.setEnabled(true);
Response response = adminClient.realm("anotherRealm").clients().create(newClient);
Assert.assertEquals(201, response.getStatus());
response.close();
} finally {
adminClient.realm("anotherRealm").remove();

View file

@ -39,11 +39,13 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.ws.rs.ClientErrorException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.keycloak.testsuite.util.RoleBuilder;
/**
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
@ -87,7 +89,14 @@ public class ClientRolesTest extends AbstractClientTest {
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(clientDbId, "role1"), role1, ResourceType.CLIENT_ROLE);
assertTrue(hasRole(rolesRsc, "role1"));
}
@Test(expected = ClientErrorException.class)
public void createRoleWithSameName() {
RoleRepresentation role = RoleBuilder.create().name("role-a").build();
rolesRsc.create(role);
rolesRsc.create(role);
}
@Test
public void testRemoveRole() {
RoleRepresentation role2 = makeRole("role2");

View file

@ -19,7 +19,6 @@ package org.keycloak.testsuite.admin.realm;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.RolesResource;
import org.keycloak.admin.client.resource.UserResource;
@ -47,6 +46,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.ClientErrorException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
@ -55,11 +55,8 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -153,6 +150,11 @@ public class RealmRolesTest extends AbstractAdminTest {
assertFalse(role.isComposite());
}
@Test(expected = ClientErrorException.class)
public void createRoleWithSameName() {
resource.create(RoleBuilder.create().name("role-a").build());
}
@Test
public void updateRole() {
RoleRepresentation role = resource.get("role-a").toRepresentation();