Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
6392405413
82 changed files with 1938 additions and 5202 deletions
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -43,6 +43,13 @@ public interface HttpFacade {
|
|||
*/
|
||||
String getURI();
|
||||
|
||||
/**
|
||||
* Get the request relative path.
|
||||
*
|
||||
* @return the request relative path
|
||||
*/
|
||||
String getRelativePath();
|
||||
|
||||
/**
|
||||
* HTTPS?
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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());
|
||||
|
@ -952,6 +952,19 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
|
|||
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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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" : {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -128,7 +128,7 @@ public class SamlAdapterTest {
|
|||
|
||||
@Test
|
||||
public void testPostPassiveLoginLogout() {
|
||||
testStrategy.testPostPassiveLoginLogout(false);
|
||||
testStrategy.testPostPassiveLoginLogout(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue