KEYCLOAK-14977 create MapRoleProvider
This commit is contained in:
parent
b494b8bb44
commit
785f2e78bc
31 changed files with 1277 additions and 189 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -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]}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -368,11 +368,13 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
|||
|
||||
@Override
|
||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||
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
|
||||
public void preRemove(RealmModel realm) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -88,6 +90,13 @@ public class ClientRolesTest extends AbstractClientTest {
|
|||
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");
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue