KEYCLOAK-15889 Streamification of ProtocolMappers

This commit is contained in:
Martin Kanis 2020-10-09 11:18:11 +02:00 committed by Hynek Mlnařík
parent e6bd12b174
commit d9029b06b9
32 changed files with 363 additions and 431 deletions

View file

@ -357,9 +357,9 @@ public class ClientAdapter implements ClientModel, CachedObject {
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
if (isUpdated()) return updated.getProtocolMappers();
return cached.getProtocolMappers();
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
if (isUpdated()) return updated.getProtocolMappersStream();
return cached.getProtocolMappers().stream();
}
@Override

View file

@ -26,7 +26,6 @@ import org.keycloak.models.utils.RoleUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
/**
@ -79,9 +78,9 @@ public class ClientScopeAdapter implements ClientScopeModel {
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
if (isUpdated()) return updated.getProtocolMappers();
return cached.getProtocolMappers();
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
if (isUpdated()) return updated.getProtocolMappersStream();
return cached.getProtocolMappers().stream();
}
@Override

View file

@ -94,9 +94,7 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
redirectUris.addAll(model.getRedirectUris());
webOrigins.addAll(model.getWebOrigins());
scope.addAll(model.getScopeMappingsStream().map(RoleModel::getId).collect(Collectors.toSet()));
for (ProtocolMapperModel mapper : model.getProtocolMappers()) {
this.protocolMappers.add(mapper);
}
protocolMappers.addAll(model.getProtocolMappersStream().collect(Collectors.toSet()));
surrogateAuthRequired = model.isSurrogateAuthRequired();
managementUrl = model.getManagementUrl();
rootUrl = model.getRootUrl();

View file

@ -48,9 +48,7 @@ public class CachedClientScope extends AbstractRevisioned implements InRealm {
description = model.getDescription();
this.realm = realm.getId();
protocol = model.getProtocol();
for (ProtocolMapperModel mapper : model.getProtocolMappers()) {
this.protocolMappers.add(mapper);
}
protocolMappers.addAll(model.getProtocolMappersStream().collect(Collectors.toSet()));
scope.addAll(model.getScopeMappingsStream().map(RoleModel::getId).collect(Collectors.toSet()));
attributes.putAll(model.getAttributes());
}

View file

@ -400,22 +400,22 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
Set<ProtocolMapperModel> mappings = new HashSet<ProtocolMapperModel>();
for (ProtocolMapperEntity entity : this.entity.getProtocolMappers()) {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
mapping.setProtocolMapper(entity.getProtocolMapper());
Map<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
mappings.add(mapping);
}
return mappings;
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return this.entity.getProtocolMappers().stream()
.map(entity -> {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
mapping.setProtocolMapper(entity.getProtocolMapper());
Map<String, String> config = new HashMap<>();
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
return mapping;
})
.distinct();
}
@Override

View file

@ -32,11 +32,9 @@ import org.keycloak.models.utils.RoleUtils;
import javax.persistence.EntityManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
/**
@ -100,22 +98,22 @@ public class ClientScopeAdapter implements ClientScopeModel, JpaModel<ClientScop
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
Set<ProtocolMapperModel> mappings = new HashSet<ProtocolMapperModel>();
for (ProtocolMapperEntity entity : this.entity.getProtocolMappers()) {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
mapping.setProtocolMapper(entity.getProtocolMapper());
Map<String, String> config = new HashMap<String, String>();
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
mappings.add(mapping);
}
return mappings;
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return this.entity.getProtocolMappers().stream()
.map(entity -> {
ProtocolMapperModel mapping = new ProtocolMapperModel();
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
mapping.setProtocolMapper(entity.getProtocolMapper());
Map<String, String> config = new HashMap<>();
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
return mapping;
})
.distinct();
}
@Override

View file

@ -26,10 +26,7 @@ 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -478,8 +475,8 @@ public abstract class MapClientAdapter extends AbstractClientModel<MapClientEnti
/*************** Protocol mappers ****************/
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
return Collections.unmodifiableSet(new HashSet<>(entity.getProtocolMappers()));
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return entity.getProtocolMappers().stream().distinct();
}
@Override

View file

@ -17,9 +17,6 @@
package org.keycloak.migration.migrators;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
@ -29,7 +26,6 @@ import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperContainerModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RoleModel;
@ -71,17 +67,10 @@ public class MigrationUtils {
}
public static void updateProtocolMappers(ProtocolMapperContainerModel client) {
List<ProtocolMapperModel> toUpdate = new LinkedList<>();
for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
if (!mapper.getConfig().containsKey("userinfo.token.claim") && mapper.getConfig().containsKey("id.token.claim")) {
mapper.getConfig().put("userinfo.token.claim", mapper.getConfig().get("id.token.claim"));
toUpdate.add(mapper);
}
}
for (ProtocolMapperModel mapper : toUpdate) {
client.updateProtocolMapper(mapper);
}
client.getProtocolMappersStream()
.filter(mapper -> !mapper.getConfig().containsKey("userinfo.token.claim") && mapper.getConfig().containsKey("id.token.claim"))
.peek(mapper -> mapper.getConfig().put("userinfo.token.claim", mapper.getConfig().get("id.token.claim")))
.forEach(client::updateProtocolMapper);
}

View file

@ -555,13 +555,10 @@ public class ModelToRepresentation {
rep.setName(clientScopeModel.getName());
rep.setDescription(clientScopeModel.getDescription());
rep.setProtocol(clientScopeModel.getProtocol());
if (!clientScopeModel.getProtocolMappers().isEmpty()) {
List<ProtocolMapperRepresentation> mappings = new LinkedList<>();
for (ProtocolMapperModel model : clientScopeModel.getProtocolMappers()) {
mappings.add(toRepresentation(model));
}
List<ProtocolMapperRepresentation> mappings = clientScopeModel.getProtocolMappersStream()
.map(ModelToRepresentation::toRepresentation).collect(Collectors.toList());
if (!mappings.isEmpty())
rep.setProtocolMappers(mappings);
}
rep.setAttributes(new HashMap<>(clientScopeModel.getAttributes()));
@ -621,13 +618,10 @@ public class ModelToRepresentation {
rep.setRegisteredNodes(new HashMap<>(clientModel.getRegisteredNodes()));
}
if (!clientModel.getProtocolMappers().isEmpty()) {
List<ProtocolMapperRepresentation> mappings = new LinkedList<>();
for (ProtocolMapperModel model : clientModel.getProtocolMappers()) {
mappings.add(toRepresentation(model));
}
List<ProtocolMapperRepresentation> mappings = clientModel.getProtocolMappersStream()
.map(ModelToRepresentation::toRepresentation).collect(Collectors.toList());
if (!mappings.isEmpty())
rep.setProtocolMappers(mappings);
}
AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
ResourceServer resourceServer = authorization.getStoreFactory().getResourceServerStore().findById(clientModel.getId());

View file

@ -1414,8 +1414,7 @@ public class RepresentationToModel {
if (resourceRep.getProtocolMappers() != null) {
// first, remove all default/built in mappers
Set<ProtocolMapperModel> mappers = client.getProtocolMappers();
for (ProtocolMapperModel mapper : mappers) client.removeProtocolMapper(mapper);
client.getProtocolMappersStream().collect(Collectors.toList()).forEach(client::removeProtocolMapper);
for (ProtocolMapperRepresentation mapper : resourceRep.getProtocolMappers()) {
client.addProtocolMapper(toModel(mapper));
@ -1556,10 +1555,10 @@ public class RepresentationToModel {
public static void updateClientProtocolMappers(ClientRepresentation rep, ClientModel resource) {
if (rep.getProtocolMappers() != null) {
Map<String,ProtocolMapperModel> existingProtocolMappers = new HashMap<>();
for (ProtocolMapperModel existingProtocolMapper : resource.getProtocolMappers()) {
existingProtocolMappers.put(generateProtocolNameKey(existingProtocolMapper.getProtocol(), existingProtocolMapper.getName()), existingProtocolMapper);
}
Map<String,ProtocolMapperModel> existingProtocolMappers =
resource.getProtocolMappersStream().collect(Collectors.toMap(mapper ->
generateProtocolNameKey(mapper.getProtocol(), mapper.getName()), Function.identity()));
for (ProtocolMapperRepresentation protocolMapperRepresentation : rep.getProtocolMappers()) {
String protocolNameKey = generateProtocolNameKey(protocolMapperRepresentation.getProtocol(), protocolMapperRepresentation.getName());
@ -1606,8 +1605,7 @@ public class RepresentationToModel {
if (resourceRep.getProtocol() != null) clientScope.setProtocol(resourceRep.getProtocol());
if (resourceRep.getProtocolMappers() != null) {
// first, remove all default/built in mappers
Set<ProtocolMapperModel> mappers = clientScope.getProtocolMappers();
for (ProtocolMapperModel mapper : mappers) clientScope.removeProtocolMapper(mapper);
clientScope.getProtocolMappersStream().collect(Collectors.toList()).forEach(clientScope::removeProtocolMapper);
for (ProtocolMapperRepresentation mapper : resourceRep.getProtocolMappers()) {
clientScope.addProtocolMapper(toModel(mapper));

View file

@ -23,7 +23,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.storage.ReadOnlyException;
import java.util.List;
import java.util.Set;
/**

View file

@ -32,7 +32,20 @@ public interface ClientSessionContext {
Set<String> getClientScopeIds();
Set<ClientScopeModel> getClientScopes();
/**
* @deprecated Use {@link #getClientScopesStream() getClientScopesStream} instead.
* @return Set of protocol mappers
*/
@Deprecated
default Set<ClientScopeModel> getClientScopes() {
return getClientScopesStream().collect(Collectors.toSet());
}
/**
* Returns client scopes as a stream.
* @return Stream of client scopes.
*/
Stream<ClientScopeModel> getClientScopesStream();
/**
* @return expanded roles (composite roles already applied)
@ -44,7 +57,20 @@ public interface ClientSessionContext {
Stream<RoleModel> getRolesStream();
Set<ProtocolMapperModel> getProtocolMappers();
/**
* @deprecated Use {@link #getProtocolMappersStream() getProtocolMappersStream} instead.
* @return Set of protocol mappers
*/
@Deprecated
default Set<ProtocolMapperModel> getProtocolMappers() {
return getProtocolMappersStream().collect(Collectors.toSet());
}
/**
* Returns protocol mappers as a stream.
* @return Stream of protocol mappers.
*/
Stream<ProtocolMapperModel> getProtocolMappersStream();
String getScopeString();

View file

@ -18,13 +18,28 @@
package org.keycloak.models;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface ProtocolMapperContainerModel {
Set<ProtocolMapperModel> getProtocolMappers();
/**
* @deprecated Use {@link #getProtocolMappersStream() getProtocolMappersStream} instead.
* @return Set of protocol mappers
*/
@Deprecated
default Set<ProtocolMapperModel> getProtocolMappers() {
return getProtocolMappersStream().collect(Collectors.toSet());
}
/**
* Returns protocol mappers as a stream.
* @return Stream of protocol mapper.
*/
Stream<ProtocolMapperModel> getProtocolMappersStream();
ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model);

View file

@ -40,6 +40,7 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -57,9 +58,10 @@ public class ApplicationsBean {
}
// Construct scope parameter with all optional scopes to see all potentially available roles
Set<ClientScopeModel> allClientScopes = new HashSet<>(client.getClientScopes(true, true).values());
allClientScopes.addAll(client.getClientScopes(false, true).values());
allClientScopes.add(client);
Stream<ClientScopeModel> allClientScopes = Stream.concat(
client.getClientScopes(true, true).values().stream(),
client.getClientScopes(false, true).values().stream());
allClientScopes = Stream.concat(allClientScopes, Stream.of(client)).distinct();
Set<RoleModel> availableRoles = TokenManager.getAccess(user, client, allClientScopes);

View file

@ -29,10 +29,10 @@ import org.keycloak.provider.ProviderFactory;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Stream;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -123,27 +123,22 @@ public class ProtocolMapperUtils {
}
public static List<Map.Entry<ProtocolMapperModel, ProtocolMapper>> getSortedProtocolMappers(KeycloakSession session, ClientSessionContext ctx) {
Set<ProtocolMapperModel> mapperModels = ctx.getProtocolMappers();
Map<ProtocolMapperModel, ProtocolMapper> result = new HashMap<>();
public static Stream<Entry<ProtocolMapperModel, ProtocolMapper>> getSortedProtocolMappers(KeycloakSession session, ClientSessionContext ctx) {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
for (ProtocolMapperModel mapperModel : mapperModels) {
ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapperModel.getProtocolMapper());
if (mapper == null) {
continue;
}
result.put(mapperModel, mapper);
}
return result.entrySet()
.stream()
.sorted(Comparator.comparing(ProtocolMapperUtils::compare))
.collect(Collectors.toList());
return ctx.getProtocolMappersStream()
.flatMap(mapperModel -> {
ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapperModel.getProtocolMapper());
if (mapper == null)
return null;
Map<ProtocolMapperModel, ProtocolMapper> protocolMapperMap = new HashMap<>();
protocolMapperMap.put(mapperModel, mapper);
return protocolMapperMap.entrySet().stream();
})
.filter(Objects::nonNull)
.sorted(Comparator.comparing(ProtocolMapperUtils::compare));
}
public static int compare(Map.Entry<ProtocolMapperModel, ProtocolMapper> entry) {
public static int compare(Entry<ProtocolMapperModel, ProtocolMapper> entry) {
int priority = entry.getValue().getPriority();
return priority;
}

View file

@ -10,12 +10,10 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.docker.mapper.DockerAuthV2AttributeMapper;
import org.keycloak.representations.docker.DockerResponse;
@ -30,7 +28,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
public class DockerAuthV2Protocol implements LoginProtocol {
protected static final Logger logger = Logger.getLogger(DockerEndpoint.class);
@ -111,17 +109,13 @@ public class DockerAuthV2Protocol implements LoginProtocol {
// Next, allow mappers to decorate the token to add/remove scopes as appropriate
for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof DockerAuthV2AttributeMapper) {
final DockerAuthV2AttributeMapper dockerAttributeMapper = (DockerAuthV2AttributeMapper) mapper;
if (dockerAttributeMapper.appliesTo(responseToken)) {
responseToken = dockerAttributeMapper.transformDockerResponseToken(responseToken, mapping, session, userSession, clientSession);
}
}
}
AtomicReference<DockerResponseToken> finalResponseToken = new AtomicReference<>(responseToken);
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)
.filter(mapper -> mapper.getValue() instanceof DockerAuthV2AttributeMapper)
.filter(mapper -> ((DockerAuthV2AttributeMapper) mapper.getValue()).appliesTo(finalResponseToken.get()))
.forEach(mapper -> finalResponseToken.set(((DockerAuthV2AttributeMapper) mapper.getValue())
.transformDockerResponseToken(finalResponseToken.get(), mapper.getKey(), session, userSession, clientSession)));
responseToken = finalResponseToken.get();
try {
// Finally, construct the response to the docker client with the token + metadata

View file

@ -43,7 +43,6 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.TokenRevocationStoreProvider;
@ -53,7 +52,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RoleUtils;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
@ -78,11 +76,13 @@ import org.keycloak.util.TokenUtil;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -199,7 +199,7 @@ public class TokenManager {
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, oldTokenScope, session);
// Check user didn't revoke granted consent
if (!verifyConsentStillAvailable(session, user, client, clientSessionCtx.getClientScopes())) {
if (!verifyConsentStillAvailable(session, user, client, clientSessionCtx.getClientScopesStream())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user");
}
@ -511,7 +511,7 @@ public class TokenManager {
}
public static Set<RoleModel> getAccess(UserModel user, ClientModel client, Set<ClientScopeModel> clientScopes) {
public static Set<RoleModel> getAccess(UserModel user, ClientModel client, Stream<ClientScopeModel> clientScopes) {
Set<RoleModel> roleMappings = RoleUtils.getDeepUserRoleMappings(user);
if (client.isFullScopeAllowed()) {
@ -525,12 +525,17 @@ public class TokenManager {
Stream<RoleModel> scopeMappings = client.getRolesStream();
// 2 - Role mappings of client itself + default client scopes + optional client scopes requested by scope parameter (if applyScopeParam is true)
for (ClientScopeModel clientScope : clientScopes) {
if (logger.isTraceEnabled()) {
logger.tracef("Adding client scope role mappings of client scope '%s' to client '%s'", clientScope.getName(), client.getClientId());
}
scopeMappings = Stream.concat(scopeMappings, clientScope.getScopeMappingsStream());
Stream<RoleModel> clientScopesMappings;
if (!logger.isTraceEnabled()) {
clientScopesMappings = clientScopes.flatMap(clientScope -> clientScope.getScopeMappingsStream());
} else {
clientScopesMappings = clientScopes.flatMap(clientScope -> {
logger.tracef("Adding client scope role mappings of client scope '%s' to client '%s'",
clientScope.getName(), client.getClientId());
return clientScope.getScopeMappingsStream();
});
}
scopeMappings = Stream.concat(scopeMappings, clientScopesMappings);
// 3 - Expand scope mappings
scopeMappings = RoleUtils.expandCompositeRolesStream(scopeMappings);
@ -544,28 +549,20 @@ public class TokenManager {
/** Return client itself + all default client scopes of client + optional client scopes requested by scope parameter **/
public static Set<ClientScopeModel> getRequestedClientScopes(String scopeParam, ClientModel client) {
// Add all default client scopes automatically
Set<ClientScopeModel> clientScopes = new HashSet<>(client.getClientScopes(true, true).values());
// Add client itself
clientScopes.add(client);
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(),
Stream.of(client)).distinct();
if (scopeParam == null) {
return clientScopes;
}
// Add optional client scopes requested by scope parameter
Collection<String> scopeParamParts = parseScopeParameter(scopeParam);
Map<String, ClientScopeModel> allOptionalScopes = client.getClientScopes(false, true);
for (String scopeParamPart : scopeParamParts) {
ClientScopeModel scope = allOptionalScopes.get(scopeParamPart);
if (scope != null) {
clientScopes.add(scope);
}
}
return clientScopes;
// Add optional client scopes requested by scope parameter
return Stream.concat(parseScopeParameter(scopeParam).map(allOptionalScopes::get).filter(Objects::nonNull),
clientScopes).distinct();
}
public static boolean isValidScope(String scopes, ClientModel client) {
@ -573,11 +570,11 @@ public class TokenManager {
return true;
}
Set<String> clientScopes = getRequestedClientScopes(scopes, client).stream()
.filter(obj -> !ClientModel.class.isInstance(obj))
Set<String> clientScopes = getRequestedClientScopes(scopes, client)
.filter(((Predicate<ClientScopeModel>) ClientModel.class::isInstance).negate())
.map(ClientScopeModel::getName)
.collect(Collectors.toSet());
Collection<String> requestedScopes = TokenManager.parseScopeParameter(scopes);
Collection<String> requestedScopes = TokenManager.parseScopeParameter(scopes).collect(Collectors.toSet());
if (TokenUtil.isOIDCRequest(scopes)) {
requestedScopes.remove(OAuth2Constants.SCOPE_OPENID);
@ -597,73 +594,61 @@ public class TokenManager {
return true;
}
public static Collection<String> parseScopeParameter(String scopeParam) {
return new HashSet<>(Arrays.asList(scopeParam.split(" ")));
public static Stream<String> parseScopeParameter(String scopeParam) {
return Arrays.stream(scopeParam.split(" ")).distinct();
}
// Check if user still has granted consents to all requested client scopes
public static boolean verifyConsentStillAvailable(KeycloakSession session, UserModel user, ClientModel client, Set<ClientScopeModel> requestedClientScopes) {
public static boolean verifyConsentStillAvailable(KeycloakSession session, UserModel user, ClientModel client,
Stream<ClientScopeModel> requestedClientScopes) {
if (!client.isConsentRequired()) {
return true;
}
UserConsentModel grantedConsent = session.users().getConsentByClient(client.getRealm(), user.getId(), client.getId());
for (ClientScopeModel requestedScope : requestedClientScopes) {
if (!requestedScope.isDisplayOnConsentScreen()) {
continue;
}
if (grantedConsent == null || !grantedConsent.getGrantedClientScopes().contains(requestedScope)) {
logger.debugf("Client '%s' no longer has requested consent from user '%s' for client scope '%s'",
client.getClientId(), user.getUsername(), requestedScope.getName());
return false;
}
}
return true;
return requestedClientScopes
.filter(ClientScopeModel::isDisplayOnConsentScreen)
.noneMatch(requestedScope -> {
if (grantedConsent == null || !grantedConsent.getGrantedClientScopes().contains(requestedScope)) {
logger.debugf("Client '%s' no longer has requested consent from user '%s' for client scope '%s'",
client.getClientId(), user.getUsername(), requestedScope.getName());
return true;
}
return false;
});
}
public AccessToken transformAccessToken(KeycloakSession session, AccessToken token,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof OIDCAccessTokenMapper) {
token = ((OIDCAccessTokenMapper) mapper).transformAccessToken(token, mapping, session, userSession, clientSessionCtx);
}
}
return token;
AtomicReference<AccessToken> finalToken = new AtomicReference<>(token);
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)
.filter(mapper -> mapper.getValue() instanceof OIDCAccessTokenMapper)
.forEach(mapper -> finalToken.set(((OIDCAccessTokenMapper) mapper.getValue())
.transformAccessToken(finalToken.get(), mapper.getKey(), session, userSession, clientSessionCtx)));
return finalToken.get();
}
public AccessToken transformUserInfoAccessToken(KeycloakSession session, AccessToken token,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof UserInfoTokenMapper) {
token = ((UserInfoTokenMapper) mapper).transformUserInfoToken(token, mapping, session, userSession, clientSessionCtx);
}
}
return token;
AtomicReference<AccessToken> finalToken = new AtomicReference<>(token);
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)
.filter(mapper -> mapper.getValue() instanceof UserInfoTokenMapper)
.forEach(mapper -> finalToken.set(((UserInfoTokenMapper) mapper.getValue())
.transformUserInfoToken(finalToken.get(), mapper.getKey(), session, userSession, clientSessionCtx)));
return finalToken.get();
}
public void transformIDToken(KeycloakSession session, IDToken token,
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof OIDCIDTokenMapper) {
token = ((OIDCIDTokenMapper) mapper).transformIDToken(token, mapping, session, userSession, clientSessionCtx);
}
}
AtomicReference<IDToken> finalToken = new AtomicReference<>(token);
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)
.filter(mapper -> mapper.getValue() instanceof OIDCIDTokenMapper)
.forEach(mapper -> finalToken.set(((OIDCIDTokenMapper) mapper.getValue())
.transformIDToken(finalToken.get(), mapper.getKey(), session, userSession, clientSessionCtx)));
}
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session,

View file

@ -127,9 +127,11 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
@ -420,13 +422,13 @@ public class TokenEndpoint {
// Compute client scopes again from scope parameter. Check if user still has them granted
// (but in code-to-token request, it could just theoretically happen that they are not available)
String scopeParam = codeData.getScope();
Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client);
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopes)) {
Supplier<Stream<ClientScopeModel>> clientScopesSupplier = () -> TokenManager.getRequestedClientScopes(scopeParam, client);
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopesSupplier.get())) {
event.error(Errors.NOT_ALLOWED);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
}
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, clientScopes, session);
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, clientScopesSupplier.get(), session);
// Set nonce as an attribute in the ClientSessionContext. Will be used for the token generation
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, codeData.getNonce());

View file

@ -25,7 +25,6 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.protocol.ClientInstallationProvider;
@ -41,6 +40,7 @@ import java.io.IOException;
import java.net.URI;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -110,15 +110,11 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
// Check if there is client scope with audience protocol mapper created for particular client. If yes, admin wants verifying token audience
String clientId = client.getClientId();
return client.getRealm().getClientScopesStream().anyMatch(clientScope -> {
for (ProtocolMapperModel protocolMapper : clientScope.getProtocolMappers()) {
if (AudienceProtocolMapper.PROVIDER_ID.equals(protocolMapper.getProtocolMapper()) &&
(clientId.equals(protocolMapper.getConfig().get(AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE)))) {
return true;
}
}
return false;
});
return client.getRealm().getClientScopesStream().anyMatch(clientScope ->
clientScope.getProtocolMappersStream().anyMatch(protocolMapper ->
Objects.equals(protocolMapper.getProtocolMapper(), AudienceProtocolMapper.PROVIDER_ID) &&
Objects.equals(clientId, protocolMapper.getConfig().get(AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE)))
);
}

View file

@ -88,6 +88,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -420,22 +421,23 @@ public class SamlProtocol implements LoginProtocol {
List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers = new LinkedList<>();
List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> loginResponseMappers = new LinkedList<>();
ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper = null;
AtomicReference<ProtocolMapperProcessor<SAMLRoleListMapper>> roleListMapper = new AtomicReference<>(null);
for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)
.forEach(entry -> {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof SAMLAttributeStatementMapper) {
attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper) mapper, mapping));
}
if (mapper instanceof SAMLLoginResponseMapper) {
loginResponseMappers.add(new ProtocolMapperProcessor<SAMLLoginResponseMapper>((SAMLLoginResponseMapper) mapper, mapping));
}
if (mapper instanceof SAMLRoleListMapper) {
roleListMapper = new ProtocolMapperProcessor<SAMLRoleListMapper>((SAMLRoleListMapper) mapper, mapping);
}
}
if (mapper instanceof SAMLAttributeStatementMapper) {
attributeStatementMappers.add(new ProtocolMapperProcessor<>((SAMLAttributeStatementMapper) mapper, mapping));
}
if (mapper instanceof SAMLLoginResponseMapper) {
loginResponseMappers.add(new ProtocolMapperProcessor<>((SAMLLoginResponseMapper) mapper, mapping));
}
if (mapper instanceof SAMLRoleListMapper) {
roleListMapper.set(new ProtocolMapperProcessor<>((SAMLRoleListMapper) mapper, mapping));
}
});
Document samlDocument = null;
KeyManager keyManager = session.keys();
@ -450,7 +452,7 @@ public class SamlProtocol implements LoginProtocol {
ResponseType samlModel = builder.buildModel();
final AttributeStatementType attributeStatement = populateAttributeStatements(attributeStatementMappers, session, userSession, clientSession);
populateRoles(roleListMapper, session, userSession, clientSessionCtx, attributeStatement);
populateRoles(roleListMapper.get(), session, userSession, clientSessionCtx, attributeStatement);
// SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
if (attributeStatement.getAttributes().size() > 0) {

View file

@ -34,6 +34,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
@ -112,32 +113,34 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
boolean singleAttribute = Boolean.parseBoolean(single);
List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
AttributeType singleAttributeType = null;
AtomicReference<AttributeType> singleAttributeType = new AtomicReference<>(null);
for (Map.Entry<ProtocolMapperModel, ProtocolMapper> entry : ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)) {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx)
.forEach(entry -> {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof SAMLRoleNameMapper) {
roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
}
if (mapper instanceof HardcodedRole) {
AttributeType attributeType;
if (singleAttribute) {
if (singleAttributeType == null) {
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
if (mapper instanceof SAMLRoleNameMapper) {
roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
}
attributeType = singleAttributeType;
} else {
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
}
attributeType.addAttributeValue(mapping.getConfig().get(HardcodedRole.ROLE_ATTRIBUTE));
}
}
if (mapper instanceof HardcodedRole) {
AttributeType attributeType;
if (singleAttribute) {
if (singleAttributeType.get() == null) {
singleAttributeType.set(AttributeStatementHelper.createAttributeType(mappingModel));
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType.get()));
}
attributeType = singleAttributeType.get();
} else {
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
}
attributeType.addAttributeValue(mapping.getConfig().get(HardcodedRole.ROLE_ATTRIBUTE));
}
});
List<String> allRoleNames = clientSessionCtx.getRolesStream()
// todo need a role mapping
@ -151,11 +154,11 @@ public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRo
for (String roleName : allRoleNames) {
AttributeType attributeType;
if (singleAttribute) {
if (singleAttributeType == null) {
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
if (singleAttributeType.get() == null) {
singleAttributeType.set(AttributeStatementHelper.createAttributeType(mappingModel));
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType.get()));
}
attributeType = singleAttributeType;
attributeType = singleAttributeType.get();
} else {
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));

View file

@ -48,9 +48,9 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -143,7 +143,7 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
// See if we have existing pairwise mapper and update it. Otherwise create new
AtomicBoolean foundPairwise = new AtomicBoolean(false);
clientModel.getProtocolMappers().stream().filter((ProtocolMapperModel mapping) -> {
clientModel.getProtocolMappersStream().filter((ProtocolMapperModel mapping) -> {
if (mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) {
foundPairwise.set(true);
return true;
@ -163,19 +163,16 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
} else {
// Rather find and remove all pairwise mappers
clientModel.getProtocolMappers().stream().filter((ProtocolMapperModel mapperRep) -> {
return mapperRep.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX);
}).forEach((ProtocolMapperModel mapping) -> {
clientModel.getProtocolMappers().remove(mapping);
});
clientModel.getProtocolMappersStream()
.filter(mapperRep -> mapperRep.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX))
.collect(Collectors.toList())
.forEach(clientModel::removeProtocolMapper);
}
}
private void updateClientRepWithProtocolMappers(ClientModel clientModel, ClientRepresentation rep) {
List<ProtocolMapperRepresentation> mappings = new LinkedList<>();
for (ProtocolMapperModel model : clientModel.getProtocolMappers()) {
mappings.add(ModelToRepresentation.toRepresentation(model));
}
List<ProtocolMapperRepresentation> mappings =
clientModel.getProtocolMappersStream().map(ModelToRepresentation::toRepresentation).collect(Collectors.toList());
rep.setProtocolMappers(mappings);
}
}

View file

@ -18,13 +18,12 @@
package org.keycloak.services.clientregistration.policy.impl;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.clientregistration.ClientRegistrationContext;
@ -75,19 +74,12 @@ public class ProtocolMappersClientRegistrationPolicy implements ClientRegistrati
public void afterRegister(ClientRegistrationContext context, ClientModel clientModel) {
// Remove mappers of unsupported type, which were added "automatically"
List<String> allowedMapperProviders = getAllowedMapperProviders();
Set<ProtocolMapperModel> createdMappers = clientModel.getProtocolMappers();
createdMappers.stream().filter((ProtocolMapperModel mapper) -> {
return !allowedMapperProviders.contains(mapper.getProtocolMapper());
}).forEach((ProtocolMapperModel mapperToRemove) -> {
logger.debugf("Removing builtin mapper '%s' of type '%s' as type is not permitted", mapperToRemove.getName(), mapperToRemove.getProtocolMapper());
clientModel.removeProtocolMapper(mapperToRemove);
});
clientModel.getProtocolMappersStream()
.filter(mapper -> !allowedMapperProviders.contains(mapper.getProtocolMapper()))
.peek(mapperToRemove -> logger.debugf("Removing builtin mapper '%s' of type '%s' as type is not permitted",
mapperToRemove.getName(), mapperToRemove.getProtocolMapper()))
.collect(Collectors.toList())
.forEach(clientModel::removeProtocolMapper);
}
// We don't take already existing protocolMappers into consideration for now

