KEYCLOAK-17460 invalidate client when assigning scope
This commit is contained in:
parent
e10f3b3672
commit
c3b9c66941
39 changed files with 381 additions and 186 deletions
|
@ -27,8 +27,8 @@ import org.keycloak.models.cache.infinispan.entities.CachedClient;
|
|||
import org.keycloak.models.utils.RoleUtils;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
@ -99,41 +99,22 @@ public class ClientAdapter implements ClientModel, CachedObject {
|
|||
|
||||
@Override
|
||||
public void addClientScope(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
getDelegateForUpdate();
|
||||
updated.addClientScope(clientScope, defaultScope);
|
||||
cacheSession.addClientScopes(getRealm(), this, Collections.singleton(clientScope), defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScopes(Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
for (ClientScopeModel clientScope : clientScopes) {
|
||||
addClientScope(clientScope, defaultScope);
|
||||
}
|
||||
cacheSession.addClientScopes(getRealm(), this, clientScopes, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(ClientScopeModel clientScope) {
|
||||
getDelegateForUpdate();
|
||||
updated.removeClientScope(clientScope);
|
||||
cacheSession.removeClientScope(getRealm(), this, clientScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
if (isUpdated()) return updated.getClientScopes(defaultScope, filterByProtocol);
|
||||
List<String> clientScopeIds = defaultScope ? cached.getDefaultClientScopesIds() : cached.getOptionalClientScopesIds();
|
||||
|
||||
// Defaults to openid-connect
|
||||
String clientProtocol = getProtocol() == null ? "openid-connect" : getProtocol();
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes = new HashMap<>();
|
||||
for (String scopeId : clientScopeIds) {
|
||||
ClientScopeModel clientScope = cacheSession.getClientScopeById(cachedRealm, scopeId);
|
||||
if (clientScope != null) {
|
||||
if (!filterByProtocol || clientScope.getProtocol().equals(clientProtocol)) {
|
||||
clientScopes.put(clientScope.getName(), clientScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
return clientScopes;
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope) {
|
||||
return cacheSession.getClientScopes(getRealm(), this, defaultScope);
|
||||
}
|
||||
|
||||
public void addWebOrigin(String webOrigin) {
|
||||
|
|
|
@ -232,4 +232,8 @@ public class ClientScopeAdapter implements ClientScopeModel {
|
|||
return getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s@%08x", getId(), hashCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,8 @@ public class RealmCacheManager extends CacheManager {
|
|||
|
||||
public void clientUpdated(String realmId, String clientUuid, String clientId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getClientByClientIdCacheKey(clientId, realmId));
|
||||
invalidations.add(RealmCacheSession.getClientScopesCacheKey(clientUuid, true));
|
||||
invalidations.add(RealmCacheSession.getClientScopesCacheKey(clientUuid, false));
|
||||
}
|
||||
|
||||
// Client roles invalidated separately
|
||||
|
|
|
@ -97,6 +97,8 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
protected static final Logger logger = Logger.getLogger(RealmCacheSession.class);
|
||||
public static final String REALM_CLIENTS_QUERY_SUFFIX = ".realm.clients";
|
||||
public static final String ROLES_QUERY_SUFFIX = ".roles";
|
||||
private static final String SCOPE_KEY_DEFAULT = "default";
|
||||
private static final String SCOPE_KEY_OPTIONAL = "optional";
|
||||
protected RealmCacheManager cache;
|
||||
protected KeycloakSession session;
|
||||
protected RealmProvider realmDelegate;
|
||||
|
@ -542,6 +544,10 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return realm + ".clientscopes";
|
||||
}
|
||||
|
||||
static String getClientScopesCacheKey(String client, boolean defaultScope) {
|
||||
return client + "." + (defaultScope ? SCOPE_KEY_DEFAULT : SCOPE_KEY_OPTIONAL) + ".clientscopes";
|
||||
}
|
||||
|
||||
static String getTopGroupsQueryCacheKey(String realm) {
|
||||
return realm + ".top.groups";
|
||||
}
|
||||
|
@ -1305,6 +1311,51 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
realm.getClientScopesStream().map(ClientScopeModel::getId).forEach(id -> removeClientScope(realm, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
getClientDelegate().addClientScopes(realm, client, clientScopes, defaultScope);
|
||||
registerClientInvalidation(client.getId(), client.getId(), realm.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) {
|
||||
getClientDelegate().removeClientScope(realm, client, clientScope);
|
||||
registerClientInvalidation(client.getId(), client.getId(), realm.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
|
||||
String cacheKey = getClientScopesCacheKey(client.getId(), defaultScopes);
|
||||
boolean queryDB = invalidations.contains(cacheKey) || invalidations.contains(client.getId()) || listInvalidations.contains(realm.getId());
|
||||
if (queryDB) {
|
||||
return getClientDelegate().getClientScopes(realm, client, defaultScopes);
|
||||
}
|
||||
ClientScopeListQuery query = cache.get(cacheKey, ClientScopeListQuery.class);
|
||||
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
Map<String, ClientScopeModel> model = getClientDelegate().getClientScopes(realm, client, defaultScopes);
|
||||
if (model == null) return null;
|
||||
Set<String> ids = model.values().stream().map(ClientScopeModel::getId).collect(Collectors.toSet());
|
||||
query = new ClientScopeListQuery(loaded, cacheKey, realm, client.getId(), ids);
|
||||
logger.tracev("adding assigned client scopes cache miss: client {0} key {1}", client.getClientId(), cacheKey);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
Map<String, ClientScopeModel> assignedScopes = new HashMap<>();
|
||||
for (String id : query.getClientScopes()) {
|
||||
ClientScopeModel clientScope = session.clientScopes().getClientScopeById(realm, id);
|
||||
if (clientScope == null) {
|
||||
invalidations.add(cacheKey);
|
||||
return getClientDelegate().getClientScopes(realm, client, defaultScopes);
|
||||
}
|
||||
if (clientScope.getProtocol().equals((client.getProtocol() == null) ? "openid-connect" : client.getProtocol())) {
|
||||
assignedScopes.put(clientScope.getName(), clientScope);
|
||||
}
|
||||
}
|
||||
return assignedScopes;
|
||||
}
|
||||
|
||||
// Don't cache ClientInitialAccessModel for now
|
||||
@Override
|
||||
public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
|
||||
|
|
|
@ -109,11 +109,11 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
|||
registeredNodes = new TreeMap<>(model.getRegisteredNodes());
|
||||
|
||||
defaultClientScopesIds = new LinkedList<>();
|
||||
for (ClientScopeModel clientScope : model.getClientScopes(true, false).values()) {
|
||||
for (ClientScopeModel clientScope : model.getClientScopes(true).values()) {
|
||||
defaultClientScopesIds.add(clientScope.getId());
|
||||
}
|
||||
optionalClientScopesIds = new LinkedList<>();
|
||||
for (ClientScopeModel clientScope : model.getClientScopes(false, false).values()) {
|
||||
for (ClientScopeModel clientScope : model.getClientScopes(false).values()) {
|
||||
optionalClientScopesIds.add(clientScope.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ public class ClientScopeListQuery extends AbstractRevisioned implements ClientSc
|
|||
private final Set<String> clientScopes;
|
||||
private final String realm;
|
||||
private final String realmName;
|
||||
private String clientUuid;
|
||||
|
||||
public ClientScopeListQuery(Long revisioned, String id, RealmModel realm, Set<String> clientScopes) {
|
||||
super(revisioned, id);
|
||||
|
@ -33,6 +34,11 @@ public class ClientScopeListQuery extends AbstractRevisioned implements ClientSc
|
|||
this.clientScopes = clientScopes;
|
||||
}
|
||||
|
||||
public ClientScopeListQuery(Long revisioned, String id, RealmModel realm, String clientUuid, Set<String> clientScopes) {
|
||||
this(revisioned, id, realm, clientScopes);
|
||||
this.clientUuid = clientUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getClientScopes() {
|
||||
return clientScopes;
|
||||
|
@ -43,11 +49,17 @@ public class ClientScopeListQuery extends AbstractRevisioned implements ClientSc
|
|||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientScopeListQuery{" +
|
||||
"id='" + getId() + "'" +
|
||||
", realmName='" + realmName + '\'' +
|
||||
", clientUuid='" + clientUuid + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ package org.keycloak.models.cache.infinispan.entities;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
public interface ClientScopeQuery extends InRealm {
|
||||
public interface ClientScopeQuery extends InClient {
|
||||
Set<String> getClientScopes();
|
||||
}
|
||||
|
|
|
@ -26,20 +26,17 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
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.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
@ -265,7 +262,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
entity.setProtocol(protocol);
|
||||
|
||||
session.getKeycloakSessionFactory().publish((ClientModel.ClientProtocolUpdatedEvent) () -> ClientAdapter.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -349,57 +346,22 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
|
||||
@Override
|
||||
public void addClientScope(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
if (getClientScopes(defaultScope, false).containsKey(clientScope.getName())) return;
|
||||
|
||||
persist(clientScope, defaultScope);
|
||||
addClientScopes(Collections.singleton(clientScope), defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScopes(Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
Map<String, ClientScopeModel> existingClientScopes = getClientScopes(defaultScope, false);
|
||||
clientScopes.stream()
|
||||
.filter(clientScope -> !existingClientScopes.containsKey(clientScope.getName()))
|
||||
.forEach(clientScope -> persist(clientScope, defaultScope));
|
||||
}
|
||||
|
||||
private void persist(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
ClientScopeClientMappingEntity entity = new ClientScopeClientMappingEntity();
|
||||
entity.setClientScopeId(clientScope.getId());
|
||||
entity.setClient(getEntity());
|
||||
entity.setDefaultScope(defaultScope);
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
em.detach(entity);
|
||||
session.clients().addClientScopes(getRealm(), this, clientScopes, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(ClientScopeModel clientScope) {
|
||||
int numRemoved = em.createNamedQuery("deleteClientScopeClientMapping")
|
||||
.setParameter("clientScopeId", clientScope.getId())
|
||||
.setParameter("client", getEntity())
|
||||
.executeUpdate();
|
||||
em.flush();
|
||||
session.clients().removeClientScope(getRealm(), this, clientScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
TypedQuery<String> query = em.createNamedQuery("clientScopeClientMappingIdsByClient", String.class);
|
||||
query.setParameter("client", getEntity());
|
||||
query.setParameter("defaultScope", defaultScope);
|
||||
List<String> ids = query.getResultList();
|
||||
|
||||
// Defaults to openid-connect
|
||||
String clientProtocol = getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : getProtocol();
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes = new HashMap<>();
|
||||
for (String clientScopeId : ids) {
|
||||
ClientScopeModel clientScope = realm.getClientScopeById(clientScopeId);
|
||||
if (clientScope == null) continue;
|
||||
if (!filterByProtocol || clientScope.getProtocol().equals(clientProtocol)) {
|
||||
clientScopes.put(clientScope.getName(), clientScope);
|
||||
}
|
||||
}
|
||||
return clientScopes;
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope) {
|
||||
return session.clients().getClientScopes(getRealm(), this, defaultScope);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -300,7 +300,9 @@ public class ClientScopeAdapter implements ClientScopeModel, JpaModel<ClientScop
|
|||
return getId().hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s@%08x", getId(), hashCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
|
@ -54,11 +56,13 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.RoleProvider;
|
||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientScopeClientMappingEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientScopeEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupEntity;
|
||||
import org.keycloak.models.jpa.entities.RealmEntity;
|
||||
import org.keycloak.models.jpa.entities.RealmLocalizationTextsEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
|
@ -715,7 +719,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
});
|
||||
|
||||
int countRemoved = em.createNamedQuery("deleteClientScopeClientMappingByClient")
|
||||
.setParameter("client", clientEntity)
|
||||
.setParameter("clientId", clientEntity.getId())
|
||||
.executeUpdate();
|
||||
em.remove(clientEntity); // i have no idea why, but this needs to come before deleteScopeMapping
|
||||
|
||||
|
@ -790,6 +794,52 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
|||
realm.getClientScopesStream().map(ClientScopeModel::getId).forEach(id -> this.removeClientScope(realm, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
// Defaults to openid-connect
|
||||
String clientProtocol = client.getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : client.getProtocol();
|
||||
|
||||
Map<String, ClientScopeModel> existingClientScopes = getClientScopes(realm, client, defaultScope);
|
||||
|
||||
clientScopes.stream()
|
||||
.filter(clientScope -> ! existingClientScopes.containsKey(clientScope.getName()))
|
||||
.filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol))
|
||||
.forEach(clientScope -> {
|
||||
ClientScopeClientMappingEntity entity = new ClientScopeClientMappingEntity();
|
||||
entity.setClientScopeId(clientScope.getId());
|
||||
entity.setClientId(client.getId());
|
||||
entity.setDefaultScope(defaultScope);
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
em.detach(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) {
|
||||
em.createNamedQuery("deleteClientScopeClientMapping")
|
||||
.setParameter("clientScopeId", clientScope.getId())
|
||||
.setParameter("clientId", client.getId())
|
||||
.executeUpdate();
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScope) {
|
||||
// Defaults to openid-connect
|
||||
String clientProtocol = client.getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : client.getProtocol();
|
||||
|
||||
TypedQuery<String> query = em.createNamedQuery("clientScopeClientMappingIdsByClient", String.class);
|
||||
query.setParameter("clientId", client.getId());
|
||||
query.setParameter("defaultScope", defaultScope);
|
||||
|
||||
return query.getResultStream()
|
||||
.map(clientScopeId -> session.clientScopes().getClientScopeById(realm, clientScopeId))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol))
|
||||
.collect(Collectors.toMap(ClientScopeModel::getName, Function.identity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> searchForGroupByNameStream(RealmModel realm, String search, Integer first, Integer max) {
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByNameContaining", String.class)
|
||||
|
|
|
@ -21,11 +21,8 @@ import java.io.Serializable;
|
|||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
|
@ -36,9 +33,9 @@ import javax.persistence.Table;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="clientScopeClientMappingIdsByClient", query="select m.clientScopeId from ClientScopeClientMappingEntity m where m.client = :client and m.defaultScope = :defaultScope"),
|
||||
@NamedQuery(name="deleteClientScopeClientMapping", query="delete from ClientScopeClientMappingEntity where client = :client and clientScopeId = :clientScopeId"),
|
||||
@NamedQuery(name="deleteClientScopeClientMappingByClient", query="delete from ClientScopeClientMappingEntity where client = :client")
|
||||
@NamedQuery(name="clientScopeClientMappingIdsByClient", query="select m.clientScopeId from ClientScopeClientMappingEntity m where m.clientId = :clientId and m.defaultScope = :defaultScope"),
|
||||
@NamedQuery(name="deleteClientScopeClientMapping", query="delete from ClientScopeClientMappingEntity where clientId = :clientId and clientScopeId = :clientScopeId"),
|
||||
@NamedQuery(name="deleteClientScopeClientMappingByClient", query="delete from ClientScopeClientMappingEntity where clientId = :clientId")
|
||||
})
|
||||
@Entity
|
||||
@Table(name="CLIENT_SCOPE_CLIENT")
|
||||
|
@ -50,9 +47,8 @@ public class ClientScopeClientMappingEntity {
|
|||
protected String clientScopeId;
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name="CLIENT_ID")
|
||||
protected ClientEntity client;
|
||||
@Column(name="CLIENT_ID")
|
||||
protected String clientId;
|
||||
|
||||
@Column(name="DEFAULT_SCOPE")
|
||||
protected boolean defaultScope;
|
||||
|
@ -65,12 +61,12 @@ public class ClientScopeClientMappingEntity {
|
|||
this.clientScopeId = clientScopeId;
|
||||
}
|
||||
|
||||
public ClientEntity getClient() {
|
||||
return client;
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClient(ClientEntity client) {
|
||||
this.client = client;
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public boolean isDefaultScope() {
|
||||
|
@ -85,22 +81,22 @@ public class ClientScopeClientMappingEntity {
|
|||
|
||||
protected String clientScopeId;
|
||||
|
||||
protected ClientEntity client;
|
||||
protected String clientId;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(String clientScopeId, ClientEntity client) {
|
||||
public Key(String clientScopeId, String clientId) {
|
||||
this.clientScopeId = clientScopeId;
|
||||
this.client = client;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientScopeId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
public ClientEntity getClient() {
|
||||
return client;
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,7 +107,7 @@ public class ClientScopeClientMappingEntity {
|
|||
ClientScopeClientMappingEntity.Key key = (ClientScopeClientMappingEntity.Key) o;
|
||||
|
||||
if (clientScopeId != null ? !clientScopeId.equals(key.getClientScopeId() != null ? key.getClientScopeId() : null) : key.getClientScopeId() != null) return false;
|
||||
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
|
||||
if (clientId != null ? !clientId.equals(key.getClientId() != null ? key.getClientId() : null) : key.getClientId() != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -119,7 +115,7 @@ public class ClientScopeClientMappingEntity {
|
|||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientScopeId != null ? clientScopeId.hashCode() : 0;
|
||||
result = 31 * result + (client != null ? client.getId().hashCode() : 0);
|
||||
result = 31 * result + (clientId != null ? clientId.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +129,7 @@ public class ClientScopeClientMappingEntity {
|
|||
ClientScopeClientMappingEntity key = (ClientScopeClientMappingEntity) o;
|
||||
|
||||
if (clientScopeId != null ? !clientScopeId.equals(key.getClientScopeId() != null ? key.getClientScopeId() : null) : key.getClientScopeId() != null) return false;
|
||||
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
|
||||
if (clientId != null ? !clientId.equals(key.getClientId() != null ? key.getClientId() : null) : key.getClientId() != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -141,7 +137,7 @@ public class ClientScopeClientMappingEntity {
|
|||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientScopeId != null ? clientScopeId.hashCode() : 0;
|
||||
result = 31 * result + (client != null ? client.getId().hashCode() : 0);
|
||||
result = 31 * result + (clientId != null ? clientId.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,5 +41,11 @@
|
|||
<changeSet author="keycloak" id="map-remove-ri-13.0.0">
|
||||
<dropForeignKeyConstraint baseTableName="DEFAULT_CLIENT_SCOPE" constraintName="FK_R_DEF_CLI_SCOPE_SCOPE"/>
|
||||
<dropForeignKeyConstraint baseTableName="CLIENT_SCOPE_CLIENT" constraintName="FK_C_CLI_SCOPE_SCOPE"/>
|
||||
<dropForeignKeyConstraint baseTableName="CLIENT_SCOPE_CLIENT" constraintName="FK_C_CLI_SCOPE_CLIENT"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="13.0.0-increase-column-size-federated">
|
||||
<modifyDataType newDataType="VARCHAR(255)" tableName="CLIENT_SCOPE_CLIENT" columnName="CLIENT_ID"/>
|
||||
<modifyDataType newDataType="VARCHAR(255)" tableName="CLIENT_SCOPE_CLIENT" columnName="SCOPE_ID"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.keycloak.models.map.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -49,9 +51,22 @@ public abstract class AbstractClientModel<E extends AbstractEntity> implements C
|
|||
|
||||
@Override
|
||||
public void addClientScopes(Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
for (ClientScopeModel cs : clientScopes) {
|
||||
addClientScope(cs, defaultScope);
|
||||
}
|
||||
session.clients().addClientScopes(getRealm(), this, clientScopes, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScope(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
addClientScopes(Collections.singleton(clientScope), defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(ClientScopeModel clientScope) {
|
||||
session.clients().removeClientScope(getRealm(), this, clientScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope) {
|
||||
return session.clients().getClientScopes(getRealm(), this, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,20 +17,16 @@
|
|||
package org.keycloak.models.map.client;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import com.google.common.base.Functions;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
|
@ -241,6 +237,7 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
|
|||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
entity.setProtocol(protocol);
|
||||
session.getKeycloakSessionFactory().publish((ClientModel.ClientProtocolUpdatedEvent) () -> MapClientAdapter.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -385,38 +382,6 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
|
|||
entity.setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
/*************** Client scopes ****************/
|
||||
|
||||
@Override
|
||||
public void addClientScope(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
final String id = clientScope == null ? null : clientScope.getId();
|
||||
if (id != null) {
|
||||
entity.addClientScope(id, defaultScope);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(ClientScopeModel clientScope) {
|
||||
final String id = clientScope == null ? null : clientScope.getId();
|
||||
if (id != null) {
|
||||
entity.removeClientScope(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
Stream<ClientScopeModel> res = this.entity.getClientScopes(defaultScope)
|
||||
.map(realm::getClientScopeById)
|
||||
.filter(Objects::nonNull);
|
||||
|
||||
if (filterByProtocol) {
|
||||
String clientProtocol = getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : getProtocol();
|
||||
res = res.filter(cs -> Objects.equals(cs.getProtocol(), clientProtocol));
|
||||
}
|
||||
|
||||
return res.collect(Collectors.toMap(ClientScopeModel::getName, Functions.identity()));
|
||||
}
|
||||
|
||||
/*************** Scopes mappings ****************/
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,8 +29,10 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.common.Serialization;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
@ -43,6 +45,8 @@ import org.keycloak.models.map.storage.MapStorage;
|
|||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import static org.keycloak.utils.StreamsUtil.paginatedStream;
|
||||
|
||||
public class MapClientProvider implements ClientProvider {
|
||||
|
@ -269,6 +273,54 @@ public class MapClientProvider implements ClientProvider {
|
|||
return paginatedStream(s, firstResult, maxResults).map(entityToAdapterFunc(realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
MapClientEntity entity = tx.read(UUID.fromString(client.getId()));
|
||||
|
||||
if (entity == null) return;
|
||||
|
||||
// Defaults to openid-connect
|
||||
String clientProtocol = client.getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : client.getProtocol();
|
||||
|
||||
LOG.tracef("addClientScopes(%s, %s, %s, %b)%s", realm, client, clientScopes, defaultScope, getShortStackTrace());
|
||||
|
||||
Map<String, ClientScopeModel> existingClientScopes = getClientScopes(realm, client, defaultScope);
|
||||
|
||||
clientScopes.stream()
|
||||
.filter(clientScope -> ! existingClientScopes.containsKey(clientScope.getName()))
|
||||
.filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol))
|
||||
.forEach(clientScope -> entity.addClientScope(clientScope.getId(), defaultScope));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) {
|
||||
MapClientEntity entity = tx.read(UUID.fromString(client.getId()));
|
||||
|
||||
if (entity == null) return;
|
||||
|
||||
LOG.tracef("removeClientScope(%s, %s, %s)%s", realm, client, clientScope, getShortStackTrace());
|
||||
|
||||
entity.removeClientScope(clientScope.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
|
||||
MapClientEntity entity = tx.read(UUID.fromString(client.getId()));
|
||||
|
||||
if (entity == null) return null;
|
||||
|
||||
// Defaults to openid-connect
|
||||
String clientProtocol = client.getProtocol() == null ? OIDCLoginProtocol.LOGIN_PROTOCOL : client.getProtocol();
|
||||
|
||||
LOG.tracef("getClientScopes(%s, %s, %b)%s", realm, client, defaultScopes, getShortStackTrace());
|
||||
|
||||
return entity.getClientScopes(defaultScopes)
|
||||
.map(clientScopeId -> session.clientScopes().getClientScopeById(realm, clientScopeId))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(clientScope -> Objects.equals(clientScope.getProtocol(), clientProtocol))
|
||||
.collect(Collectors.toMap(ClientScopeModel::getName, Function.identity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ public class MigrateTo4_0_0 implements Migration {
|
|||
realm.getClientsStream()
|
||||
.filter(MigrationUtils::isOIDCNonBearerOnlyClient)
|
||||
.filter(c -> c.hasScope(offlineAccessRole))
|
||||
.filter(c -> !c.getClientScopes(false, true).containsKey(OAuth2Constants.OFFLINE_ACCESS))
|
||||
.filter(c -> !c.getClientScopes(false).containsKey(OAuth2Constants.OFFLINE_ACCESS))
|
||||
.peek(c -> {
|
||||
LOG.debugf("Adding client scope 'offline_access' as optional scope to client '%s' in realm '%s'.", c.getClientId(), realm.getName());
|
||||
c.addClientScope(offlineAccessScope, false);
|
||||
|
@ -119,7 +119,7 @@ public class MigrateTo4_0_0 implements Migration {
|
|||
// Clients with consentRequired, which don't have any client scopes will be added itself to require consent, so that consent screen is shown when users authenticate
|
||||
realm.getClientsStream()
|
||||
.filter(ClientModel::isConsentRequired)
|
||||
.filter(c -> c.getClientScopes(true, true).isEmpty())
|
||||
.filter(c -> c.getClientScopes(true).isEmpty())
|
||||
.forEach(c -> {
|
||||
LOG.debugf("Adding client '%s' of realm '%s' to display itself on consent screen", c.getClientId(), realm.getName());
|
||||
c.setDisplayOnConsentScreen(true);
|
||||
|
|
|
@ -704,8 +704,8 @@ public final class KeycloakModelUtils {
|
|||
|
||||
public static boolean isClientScopeUsed(RealmModel realm, ClientScopeModel clientScope) {
|
||||
return realm.getClientsStream()
|
||||
.filter(c -> (c.getClientScopes(true, false).containsKey(clientScope.getName())) ||
|
||||
(c.getClientScopes(false, false).containsKey(clientScope.getName())))
|
||||
.filter(c -> (c.getClientScopes(true).containsKey(clientScope.getName())) ||
|
||||
(c.getClientScopes(false).containsKey(clientScope.getName())))
|
||||
.findFirst().isPresent();
|
||||
}
|
||||
|
||||
|
|
|
@ -592,8 +592,8 @@ public class ModelToRepresentation {
|
|||
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
||||
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
|
||||
|
||||
rep.setDefaultClientScopes(new LinkedList<>(clientModel.getClientScopes(true, false).keySet()));
|
||||
rep.setOptionalClientScopes(new LinkedList<>(clientModel.getClientScopes(false, false).keySet()));
|
||||
rep.setDefaultClientScopes(new LinkedList<>(clientModel.getClientScopes(true).keySet()));
|
||||
rep.setOptionalClientScopes(new LinkedList<>(clientModel.getClientScopes(false).keySet()));
|
||||
|
||||
Set<String> redirectUris = clientModel.getRedirectUris();
|
||||
if (redirectUris != null) {
|
||||
|
|
|
@ -1462,12 +1462,12 @@ public class RepresentationToModel {
|
|||
|
||||
if (resourceRep.getDefaultClientScopes() != null || resourceRep.getOptionalClientScopes() != null) {
|
||||
// First remove all default/built in client scopes
|
||||
for (ClientScopeModel clientScope : client.getClientScopes(true, false).values()) {
|
||||
for (ClientScopeModel clientScope : client.getClientScopes(true).values()) {
|
||||
client.removeClientScope(clientScope);
|
||||
}
|
||||
|
||||
// First remove all default/built in client scopes
|
||||
for (ClientScopeModel clientScope : client.getClientScopes(false, false).values()) {
|
||||
for (ClientScopeModel clientScope : client.getClientScopes(false).values()) {
|
||||
client.removeClientScope(clientScope);
|
||||
}
|
||||
}
|
||||
|
@ -2039,7 +2039,7 @@ public class RepresentationToModel {
|
|||
// Backwards compatibility. If user had consent for "offline_access" role, we treat it as he has consent for "offline_access" client scope
|
||||
if (consentRep.getGrantedRealmRoles() != null) {
|
||||
if (consentRep.getGrantedRealmRoles().contains(OAuth2Constants.OFFLINE_ACCESS)) {
|
||||
ClientScopeModel offlineScope = client.getClientScopes(false, true).get(OAuth2Constants.OFFLINE_ACCESS);
|
||||
ClientScopeModel offlineScope = client.getClientScopes(false).get(OAuth2Constants.OFFLINE_ACCESS);
|
||||
if (offlineScope == null) {
|
||||
logger.warn("Unable to find offline_access scope referenced in grantedRoles of user");
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@ public abstract class AbstractLoginProtocolFactory implements LoginProtocolFacto
|
|||
factory.register(new ProviderEventListener() {
|
||||
@Override
|
||||
public void onEvent(ProviderEvent event) {
|
||||
if (event instanceof ClientModel.ClientCreationEvent) {
|
||||
ClientModel client = ((ClientModel.ClientCreationEvent)event).getCreatedClient();
|
||||
if (event instanceof ClientModel.ClientProtocolUpdatedEvent) {
|
||||
ClientModel client = ((ClientModel.ClientProtocolUpdatedEvent)event).getClient();
|
||||
addDefaultClientScopes(client.getRealm(), client);
|
||||
addDefaults(client);
|
||||
}
|
||||
|
|
|
@ -58,6 +58,10 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
|||
KeycloakSession getKeycloakSession();
|
||||
}
|
||||
|
||||
interface ClientProtocolUpdatedEvent extends ProviderEvent {
|
||||
ClientModel getClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies other providers that this client has been updated.
|
||||
* <p>
|
||||
|
@ -221,10 +225,9 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
|||
* Return all default scopes (if 'defaultScope' is true) or all optional scopes (if 'defaultScope' is false) linked with this client
|
||||
*
|
||||
* @param defaultScope
|
||||
* @param filterByProtocol if true, then just client scopes of same protocol like current client will be returned
|
||||
* @return map where key is the name of the clientScope, value is particular clientScope. Returns empty map if no scopes linked (never returns null).
|
||||
*/
|
||||
Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol);
|
||||
Map<String, ClientScopeModel> getClientScopes(boolean defaultScope);
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link ClientScopeModel} associated with this client.
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.provider.Provider;
|
|||
import org.keycloak.storage.client.ClientLookupProvider;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -146,4 +147,24 @@ public interface ClientProvider extends ClientLookupProvider, Provider {
|
|||
* @param realm Realm.
|
||||
*/
|
||||
void removeClients(RealmModel realm);
|
||||
|
||||
/**
|
||||
* Assign clientScopes to the client. Add as default scopes (if parameter 'defaultScope' is true)
|
||||
* or optional scopes (if parameter 'defaultScope' is false)
|
||||
*
|
||||
* @param realm Realm.
|
||||
* @param client Client.
|
||||
* @param clientScopes to be assigned
|
||||
* @param defaultScope if true the scopes are assigned as default, or optional in case of false
|
||||
*/
|
||||
void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope);
|
||||
|
||||
/**
|
||||
* Unassign clientScope from the client.
|
||||
*
|
||||
* @param realm Realm.
|
||||
* @param client Client.
|
||||
* @param clientScope to be unassigned
|
||||
*/
|
||||
void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
package org.keycloak.storage.client;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -92,4 +94,14 @@ public interface ClientLookupProvider {
|
|||
* @return Stream of ClientModel or an empty stream if no client is found. Never returns {@code null}.
|
||||
*/
|
||||
Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults);
|
||||
|
||||
/**
|
||||
* Return all default scopes (if {@code defaultScope} is {@code true}) or all optional scopes (if {@code defaultScope} is {@code false}) linked with the client
|
||||
*
|
||||
* @param realm Realm
|
||||
* @param client Client
|
||||
* @param defaultScopes if true default scopes, if false optional scopes, are returned
|
||||
* @return map where key is the name of the clientScope, value is particular clientScope. Returns empty map if no scopes linked (never returns null).
|
||||
*/
|
||||
Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes);
|
||||
}
|
||||
|
|
|
@ -183,8 +183,8 @@ public class ApplicationsBean {
|
|||
|
||||
// Construct scope parameter with all optional scopes to see all potentially available roles
|
||||
Stream<ClientScopeModel> allClientScopes = Stream.concat(
|
||||
client.getClientScopes(true, true).values().stream(),
|
||||
client.getClientScopes(false, true).values().stream());
|
||||
client.getClientScopes(true).values().stream(),
|
||||
client.getClientScopes(false).values().stream());
|
||||
allClientScopes = Stream.concat(allClientScopes, Stream.of(client)).distinct();
|
||||
|
||||
Set<RoleModel> availableRoles = TokenManager.getAccess(user, client, allClientScopes);
|
||||
|
|
|
@ -548,14 +548,14 @@ public class TokenManager {
|
|||
public static Stream<ClientScopeModel> getRequestedClientScopes(String scopeParam, ClientModel client) {
|
||||
// Add all default client scopes automatically and client itself
|
||||
Stream<ClientScopeModel> clientScopes = Stream.concat(
|
||||
client.getClientScopes(true, true).values().stream(),
|
||||
client.getClientScopes(true).values().stream(),
|
||||
Stream.of(client)).distinct();
|
||||
|
||||
if (scopeParam == null) {
|
||||
return clientScopes;
|
||||
}
|
||||
|
||||
Map<String, ClientScopeModel> allOptionalScopes = client.getClientScopes(false, true);
|
||||
Map<String, ClientScopeModel> allOptionalScopes = client.getClientScopes(false);
|
||||
// Add optional client scopes requested by scope parameter
|
||||
return Stream.concat(parseScopeParameter(scopeParam).map(allOptionalScopes::get).filter(Objects::nonNull),
|
||||
clientScopes).distinct();
|
||||
|
|
|
@ -72,8 +72,8 @@ public class ClientScopesCondition extends AbstractClientPolicyConditionProvider
|
|||
private boolean isScopeMatched(String explicitScopes, ClientModel client) {
|
||||
if (explicitScopes == null) explicitScopes = "";
|
||||
Collection<String> explicitSpecifiedScopes = new HashSet<>(Arrays.asList(explicitScopes.split(" ")));
|
||||
Set<String> defaultScopes = client.getClientScopes(true, true).keySet();
|
||||
Set<String> optionalScopes = client.getClientScopes(false, true).keySet();
|
||||
Set<String> defaultScopes = client.getClientScopes(true).keySet();
|
||||
Set<String> optionalScopes = client.getClientScopes(false).keySet();
|
||||
Set<String> expectedScopes = getScopesForMatching();
|
||||
if (expectedScopes == null) expectedScopes = new HashSet<>();
|
||||
|
||||
|
|
|
@ -73,10 +73,10 @@ public class ClientScopesClientRegistrationPolicy implements ClientRegistrationP
|
|||
|
||||
// Allow scopes, which were already presented before
|
||||
if (requestedDefaultScopeNames != null) {
|
||||
requestedDefaultScopeNames.removeAll(clientModel.getClientScopes(true, false).keySet());
|
||||
requestedDefaultScopeNames.removeAll(clientModel.getClientScopes(true).keySet());
|
||||
}
|
||||
if (requestedOptionalScopeNames != null) {
|
||||
requestedOptionalScopeNames.removeAll(clientModel.getClientScopes(false, false).keySet());
|
||||
requestedOptionalScopeNames.removeAll(clientModel.getClientScopes(false).keySet());
|
||||
}
|
||||
|
||||
List<String> allowedDefaultScopeNames = getAllowedScopeNames(realm, true);
|
||||
|
|
|
@ -318,7 +318,7 @@ public class ClientResource {
|
|||
private Stream<ClientScopeRepresentation> getDefaultClientScopes(boolean defaultScope) {
|
||||
auth.clients().requireView(client);
|
||||
|
||||
return client.getClientScopes(defaultScope, true).values().stream().map(ClientResource::toRepresentation);
|
||||
return client.getClientScopes(defaultScope).values().stream().map(ClientResource::toRepresentation);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -249,6 +249,7 @@ public class RealmAdminResource {
|
|||
ClientScopeRepresentation rep = new ClientScopeRepresentation();
|
||||
rep.setId(clientScope.getId());
|
||||
rep.setName(clientScope.getName());
|
||||
rep.setProtocol(clientScope.getProtocol());
|
||||
return rep;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.storage;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.reflections.Types;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
@ -31,7 +32,9 @@ import org.keycloak.storage.client.ClientStorageProviderModel;
|
|||
import org.keycloak.utils.ServicesUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -160,6 +163,18 @@ public class ClientStorageManager implements ClientProvider {
|
|||
return Stream.concat(local, ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
|
||||
StorageId storageId = new StorageId(client.getId());
|
||||
if (storageId.getProviderId() == null) {
|
||||
return session.clientLocalStorage().getClientScopes(realm, client, defaultScopes);
|
||||
}
|
||||
ClientLookupProvider provider = (ClientLookupProvider)getStorageProvider(session, client.getRealm(), storageId.getProviderId());
|
||||
if (provider == null) return null;
|
||||
if (!isStorageProviderEnabled(client.getRealm(), storageId.getProviderId())) return null;
|
||||
return provider.getClientScopes(realm, client, defaultScopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel addClient(RealmModel realm, String clientId) {
|
||||
return session.clientLocalStorage().addClient(realm, clientId);
|
||||
|
@ -195,6 +210,22 @@ public class ClientStorageManager implements ClientProvider {
|
|||
session.clientLocalStorage().removeClients(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) {
|
||||
if (!StorageId.isLocalStorage(client.getId())) {
|
||||
throw new RuntimeException("Federated clients do not support this operation");
|
||||
}
|
||||
session.clientLocalStorage().addClientScopes(realm, client, clientScopes, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) {
|
||||
if (!StorageId.isLocalStorage(client.getId())) {
|
||||
throw new RuntimeException("Federated clients do not support this operation");
|
||||
}
|
||||
session.clientLocalStorage().removeClientScope(realm, client, clientScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -19,7 +19,10 @@ package org.keycloak.storage.openshift;
|
|||
import com.openshift.restclient.IClient;
|
||||
import com.openshift.restclient.NotFoundException;
|
||||
import com.openshift.restclient.model.IResource;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.storage.StorageId;
|
||||
|
@ -92,4 +95,10 @@ public class OpenshiftClientStorageProvider implements ClientStorageProvider {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
|
||||
// TODO not sure about this, this implementation doesn't use it now
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,7 +275,7 @@ public final class OpenshiftSAClientAdapter extends AbstractReadOnlyClientStorag
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope) {
|
||||
if (defaultScope) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
|
|
@ -92,6 +92,21 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
|
|||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScope) {
|
||||
if (defaultScope) {
|
||||
ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.ROLES_SCOPE);
|
||||
ClientScopeModel webOriginsScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE);
|
||||
return Arrays.asList(rolesScope, webOriginsScope)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(ClientScopeModel::getName, clientScope -> clientScope));
|
||||
|
||||
} else {
|
||||
ClientScopeModel offlineScope = KeycloakModelUtils.getClientScopeByName(realm, "offline_access");
|
||||
return Collections.singletonMap("offline_access", offlineScope);
|
||||
}
|
||||
}
|
||||
|
||||
public class ClientAdapter extends AbstractReadOnlyClientStorageAdapter {
|
||||
|
||||
public ClientAdapter(RealmModel realm) {
|
||||
|
@ -241,18 +256,8 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope, boolean filterByProtocol) {
|
||||
if (defaultScope) {
|
||||
ClientScopeModel rolesScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.ROLES_SCOPE);
|
||||
ClientScopeModel webOriginsScope = KeycloakModelUtils.getClientScopeByName(realm, OIDCLoginProtocolFactory.WEB_ORIGINS_SCOPE);
|
||||
return Arrays.asList(rolesScope, webOriginsScope)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(ClientScopeModel::getName, clientScope -> clientScope));
|
||||
|
||||
} else {
|
||||
ClientScopeModel offlineScope = KeycloakModelUtils.getClientScopeByName(realm, "offline_access");
|
||||
return Collections.singletonMap("offline_access", offlineScope);
|
||||
}
|
||||
public Map<String, ClientScopeModel> getClientScopes(boolean defaultScope) {
|
||||
return session.clients().getClientScopes(getRealm(), this, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -341,6 +341,7 @@ public class ClientScopeTest extends AbstractClientTest {
|
|||
// Add client scope
|
||||
ClientScopeRepresentation scopeRep = new ClientScopeRepresentation();
|
||||
scopeRep.setName("foo-scope");
|
||||
scopeRep.setProtocol("openid-connect");
|
||||
String scopeId = createClientScope(scopeRep);
|
||||
|
||||
// Add client with the clientScope
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
|
||||
|
@ -112,6 +113,7 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
|||
kcinit.setEnabled(true);
|
||||
kcinit.addRedirectUri("*");
|
||||
kcinit.setPublicClient(true);
|
||||
kcinit.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
kcinit.removeRole(realm.getRole(OAuth2Constants.OFFLINE_ACCESS));
|
||||
|
||||
ClientModel app = realm.addClient(APP);
|
||||
|
|
|
@ -382,12 +382,14 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
|
|||
// Add some clientScopes
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("foo");
|
||||
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Response response = realmResource().clientScopes().create(clientScope);
|
||||
String fooScopeId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
|
||||
clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("bar");
|
||||
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
response = realmResource().clientScopes().create(clientScope);
|
||||
String barScopeId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
|
@ -436,6 +438,7 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
|
|||
// Add some clientScope through Admin REST
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("foo");
|
||||
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Response response = realmResource().clientScopes().create(clientScope);
|
||||
String clientScopeId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
|
@ -475,6 +478,7 @@ public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTe
|
|||
// Add some clientScope through Admin REST
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("foo");
|
||||
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
Response response = realmResource().clientScopes().create(clientScope);
|
||||
String clientScopeId = ApiUtil.getCreatedId(response);
|
||||
response.close();
|
||||
|
|
|
@ -55,6 +55,7 @@ import static org.junit.Assert.assertNull;
|
|||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import static org.keycloak.services.clientregistration.ErrorCodes.INVALID_CLIENT_METADATA;
|
||||
import static org.keycloak.services.clientregistration.ErrorCodes.INVALID_REDIRECT_URI;
|
||||
|
||||
|
@ -558,10 +559,12 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
|
||||
@Test
|
||||
public void registerClientAsAdminWithoutScope() throws ClientRegistrationException {
|
||||
Set<String> realmDefaultClientScopes = new HashSet<>(adminClient.realm(REALM_NAME).getDefaultDefaultClientScopes()
|
||||
.stream().map(i->i.getName()).collect(Collectors.toList()));
|
||||
Set<String> realmOptionalClientScopes = new HashSet<>(adminClient.realm(REALM_NAME).getDefaultOptionalClientScopes()
|
||||
.stream().map(i->i.getName()).collect(Collectors.toList()));
|
||||
Set<String> realmDefaultClientScopes = new HashSet<>(adminClient.realm(REALM_NAME).getDefaultDefaultClientScopes().stream()
|
||||
.filter(scope -> Objects.equals(scope.getProtocol(), OIDCLoginProtocol.LOGIN_PROTOCOL))
|
||||
.map(i->i.getName()).collect(Collectors.toList()));
|
||||
Set<String> realmOptionalClientScopes = new HashSet<>(adminClient.realm(REALM_NAME).getDefaultOptionalClientScopes().stream()
|
||||
.filter(scope -> Objects.equals(scope.getProtocol(), OIDCLoginProtocol.LOGIN_PROTOCOL))
|
||||
.map(i->i.getName()).collect(Collectors.toList()));
|
||||
|
||||
authManageClients();
|
||||
ClientRepresentation client = new ClientRepresentation();
|
||||
|
|
|
@ -520,7 +520,9 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
@Test
|
||||
public void testClientWithoutScope() throws ClientRegistrationException {
|
||||
Set<String> realmOptionalClientScopes = new HashSet<>(adminClient.realm(REALM_NAME).getDefaultOptionalClientScopes()
|
||||
.stream().map(i->i.getName()).collect(Collectors.toList()));
|
||||
.stream()
|
||||
.filter(scope -> Objects.equals(scope.getProtocol(), OIDCLoginProtocol.LOGIN_PROTOCOL))
|
||||
.map(i->i.getName()).collect(Collectors.toList()));
|
||||
|
||||
OIDCClientRepresentation clientRep = null;
|
||||
OIDCClientRepresentation response = null;
|
||||
|
@ -535,7 +537,9 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
ClientRepresentation rep = clientResource.toRepresentation();
|
||||
|
||||
Set<String> realmDefaultClientScopes = new HashSet<>(adminClient.realm(REALM_NAME).getDefaultDefaultClientScopes()
|
||||
.stream().map(i->i.getName()).collect(Collectors.toList()));
|
||||
.stream()
|
||||
.filter(scope -> Objects.equals(scope.getProtocol(), OIDCLoginProtocol.LOGIN_PROTOCOL))
|
||||
.map(i->i.getName()).collect(Collectors.toList()));
|
||||
|
||||
Set<String> registeredDefaultClientScopes = new HashSet<>(rep.getDefaultClientScopes());
|
||||
assertTrue(realmDefaultClientScopes.equals(new HashSet<>(registeredDefaultClientScopes)));
|
||||
|
|
|
@ -371,12 +371,12 @@ public class ClientModelTest extends AbstractKeycloakTest {
|
|||
ClientScopeModel scope1 = scope1Atomic.get();
|
||||
ClientScopeModel scope2 = scope2Atomic.get();
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true, true);
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes1.containsKey("scope1"), is(true));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes1.containsKey("scope2"), is(false));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes1.containsKey("scope3"), is(false));
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false, true);
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes2.containsKey("scope1"), is(false));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes2.containsKey("scope2"), is(true));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes2.containsKey("scope3"), is(true));
|
||||
|
@ -392,12 +392,12 @@ public class ClientModelTest extends AbstractKeycloakTest {
|
|||
client = realm.getClientByClientId("templatized");
|
||||
ClientScopeModel scope3 = scope3Atomic.get();
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true, true);
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes1.containsKey("scope1"), is(false));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes1.containsKey("scope2"), is(false));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes1.containsKey("scope3"), is(false));
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false, true);
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes2.containsKey("scope1"), is(false));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes2.containsKey("scope2"), is(false));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes2.containsKey("scope3"), is(true));
|
||||
|
@ -420,6 +420,7 @@ public class ClientModelTest extends AbstractKeycloakTest {
|
|||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
||||
client = realm.addClient("templatized");
|
||||
ClientScopeModel scope1 = realm.addClientScope("template");
|
||||
scope1.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
scope1Atomic.set(scope1);
|
||||
client.addClientScope(scope1, true);
|
||||
});
|
||||
|
@ -505,13 +506,13 @@ public class ClientModelTest extends AbstractKeycloakTest {
|
|||
ClientScopeModel scope2 = scope2Atomic.get();
|
||||
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true, true);
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes1.containsKey("scope1"), is(true));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes1.containsKey("scope2"), is(false));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes1.containsKey("scope3"), is(false));
|
||||
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false, true);
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes2.containsKey("scope1"), is(false));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes2.containsKey("scope2"), is(true));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes2.containsKey("scope3"), is(true));
|
||||
|
@ -534,12 +535,12 @@ public class ClientModelTest extends AbstractKeycloakTest {
|
|||
RealmModel realm = currentSession.realms().getRealmByName(realmName);
|
||||
client = realm.getClientByClientId("foo2");
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true, true);
|
||||
Map<String, ClientScopeModel> clientScopes1 = client.getClientScopes(true);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes1.containsKey("scope1"), is(false));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes1.containsKey("scope2"), is(false));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes1.containsKey("scope3"), is(false));
|
||||
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false, true);
|
||||
Map<String, ClientScopeModel> clientScopes2 = client.getClientScopes(false);
|
||||
assertThat("Client Scope contains 'scope1':", clientScopes2.containsKey("scope1"), is(false));
|
||||
assertThat("Client Scope contains 'scope2':", clientScopes2.containsKey("scope2"), is(false));
|
||||
assertThat("Client Scope contains 'scope3':", clientScopes2.containsKey("scope3"), is(true));
|
||||
|
|
Loading…
Reference in a new issue