Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2017-01-24 07:13:52 -05:00
commit 6392405413
82 changed files with 1938 additions and 5202 deletions

View file

@ -196,10 +196,8 @@ public class OAuthRequestAuthenticator {
return sslRedirectPort;
}
protected static final AtomicLong counter = new AtomicLong();
protected String getStateCode() {
return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
return AdapterUtils.generateId();
}
protected AuthChallenge loginRedirect() {

View file

@ -253,8 +253,7 @@ public abstract class AbstractPolicyEnforcer {
}
private String getPath(Request request) {
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
return pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
return request.getRelativePath();
}
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {

View file

@ -66,6 +66,11 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
return requestContext.getUriInfo().getRequestUri().toString();
}
@Override
public String getRelativePath() {
return requestContext.getUriInfo().getPath();
}
@Override
public boolean isSecure() {
return securityContext.isSecure();

View file

@ -218,6 +218,11 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
return servletRequest.getRequestURL().toString();
}
@Override
public String getRelativePath() {
return servletRequest.getServletPath();
}
@Override
public boolean isSecure() {
return servletRequest.isSecure();

View file

@ -70,6 +70,11 @@ class WrappedHttpServletRequest implements Request {
return buf.toString();
}
@Override
public String getRelativePath() {
return request.getServletPath();
}
@Override
public boolean isSecure() {
return request.isSecure();

View file

@ -60,6 +60,7 @@ import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
import org.keycloak.saml.processing.web.util.PostBindingUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.io.IOException;
@ -70,14 +71,11 @@ import java.security.KeyManagementException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.w3c.dom.Element;
/**
*
@ -196,7 +194,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, statusResponse);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
@ -312,8 +310,25 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
protected AuthOutcome handleLoginResponse(ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
AssertionType assertion = null;
if (! isSuccessfulSamlResponse(responseType) || responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, responseType);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
}
@Override
public int getResponseCode() {
return 403;
}
};
return AuthOutcome.FAILED;
}
try {
assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
if (AssertionUtil.hasExpired(assertion)) {
@ -335,6 +350,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return 403;
}
};
return AuthOutcome.FAILED;
}
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
@ -346,7 +362,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
@ -449,6 +465,14 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return AuthOutcome.AUTHENTICATED;
}
private boolean isSuccessfulSamlResponse(ResponseType responseType) {
return responseType != null
&& responseType.getStatus() != null
&& responseType.getStatus().getStatusCode() != null
&& responseType.getStatus().getStatusCode().getValue() != null
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
}
private String getAttributeValue(Object attrValue) {
String value = null;
if (attrValue instanceof String) {

View file

@ -43,6 +43,13 @@ public interface HttpFacade {
*/
String getURI();
/**
* Get the request relative path.
*
* @return the request relative path
*/
String getRelativePath();
/**
* HTTPS?
*

View file

@ -78,6 +78,11 @@ public class JettyHttpFacade implements HttpFacade {
return buf.toString();
}
@Override
public String getRelativePath() {
return request.getServletPath();
}
@Override
public String getFirstParam(String param) {
return request.getParameter(param);

View file

@ -65,6 +65,11 @@ public class ServletHttpFacade implements HttpFacade {
return buf.toString();
}
@Override
public String getRelativePath() {
return request.getServletPath();
}
@Override
public boolean isSecure() {
return request.isSecure();

View file

@ -78,6 +78,11 @@ public class CatalinaHttpFacade implements HttpFacade {
return buf.toString();
}
@Override
public String getRelativePath() {
return request.getServletPath();
}
@Override
public boolean isSecure() {
return request.isSecure();

View file

@ -83,6 +83,11 @@ public class UndertowHttpFacade implements HttpFacade {
return uriBuilder.build().toString();
}
@Override
public String getRelativePath() {
return exchange.getRelativePath();
}
@Override
public boolean isSecure() {
String protocol = exchange.getRequestScheme();

View file

@ -181,6 +181,11 @@ public class OfflineAccessPortalServlet extends HttpServlet {
return servletRequest.getRequestURL().toString();
}
@Override
public String getRelativePath() {
return servletRequest.getServletPath();
}
@Override
public boolean isSecure() {
return servletRequest.isSecure();

View file

@ -20,11 +20,15 @@ package org.keycloak.storage.ldap;
import org.jboss.logging.Logger;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.storage.ldap.mappers.LDAPConfigDecorator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -35,7 +39,7 @@ public class LDAPIdentityStoreRegistry {
private Map<String, LDAPIdentityStoreContext> ldapStores = new ConcurrentHashMap<>();
public LDAPIdentityStore getLdapStore(ComponentModel ldapModel, Map<ComponentModel, LDAPConfigDecorator> configDecorators) {
public LDAPIdentityStore getLdapStore(KeycloakSession session, ComponentModel ldapModel, Map<ComponentModel, LDAPConfigDecorator> configDecorators) {
LDAPIdentityStoreContext context = ldapStores.get(ldapModel.getId());
// Ldap config might have changed for the realm. In this case, we must re-initialize
@ -49,7 +53,7 @@ public class LDAPIdentityStoreRegistry {
}
if (context == null || !ldapConfig.equals(context.config)) {
logLDAPConfig(ldapModel.getName(), ldapConfig);
logLDAPConfig(session, ldapModel, ldapConfig);
LDAPIdentityStore store = createLdapIdentityStore(ldapConfig);
context = new LDAPIdentityStoreContext(ldapConfig, store);
@ -59,8 +63,18 @@ public class LDAPIdentityStoreRegistry {
}
// Don't log LDAP password
private void logLDAPConfig(String fedProviderDisplayName, LDAPConfig ldapConfig) {
logger.infof("Creating new LDAP Store for the LDAP storage provider: '%s', LDAP Configuration: %s", fedProviderDisplayName, ldapConfig.toString());
private void logLDAPConfig(KeycloakSession session, ComponentModel ldapModel, LDAPConfig ldapConfig) {
logger.infof("Creating new LDAP Store for the LDAP storage provider: '%s', LDAP Configuration: %s", ldapModel.getName(), ldapConfig.toString());
if (logger.isDebugEnabled()) {
RealmModel realm = session.realms().getRealm(ldapModel.getParentId());
List<ComponentModel> mappers = realm.getComponents(ldapModel.getId());
mappers.stream().forEach((ComponentModel c) -> {
logger.debugf("Mapper for provider: %s, Mapper name: %s, Provider: %s, Mapper configuration: %s", ldapModel.getName(), c.getName(), c.getProviderId(), c.getConfig().toString());
});
}
}
/**

View file

@ -92,6 +92,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
protected LDAPProviderKerberosConfig kerberosConfig;
protected PasswordUpdateCallback updater;
protected LDAPStorageMapperManager mapperManager;
protected LDAPStorageUserManager userManager;
protected final Set<String> supportedCredentialTypes = new HashSet<>();
@ -103,6 +104,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
this.editMode = ldapIdentityStore.getConfig().getEditMode();
this.mapperManager = new LDAPStorageMapperManager(this);
this.userManager = new LDAPStorageUserManager(this);
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
if (kerberosConfig.isAllowKerberosAuthentication()) {
@ -134,6 +136,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
return mapperManager;
}
public LDAPStorageUserManager getUserManager() {
return userManager;
}
@Override
public UserModel validate(RealmModel realm, UserModel local) {
LDAPObject ldapObject = loadAndValidateUser(realm, local);
@ -145,6 +152,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
}
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
UserModel existing = userManager.getManagedProxiedUser(local.getId());
if (existing != null) {
return existing;
}
UserModel proxied = local;
checkDNChanged(realm, local, ldapObject);
@ -167,6 +179,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
proxied = ldapMapper.proxy(ldapObject, proxied, realm);
}
userManager.setManagedProxiedUser(proxied, ldapObject);
return proxied;
}
@ -227,6 +241,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
}
ldapIdentityStore.remove(ldapObject);
userManager.removeManagedUserEntry(user.getId());
return true;
}
@ -385,6 +401,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
* @return ldapUser corresponding to local user or null if user is no longer in LDAP
*/
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
LDAPObject existing = userManager.getManagedLDAPUser(local.getId());
if (existing != null) {
return existing;
}
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
if (ldapUser == null) {
return null;

View file

@ -192,7 +192,7 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
public LDAPStorageProvider create(KeycloakSession session, ComponentModel model) {
Map<ComponentModel, LDAPConfigDecorator> configDecorators = getLDAPConfigDecorators(session, model);
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model, configDecorators);
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(session, model, configDecorators);
return new LDAPStorageProvider(this, session, model, ldapIdentityStore);
}

View file

@ -0,0 +1,103 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.ldap;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.models.UserModel;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.mappers.LDAPTransaction;
/**
* Track which LDAP users were already enlisted during this transaction
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPStorageUserManager {
private final Map<String, ManagedUserEntry> managedUsers = new HashMap<>();
private final LDAPStorageProvider provider;
public LDAPStorageUserManager(LDAPStorageProvider provider) {
this.provider = provider;
}
public UserModel getManagedProxiedUser(String userId) {
ManagedUserEntry entry = managedUsers.get(userId);
return entry==null ? null : entry.getManagedProxiedUser();
}
public LDAPObject getManagedLDAPUser(String userId) {
ManagedUserEntry entry = managedUsers.get(userId);
return entry==null ? null : entry.getLdapUser();
}
public LDAPTransaction getTransaction(String userId) {
ManagedUserEntry entry = managedUsers.get(userId);
if (entry == null) {
throw new IllegalStateException("Shouldn't happen to not have entry for userId: " + userId);
}
return entry.getLdapTransaction();
}
public void setManagedProxiedUser(UserModel proxiedUser, LDAPObject ldapObject) {
String userId = proxiedUser.getId();
ManagedUserEntry entry = managedUsers.get(userId);
if (entry != null) {
throw new IllegalStateException("Don't expect to have entry for user " + userId);
}
LDAPTransaction ldapTransaction = new LDAPTransaction(provider, ldapObject);
ManagedUserEntry newEntry = new ManagedUserEntry(proxiedUser, ldapObject, ldapTransaction);
managedUsers.put(userId, newEntry);
}
public void removeManagedUserEntry(String userId) {
managedUsers.remove(userId);
}
private static class ManagedUserEntry {
private final UserModel managedProxiedUser;
private final LDAPObject ldapUser;
private final LDAPTransaction ldapTransaction;
public ManagedUserEntry(UserModel managedProxiedUser, LDAPObject ldapUser, LDAPTransaction ldapTransaction) {
this.managedProxiedUser = managedProxiedUser;
this.ldapUser = ldapUser;
this.ldapTransaction = ldapTransaction;
}
public UserModel getManagedProxiedUser() {
return managedProxiedUser;
}
public LDAPObject getLdapUser() {
return ldapUser;
}
public LDAPTransaction getLdapTransaction() {
return ldapTransaction;
}
}
}

View file

@ -27,7 +27,15 @@ import java.util.LinkedList;
*/
public class LDAPDn {
private final Deque<Entry> entries = new LinkedList<>();
private final Deque<Entry> entries;
private LDAPDn() {
this.entries = new LinkedList<>();
}
private LDAPDn(Deque<Entry> entries) {
this.entries = entries;
}
public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn();
@ -115,12 +123,14 @@ public class LDAPDn {
/**
*
* @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
* @return DN like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org".
* Returned DN will be new clone not related to the original DN instance.
*
*/
public String getParentDn() {
public LDAPDn getParentDn() {
LinkedList<Entry> parentDnEntries = new LinkedList<>(entries);
parentDnEntries.remove();
return toString(parentDnEntries);
return new LDAPDn(parentDnEntries);
}
public boolean isDescendantOf(LDAPDn expectedParentDn) {

View file

@ -103,6 +103,8 @@ public class LDAPIdentityStore implements IdentityStore {
@Override
public void update(LDAPObject ldapObject) {
checkRename(ldapObject);
BasicAttributes updatedAttributes = extractAttributes(ldapObject, false);
NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
@ -114,6 +116,33 @@ public class LDAPIdentityStore implements IdentityStore {
}
}
protected void checkRename(LDAPObject ldapObject) {
String rdnAttrName = ldapObject.getRdnAttributeName();
if (ldapObject.getReadOnlyAttributeNames().contains(rdnAttrName.toLowerCase())) {
return;
}
String rdnAttrVal = ldapObject.getAttributeAsString(rdnAttrName);
String oldRdnAttrVal = ldapObject.getDn().getFirstRdnAttrValue();
if (!oldRdnAttrVal.equals(rdnAttrVal)) {
LDAPDn newLdapDn = ldapObject.getDn().getParentDn();
newLdapDn.addFirst(rdnAttrName, rdnAttrVal);
String oldDn = ldapObject.getDn().toString();
String newDn = newLdapDn.toString();
if (logger.isDebugEnabled()) {
logger.debugf("Renaming LDAP Object. Old DN: [%s], New DN: [%s]", oldDn, newDn);
}
// In case, that there is conflict (For example already existing "CN=John Anthony"), the different DN is returned
newDn = this.operationManager.renameEntry(oldDn, newDn, true);
ldapObject.setDn(LDAPDn.fromString(newDn));
}
}
@Override
public void remove(LDAPObject ldapObject) {
this.operationManager.removeEntry(ldapObject.getDn().toString());

View file

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.idm.model.LDAPDn;
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
@ -28,6 +29,7 @@ import javax.naming.AuthenticationException;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
@ -158,6 +160,64 @@ public class LDAPOperationManager {
}
}
/**
* Rename LDAPObject name (DN)
*
* @param oldDn
* @param newDn
* @param fallback With fallback=true, we will try to find the another DN in case of conflict. For example if there is an
* attempt to rename to "CN=John Doe", but there is already existing "CN=John Doe", we will try "CN=John Doe0"
* @return the non-conflicting DN, which was used in the end
*/
public String renameEntry(String oldDn, String newDn, boolean fallback) {
try {
String newNonConflictingDn = execute(new LdapOperation<String>() {
@Override
public String execute(LdapContext context) throws NamingException {
String dn = newDn;
// Max 5 attempts for now
int max = 5;
for (int i=0 ; i<max ; i++) {
try {
context.rename(oldDn, dn);
return dn;
} catch (NameAlreadyBoundException ex) {
if (!fallback) {
throw ex;
} else {
String failedDn = dn;
if (i<max) {
dn = findNextDNForFallback(newDn, i);
logger.warnf("Failed to rename DN [%s] to [%s]. Will try to fallback to DN [%s]", oldDn, failedDn, dn);
} else {
logger.warnf("Failed all fallbacks for renaming [%s]", oldDn);
throw ex;
}
}
}
}
throw new ModelException("Could not rename entry from DN [" + oldDn + "] to new DN [" + newDn + "]. All fallbacks failed");
}
});
return newNonConflictingDn;
} catch (NamingException e) {
throw new ModelException("Could not rename entry from DN [" + oldDn + "] to new DN [" + newDn + "]", e);
}
}
private String findNextDNForFallback(String newDn, int counter) {
LDAPDn dn = LDAPDn.fromString(newDn);
String rdnAttrName = dn.getFirstRdnAttrName();
String rdnAttrVal = dn.getFirstRdnAttrValue();
LDAPDn parentDn = dn.getParentDn();
parentDn.addFirst(rdnAttrName, rdnAttrVal + counter);
return parentDn.toString();
}
public List<SearchResult> search(final String baseDN, final String filter, Collection<String> returningAttributes, int searchScope) throws NamingException {
final List<SearchResult> result = new ArrayList<SearchResult>();
final SearchControls cons = getSearchControls(returningAttributes, searchScope);
@ -394,6 +454,11 @@ public class LDAPOperationManager {
values = "No values";
}
String attrName = item.getAttribute().getID().toUpperCase();
if (attrName.contains("PASSWORD") || attrName.contains("UNICODEPWD")) {
values = "********************";
}
logger.tracef(" Op [%s]: %s = %s", item.getModificationOp(), item.getAttribute().getID(), values);
}
@ -540,7 +605,11 @@ public class LDAPOperationManager {
}
if (logger.isDebugEnabled()) {
logger.debugf("Creating LdapContext using properties: [%s]", env);
Map<String, Object> copyEnv = new HashMap<>(env);
if (copyEnv.containsKey(Context.SECURITY_CREDENTIALS)) {
copyEnv.put(Context.SECURITY_CREDENTIALS, "**************************************");
}
logger.debugf("Creating LdapContext using properties: [%s]", copyEnv);
}
return env;

View file

@ -75,7 +75,7 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
@Override
public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
String ldapFullNameAttrName = getLdapFullNameAttrName();
String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
String fullName = getFullNameForWriteToLDAP(localUser.getFirstName(), localUser.getLastName(), localUser.getUsername());
ldapUser.setSingleAttribute(ldapFullNameAttrName, fullName);
if (isReadOnly()) {
@ -103,7 +103,7 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
}
private void setFullNameToLDAPObject() {
String fullName = getFullName(getFirstName(), getLastName());
String fullName = getFullNameForWriteToLDAP(getFirstName(), getLastName(), getUsername());
if (logger.isTraceEnabled()) {
logger.tracef("Pushing full name attribute to LDAP. Full name: %s", fullName);
}
@ -177,18 +177,24 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
return ldapFullNameAttrName == null ? LDAPConstants.CN : ldapFullNameAttrName;
}
protected String getFullName(String firstName, String lastName) {
if (firstName != null && lastName != null) {
protected String getFullNameForWriteToLDAP(String firstName, String lastName, String username) {
if (!isBlank(firstName) && !isBlank(lastName)) {
return firstName + " " + lastName;
} else if (firstName != null) {
} else if (!isBlank(firstName)) {
return firstName;
} else if (lastName != null) {
} else if (!isBlank(lastName)) {
return lastName;
} else if (isFallbackToUsername()) {
return username;
} else {
return LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
}
}
private boolean isBlank(String attr) {
return attr == null || attr.trim().isEmpty();
}
private boolean isReadOnly() {
return parseBooleanParameter(mapperModel, READ_ONLY);
}
@ -196,4 +202,11 @@ public class FullNameLDAPStorageMapper extends AbstractLDAPStorageMapper {
private boolean isWriteOnly() {
return parseBooleanParameter(mapperModel, WRITE_ONLY);
}
// Used just in case that we have Writable LDAP and fullName is mapped to "cn", which is used as RDN (this typically happens only on MSAD)
private boolean isFallbackToUsername() {
String rdnLdapAttrConfig = getLdapProvider().getLdapIdentityStore().getConfig().getRdnLdapAttribute();
return !isReadOnly() && getLdapFullNameAttrName().equalsIgnoreCase(rdnLdapAttrConfig);
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.ldap.mappers;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPTransaction implements KeycloakTransaction {
public static final Logger logger = Logger.getLogger(LDAPTransaction.class);
protected TransactionState state = TransactionState.NOT_STARTED;
private final LDAPStorageProvider ldapProvider;
private final LDAPObject ldapUser;
public LDAPTransaction(LDAPStorageProvider ldapProvider, LDAPObject ldapUser) {
this.ldapProvider = ldapProvider;
this.ldapUser = ldapUser;
}
@Override
public void begin() {
if (state != TransactionState.NOT_STARTED) {
throw new IllegalStateException("Transaction already started");
}
state = TransactionState.STARTED;
}
@Override
public void commit() {
if (state != TransactionState.STARTED) {
throw new IllegalStateException("Transaction in illegal state for commit: " + state);
}
if (logger.isTraceEnabled()) {
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
}
ldapProvider.getLdapIdentityStore().update(ldapUser);
state = TransactionState.FINISHED;
}
@Override
public void rollback() {
if (state != TransactionState.STARTED && state != TransactionState.ROLLBACK_ONLY) {
throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
}
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
state = TransactionState.FINISHED;
}
@Override
public void setRollbackOnly() {
state = TransactionState.ROLLBACK_ONLY;
}
@Override
public boolean getRollbackOnly() {
return state == TransactionState.ROLLBACK_ONLY;
}
@Override
public boolean isActive() {
return state == TransactionState.STARTED || state == TransactionState.ROLLBACK_ONLY;
}
protected enum TransactionState {
NOT_STARTED, STARTED, ROLLBACK_ONLY, FINISHED
}
}

View file

@ -18,7 +18,6 @@
package org.keycloak.storage.ldap.mappers;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
import org.keycloak.storage.ldap.LDAPStorageProvider;
@ -33,39 +32,16 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
protected LDAPStorageProvider provider;
protected LDAPObject ldapUser;
private final LDAPTransaction transaction;
public TxAwareLDAPUserModelDelegate(UserModel delegate, LDAPStorageProvider provider, LDAPObject ldapUser) {
super(delegate);
this.provider = provider;
this.ldapUser = ldapUser;
this.transaction = findOrCreateTransaction();
}
public LDAPTransaction getTransaction() {
return transaction;
}
// Try to find transaction in any delegate. We want to enlist just single transaction per all delegates
private LDAPTransaction findOrCreateTransaction() {
UserModelDelegate delegate = this;
while (true) {
UserModel deleg = delegate.getDelegate();
if (!(deleg instanceof UserModelDelegate)) {
return new LDAPTransaction();
} else {
delegate = (UserModelDelegate) deleg;
}
if (delegate instanceof TxAwareLDAPUserModelDelegate) {
TxAwareLDAPUserModelDelegate txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
return txDelegate.getTransaction();
}
}
}
protected void ensureTransactionStarted() {
if (transaction.state == TransactionState.NOT_STARTED) {
LDAPTransaction transaction = provider.getUserManager().getTransaction(getId());
if (transaction.state == LDAPTransaction.TransactionState.NOT_STARTED) {
if (logger.isTraceEnabled()) {
logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString());
}
@ -74,63 +50,4 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate {
}
}
protected class LDAPTransaction implements KeycloakTransaction {
protected TransactionState state = TransactionState.NOT_STARTED;
@Override
public void begin() {
if (state != TransactionState.NOT_STARTED) {
throw new IllegalStateException("Transaction already started");
}
state = TransactionState.STARTED;
}
@Override
public void commit() {
if (state != TransactionState.STARTED) {
throw new IllegalStateException("Transaction in illegal state for commit: " + state);
}
if (logger.isTraceEnabled()) {
logger.trace("Transaction commit! Updating LDAP attributes for object " + ldapUser.getDn().toString() + ", attributes: " + ldapUser.getAttributes());
}
provider.getLdapIdentityStore().update(ldapUser);
state = TransactionState.FINISHED;
}
@Override
public void rollback() {
if (state != TransactionState.STARTED && state != TransactionState.ROLLBACK_ONLY) {
throw new IllegalStateException("Transaction in illegal state for rollback: " + state);
}
logger.warn("Transaction rollback! Ignoring LDAP updates for object " + ldapUser.getDn().toString());
state = TransactionState.FINISHED;
}
@Override
public void setRollbackOnly() {
state = TransactionState.ROLLBACK_ONLY;
}
@Override
public boolean getRollbackOnly() {
return state == TransactionState.ROLLBACK_ONLY;
}
@Override
public boolean isActive() {
return state == TransactionState.STARTED || state == TransactionState.ROLLBACK_ONLY;
}
}
protected enum TransactionState {
NOT_STARTED, STARTED, ROLLBACK_ONLY, FINISHED
}
}

View file

@ -33,6 +33,7 @@ import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
import org.keycloak.storage.ldap.mappers.TxAwareLDAPUserModelDelegate;
import javax.naming.AuthenticationException;
import java.util.HashSet;
@ -101,7 +102,7 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
control.remove(UserAccountControl.ACCOUNTDISABLE);
}
updateUserAccountControl(ldapUser, control);
updateUserAccountControl(true, ldapUser, control);
}
@Override
@ -142,11 +143,15 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
if (ldapProvider.getEditMode() == UserStorageProvider.EditMode.WRITABLE) {
if (errorCode.equals("532") || errorCode.equals("773")) {
// User needs to change his MSAD password. Allow him to login, but add UPDATE_PASSWORD required action
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
if (!user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_PASSWORD.name())) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}
return true;
} else if (errorCode.equals("533")) {
// User is disabled in MSAD. Set him to disabled in KC as well
user.setEnabled(false);
if (user.isEnabled()) {
user.setEnabled(false);
}
return true;
} else if (errorCode.equals("775")) {
logger.warnf("Locked user '%s' attempt to login", user.getUsername());
@ -187,23 +192,26 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
return new UserAccountControl(longValue);
}
// Update user in LDAP
protected void updateUserAccountControl(LDAPObject ldapUser, UserAccountControl accountControl) {
// Update user in LDAP if "updateInLDAP" is true. Otherwise it is assumed that LDAP update will be called at the end of transaction
protected void updateUserAccountControl(boolean updateInLDAP, LDAPObject ldapUser, UserAccountControl accountControl) {
String userAccountControlValue = String.valueOf(accountControl.getValue());
logger.debugf("Updating userAccountControl of user '%s' to value '%s'", ldapUser.getDn().toString(), userAccountControlValue);
ldapUser.setSingleAttribute(LDAPConstants.USER_ACCOUNT_CONTROL, userAccountControlValue);
ldapProvider.getLdapIdentityStore().update(ldapUser);
if (updateInLDAP) {
ldapProvider.getLdapIdentityStore().update(ldapUser);
}
}
public class MSADUserModelDelegate extends UserModelDelegate {
public class MSADUserModelDelegate extends TxAwareLDAPUserModelDelegate {
private final LDAPObject ldapUser;
public MSADUserModelDelegate(UserModel delegate, LDAPObject ldapUser) {
super(delegate);
super(delegate, ldapProvider, ldapUser);
this.ldapUser = ldapUser;
}
@ -235,7 +243,9 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
control.add(UserAccountControl.ACCOUNTDISABLE);
}
updateUserAccountControl(ldapUser, control);
ensureTransactionStarted();
updateUserAccountControl(false, ldapUser, control);
}
}
@ -257,7 +267,8 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
ldapUser.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "0");
ldapProvider.getLdapIdentityStore().update(ldapUser);
ensureTransactionStarted();
}
}
@ -283,7 +294,8 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
ldapUser.removeReadOnlyAttributeName(LDAPConstants.PWD_LAST_SET);
ldapUser.setSingleAttribute(LDAPConstants.PWD_LAST_SET, "-1");
ldapProvider.getLdapIdentityStore().update(ldapUser);
ensureTransactionStarted();
}
}
}

View file

@ -48,7 +48,7 @@ public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStor
return ProviderConfigurationBuilder.create()
.property().name(MSADUserAccountControlStorageMapper.LDAP_PASSWORD_POLICY_HINTS_ENABLED)
.label("Password Policy Hints Enabled")
.helpText("Applicable just for writable MSAD. If on, then updating password in MSAD will use LDAP_SERVER_POLICY_HINTS_OID " +
.helpText("Applicable just for writable MSAD. If on, then updating password of MSAD user will use LDAP_SERVER_POLICY_HINTS_OID " +
"extension, which means that advanced MSAD password policies like 'password history' or 'minimal password age' will be applied. This extension works just for MSAD 2008 R2 or newer.")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")

View file

@ -133,11 +133,15 @@ public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageM
if (ldapProvider.getEditMode() == UserStorageProvider.EditMode.WRITABLE) {
if (errorCode.equals("532") || errorCode.equals("773")) {
// User needs to change his MSAD password. Allow him to login, but add UPDATE_PASSWORD required action
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
if (!user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_PASSWORD.name())) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}
return true;
} else if (errorCode.equals("533")) {
// User is disabled in MSAD LDS. Set him to disabled in KC as well
user.setEnabled(false);
if (user.isEnabled()) {
user.setEnabled(false);
}
return true;
} else if (errorCode.equals("775")) {
logger.warnf("Locked user '%s' attempt to login", user.getUsername());

View file

@ -35,7 +35,7 @@ public class LDAPDnTest {
Assert.assertEquals("uid=Johny\\,Depp\\+Pepp\\\\Foo,ou=People,dc=keycloak,dc=org", dn.toString());
Assert.assertEquals(LDAPDn.fromString("uid=Johny\\,Depp\\+Pepp\\\\Foo,ou=People,dc=keycloak,dc=org"), dn);
Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn());
Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn().toString());
Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=keycloak, dc=org")));
Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=org")));
@ -46,4 +46,22 @@ public class LDAPDnTest {
Assert.assertEquals("uid", dn.getFirstRdnAttrName());
Assert.assertEquals("Johny,Depp+Pepp\\Foo", dn.getFirstRdnAttrValue());
}
@Test
public void testCorrectEscape() throws Exception {
LDAPDn dn = LDAPDn.fromString("dc=keycloak, dc=org");
dn.addFirst("cn", "Johny,Džýa Foo");
Assert.assertEquals("cn=Johny\\,Džýa Foo,dc=keycloak,dc=org", dn.toString());
Assert.assertEquals("Johny,Džýa Foo", dn.getFirstRdnAttrValue());
dn = LDAPDn.fromString("dc=keycloak, dc=org");
dn.addFirst("cn", "Johny,Džýa Foo ");
Assert.assertEquals("cn=Johny\\,Džýa Foo\\ ,dc=keycloak,dc=org", dn.toString());
Assert.assertEquals("Johny,Džýa Foo ", dn.getFirstRdnAttrValue());
dn = LDAPDn.fromString("dc=keycloak, dc=org");
dn.addFirst("cn", "Johny,Džýa ");
Assert.assertEquals("cn=Johny\\,Džýa\\ ,dc=keycloak,dc=org", dn.toString());
Assert.assertEquals("Johny,Džýa ", dn.getFirstRdnAttrValue());
}
}

View file

@ -616,7 +616,7 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public RoleModel addRealmRole(RealmModel realm, String id, String name) {
RoleModel role = getDelegate().addRealmRole(realm, name);
RoleModel role = getDelegate().addRealmRole(realm, id, name);
addedRole(role.getId(), realm.getId());
return role;
}

View file

@ -176,7 +176,7 @@ public class RoleAdapter implements RoleModel {
@Override
public boolean hasRole(RoleModel role) {
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>());
}
@Override

View file

@ -55,7 +55,7 @@ public class PersistenceExceptionConverter implements InvocationHandler {
public static ModelException convert(Throwable t) {
if (t.getCause() != null && t.getCause() instanceof ConstraintViolationException) {
throw new ModelDuplicateException(t);
} if (t instanceof EntityExistsException) {
} if (t instanceof EntityExistsException || t instanceof ConstraintViolationException) {
throw new ModelDuplicateException(t);
} else {
throw new ModelException(t);

View file

@ -128,7 +128,7 @@ public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
@Override
public boolean hasRole(RoleModel role) {
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>());
}
@Override

View file

@ -172,7 +172,7 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
@Override
public boolean hasRole(RoleModel role) {
return this.equals(role) || KeycloakModelUtils.searchFor(role, this);
return this.equals(role) || KeycloakModelUtils.searchFor(role, this, new HashSet<>());
}
public MongoRoleEntity getRole() {

View file

@ -29,7 +29,7 @@ public enum JBossSAMLConstants {
"AssertionConsumerService"), ASSERTION_CONSUMER_SERVICE_URL("AssertionConsumerServiceURL"), ASSERTION_CONSUMER_SERVICE_INDEX(
"AssertionConsumerServiceIndex"), ASSERTION_ID_REQUEST_SERVICE("AssertionIDRequestService"), ATTRIBUTE("Attribute"), ATTRIBUTE_QUERY(
"AttributeQuery"), ATTRIBUTE_AUTHORITY_DESCRIPTOR("AttributeAuthorityDescriptor"), ATTRIBUTE_CONSUMING_SERVICE(
"AttributeConsumingService"), ATTRIBUTE_CONSUMING_SERVICE_INDEX("AttributeConsumingServiceIndex"), ATTRIBUTE_SERVICE(
"AttributeConsumingService"), ATTRIBUTE_CONSUMING_SERVICE_INDEX("AttributeConsumingServiceIndex"), ATTRIBUTE_PROFILE("AttributeProfile"), ATTRIBUTE_SERVICE(
"AttributeService"), ATTRIBUTE_STATEMENT("AttributeStatement"), ATTRIBUTE_VALUE("AttributeValue"), AUDIENCE(
"Audience"), AUDIENCE_RESTRICTION("AudienceRestriction"), AUTHN_CONTEXT("AuthnContext"), AUTHENTICATING_AUTHORITY(
"AuthenticatingAuthority"), AUTHN_AUTHORITY_DESCRIPTOR("AuthnAuthorityDescriptor"), AUTHN_CONTEXT_CLASS_REF(

View file

@ -22,6 +22,7 @@ import java.util.List;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
@ -66,7 +67,7 @@ public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBui
public Document buildDocument() throws ProcessingException {
try {
StatusResponseType statusResponse = new StatusResponseType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
StatusResponseType statusResponse = new ResponseType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
statusResponse.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
NameIDType issuer = new NameIDType();

View file

@ -363,6 +363,9 @@ public class SAMLEntityDescriptorParser extends AbstractDescriptorParser impleme
StaxParserUtil.validate(endElement, JBossSAMLConstants.ATTRIBUTE_SERVICE.get());
attributeAuthority.addAttributeService(endpoint);
} else if (JBossSAMLConstants.ATTRIBUTE_PROFILE.get().equalsIgnoreCase(localPart)) {
startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
attributeAuthority.addAttributeProfile(StaxParserUtil.getElementText(xmlEventReader));
} else if (JBossSAMLConstants.KEY_DESCRIPTOR.get().equalsIgnoreCase(localPart)) {
attributeAuthority.addKeyDescriptor(parseKeyDescriptor(xmlEventReader));
} else if (JBossSAMLConstants.NAMEID_FORMAT.get().equalsIgnoreCase(localPart)) {

View file

@ -155,4 +155,12 @@ public class SAMLParserTest {
assertThat(parsedObject, instanceOf(EntityDescriptorType.class));
}
}
@Test
public void testAttributeProfileMetadata() throws Exception {
try (InputStream st = SAMLParserTest.class.getResourceAsStream("KEYCLOAK-4236-AttributeProfile-element.xml")) {
Object parsedObject = parser.parse(st);
assertThat(parsedObject, instanceOf(EntityDescriptorType.class));
}
}
}

View file

@ -0,0 +1,180 @@
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:enc="http://www.w3.org/2001/04/xmlenc#" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:mdext="urn:oasis:names:tc:SAML:metadata:extension" xmlns:ns10="urn:oasis:names:tc:SAML:profiles:v1metadata" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="id-x-io4poU3tSqnNHhcDCTgbsMAMYMc-DQFWTm61QB" cacheDuration="P30DT0H0M0S" entityID="http://host.localdomain:14100/oam/fed" validUntil="2025-09-05T15:21:38Z">
<dsig:Signature>
<dsig:SignedInfo>
<dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<dsig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<dsig:Reference URI="#id-x-io4poU3tSqnNHhcDCTgbsMAMYMc-DQFWTm61QB">
<dsig:Transforms>
<dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<dsig:DigestValue>XKGk9TDAD9Exf4cz5B/HN4WyuII=</dsig:DigestValue>
</dsig:Reference>
</dsig:SignedInfo>
<dsig:SignatureValue>
C9dJFysqd2DsRSshxU8TIuqo1ECN5ASx6m8wT1sXxuBjQ1eitkgTs0ufC8P/t1aewOaDtg955+HTFnuOhV2r+rjoo8MY6Vrfdb14sj5UkTRU8Bv+ktnaPlBv+hKBVSwBVUwruSraTSaka7N42MfpteHupZGOcbeA3dSde/qg1AQ=
</dsig:SignatureValue>
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</dsig:Signature>
<md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</md:KeyDescriptor>
<md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://host.localdomain:14100/oamfed/idp/soap" index="1" isDefault="true"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://host.localdomain:14100/oamfed/idp/samlv20" ResponseLocation="http://host.localdomain:14100/oamfed/idp/samlv20"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://host.localdomain:14100/oamfed/idp/samlv20" ResponseLocation="http://host.localdomain:14100/oamfed/idp/samlv20"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://host.localdomain:14100/oamfed/idp/samlv20"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://host.localdomain:14100/oamfed/idp/soap"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://host.localdomain:14100/oamfed/idp/samlv20"/>
</md:IDPSSODescriptor>
<md:AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</md:KeyDescriptor>
<md:AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://host.localdomain:14100/oamfed/aa/soap"/>
<md:AttributeProfile>
urn:oasis:names:tc:SAML:2.0:profiles:attribute:basic
</md:AttributeProfile>
</md:AttributeAuthorityDescriptor>
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://host.localdomain:14100/oamfed/sp/samlv20" ResponseLocation="http://host.localdomain:14100/oamfed/sp/samlv20"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://host.localdomain:14100/oamfed/sp/samlv20" ResponseLocation="http://host.localdomain:14100/oamfed/sp/samlv20"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="http://host.localdomain:14100/oam/server/fed/sp/sso" index="0" isDefault="true"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://host.localdomain:14100/oam/server/fed/sp/sso" index="1"/>
</md:SPSSODescriptor>
<md:RoleDescriptor WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" xsi:type="query:AttributeQueryDescriptorType">
<md:KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<dsig:KeyInfo>
<dsig:X509Data>
<dsig:X509Certificate>
MIIB/DCCAWWgAwIBAgIBCjANBgkqhkiG9w0BAQQFADAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wHhcNMTUwOTA4MTUyMTM4WhcNMjUwOTA1MTUyMTM4WjAjMSEwHwYDVQQDExhvYW1zZXJ2ZXJwczMubG9jYWxkb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIYXVJI+3G8AL/8sRC2BRVc9uGZudAuc/KZARTwK5+fEJywBSOnB+p+MCYjDTkCOehtK7V3UX/lXJvkQwSBaAl938RUNyW5WcOV+mi0C8yqR8VEAHL4EqnikUtOD7kysp0FNBT+Z71G6c4kJ2fszZyggiUUdjPuQHSqHFB4smfQrAgMBAAGjQDA+MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAwfYADAdBgNVHQ4EFgQUql4UpKGYI9j30VJGuJkBoTqCwjAwDQYJKoZIhvcNAQEEBQADgYEAc9du+MB7/uZDd73JX5/31naQnW0GvORIH5hszlp8c8Z7KlQzfwxLgldK5RCO61Qw10LjYARZiVm/1YhsRJ5qRWeMDfO4+soTBgMd2/dyyp25RsmEoANMToB1CWGWujlB2L/A33dU6Zbo1qtsuxhfQg1mYHd935+Xyd8j8175/mk=
</dsig:X509Certificate>
<dsig:X509IssuerSerial>
<dsig:X509IssuerName>CN=host.localdomain</dsig:X509IssuerName>
<dsig:X509SerialNumber>10</dsig:X509SerialNumber>
</dsig:X509IssuerSerial>
<dsig:X509SubjectName>CN=host.localdomain</dsig:X509SubjectName>
</dsig:X509Data>
</dsig:KeyInfo>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</md:KeyDescriptor>
</md:RoleDescriptor>
</md:EntityDescriptor>

View file

@ -177,14 +177,23 @@ public final class KeycloakModelUtils {
* @param visited set of already visited roles (used for recursion)
* @return true if "role" is descendant of "composite"
*/
public static boolean searchFor(RoleModel role, RoleModel composite) {
return composite.isComposite() && (
composite.getComposites().contains(role) ||
composite.getComposites().stream()
.filter(x -> x.isComposite() && x.hasRole(role))
public static boolean searchFor(RoleModel role, RoleModel composite, Set<String> visited) {
if (visited.contains(composite.getId())) {
return false;
}
visited.add(composite.getId());
if (!composite.isComposite()) {
return false;
}
Set<RoleModel> compositeRoles = composite.getComposites();
return compositeRoles.contains(role) ||
compositeRoles.stream()
.filter(x -> x.isComposite() && searchFor(role, x, visited))
.findFirst()
.isPresent()
);
.isPresent();
}
/**

View file

@ -19,6 +19,7 @@ package org.keycloak.broker.saml;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
@ -78,10 +79,13 @@ import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -333,6 +337,13 @@ public class SAMLEndpoint {
try {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
if (! isSuccessfulSamlResponse(responseType)) {
String statusMessage = responseType.getStatus() == null ? Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR : responseType.getStatus().getStatusMessage();
return callback.error(relayState, statusMessage);
}
if (responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
return callback.error(relayState, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
AssertionType assertion = AssertionUtil.getAssertion(responseType, keys.getPrivateKey());
SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType();
@ -395,6 +406,13 @@ public class SAMLEndpoint {
}
private boolean isSuccessfulSamlResponse(ResponseType responseType) {
return responseType != null
&& responseType.getStatus() != null
&& responseType.getStatus().getStatusCode() != null
&& responseType.getStatus().getStatusCode().getValue() != null
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
}
public Response handleSamlResponse(String samlResponse, String relayState, String clientId) {

View file

@ -0,0 +1,51 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.keys.loader;
import org.keycloak.common.util.PemUtils;
import org.keycloak.keys.PublicKeyLoader;
import java.security.PublicKey;
import java.util.*;
/**
*
* @author hmlnarik
*/
public class HardcodedPublicKeyLoader implements PublicKeyLoader {
private final String kid;
private final String pem;
public HardcodedPublicKeyLoader(String kid, String pem) {
this.kid = kid;
this.pem = pem;
}
@Override
public Map<String, PublicKey> loadKeys() throws Exception {
return Collections.unmodifiableMap(Collections.singletonMap(kid, getSavedPublicKey()));
}
protected PublicKey getSavedPublicKey() {
if (pem != null && ! pem.trim().equals("")) {
return PemUtils.decodePublicKey(pem);
} else {
return null;
}
}
}

View file

@ -21,17 +21,20 @@ import java.security.PublicKey;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.keys.PublicKeyStorageUtils;
import org.keycloak.keys.*;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PublicKeyStorageManager {
private static final Logger logger = Logger.getLogger(PublicKeyStorageManager.class);
public static PublicKey getClientPublicKey(KeycloakSession session, ClientModel client, JWSInput input) {
String kid = input.getHeader().getKeyId();
@ -44,13 +47,31 @@ public class PublicKeyStorageManager {
public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) {
boolean keyIdSetInConfiguration = idpConfig.getPublicKeySignatureVerifierKeyId() != null
&& ! idpConfig.getPublicKeySignatureVerifierKeyId().trim().isEmpty();
String kid = input.getHeader().getKeyId();
PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
String modelKey = PublicKeyStorageUtils.getIdpModelCacheKey(realm.getId(), idpConfig.getInternalId());
OIDCIdentityProviderPublicKeyLoader loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig);
PublicKeyLoader loader;
if (idpConfig.isUseJwksUrl()) {
loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig);
} else {
String pem = idpConfig.getPublicKeySignatureVerifier();
if (pem == null || pem.trim().isEmpty()) {
logger.warnf("No public key saved on identityProvider %s", idpConfig.getAlias());
return null;
}
loader = new HardcodedPublicKeyLoader(
keyIdSetInConfiguration
? idpConfig.getPublicKeySignatureVerifierKeyId().trim()
: kid, pem);
}
return keyStorage.getPublicKey(modelKey, kid, loader);
}
}

View file

@ -66,6 +66,8 @@ import org.keycloak.common.util.Time;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -287,7 +289,17 @@ public class TokenManager {
public RefreshToken toRefreshToken(KeycloakSession session, RealmModel realm, String encodedRefreshToken) throws JWSInputException, OAuthErrorException {
JWSInput jws = new JWSInput(encodedRefreshToken);
if (!RSAProvider.verify(jws, session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId()))) {
PublicKey publicKey;
// Backwards compatibility. Old offline tokens didn't have KID in the header
if (jws.getHeader().getKeyId() == null && TokenUtil.isOfflineToken(encodedRefreshToken)) {
logger.debugf("KID is null in offline token. Using the realm active key to verify token signature.");
publicKey = session.keys().getActiveRsaKey(realm).getPublicKey();
} else {
publicKey = session.keys().getRsaPublicKey(realm, jws.getHeader().getKeyId());
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
}

View file

@ -161,13 +161,15 @@ public class SamlProtocol implements LoginProtocol {
@Override
public Response sendError(ClientSessionModel clientSession, Error error) {
try {
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
ClientModel client = clientSession.getClient();
if ("true".equals(client.getAttribute(SAML_IDP_INITIATED_LOGIN))) {
if (error == Error.CANCELLED_BY_USER) {
UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
Map<String, String> params = new HashMap<>();
params.put("realm", realm.getName());
params.put("protocol", LOGIN_PROTOCOL);
params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
params.put("client", client.getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
URI redirect = builder.buildFromMap(params);
return Response.status(302).location(redirect).build();
} else {
@ -177,6 +179,27 @@ public class SamlProtocol implements LoginProtocol {
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(clientSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
try {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
SamlClient samlClient = new SamlClient(client);
KeyManager keyManager = session.keys();
if (samlClient.requiresRealmSignature()) {
KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
binding.canonicalizationMethod(canonicalization);
}
binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
}
if (samlClient.requiresEncryption()) {
PublicKey publicKey;
try {
publicKey = SamlProtocolUtils.getEncryptionValidationKey(client);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
binding.encrypt(publicKey);
}
Document document = builder.buildDocument();
return buildErrorResponse(clientSession, binding, document);
} catch (Exception e) {

View file

@ -0,0 +1,62 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.theme;
import java.util.Properties;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class TemplatingUtil {
public static String resolveVariables(String text, Properties props) {
return resolveVariables(text, props, "${", "}");
}
public static String resolveVariables(String text, Properties props, String startMarker, String endMarker) {
int e = 0;
int s = text.indexOf(startMarker);
if (s == -1) {
return text;
} else {
StringBuilder sb = new StringBuilder();
do {
if (e < s) {
sb.append(text.substring(e, s));
}
e = text.indexOf(endMarker, s + startMarker.length());
if (e != -1) {
String key = text.substring(s + startMarker.length(), e);
sb.append(props.getProperty(key, key));
e += endMarker.length();
s = text.indexOf(startMarker, e);
} else {
e = s;
break;
}
} while (s != -1);
if (e < text.length()) {
sb.append(text.substring(e));
}
return sb.toString();
}
}
}

View file

@ -17,10 +17,13 @@
package org.keycloak.theme.beans;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
import org.keycloak.theme.TemplatingUtil;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
@ -40,10 +43,27 @@ public class MessageFormatterMethod implements TemplateMethodModelEx {
@Override
public Object exec(List list) throws TemplateModelException {
if (list.size() >= 1) {
// resolve any remaining ${} expressions
List<Object> resolved = resolve(list.subList(1, list.size()));
String key = list.get(0).toString();
return new MessageFormat(messages.getProperty(key,key),locale).format(list.subList(1, list.size()).toArray());
return new MessageFormat(messages.getProperty(key,key),locale).format(resolved.toArray());
} else {
return null;
}
}
private List<Object> resolve(List<Object> list) {
ArrayList<Object> result = new ArrayList<>();
for (Object item: list) {
if (item instanceof SimpleScalar) {
item = ((SimpleScalar) item).getAsString();
}
if (item instanceof String) {
result.add(TemplatingUtil.resolveVariables((String) item, messages));
} else {
result.add(item);
}
}
return result;
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.theme.beans;
import freemarker.template.TemplateModelException;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.Locale;
import java.util.Properties;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class MessageFormatterMethodTest {
@Test
public void test() throws TemplateModelException {
Locale locale = Locale.US;
Properties properties = new Properties();
properties.setProperty("backToApplication", "Back to application");
properties.setProperty("backToClient", "Back to {0}");
properties.setProperty("client_admin-console", "Admin Console");
properties.setProperty("realm_example-realm", "Example Realm");
MessageFormatterMethod fmt = new MessageFormatterMethod(locale, properties);
String msg = (String) fmt.exec(Arrays.asList("backToClient", "${client_admin-console}"));
Assert.assertEquals("Back to Admin Console", msg);
msg = (String) fmt.exec(Arrays.asList("backToClient", "client_admin-console"));
Assert.assertEquals("Back to client_admin-console", msg);
msg = (String) fmt.exec(Arrays.asList("backToClient", "client '${client_admin-console}' from '${realm_example-realm}'."));
Assert.assertEquals("Back to client 'Admin Console' from 'Example Realm'.", msg);
}
}

View file

@ -50,8 +50,9 @@
<undertow-embedded.version>1.0.0.Alpha2</undertow-embedded.version>
<!--migration properties-->
<migration.project.version>2.2.1.Final</migration.project.version>
<migration.product.version>1.9.8.Final</migration.product.version>
<migration.70.version>1.9.8.Final</migration.70.version>
<migration.70.authz.version>2.2.1.Final</migration.70.authz.version>
<!--<migration.71.version>2.5.1.Final</migration.71.version>-->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
@ -135,9 +136,9 @@
<profiles>
<profile>
<id>test-project-migration</id>
<id>test-70-migration</id>
<properties>
<migrated.auth.server.version>${migration.project.version}</migrated.auth.server.version>
<migrated.auth.server.version>${migration.70.version}</migrated.auth.server.version>
</properties>
<build>
<pluginManagement>
@ -155,9 +156,9 @@
</build>
</profile>
<profile>
<id>test-product-migration</id>
<id>test-70-authz-migration</id>
<properties>
<migrated.auth.server.version>${migration.product.version}</migrated.auth.server.version>
<migrated.auth.server.version>${migration.70.authz.version}</migrated.auth.server.version>
</properties>
<build>
<pluginManagement>
@ -174,6 +175,26 @@
</pluginManagement>
</build>
</profile>
<!-- <profile>
<id>test-71-migration</id>
<properties>
<migrated.auth.server.version>${migration.71.version}</migrated.auth.server.version>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<migrated.auth.server.version>${migrated.auth.server.version}</migrated.auth.server.version>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>-->
</profiles>
</project>

View file

@ -28,10 +28,17 @@ public class ConsentPage extends AbstractPage {
@FindBy(id = "kc-login")
private WebElement submitButton;
@FindBy(id = "kc-cancel")
private WebElement cancelButton;
public void confirm() {
submitButton.click();
}
public void cancel() {
cancelButton.click();
}
@Override
public boolean isCurrent() {
return driver.getTitle().equalsIgnoreCase("grant access");

View file

@ -864,7 +864,7 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
Assert.assertTrue(applicationsPage.isCurrent());
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
Assert.assertThat(apps.keySet(), containsInAnyOrder("Account", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App"));
Assert.assertThat(apps.keySet(), containsInAnyOrder("Account", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}"));
AccountApplicationsPage.AppEntry accountEntry = apps.get("Account");
Assert.assertEquals(2, accountEntry.getRolesAvailable().size());
@ -951,7 +951,20 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
// When a client has a name provided, the name should be available to the back link
Assert.assertEquals("Back to " + namedClient.getName(), profilePage.getBackToApplicationLinkText());
Assert.assertEquals(namedClient.getBaseUrl(), profilePage.getBackToApplicationLinkHref());
foundClients = testRealm.clients().findByClientId("var-named-test-app");
if (foundClients.isEmpty()) {
Assert.fail("Unable to find var-named-test-app");
}
namedClient = foundClients.get(0);
driver.navigate().to(profilePage.getPath() + "?referrer=" + namedClient.getClientId());
Assert.assertTrue(profilePage.isCurrent());
// When a client has a name provided as a variable, the name should be resolved using a localized bundle and available to the back link
Assert.assertEquals("Back to Test App Named - Account", profilePage.getBackToApplicationLinkText());
Assert.assertEquals(namedClient.getBaseUrl(), profilePage.getBackToApplicationLinkHref());
foundClients = testRealm.clients().findByClientId("test-app");
if (foundClients.isEmpty()) {
Assert.fail("Unable to find test-app");

View file

@ -20,10 +20,7 @@ package org.keycloak.testsuite.broker;
import java.util.List;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.Retry;
@ -35,8 +32,6 @@ import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
import org.openqa.selenium.TimeoutException;
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
import static org.keycloak.testsuite.broker.BrokerTestTools.encodeUrl;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;

View file

@ -2,28 +2,31 @@ package org.keycloak.testsuite.broker;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.pages.ConsentPage;
import org.keycloak.testsuite.util.*;
import org.openqa.selenium.TimeoutException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
import static org.keycloak.testsuite.broker.BrokerTestConstants.USER_EMAIL;
import static org.keycloak.testsuite.broker.BrokerTestTools.*;
import static org.keycloak.testsuite.util.MailAssert.assertEmailAndGetUrl;
import org.keycloak.testsuite.util.MailServer;
import org.keycloak.testsuite.util.MailServerConfiguration;
import org.keycloak.testsuite.util.UserBuilder;
import org.jboss.arquillian.graphene.page.Page;
import static org.keycloak.testsuite.broker.BrokerTestTools.*;
public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
@ -256,6 +259,44 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
assertEquals("Account is disabled, contact admin.", errorPage.getError());
}
@Page
ConsentPage consentPage;
// KEYCLOAK-4181
@Test
public void loginWithExistingUserWithErrorFromProviderIdP() {
ClientRepresentation client = adminClient.realm(bc.providerRealmName())
.clients()
.findByClientId(bc.getIDPClientIdInProviderRealm(suiteContext))
.get(0);
adminClient.realm(bc.providerRealmName())
.clients()
.get(client.getId())
.update(ClientBuilder.edit(client).consentRequired(true).build());
driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
log.debug("Clicking social " + bc.getIDPAlias());
accountLoginPage.clickSocial(bc.getIDPAlias());
waitForPage(driver, "log in to");
Assert.assertTrue("Driver should be on the provider realm page right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
log.debug("Logging in");
accountLoginPage.login(bc.getUserLogin(), bc.getUserPassword());
driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.MINUTES);
waitForPage(driver, "grant access");
consentPage.cancel();
waitForPage(driver, "log in to");
}
protected void testSingleLogout() {
log.debug("Testing single log out");

View file

@ -42,6 +42,11 @@ public interface BrokerConfiguration {
*/
String consumerRealmName();
/**
* @return Client ID of the identity provider as set in provider realm.
*/
String getIDPClientIdInProviderRealm(SuiteContext suiteContext);
/**
* @return User login name of the brokered user
*/

View file

@ -211,6 +211,23 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
// Set key id to a valid one
cfg.setPublicKeySignatureVerifierKeyId(expectedKeyId);
updateIdentityProvider(idpRep);
logInAsUserInIDP();
assertLoggedInAccountManagement();
logoutFromRealm(bc.consumerRealmName());
// Set key id to empty
cfg.setPublicKeySignatureVerifierKeyId("");
updateIdentityProvider(idpRep);
logInAsUserInIDP();
assertLoggedInAccountManagement();
logoutFromRealm(bc.consumerRealmName());
// Unset key id
cfg.setPublicKeySignatureVerifierKeyId(null);
updateIdentityProvider(idpRep);
logInAsUserInIDP();
assertLoggedInAccountManagement();
logoutFromRealm(bc.consumerRealmName());
}

View file

@ -50,6 +50,7 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
ClientRepresentation client = new ClientRepresentation();
client.setId(CLIENT_ID);
client.setClientId(getIDPClientIdInProviderRealm(suiteContext));
client.setName(CLIENT_ID);
client.setSecret(CLIENT_SECRET);
client.setEnabled(true);
@ -123,6 +124,11 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
return USER_LOGIN;
}
@Override
public String getIDPClientIdInProviderRealm(SuiteContext suiteContext) {
return CLIENT_ID;
}
@Override
public String getUserPassword() {
return USER_PASSWORD;

View file

@ -54,7 +54,7 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(getAuthRoot(suiteContext) + "/auth/realms/" + REALM_CONS_NAME);
client.setClientId(getIDPClientIdInProviderRealm(suiteContext));
client.setEnabled(true);
client.setProtocol(IDP_SAML_PROVIDER_ID);
client.setRedirectUris(Collections.singletonList(
@ -156,6 +156,11 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
return REALM_CONS_NAME;
}
@Override
public String getIDPClientIdInProviderRealm(SuiteContext suiteContext) {
return getAuthRoot(suiteContext) + "/auth/realms/" + consumerRealmName();
}
@Override
public String getUserLogin() {
return USER_LOGIN;

View file

@ -15,8 +15,6 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
public static class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
public static final KcSamlSignedBrokerConfiguration INSTANCE = new KcSamlSignedBrokerConfiguration();
@Override
public RealmRepresentation createProviderRealm() {
RealmRepresentation realm = super.createProviderRealm();

View file

@ -17,7 +17,6 @@
package org.keycloak.testsuite.composites;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
@ -28,6 +27,7 @@ import org.keycloak.common.enums.SslRequired;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.ClientBuilder;
@ -345,4 +345,22 @@ public class CompositeRoleTest extends AbstractCompositeKeycloakTest {
Assert.assertEquals(200, refreshResponse.getStatusCode());
}
// KEYCLOAK-4274
@Test
public void testRecursiveComposites() throws Exception {
// This will create recursive composite mappings between "REALM_COMPOSITE_1" and "REALM_ROLE_1"
RoleRepresentation realmComposite1 = testRealm().roles().get("REALM_COMPOSITE_1").toRepresentation();
testRealm().roles().get("REALM_ROLE_1").addComposites(Collections.singletonList(realmComposite1));
UserResource userResource = ApiUtil.findUserByUsernameId(testRealm(), "REALM_COMPOSITE_1_USER");
List<RoleRepresentation> realmRoles = userResource.roles().realmLevel().listEffective();
Assert.assertNames(realmRoles, "REALM_COMPOSITE_1", "REALM_ROLE_1");
userResource = ApiUtil.findUserByUsernameId(testRealm(), "REALM_ROLE_1_USER");
realmRoles = userResource.roles().realmLevel().listEffective();
Assert.assertNames(realmRoles, "REALM_COMPOSITE_1", "REALM_ROLE_1");
}
}

View file

@ -179,6 +179,9 @@ public class ExportImportTest extends AbstractExportImportTest {
List<ComponentRepresentation> components = adminClient.realm("test").components().query();
KeysMetadataRepresentation keyMetadata = adminClient.realm("test").keys().getKeyMetadata();
String sampleRealmRoleId = adminClient.realm("test").roles().get("sample-realm-role").toRepresentation().getId();
String testAppId = adminClient.realm("test").clients().findByClientId("test-app").get(0).getId();
String sampleClientRoleId = adminClient.realm("test").clients().get(testAppId).roles().get("sample-client-role").toRepresentation().getId();
// Delete some realm (and some data in admin realm)
adminClient.realm("test").remove();
@ -208,6 +211,12 @@ public class ExportImportTest extends AbstractExportImportTest {
KeysMetadataRepresentation keyMetadataImported = adminClient.realm("test").keys().getKeyMetadata();
assertEquals(keyMetadata.getActive(), keyMetadataImported.getActive());
String importedSampleRealmRoleId = adminClient.realm("test").roles().get("sample-realm-role").toRepresentation().getId();
assertEquals(sampleRealmRoleId, importedSampleRealmRoleId);
String importedSampleClientRoleId = adminClient.realm("test").clients().get(testAppId).roles().get("sample-client-role").toRepresentation().getId();
assertEquals(sampleClientRoleId, importedSampleClientRoleId);
}
private void assertAuthenticated(String realmName, String username, String password) {

View file

@ -38,7 +38,6 @@ import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertNotNull;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.exportimport.Strategy;
import static org.keycloak.testsuite.Assert.assertNames;
@ -62,25 +61,8 @@ public class LegacyImportTest extends AbstractExportImportTest {
}
@Test
public void importPreviousProject() throws Exception {
String projectVersion = System.getProperty("migration.project.version");
assertNotNull(projectVersion);
testLegacyImport(projectVersion);
}
@Test
public void importPreviousProduct() throws Exception {
String productVersion = System.getProperty("migration.product.version");
assertNotNull(productVersion);
testLegacyImport(productVersion);
}
private void testLegacyImport(String version) {
String file = "/migration-test/migration-realm-" + version + ".json";
public void testLegacyImport(String version) {
String file = "/migration-test/migration-realm-1.9.8.Final.json";
URL url = LegacyImportTest.class.getResource(file);
String targetFilePath = new File(url.getFile()).getAbsolutePath();

View file

@ -19,9 +19,13 @@ package org.keycloak.testsuite.i18n;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import java.util.List;
/**
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
@ -49,4 +53,25 @@ public class AccountPageTest extends AbstractI18NTest {
Assert.assertEquals("English", accountUpdateProfilePage.getLanguageDropdownText());
accountUpdateProfilePage.logout();
}
@Test
public void testLocalizedReferrerLinkContent() {
RealmResource testRealm = testRealm();
List<ClientRepresentation> foundClients = testRealm.clients().findByClientId("var-named-test-app");
if (foundClients.isEmpty()) {
Assert.fail("Unable to find var-named-test-app");
}
ClientRepresentation namedClient = foundClients.get(0);
driver.navigate().to(accountUpdateProfilePage.getPath() + "?referrer=" + namedClient.getClientId());
loginPage.login("test-user@localhost", "password");
Assert.assertTrue(accountUpdateProfilePage.isCurrent());
accountUpdateProfilePage.openLanguage("Deutsch");
Assert.assertEquals("Deutsch", accountUpdateProfilePage.getLanguageDropdownText());
// When a client has a name provided as a variable, the name should be resolved using a localized bundle and available to the back link
Assert.assertEquals("Zur\u00FCck zu Test App Named - Konto", accountUpdateProfilePage.getBackToApplicationLinkText());
Assert.assertEquals(namedClient.getBaseUrl(), accountUpdateProfilePage.getBackToApplicationLinkHref());
}
}

View file

@ -17,38 +17,37 @@
package org.keycloak.testsuite.migration;
import java.util.HashSet;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.component.PrioritizedComponentModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.LDAPConstants;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.migration.Migration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.NotFoundException;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.component.PrioritizedComponentModel;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientTemplateRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.migration.Migration;
import static org.keycloak.testsuite.Assert.assertEquals;
import static org.keycloak.testsuite.Assert.assertFalse;
@ -64,7 +63,7 @@ public class MigrationTest extends AbstractKeycloakTest {
public static final String MIGRATION = "Migration";
public static final String MIGRATION2 = "Migration2";
public static final String MIGRATION3 = "authorization";
private RealmResource migrationRealm;
private RealmResource migrationRealm2;
private RealmResource migrationRealm3;
@ -79,11 +78,21 @@ public class MigrationTest extends AbstractKeycloakTest {
public void beforeMigrationTest() {
migrationRealm = adminClient.realms().realm(MIGRATION);
migrationRealm2 = adminClient.realms().realm(MIGRATION2);
migrationRealm3 = adminClient.realms().realm(MIGRATION3);
migrationRealm3 = adminClient.realms().realm("authorization");
masterRealm = adminClient.realms().realm(MASTER);
//add migration realm to testRealmReps to make the migration removed after test
testRealmReps.add(adminClient.realms().realm(MIGRATION).toRepresentation());
//add migration realms to testRealmReps to make them removed after test
addTestRealmToTestRealmReps(migrationRealm);
addTestRealmToTestRealmReps(migrationRealm2);
addTestRealmToTestRealmReps(migrationRealm3);
}
private void addTestRealmToTestRealmReps(RealmResource realm) {
try {
testRealmReps.add(realm.toRepresentation());
} catch (NotFoundException ex) {
}
}
@Test
@ -95,18 +104,16 @@ public class MigrationTest extends AbstractKeycloakTest {
testMigrationTo2_2_0();
testMigrationTo2_3_0();
testMigrationTo2_5_0();
testLdapKerberosMigration_2_5_0();
}
@Test
@Migration(versionFrom = "2.2.1.Final")
public void migration2_2_1Test() {
testMigrationTo2_3_0();
testMigrationTo2_5_0();
testMigrationTo2_5_1();
public void migrationInAuthorizationServicesTest() {
testDroolsToRulesPolicyTypeMigration();
}
private void testMigratedData() {
log.info("testing migrated data");
//master realm
assertNames(masterRealm.roles().list(), "offline_access", "uma_authorization", "create-realm", "master-test-realm-role", "admin");
assertNames(masterRealm.clients().findAll(), "admin-cli", "security-admin-console", "broker", "account",
@ -155,7 +162,15 @@ public class MigrationTest extends AbstractKeycloakTest {
testExtractRealmKeys(masterRealm, migrationRealm);
}
private void testMigrationTo2_5_0() {
testLdapKerberosMigration_2_5_0();
//https://github.com/keycloak/keycloak/pull/3630
testDuplicateEmailSupport(masterRealm, migrationRealm);
}
private void testExtractRealmKeys(RealmResource masterRealm, RealmResource migrationRealm) {
log.info("testing extract realm keys");
String expectedMasterRealmKey = "MIIEowIBAAKCAQEAiU54OXoCbHy0L0gHn1yasctcnKHRU1pHFIJnWvaI7rClJydet9dDJaiYXOxMKseiBm3eYznfN3cPyU8udYmRnMuKjiocZ77LT2IEttAjXb6Ggazx7loriFHRy0IOJeX4KxXhAPWmxqa3mkFNfLBEvFqVaBgUDHQ60cmnPvNSHYudBTW9K80s8nvmP2pso7HTwWJ1+Xatj1Ey/gTmB3CXlyqBegGWC9TeuErEYpYhdh+11TVWasgMBZyUCtL3NRPaBuhaPg1LpW8lWGk05nS+YM6dvTk3Mppv+z2RygEpxyO09oT3b4G+Zfwit1STqn0AvDTGzINdoKcNtFScV0j8TwIDAQABAoIBAHcbPKsPLZ8SJfOF1iblW8OzFulAbaaSf2pJHIMJrQrw7LKkMkPjVXoLX+/rgr7xYZmWIP2OLBWfEHCeYTzQUyHiZpSf7vgHx7Fa45/5uVQOe/ttHIiYa37bCtP4vvEdJkOpvP7qGPvljwsebqsk9Ns28LfVez66bHOjK5Mt2yOIulbTeEs7ch//h39YwKJv96vc+CHbV2O6qoOxZessO6y+287cOBvbFXmS2GaGle5Nx/EwncBNS4b7czoetmm70+9ht3yX+kxaP311YUT31KQjuaJt275kOiKsrXr27PvgO++bsIyGuSzqyS7G7fmxF2zUyphEqEpalyDGMKMnrAECgYEA1fCgFox03rPDjm0MhW/ThoS2Ld27sbWQ6reS+PBMdUTJZVZIU1D2//h6VXDnlddhk6avKjA4smdy1aDKzmjz3pt9AKn+kgkXqtTC2fD3wp+fC9hND0z+rQPGe/Gk7ZUnTdsqnfyowxr+woIgzdnRukOUrG+xQiP3RUUT7tt6NQECgYEApEz2xvgqMm+9/f/YxjLdsFUfLqc4WlafB863stYEVqlCYy5ujyo0VQ0ahKSKJkLDnf52+aMUqPOpwaGePpu3O6VkvpcKfPY2MUlZW7/6Sa9et9hxNkdTS7Gui2d1ELpaCBe1Bc62sk8EA01iHXE1PpvyUqDWrhNh+NrDICA9oU8CgYBgGDYACtTP11TmW2r9YK5VRLUDww30k4ZlN1GnyV++aMhBYVEZQ0u+y+A/EnijIFwu0vbo70H4OGknNZMCxbeMbLDoJHM5KyZbUDe5ZvgSjloFGwH59m6KTiDQOUkIgi9mVCQ/VGaFRFHcElEjxUvj60kTbxPijn8ZuR5r8l9hAQKBgQCQ9jL5pHWeoIayN20smi6M6N2lTPbkhe60dcgQatHTIG2pkosLl8IqlHAkPgSB84AiwyR351JQKwRJCm7TcJI/dxMnMZ6YWKfB3qSP1hdfsfJRJQ/mQxIUBAYrizF3e+P5peka4aLCOgMhYsJBlePThMZN7wja99EGPwXQL4IQ8wKBgB8Nis1lQK6Z30GCp9u4dYleGfEP71Lwqvk/eJb89/uz0fjF9CTpJMULFc+nA5u4yHP3LFnRg3zCU6aEwfwUyk4GH9lWGV/qIAisQtgrCEraVe4qxz0DVE59C7qjO26IhU2U66TEzPAqvQ3zqey+woDn/cz/JMWK1vpcSk+TKn3K";
String expectedMigrationRealmKey = "MIIEpAIBAAKCAQEApt6gCllWkVTZ7fy/oRIx6Bxjt9x3eKKyKGFXvN4iaafrNqpYU9lcqPngWJ9DyXGqUf8RpjPaQWiLWLxjw3xGBqLk2E1/Frb9e/dy8rj//fHGq6bujN1iguzyFwxPGT5Asd7jflRI3qU04M8JE52PArqPhGL2Fn+FiSK5SWRIGm+hVL7Ck/E/tVxM25sFG1/UTQqvrROm4q76TmP8FsyZaTLVf7cCwW2QPIX0N5HTVb3QbBb5KIsk4kKmk/g7uUxS9r42tu533LISzRr5CTyWZAL2XFRuF2RrKdE8gwqkEubw6sDmB2mE0EoPdY1DUhBQgVP/5rwJrCtTsUBR2xdEYQIDAQABAoIBAFbbsNBSOlZBpYJUOmcb8nBQPrOYhXN8tGGCccn0klMOvcdhmcJjdPDbyCQ5Gm7DxJUTwNsTSHsdcNMKlJ9Pk5+msJnKlOl87KrXXbTsCQvlCrWUmb0nCzz9GvJWTOHl3oT3cND0DE4gDksqWR4luCgCdevCGzgQvrBoK6wBD+r578uEW3iw10hnJ0+wnGiw8IvPzE1a9xbY4HD8/QrYdaLxuLb/aC1PDuzrz0cOjnvPkrws5JrbUSnbFygJiOv1z4l2Q00uGIxlHtXdwQBnTZZjVi4vOec2BYSHffgwDYEZIglw1mnrV7y0N1nnPbtJK/cegIkXoBQHXm8Q99TrWMUCgYEA9au86qcwrXZZg5H4BpR5cpy0MSkcKDbA1aRL1cAyTCqJxsczlAtLhFADF+NhnlXj4y7gwDEYWrz064nF73I+ZGicvCiyOy+tCTugTyTGS+XR948ElDMS6PCUUXsotS3dKa0b3c9wd2mxeddTjq/ArfgEVZJ6fE1KtjLt9dtfA+8CgYEAreK3JsvjR5b/Xct28TghYUU7Qnasombb/shqqy8FOMjYUr5OUm/OjNIgoCqhOlE8oQDJ4dOZofNSa7tL+oM8Gmbal+E3fRzxnx/9/EC4QV6sVaPLTIyk7EPfKTcZuzH7+BNZtAziTxJw9d6YJQRbkpg92EZIEoR8iDj2Xs5xrK8CgYEAwMVWwwYX8zT3vn7ukTM2LRH7bsvkVUXJgJqgCwT6Mrv6SmkK9vL5+cPS+Y6pjdW1sRGauBSOGL1Grf/4ug/6F03jFt4UJM8fRyxreU7Q7sNSQ6AMpsGA6BnHODycz7ZCYa59PErG5FyiL4of/cm5Nolz1TXQOPNpWZiTEqVlZC8CgYA4YPbjVF4nuxSnU64H/hwMjsbtAM9uhI016cN0J3W4+J3zDhMU9X1x+Tts0wWdg/N1fGz4lIQOl3cUyRCUc/KL2OdtMS+tmDHbVyMho9ZaE5kq10W2Vy+uDz+O/HeSU12QDK4cC8Vgv+jyPy7zaZtLR6NduUPrBRvfiyCOkr8WrwKBgQCY0h4RCdNFhr0KKLLmJipAtV8wBCGcg1jY1KoWKQswbcykfBKwHbF6EooVqkRW0ITjWB7ZZCf8TnSUxe0NXCUAkVBrhzS4DScgtoSZYOOUaSHgOxpfwgnQ3oYotKi98Yg3IsaLs1j4RuPG5Sp1z6o+ELP1uvr8azyn9YlLa+523Q==";
@ -180,18 +195,8 @@ public class MigrationTest extends AbstractKeycloakTest {
assertEquals(1, components.size());
}
private void testMigrationTo2_5_0() {
//TODO org.keycloak.migration.migrators.MigrateTo2_5_0
//https://github.com/keycloak/keycloak/pull/3630
testDuplicateEmailSupport(masterRealm, migrationRealm);
}
private void testMigrationTo2_5_1() {
testDroolsToRulesPolicyTypeMigration();
}
private void testLdapKerberosMigration_2_5_0() {
log.info("testing ldap kerberos migration");
RealmRepresentation realmRep = migrationRealm2.toRepresentation();
List<ComponentRepresentation> components = migrationRealm2.components().query(realmRep.getId(), UserStorageProvider.class.getName());
assertEquals(2, components.size());
@ -226,6 +231,7 @@ public class MigrationTest extends AbstractKeycloakTest {
}
private void testDroolsToRulesPolicyTypeMigration() {
log.info("testing drools to rules in authorization services");
List<ClientRepresentation> client = migrationRealm3.clients().findByClientId("photoz-restful-api");
assertEquals(1, client.size());
@ -240,6 +246,7 @@ public class MigrationTest extends AbstractKeycloakTest {
}
private void testAuthorizationServices(RealmResource... realms) {
log.info("testing authorization services");
for (RealmResource realm : realms) {
//test setup of authorization services
for (String roleName : Constants.AUTHZ_DEFAULT_AUTHORIZATION_ROLES) {
@ -270,6 +277,7 @@ public class MigrationTest extends AbstractKeycloakTest {
}
private void testNameOfOTPRequiredAction(RealmResource... realms) {
log.info("testing OTP Required Action");
for (RealmResource realm : realms) {
RequiredActionProviderRepresentation otpAction = realm.flows().getRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP.name());
@ -278,6 +286,7 @@ public class MigrationTest extends AbstractKeycloakTest {
}
private void testIdentityProviderAuthenticator(RealmResource... realms) {
log.info("testing identity provider authenticator");
for (RealmResource realm : realms) {
boolean success = false;
for (AuthenticationFlowRepresentation flow : realm.flows().getFlows()) {
@ -298,6 +307,7 @@ public class MigrationTest extends AbstractKeycloakTest {
}
private void testUpdateProtocolMappers(RealmResource... realms) {
log.info("testing updated protocol mappers");
for (RealmResource realm : realms) {
for (ClientRepresentation client : realm.clients().findAll()) {
for (ProtocolMapperRepresentation protocolMapper : client.getProtocolMappers()) {
@ -320,6 +330,7 @@ public class MigrationTest extends AbstractKeycloakTest {
}
private void testDuplicateEmailSupport(RealmResource... realms) {
log.info("testing duplicate email");
for (RealmResource realm : realms) {
RealmRepresentation rep = realm.toRepresentation();
assertTrue("LoginWithEmailAllowed should be enabled.", rep.isLoginWithEmailAllowed());

View file

@ -61,6 +61,11 @@ public class ClientBuilder {
return this;
}
public ClientBuilder consentRequired(boolean consentRequired) {
rep.setConsentRequired(consentRequired);
return this;
}
public ClientBuilder publicClient() {
rep.setPublicClient(true);
return this;

View file

@ -331,6 +331,17 @@
],
"adminUrl": "http://localhost:8180/namedapp/base/admin",
"secret": "password"
},
{
"clientId": "var-named-test-app",
"name": "Test App Named - ${client_account}",
"enabled": true,
"baseUrl": "http://localhost:8180/varnamedapp/base",
"redirectUris": [
"http://localhost:8180/varnamedapp/base/*"
],
"adminUrl": "http://localhost:8180/varnamedapp/base/admin",
"secret": "password"
}
],
"roles" : {

View file

@ -18,10 +18,13 @@
package org.keycloak.testsuite.authorization;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.common.Profile;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@ -51,6 +54,11 @@ public abstract class AbstractAuthorizationTest {
private Keycloak adminClient;
@BeforeClass
public static void enabled() {
Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", !Profile.getName().equals("product"));
}
@Before
public void onBefore() {
adminClient = Keycloak.getInstance(AUTH_SERVER_ROOT, MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);

View file

@ -109,7 +109,6 @@ public class LDAPGroupMapperTest {
LDAPObject group1 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description");
LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");
LDAPObject group12 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "group12 - description");
LDAPObject groupSpecialCharacters = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group-spec,ia*l_characžter)s", descriptionAttrName, "group-special-characters");
LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11, false);
LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12, true);
@ -134,14 +133,11 @@ public class LDAPGroupMapperTest {
LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
LDAPObject james2 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jamees,key*cložak)ppp", "James2", "Brown2", "james2@email.org", null, "89102");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james2, "Password1");
postSetup();
postSetup(appRealm, ldapFedProvider);
}
void postSetup() {
void postSetup(RealmModel appRealm, LDAPStorageProvider ldapProvider) {
LDAPGroupMapperTest.ldapModel = this.ldapModel;
LDAPGroupMapperTest.descriptionAttrName = this.descriptionAttrName;
}

View file

@ -0,0 +1,371 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage.ldap;
import java.util.Map;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
/**
* Test for the MSAD setup with usernameAttribute=sAMAccountName, rdnAttribute=cn and fullNameMapper mapped to cn
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPMSADFullNameTest {
// Run this test just on MSAD and just when sAMAccountName is mapped to username
private static LDAPRule ldapRule = new LDAPRule((Map<String, String> ldapConfig) -> {
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
if (!vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY)) {
return true;
}
String usernameAttr = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
return !usernameAttr.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME);
});
private static ComponentModel ldapModel = null;
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app");
MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
UserStorageProviderModel model = new UserStorageProviderModel();
model.setLastSync(0);
model.setChangedSyncPeriod(-1);
model.setFullSyncPeriod(-1);
model.setName("test-ldap");
model.setPriority(1);
model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
model.getConfig().addAll(ldapConfig);
ldapModel = appRealm.addComponentModel(model);
LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
// Delete all LDAP users and add some new for testing
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
// Remove the mapper for "username-cn" and create new mapper for fullName
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "username-cn");
Assert.assertNotNull(mapperModel);
appRealm.removeComponent(mapperModel);
mapperModel = KeycloakModelUtils.createComponentModel("fullNameWritable", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
FullNameLDAPStorageMapper.READ_ONLY, "false",
FullNameLDAPStorageMapper.WRITE_ONLY, "true");
appRealm.addComponentModel(mapperModel);
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@ClassRule
public static TestRule chain = RuleChain
.outerRule(ldapRule)
.around(keycloakRule);
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected OAuthClient oauth;
@WebResource
protected WebDriver driver;
@WebResource
protected AppPage appPage;
@WebResource
protected RegisterPage registerPage;
@WebResource
protected LoginPage loginPage;
@WebResource
protected AccountUpdateProfilePage profilePage;
@WebResource
protected AccountPasswordPage changePasswordPage;
// @Test
// public void test01Sleep() throws Exception {
// Thread.sleep(1000000);
// }
@Test
public void test01_addUserWithoutFullName() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().addUser(appRealm, "johnkeycloak");
john.setEmail("johnkeycloak@email.cz");
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertNotNull(john.getFederationLink());
assertDnStartsWith(session, john, "cn=johnkeycloak");
session.users().removeUser(appRealm, john);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test02_registerUserWithFullName() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("Johny", "Anthony", "johnyanth@check.cz", "johnkeycloak", "Password1", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
assertUser(session, john, "johnkeycloak", "Johny", "Anthony", true, "cn=Johny Anthony");
session.users().removeUser(appRealm, john);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test03_addUserWithFirstNameOnly() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().addUser(appRealm, "johnkeycloak");
john.setEmail("johnkeycloak@email.cz");
john.setFirstName("Johnyyy");
john.setEnabled(true);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
assertUser(session, john, "johnkeycloak", "Johnyyy", "", true, "cn=Johnyyy");
session.users().removeUser(appRealm, john);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test04_addUserWithLastNameOnly() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().addUser(appRealm, "johnkeycloak");
john.setEmail("johnkeycloak@email.cz");
john.setLastName("Anthonyy");
john.setEnabled(true);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
assertUser(session, john, "johnkeycloak", "", "Anthonyy", true, "cn=Anthonyy");
session.users().removeUser(appRealm, john);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test05_registerUserWithFullNameSpecialChars() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("Jož,o", "Baříč", "johnyanth@check.cz", "johnkeycloak", "Password1", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
assertUser(session, john, "johnkeycloak", "Jož,o", "Baříč", true, "cn=Jož\\,o Baříč");
session.users().removeUser(appRealm, john);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test06_conflicts() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel john = session.users().addUser(appRealm, "existingkc");
john.setFirstName("John");
john.setLastName("Existing");
john.setEnabled(true);
UserModel john2 = session.users().addUser(appRealm, "existingkc1");
john2.setEnabled(true);
} finally {
keycloakRule.stopSession(session, true);
}
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("John", "Existing", "johnyanth@check.cz", "existingkc", "Password1", "Password1");
Assert.assertEquals("Username already exists.", registerPage.getError());
registerPage.register("John", "Existing", "johnyanth@check.cz", "existingkc2", "Password1", "Password1");
appPage.logout();
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("John", "Existing", "johnyanth2@check.cz", "existingkc3", "Password1", "Password1");
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel existingKc = session.users().getUserByUsername("existingkc", appRealm);
assertUser(session, existingKc, "existingkc", "John", "Existing", true, "cn=John Existing");
UserModel existingKc1 = session.users().getUserByUsername("existingkc1", appRealm);
assertUser(session, existingKc1, "existingkc1", "", "", true, "cn=existingkc1");
UserModel existingKc2 = session.users().getUserByUsername("existingkc2", appRealm);
assertUser(session, existingKc2, "existingkc2", "John", "Existing", true, "cn=John Existing0");
UserModel existingKc3 = session.users().getUserByUsername("existingkc3", appRealm);
assertUser(session, existingKc3, "existingkc3", "John", "Existing", true, "cn=John Existing1");
session.users().removeUser(appRealm, existingKc);
session.users().removeUser(appRealm, existingKc1);
session.users().removeUser(appRealm, existingKc2);
session.users().removeUser(appRealm, existingKc3);
} finally {
keycloakRule.stopSession(session, true);
}
}
private void assertUser(KeycloakSession session, UserModel user, String expectedUsername, String expectedFirstName, String expectedLastName, boolean expectedEnabled, String expectedDn) {
Assert.assertNotNull(user);
Assert.assertNotNull(user.getFederationLink());
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
Assert.assertEquals(expectedUsername, user.getUsername());
Assert.assertEquals(expectedFirstName, user.getFirstName());
Assert.assertEquals(expectedLastName, user.getLastName());
Assert.assertEquals(expectedEnabled, user.isEnabled());
assertDnStartsWith(session, user, expectedDn);
}
private void assertDnStartsWith(KeycloakSession session, UserModel user, String expectedRDn) {
String usersDn = LDAPTestUtils.getLdapProvider(session, ldapModel).getLdapIdentityStore().getConfig().getUsersDn();
String userDN = user.getFirstAttribute(LDAPConstants.LDAP_ENTRY_DN);
Assert.assertTrue(userDN.equalsIgnoreCase(expectedRDn + "," + usersDn));
}
}

View file

@ -0,0 +1,176 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage.ldap;
import java.util.Map;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPMSADMapperTest {
// Run this test just on MSAD
private static LDAPRule ldapRule = new LDAPRule((Map<String, String> ldapConfig) -> {
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
// TODO: This is skipped as it requires that MSAD server is set to not allow weak passwords (There needs to be pwdProperties=1 set on MSAD side).
// TODO: Currently we can't rely on it. See KEYCLOAK-4276
return true;
// return !(vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY));
});
private static ComponentModel ldapModel = null;
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app");
MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
UserStorageProviderModel model = new UserStorageProviderModel();
model.setLastSync(0);
model.setChangedSyncPeriod(-1);
model.setFullSyncPeriod(-1);
model.setName("test-ldap");
model.setPriority(0);
model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
model.setConfig(ldapConfig);
ldapModel = appRealm.addComponentModel(model);
LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
// Delete all LDAP users and add some new for testing
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@ClassRule
public static TestRule chain = RuleChain
.outerRule(ldapRule)
.around(keycloakRule);
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected OAuthClient oauth;
@WebResource
protected WebDriver driver;
@WebResource
protected AppPage appPage;
@WebResource
protected RegisterPage registerPage;
@WebResource
protected LoginPage loginPage;
@WebResource
protected AccountUpdateProfilePage profilePage;
@WebResource
protected AccountPasswordPage changePasswordPage;
@WebResource
protected LoginPasswordUpdatePage passwordUpdatePage;
@Test
public void test01RegisterUserWithWeakPasswordFirst() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
// Weak password. This will fail to update password to MSAD due to password policy.
registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "password", "password");
// Another weak password
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("pass", "pass");
Assert.assertEquals("Invalid password: new password doesn't match password policies.", passwordUpdatePage.getError());
// Strong password. Successfully update password and being redirected to the app
passwordUpdatePage.changePassword("Password1", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
Assert.assertNotNull(user);
Assert.assertNotNull(user.getFederationLink());
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
Assert.assertEquals("registerusersuccess2", user.getUsername());
Assert.assertEquals("firstName", user.getFirstName());
Assert.assertEquals("lastName", user.getLastName());
Assert.assertTrue(user.isEnabled());
Assert.assertEquals(0, user.getRequiredActions().size());
} finally {
keycloakRule.stopSession(session, false);
}
}
}

View file

@ -17,6 +17,7 @@
package org.keycloak.testsuite.federation.storage.ldap;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
@ -74,6 +75,8 @@ import static org.junit.Assert.assertEquals;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPProvidersIntegrationTest {
private static final Logger log = Logger.getLogger(LDAPProvidersIntegrationTest.class);
private static LDAPRule ldapRule = new LDAPRule();
private static ComponentModel ldapModel = null;
@ -388,6 +391,10 @@ public class LDAPProvidersIntegrationTest {
Assert.assertNotNull(user);
Assert.assertNotNull(user.getFederationLink());
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
Assert.assertEquals("registerusersuccess2", user.getUsername());
Assert.assertEquals("firstName", user.getFirstName());
Assert.assertEquals("lastName", user.getLastName());
Assert.assertTrue(user.isEnabled());
} finally {
keycloakRule.stopSession(session, false);
}

View file

@ -17,16 +17,13 @@
package org.keycloak.testsuite.federation.storage.ldap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
@ -40,11 +37,15 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AppPage;
@ -66,7 +67,16 @@ import static org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPSpecialCharsTest {
private static LDAPRule ldapRule = new LDAPRule();
// Skip this test for MSAD with sAMAccountName as it is not allowed to use specialCharacters in sAMAccountName attribute
private static LDAPRule ldapRule = new LDAPRule((Map<String, String> ldapConfig) -> {
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
String usernameAttr = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
return (vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY) && usernameAttr.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME));
});
static ComponentModel ldapModel = null;
static String descriptionAttrName = null;
@ -75,9 +85,18 @@ public class LDAPSpecialCharsTest {
private static KeycloakRule keycloakRule = new KeycloakRule(new LDAPGroupMapperTest.GroupTestKeycloakSetup(ldapRule) {
@Override
protected void postSetup() {
protected void postSetup(RealmModel appRealm, LDAPStorageProvider ldapProvider) {
LDAPSpecialCharsTest.ldapModel = this.ldapModel;
LDAPSpecialCharsTest.descriptionAttrName = this.descriptionAttrName;
LDAPObject groupSpecialCharacters = LDAPTestUtils.createLDAPGroup(session, appRealm, ldapModel, "group-spec,ia*l_characžter)s", descriptionAttrName, "group-special-characters");
// Resync LDAP groups to Keycloak DB
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(appRealm);
LDAPObject james2 = LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "jamees,key*cložak)ppp", "James2", "Brown2", "james2@email.org", null, "89102");
LDAPTestUtils.updateLDAPPassword(ldapProvider, james2, "Password1");
}
});

View file

@ -117,8 +117,12 @@ public class SamlAdapterTest {
testStrategy.testSavedPostRequest();
}
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test
public void testMetadataPostSignedLoginLogout() throws Exception {

View file

@ -20,10 +20,12 @@ package org.keycloak.testsuite.keycloaksaml;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.rules.ExternalResource;
import org.keycloak.adapters.saml.SamlAuthenticationError;
import org.keycloak.adapters.saml.SamlPrincipal;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.*;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@ -38,9 +40,8 @@ import org.keycloak.protocol.saml.mappers.RoleNameMapper;
import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.BaseSAML2BindingBuilder;
import org.keycloak.saml.SAML2ErrorResponseBuilder;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.*;
import org.keycloak.saml.common.constants.*;
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer;
@ -51,6 +52,7 @@ import org.keycloak.testsuite.rule.ErrorServlet;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import org.w3c.dom.Document;
@ -61,10 +63,11 @@ import javax.ws.rs.core.Form;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.URI;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import java.util.Base64;
import java.util.logging.*;
import static org.junit.Assert.assertEquals;
@ -77,6 +80,24 @@ public class SamlAdapterTestStrategy extends ExternalResource {
protected String APP_SERVER_BASE_URL = "http://localhost:8081";
protected AbstractKeycloakRule keycloakRule;
private static final String REALM_PRIVATE_KEY_STR = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
private static PrivateKey REALM_PRIVATE_KEY;
private static final String REALM_PUBLIC_KEY_STR = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
private static PublicKey REALM_PUBLIC_KEY;
static {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
byte[] encoded = Base64.getDecoder().decode(REALM_PUBLIC_KEY_STR);
REALM_PUBLIC_KEY = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
encoded = Base64.getDecoder().decode(REALM_PRIVATE_KEY_STR);
REALM_PRIVATE_KEY = (PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
Logger.getLogger(SamlAdapterTestStrategy.class.getName()).log(Level.SEVERE, null, ex);
}
}
public SamlAdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) {
this.AUTH_SERVER_URL = AUTH_SERVER_URL;
this.APP_SERVER_BASE_URL = APP_SERVER_BASE_URL;
@ -173,7 +194,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
public void testErrorHandling() throws Exception {
public void testErrorHandlingUnsigned() throws Exception {
ErrorServlet.authError = null;
Client client = ClientBuilder.newClient();
// make sure
@ -194,6 +215,36 @@ public class SamlAdapterTestStrategy extends ExternalResource {
client.close();
Assert.assertNotNull(ErrorServlet.authError);
SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
Assert.assertNotNull(error.getStatus());
ErrorServlet.authError = null;
}
public void testErrorHandlingSigned() throws Exception {
ErrorServlet.authError = null;
Client client = ClientBuilder.newClient();
// make sure
Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get();
response.close();
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
.destination(APP_SERVER_BASE_URL + "/employee-sig/saml")
.issuer(AUTH_SERVER_URL + "/realms/demo")
.status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder()
.relayState(null)
.signatureAlgorithm(SignatureAlgorithm.RSA_SHA256)
.signWith(KeyUtils.createKeyId(REALM_PRIVATE_KEY), REALM_PRIVATE_KEY, REALM_PUBLIC_KEY)
.signDocument();
Document document = builder.buildDocument();
URI uri = binding.generateRedirectUri(GeneralConstants.SAML_RESPONSE_KEY, APP_SERVER_BASE_URL + "/employee-sig/saml", document);
response = client.target(uri).request().get();
String errorPage = response.readEntity(String.class);
response.close();
Assert.assertTrue(errorPage.contains("Error Page"));
client.close();
Assert.assertNotNull(ErrorServlet.authError);
SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
Assert.assertEquals(SamlAuthenticationError.Reason.ERROR_STATUS, error.getReason());
Assert.assertNotNull(error.getStatus());
ErrorServlet.authError = null;

View file

@ -128,7 +128,7 @@ public class SamlAdapterTest {
@Test
public void testPostPassiveLoginLogout() {
testStrategy.testPostPassiveLoginLogout(false);
testStrategy.testPostPassiveLoginLogout(true);
}
@Test

View file

@ -111,8 +111,12 @@ public class JettySamlTest {
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -111,8 +111,12 @@ public class JettySamlTest {
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -111,8 +111,12 @@ public class JettySamlTest {
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -111,8 +111,12 @@ public class JettySamlTest {
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -120,8 +120,12 @@ public class TomcatSamlTest {
}
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -98,8 +98,12 @@ public class TomcatSamlTest {
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -99,8 +99,12 @@ public class TomcatSamlTest {
@Test
public void testErrorHandling() throws Exception {
testStrategy.testErrorHandling();
public void testErrorHandlingSigned() throws Exception {
testStrategy.testErrorHandlingSigned();
}
@Test
public void testErrorHandlingUnsigned() throws Exception {
testStrategy.testErrorHandlingUnsigned();
}
@Test

View file

@ -50,6 +50,7 @@ role_manage-clients=Clients verwalten
role_manage-events=Events verwalten
role_view-profile=Profile ansehen
role_manage-account=Profile verwalten
client_account=Konto
requiredFields=Erforderliche Felder
allFieldsRequired=Alle Felder sind Erforderlich

View file

@ -504,7 +504,7 @@ identity-provider.jwks-url.tooltip=URL where identity provider keys in JWK forma
validating-public-key=Validating Public Key
identity-provider.validating-public-key.tooltip=The public key in PEM format that must be used to verify external IDP signatures.
validating-public-key-id=Validating Public Key Id
identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave unset if the external IDP is Keycloak or uses the same mechanism to determine key ID.
identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave blank if the key above should be used always, regardless of key ID specified by external IDP; set it if the key should only be used for verifying if key ID from external IDP matches.
import-external-idp-config=Import External IDP Config
import-external-idp-config.tooltip=Allows you to load external IDP metadata from a config file or to download it from a URL.
import-from-url=Import from URL

View file

@ -253,10 +253,13 @@ ol#kc-totp-settings li:first-of-type {
#kc-container-wrapper {
position: absolute;
width: 100%;
margin-top: 50px;
}
#kc-logo-wrapper {
margin-left: auto;
position: absolute;
top: 50px;
right: 50px;
}
.login-pf .container {
@ -319,13 +322,14 @@ ol#kc-totp-settings li:first-of-type {
}
}
@media (min-height: 621px) {
@media (min-height: 646px) {
#kc-container-wrapper {
bottom: 12%;
}
}
@media (max-height: 620px) {
@media (max-height: 645px) {
#kc-container-wrapper {
top: 20%;
}
}