View file

@ -91,7 +91,6 @@ import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -1098,10 +1097,9 @@ public class AuthenticationManager {
// todo scope param protocol independent
String scopeParam = authSession.getClientNote(OAuth2Constants.SCOPE);
Set<String> requestedClientScopes = new HashSet<String>();
for (ClientScopeModel clientScope : org.keycloak.protocol.oidc.TokenManager.getRequestedClientScopes(scopeParam, client)) {
requestedClientScopes.add(clientScope.getId());
}
Set<String> requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, client)
.map(ClientScopeModel::getId).collect(Collectors.toSet());
authSession.setClientScopes(requestedClientScopes);
}

View file

@ -48,6 +48,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -85,8 +86,7 @@ public class ClientManager {
// remove default mappers if there is a template
if (rep.getProtocolMappers() == null && rep.getClientTemplate() != null) {
Set<ProtocolMapperModel> mappers = client.getProtocolMappers();
for (ProtocolMapperModel mapper : mappers) client.removeProtocolMapper(mapper);
client.getProtocolMappersStream().collect(Collectors.toList()).forEach(client::removeProtocolMapper);
}
return client;

View file

@ -19,9 +19,8 @@ package org.keycloak.services.resources.admin;
import static org.keycloak.protocol.ProtocolMapperUtils.isEnabled;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Objects;
import java.util.stream.Stream;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
@ -114,43 +113,36 @@ public class ClientScopeEvaluateResource {
@Path("protocol-mappers")
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<ProtocolMapperEvaluationRepresentation> getGrantedProtocolMappers(@QueryParam("scope") String scopeParam) {
public Stream<ProtocolMapperEvaluationRepresentation> getGrantedProtocolMappers(@QueryParam("scope") String scopeParam) {
auth.clients().requireView(client);
List<ProtocolMapperEvaluationRepresentation> protocolMappers = new LinkedList<>();
Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client);
for (ClientScopeModel mapperContainer : clientScopes) {
Set<ProtocolMapperModel> currentMappers = mapperContainer.getProtocolMappers();
for (ProtocolMapperModel current : currentMappers) {
if (isEnabled(session, current) && current.getProtocol().equals(client.getProtocol())) {
ProtocolMapperEvaluationRepresentation rep = new ProtocolMapperEvaluationRepresentation();
rep.setMapperId(current.getId());
rep.setMapperName(current.getName());
rep.setProtocolMapper(current.getProtocolMapper());
if (mapperContainer.getId().equals(client.getId())) {
// Must be this client
rep.setContainerId(client.getId());
rep.setContainerName("");
rep.setContainerType("client");
} else {
ClientScopeModel clientScope = (ClientScopeModel) mapperContainer;
rep.setContainerId(clientScope.getId());
rep.setContainerName(clientScope.getName());
rep.setContainerType("client-scope");
}
protocolMappers.add(rep);
}
}
}
return protocolMappers;
return TokenManager.getRequestedClientScopes(scopeParam, client)
.flatMap(mapperContainer -> mapperContainer.getProtocolMappersStream()
.filter(current -> isEnabled(session, current) && Objects.equals(current.getProtocol(), client.getProtocol()))
.map(current -> toProtocolMapperEvaluationRepresentation(current, mapperContainer)));
}
private ProtocolMapperEvaluationRepresentation toProtocolMapperEvaluationRepresentation(ProtocolMapperModel mapper,
ClientScopeModel mapperContainer) {
ProtocolMapperEvaluationRepresentation rep = new ProtocolMapperEvaluationRepresentation();
rep.setMapperId(mapper.getId());
rep.setMapperName(mapper.getName());
rep.setProtocolMapper(mapper.getProtocolMapper());
if (mapperContainer.getId().equals(client.getId())) {
// Must be this client
rep.setContainerId(client.getId());
rep.setContainerName("");
rep.setContainerType("client");
} else {
ClientScopeModel clientScope = mapperContainer;
rep.setContainerId(clientScope.getId());
rep.setContainerName(clientScope.getName());
rep.setContainerType("client-scope");
}
return rep;
}
/**
* Create JSON with payload of example access token

View file

@ -17,9 +17,8 @@
package org.keycloak.services.resources.admin;
import java.util.List;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -73,12 +72,8 @@ public class ClientScopeEvaluateScopeMappingsResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public List<RoleRepresentation> getGrantedScopeMappings() {
return getGrantedRoles().stream().map((RoleModel role) -> {
return ModelToRepresentation.toBriefRepresentation(role);
}).collect(Collectors.toList());
public Stream<RoleRepresentation> getGrantedScopeMappings() {
return getGrantedRoles().map(ModelToRepresentation::toBriefRepresentation);
}
@ -93,31 +88,30 @@ public class ClientScopeEvaluateScopeMappingsResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public Stream<RoleRepresentation> getNotGrantedScopeMappings() {
Set<RoleModel> grantedRoles = getGrantedRoles();
Set<RoleModel> grantedRoles = getGrantedRoles().collect(Collectors.toSet());
return roleContainer.getRolesStream()
.filter(r -> !grantedRoles.contains(r))
.filter(((Predicate<RoleModel>) grantedRoles::contains).negate())
.map(ModelToRepresentation::toBriefRepresentation);
}
private Set<RoleModel> getGrantedRoles() {
private Stream<RoleModel> getGrantedRoles() {
if (client.isFullScopeAllowed()) {
// intentionally using deprecated method as a set is more appropriate here
return roleContainer.getRoles();
return roleContainer.getRolesStream();
}
Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client);
Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client)
.collect(Collectors.toSet());
BiPredicate<Set<ClientScopeModel>, RoleModel> hasClientScope = (scopes, role) ->
scopes.stream().anyMatch(scopeContainer -> scopeContainer.hasScope(role));
Predicate<RoleModel> hasClientScope = role ->
clientScopes.stream().anyMatch(scopeContainer -> scopeContainer.hasScope(role));
return roleContainer.getRolesStream()
.filter(auth.roles()::canView)
.filter(r -> hasClientScope.test(clientScopes, r))
.collect(Collectors.toSet());
.filter(hasClientScope);
}
}

View file

@ -49,9 +49,10 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Stream;
/**
* Base resource for managing users
@ -99,14 +100,12 @@ public class ProtocolMappersResource {
@NoCache
@Path("protocol/{protocol}")
@Produces(MediaType.APPLICATION_JSON)
public List<ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
public Stream<ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
viewPermission.require();
List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
if (isEnabled(session, mapper) && mapper.getProtocol().equals(protocol)) mappers.add(ModelToRepresentation.toRepresentation(mapper));
}
return mappers;
return client.getProtocolMappersStream()
.filter(mapper -> isEnabled(session, mapper) && Objects.equals(mapper.getProtocol(), protocol))
.map(ModelToRepresentation::toRepresentation);
}
/**
@ -163,16 +162,12 @@ public class ProtocolMappersResource {
@NoCache
@Path("models")
@Produces(MediaType.APPLICATION_JSON)
public List<ProtocolMapperRepresentation> getMappers() {
public Stream<ProtocolMapperRepresentation> getMappers() {
viewPermission.require();
List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
if (isEnabled(session, mapper)) {
mappers.add(ModelToRepresentation.toRepresentation(mapper));
}
}
return mappers;
return client.getProtocolMappersStream()
.filter(mapper -> isEnabled(session, mapper))
.map(ModelToRepresentation::toRepresentation);
}
/**

View file

@ -20,7 +20,9 @@ package org.keycloak.services.util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -81,7 +83,7 @@ public class DefaultClientSessionContext implements ClientSessionContext {
public static DefaultClientSessionContext fromClientSessionAndScopeParameter(AuthenticatedClientSessionModel clientSession, String scopeParam, KeycloakSession session) {
Set<ClientScopeModel> requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, clientSession.getClient());
Stream<ClientScopeModel> requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, clientSession.getClient());
return fromClientSessionAndClientScopes(clientSession, requestedClientScopes, session);
}
@ -91,12 +93,10 @@ public class DefaultClientSessionContext implements ClientSessionContext {
}
public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession, Set<ClientScopeModel> clientScopes, KeycloakSession session) {
Set<String> clientScopeIds = new HashSet<>();
for (ClientScopeModel clientScope : clientScopes) {
clientScopeIds.add(clientScope.getId());
}
public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession,
Stream<ClientScopeModel> clientScopes,
KeycloakSession session) {
Set<String> clientScopeIds = clientScopes.map(ClientScopeModel::getId).collect(Collectors.toSet());
return new DefaultClientSessionContext(clientSession, clientScopeIds, session);
}
@ -114,12 +114,12 @@ public class DefaultClientSessionContext implements ClientSessionContext {
@Override
public Set<ClientScopeModel> getClientScopes() {
public Stream<ClientScopeModel> getClientScopesStream() {
// Load client scopes if not yet present
if (clientScopes == null) {
clientScopes = loadClientScopes();
}
return clientScopes;
return clientScopes.stream();
}
@ -134,12 +134,12 @@ public class DefaultClientSessionContext implements ClientSessionContext {
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
// Load protocolMappers if not yet present
if (protocolMappers == null) {
protocolMappers = loadProtocolMappers();
}
return protocolMappers;
return protocolMappers.stream();
}
@ -154,28 +154,12 @@ public class DefaultClientSessionContext implements ClientSessionContext {
@Override
public String getScopeString() {
StringBuilder builder = new StringBuilder();
// Add both default and optional scopes to scope parameter. Don't add client itself
boolean first = true;
for (ClientScopeModel clientScope : getClientScopes()) {
if (clientScope instanceof ClientModel) {
continue;
}
if (!clientScope.isIncludeInTokenScope()) {
continue;
}
if (first) {
first = false;
} else {
builder.append(" ");
}
builder.append(clientScope.getName());
}
String scopeParam = builder.toString();
String scopeParam = getClientScopesStream()
.filter(((Predicate<ClientScopeModel>) ClientModel.class::isInstance).negate())
.filter(ClientScopeModel::isIncludeInTokenScope)
.map(ClientScopeModel::getName)
.collect(Collectors.joining(" "));
// See if "openid" scope is requested
String scopeSent = clientSession.getNote(OAuth2Constants.SCOPE);
@ -246,34 +230,26 @@ public class DefaultClientSessionContext implements ClientSessionContext {
private Set<RoleModel> loadRoles() {
UserModel user = clientSession.getUserSession().getUser();
ClientModel client = clientSession.getClient();
Set<ClientScopeModel> clientScopes = getClientScopes();
return TokenManager.getAccess(user, client, clientScopes);
return TokenManager.getAccess(user, client, getClientScopesStream());
}
private Set<ProtocolMapperModel> loadProtocolMappers() {
Set<ClientScopeModel> clientScopes = getClientScopes();
String protocol = clientSession.getClient().getProtocol();
// Being rather defensive. But protocol should normally always be there
if (protocol == null) {
logger.warnf("Client '%s' doesn't have protocol set. Fallback to openid-connect. Please fix client configuration", clientSession.getClient().getClientId());
logger.warnf("Client '%s' doesn't have protocol set. Fallback to openid-connect. Please fix client configuration",
clientSession.getClient().getClientId());
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
}
Set<ProtocolMapperModel> protocolMappers = new HashSet<>();
for (ClientScopeModel clientScope : clientScopes) {
Set<ProtocolMapperModel> currentMappers = clientScope.getProtocolMappers();
for (ProtocolMapperModel currentMapper : currentMappers) {
if (protocol.equals(currentMapper.getProtocol()) && ProtocolMapperUtils.isEnabled(session, currentMapper)) {
protocolMappers.add(currentMapper);
}
}
}
return protocolMappers;
String finalProtocol = protocol;
return getClientScopesStream()
.flatMap(clientScope -> clientScope.getProtocolMappersStream()
.filter(mapper -> Objects.equals(finalProtocol, mapper.getProtocol()) &&
ProtocolMapperUtils.isEnabled(session, mapper)))
.collect(Collectors.toSet());
}

View file

@ -62,13 +62,12 @@ public final class OpenshiftSAClientAdapter extends AbstractReadOnlyClientStorag
private static final String ANNOTATION_OAUTH_REDIRECT_REFERENCE = "serviceaccounts.openshift.io/oauth-redirectreference";
private static final Pattern ROLE_SCOPE_PATTERN = Pattern.compile("role:([^:]+):([^:!]+)(:[!])?");
private static final Set<String> OPTIONAL_SCOPES = Stream.of("user:info", "user:check-access").collect(Collectors.toSet());
private static final Set<ProtocolMapperModel> DEFAULT_PROTOCOL_MAPPERS = createDefaultProtocolMappers();
private static Set<ProtocolMapperModel> createDefaultProtocolMappers() {
Set<ProtocolMapperModel> mappers = new HashSet<>();
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper("username", "username", "preferred_username", "string", true, true, UserPropertyMapper.PROVIDER_ID);
ProtocolMapperModel mapper = OIDCAttributeMapperHelper.createClaimMapper("username", "username",
"preferred_username", "string", true, true, UserPropertyMapper.PROVIDER_ID);
mapper.setId(KeycloakModelUtils.generateId());
mappers.add(mapper);
@ -315,34 +314,33 @@ public final class OpenshiftSAClientAdapter extends AbstractReadOnlyClientStorag
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
return getConfigOrDefault(() -> {
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
List<ProtocolMapperRepresentation> mappers = defaultConfig.getProtocolMappers();
if (mappers == null) {
return null;
Set<ProtocolMapperModel> defaultProtocolMappers = createDefaultProtocolMappers();
defaultConfig.setProtocolMappers(defaultProtocolMappers.stream()
.map(ModelToRepresentation::toRepresentation).collect(Collectors.toList()));
return defaultProtocolMappers.stream();
}
Set<ProtocolMapperModel> model = new HashSet<>();
for (ProtocolMapperRepresentation mapper : mappers) {
model.add(RepresentationToModel.toModel(mapper));
}
return model;
}, (Consumer<Set<ProtocolMapperModel>>) mappers -> {
defaultConfig.setProtocolMappers(mappers.stream().map(ModelToRepresentation::toRepresentation).collect(Collectors.toList()));
}, (Supplier<Set<ProtocolMapperModel>>) () -> DEFAULT_PROTOCOL_MAPPERS);
return mappers.stream().map(RepresentationToModel::toModel);
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return getProtocolMappers().stream().filter(protocolMapperModel -> id.equals(protocolMapperModel.getId())).findAny().get();
return getProtocolMappersStream()
.filter(protocolMapperModel -> Objects.equals(id, protocolMapperModel.getId()))
.findAny()
.orElse(null);
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
return getProtocolMappers().stream().filter(protocolMapperModel -> name.equals(protocolMapperModel.getName())).findAny().get();
return getProtocolMappersStream()
.filter(protocolMapperModel -> Objects.equals(name, protocolMapperModel.getName()))
.findAny()
.orElse(null);
}
@Override
@ -452,8 +450,8 @@ public final class OpenshiftSAClientAdapter extends AbstractReadOnlyClientStorag
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
return DEFAULT_PROTOCOL_MAPPERS;
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return createDefaultProtocolMappers().stream();
}
@Override

View file

@ -261,8 +261,8 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
return Collections.EMPTY_SET;
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
return Stream.empty();
}
@Override

View file

@ -32,7 +32,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -52,6 +51,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
@ -108,14 +108,14 @@ public class UserStorageConsentTest extends AbstractServletsAdapterTest {
product.setConsentRequired(true);
ClientScopeModel clientScope = realm.addClientScope("clientScope");
clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
System.err.println("client scope protocol mappers size: " + clientScope.getProtocolMappers().size());
System.err.println("client scope protocol mappers size: " + clientScope.getProtocolMappersStream().count());
for (ProtocolMapperModel mapper : product.getProtocolMappers()) {
for (ProtocolMapperModel mapper : product.getProtocolMappersStream().collect(Collectors.toList())) {
if (mapper.getProtocol().equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
if (mapper.getName().equals(OIDCLoginProtocolFactory.USERNAME)
|| mapper.getName().equals(OIDCLoginProtocolFactory.EMAIL)
|| mapper.getName().equals(OIDCLoginProtocolFactory.GIVEN_NAME)
) {
) {
ProtocolMapperModel copy = new ProtocolMapperModel();
copy.setName(mapper.getName());
copy.setProtocol(mapper.getProtocol());