Compare commits

..
Author SHA1 Message Date
keycloak-bot 72d38779ab Set version to 2.3.0.CR1 2016-10-21 05:12:37 +00:00
2206 changed files with 27836 additions and 98831 deletions

No files matched your search

-1
View File
@@ -14,7 +14,6 @@
nbactions.xml
nb-configuration.xml
catalog.xml
nbproject
# Compiled source #
###################
+1 -8
View File
@@ -11,13 +11,6 @@ env:
global:
- MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx2048m"
matrix:
- TESTS=group1
- TESTS=group2
- TESTS=group3
- TESTS=group4
- TESTS=adapter
- TESTS=old
jdk:
- oraclejdk8
@@ -29,6 +22,6 @@ install:
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
script:
- ./travis-run-tests.sh $TESTS
- mvn test -B
sudo: false
+1 -1
View File
@@ -9,7 +9,7 @@ For more information about Keycloak visit [Keycloak homepage](http://keycloak.or
Building
--------
Ensure you have JDK 8 (or newer), Maven 3.1.1 (or newer) and Git installed
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
java -version
mvn -version
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -342,12 +342,6 @@ public class AdapterDeploymentContext {
return delegate.getNotBefore();
}
@Override
public void updateNotBefore(int notBefore) {
delegate.setNotBefore(notBefore);
getPublicKeyLocator().reset(this);
}
@Override
public void setExposeToken(boolean exposeToken) {
delegate.setExposeToken(exposeToken);
@@ -452,16 +446,6 @@ public class AdapterDeploymentContext {
public int getMinTimeBetweenJwksRequests() {
return delegate.getMinTimeBetweenJwksRequests();
}
@Override
public int getPublicKeyCacheTtl() {
return delegate.getPublicKeyCacheTtl();
}
@Override
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
}
}
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
@@ -142,13 +142,9 @@ public class AuthenticatedActionsHandler {
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
if (session != null) {
session.setAuthorizationContext(authorizationContext);
session.setAuthorizationContext(authorizationContext);
return authorizationContext.isGranted();
}
return true;
return authorizationContext.isGranted();
} catch (Exception e) {
throw new RuntimeException("Failed to enforce policy decisions.", e);
}
@@ -46,7 +46,14 @@ public class HttpAdapterUtils {
}
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, clazz);
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
return JsonSerialization.readValue(json, clazz);
} finally {
try {
is.close();
@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
import org.keycloak.representations.adapters.config.AdapterConfig;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -333,7 +333,7 @@ public class HttpClientBuilder {
}
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
public HttpClient build(AdapterConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
/**
* Configures a the proxy to use for auth-server requests if provided.
* <p>
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
* If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored.
* </p>
*
* @param adapterConfig
*/
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return;
@@ -57,7 +57,6 @@ public class KeycloakDeployment {
protected String resourceName;
protected boolean bearerOnly;
protected boolean autodetectBearerOnly;
protected boolean enableBasicAuth;
protected boolean publicClient;
protected Map<String, Object> resourceCredentials = new HashMap<>();
@@ -82,7 +81,6 @@ public class KeycloakDeployment {
protected volatile int notBefore;
protected int tokenMinimumTimeToLive;
protected int minTimeBetweenJwksRequests;
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer;
public KeycloakDeployment() {
@@ -202,14 +200,6 @@ public class KeycloakDeployment {
this.bearerOnly = bearerOnly;
}
public boolean isAutodetectBearerOnly() {
return autodetectBearerOnly;
}
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
this.autodetectBearerOnly = autodetectBearerOnly;
}
public boolean isEnableBasicAuth() {
return enableBasicAuth;
}
@@ -338,11 +328,6 @@ public class KeycloakDeployment {
this.notBefore = notBefore;
}
public void updateNotBefore(int notBefore) {
this.notBefore = notBefore;
getPublicKeyLocator().reset(this);
}
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}
@@ -399,14 +384,6 @@ public class KeycloakDeployment {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
}
public int getPublicKeyCacheTtl() {
return publicKeyCacheTtl;
}
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
this.publicKeyCacheTtl = publicKeyCacheTtl;
}
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
}
@@ -99,14 +99,12 @@ public class KeycloakDeploymentBuilder {
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
@@ -25,7 +25,6 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import org.keycloak.constants.AdapterConstants;
@@ -170,7 +169,7 @@ public class OAuthRequestAuthenticator {
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
.queryParam(OAuth2Constants.REDIRECT_URI, url)
.queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){
@@ -196,13 +195,15 @@ public class OAuthRequestAuthenticator {
return sslRedirectPort;
}
protected static final AtomicLong counter = new AtomicLong();
protected String getStateCode() {
return AdapterUtils.generateId();
return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
}
protected AuthChallenge loginRedirect() {
final String state = getStateCode();
final String redirect = getRedirectUri(state);
final String redirect = getRedirectUri(state);
if (redirect == null) {
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
}
@@ -356,7 +357,7 @@ public class OAuthRequestAuthenticator {
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
}
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.updateNotBefore(tokenResponse.getNotBeforePolicy());
deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
}
if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token");
@@ -155,7 +155,7 @@ public class PreAuthActionsHandler {
} else {
log.debugf("logout of all sessions for application '%s'", action.getResource());
if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.updateNotBefore(action.getNotBefore());
deployment.setNotBefore(action.getNotBefore());
}
userSessionManagement.logoutAll();
}
@@ -177,7 +177,7 @@ public class PreAuthActionsHandler {
}
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
if (!validateAction(action)) return;
deployment.updateNotBefore(action.getNotBefore());
deployment.setNotBefore(action.getNotBefore());
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -144,7 +144,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
}
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.updateNotBefore(response.getNotBeforePolicy());
deployment.setNotBefore(response.getNotBeforePolicy());
}
this.token = token;
@@ -17,8 +17,6 @@
package org.keycloak.adapters;
import java.util.Collections;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.spi.AuthChallenge;
@@ -118,12 +116,6 @@ public abstract class RequestAuthenticator {
return AuthOutcome.NOT_ATTEMPTED;
}
if (isAutodetectedBearerOnly(facade.getRequest())) {
challenge = bearer.getChallenge();
log.debug("NOT_ATTEMPTED: Treating as bearer only");
return AuthOutcome.NOT_ATTEMPTED;
}
if (log.isTraceEnabled()) {
log.trace("try oauth");
}
@@ -166,36 +158,6 @@ public abstract class RequestAuthenticator {
return false;
}
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
if (!deployment.isAutodetectBearerOnly()) return false;
String headerValue = facade.getRequest().getHeader("X-Requested-With");
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
return true;
}
headerValue = facade.getRequest().getHeader("Faces-Request");
if (headerValue != null && headerValue.startsWith("partial/")) {
return true;
}
headerValue = facade.getRequest().getHeader("SOAPAction");
if (headerValue != null) {
return true;
}
List<String> accepts = facade.getRequest().getHeaders("Accept");
if (accepts == null) accepts = Collections.emptyList();
for (String accept : accepts) {
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
return false;
}
}
return true;
}
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
@@ -17,16 +17,8 @@
*/
package org.keycloak.adapters.authorization;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.adapters.spi.HttpFacade.Response;
@@ -39,6 +31,13 @@ import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.Enforce
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@@ -48,7 +47,7 @@ public abstract class AbstractPolicyEnforcer {
private final PolicyEnforcerConfig enforcerConfig;
private final PolicyEnforcer policyEnforcer;
private Map<String, PathConfig> paths;
private List<PathConfig> paths;
private AuthzClient authzClient;
private PathMatcher pathMatcher;
@@ -57,7 +56,7 @@ public abstract class AbstractPolicyEnforcer {
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
this.authzClient = policyEnforcer.getClient();
this.pathMatcher = new PathMatcher();
this.paths = policyEnforcer.getPaths();
this.paths = new ArrayList<>(policyEnforcer.getPaths());
}
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
@@ -67,50 +66,40 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(true);
}
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
AccessToken accessToken = httpFacade.getSecurityContext().getToken();
Request request = httpFacade.getRequest();
Response response = httpFacade.getResponse();
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
if (securityContext != null) {
AccessToken accessToken = securityContext.getToken();
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
if (accessToken != null) {
Request request = httpFacade.getRequest();
Response response = httpFacade.getResponse();
String path = getPath(request);
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken);
}
LOGGER.debugf("Could not find a configuration for path [%s]", path);
response.sendError(403, "Could not find a configuration for path [" + path + "].");
return createEmptyAuthorizationContext(false);
}
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createEmptyAuthorizationContext(true);
}
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
}
}
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
response.sendError(403, "Authorization failed.");
}
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken);
}
LOGGER.debugf("Could not find a configuration for path [%s]", path);
response.sendError(403, "Could not find a configuration for path [" + path + "].");
return createEmptyAuthorizationContext(false);
}
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
}
}
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
response.sendError(403, "Authorization failed.");
}
return createEmptyAuthorizationContext(false);
@@ -121,9 +110,12 @@ public abstract class AbstractPolicyEnforcer {
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
Request request = httpFacade.getRequest();
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
String accessDeniedPath = enforcerConfig.getAccessDeniedPath();
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return true;
if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true;
}
}
AccessToken.Authorization authorization = accessToken.getAuthorization();
@@ -133,17 +125,14 @@ public abstract class AbstractPolicyEnforcer {
}
List<Permission> permissions = authorization.getPermissions();
boolean hasPermission = false;
for (Permission permission : permissions) {
if (permission.getResourceSetId() != null) {
if (isResourcePermission(actualPathConfig, permission)) {
hasPermission = true;
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
continue;
}
}
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
@@ -154,32 +143,16 @@ public abstract class AbstractPolicyEnforcer {
}
} else {
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
hasPermission = true;
return true;
}
}
}
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
return true;
}
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
return false;
}
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true;
}
}
return false;
}
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
Set<String> allowedScopes = permission.getScopes();
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
@@ -227,23 +200,26 @@ public abstract class AbstractPolicyEnforcer {
}
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
String path = getPath(request);
if (originalConfig.hasPattern()) {
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
ProtectedResource resource = this.authzClient.protection().resource();
Set<String> search = resource.findByFilter("uri=" + path);
if (!search.isEmpty()) {
// resource does exist on the server, cache it
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
PathConfig config = new PathConfig();
config.setId(targetResource.getId());
config.setName(targetResource.getName());
config.setType(targetResource.getType());
config.setPath(targetResource.getUri());
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
this.policyEnforcer.addPath(config);
this.paths.add(config);
return config;
}
@@ -252,10 +228,6 @@ public abstract class AbstractPolicyEnforcer {
return originalConfig;
}
private String getPath(Request request) {
return request.getRelativePath();
}
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
Set<String> requiredScopes = new HashSet<>();
@@ -41,7 +41,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
@Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
if (getEnforcerConfig().getUmaProtocolConfig() != null) {
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
} else {
challengeEntitlementAuthentication(facade);
@@ -17,10 +17,6 @@
*/
package org.keycloak.adapters.authorization;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
@@ -38,6 +34,10 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@@ -51,39 +51,44 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
@Override
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
int retry = 2;
AccessToken original = accessToken;
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
return true;
while (retry > 0) {
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
return true;
}
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
if (accessToken == null) {
return false;
}
AccessToken.Authorization authorization = original.getAuthorization();
if (authorization == null) {
authorization = new AccessToken.Authorization();
authorization.setPermissions(new ArrayList<Permission>());
}
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
if (newAuthorization != null) {
authorization.getPermissions().addAll(newAuthorization.getPermissions());
}
original.setAuthorization(authorization);
retry--;
}
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
if (accessToken == null) {
return false;
}
AccessToken.Authorization authorization = original.getAuthorization();
if (authorization == null) {
authorization = new AccessToken.Authorization();
authorization.setPermissions(new ArrayList<Permission>());
}
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
if (newAuthorization != null) {
authorization.getPermissions().addAll(newAuthorization.getPermissions());
}
original.setAuthorization(authorization);
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
return false;
}
@Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
String accessDeniedPath = getEnforcerConfig().getAccessDeniedPath();
HttpFacade.Response response = facade.getResponse();
if (accessDeniedPath != null) {
@@ -102,8 +107,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AuthzClient authzClient = getAuthzClient();
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
if (getEnforcerConfig().getUserManagedAccess() != null) {
LOGGER.debug("Obtaining authorization for authenticated user.");
if (getEnforcerConfig().getUmaProtocolConfig() != null) {
LOGGER.debug("Obtaining authorization for authenticated user.");
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId());
@@ -131,14 +136,12 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setResourceSetId(pathConfig.getId());
permissionRequest.setResourceSetName(pathConfig.getName());
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
request.addPermission(permissionRequest);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
}
} catch (AuthorizationDeniedException e) {
LOGGER.debug("Authorization denied", e);
return null;
} catch (Exception e) {
throw new RuntimeException("Unexpected error during authorization request.", e);
@@ -19,7 +19,7 @@ package org.keycloak.adapters.authorization;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Map;
import java.util.List;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -28,16 +28,10 @@ class PathMatcher {
private static final String ANY_RESOURCE_PATTERN = "/*";
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
PathConfig pathConfig = paths.get(requestedUri);
if (pathConfig != null) {
return pathConfig;
}
PathConfig matches(final String requestedUri, List<PathConfig> paths) {
PathConfig actualConfig = null;
for (PathConfig entry : paths.values()) {
for (PathConfig entry : paths) {
String protectedUri = entry.getPath();
String selectedUri = null;
@@ -34,10 +34,8 @@ import org.keycloak.representations.idm.authorization.Permission;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -50,7 +48,7 @@ public class PolicyEnforcer {
private final KeycloakDeployment deployment;
private final AuthzClient authzClient;
private final PolicyEnforcerConfig enforcerConfig;
private final Map<String, PathConfig> paths;
private final List<PathConfig> paths;
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment;
@@ -60,7 +58,7 @@ public class PolicyEnforcer {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initialization complete. Path configurations:");
for (PathConfig pathConfig : this.paths.values()) {
for (PathConfig pathConfig : this.paths) {
LOGGER.debug(pathConfig);
}
}
@@ -98,29 +96,16 @@ public class PolicyEnforcer {
return authzClient;
}
public Map<String, PathConfig> getPaths() {
return paths;
}
void addPath(PathConfig pathConfig) {
paths.put(pathConfig.getPath(), pathConfig);
public List<PathConfig> getPaths() {
return Collections.unmodifiableList(paths);
}
KeycloakDeployment getDeployment() {
return deployment;
}
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
boolean loadPathsFromServer = true;
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
loadPathsFromServer = false;
break;
}
}
if (loadPathsFromServer) {
private List<PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
if (enforcerConfig.getPaths().isEmpty()) {
LOGGER.info("No path provided in configuration.");
return configureAllPathsForResourceServer(protectedResource);
} else {
@@ -129,8 +114,8 @@ public class PolicyEnforcer {
}
}
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
private List<PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
List<PathConfig> paths = new ArrayList<>();
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
Set<String> search;
@@ -178,7 +163,7 @@ public class PolicyEnforcer {
PathConfig existingPath = null;
for (PathConfig current : paths.values()) {
for (PathConfig current : paths) {
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
existingPath = current;
break;
@@ -186,7 +171,7 @@ public class PolicyEnforcer {
}
if (existingPath == null) {
paths.put(pathConfig.getPath(), pathConfig);
paths.add(pathConfig);
} else {
existingPath.getMethods().addAll(pathConfig.getMethods());
existingPath.getScopes().addAll(pathConfig.getScopes());
@@ -196,24 +181,23 @@ public class PolicyEnforcer {
return paths;
}
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
private List<PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
LOGGER.info("Querying the server for all resources associated with this application.");
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
List<PathConfig> paths = new ArrayList<>();
for (String id : protectedResource.findAll()) {
RegistrationResponse response = protectedResource.findById(id);
ResourceRepresentation resourceDescription = response.getResourceDescription();
if (resourceDescription.getUri() != null) {
PathConfig pathConfig = createPathConfig(resourceDescription);
paths.put(pathConfig.getPath(), pathConfig);
paths.add(createPathConfig(resourceDescription));
}
}
return paths;
}
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
private PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
PathConfig pathConfig = new PathConfig();
pathConfig.setId(resourceDescription.getId());
@@ -37,9 +37,4 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
return publicKey;
}
@Override
public void reset(KeycloakDeployment deployment) {
}
}
@@ -25,6 +25,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.util.JWKSUtils;
import java.security.PublicKey;
@@ -47,51 +48,36 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
@Override
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
int currentTime = Time.currentTime();
// Check if key is in cache.
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
PublicKey publicKey = currentKeys.get(kid);
if (publicKey != null) {
return publicKey;
}
int currentTime = Time.currentTime();
// Check if we are allowed to send request
synchronized (this) {
currentTime = Time.currentTime();
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
sendRequest(deployment);
lastRequestTime = currentTime;
} else {
log.debug("Won't send request to realm jwks url. Last request time was " + lastRequestTime);
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
synchronized (this) {
currentTime = Time.currentTime();
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
sendRequest(deployment);
lastRequestTime = currentTime;
} else {
log.debugf("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
}
}
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
}
}
return currentKeys.get(kid);
@Override
public void reset(KeycloakDeployment deployment) {
synchronized (this) {
sendRequest(deployment);
lastRequestTime = Time.currentTime();
}
}
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
return currentKeys.get(kid);
} else {
return null;
}
}
private void sendRequest(KeycloakDeployment deployment) {
if (log.isTraceEnabled()) {
log.trace("Going to send request to retrieve new set of realm public keys for client " + deployment.getResourceName());
log.tracef("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
}
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
@@ -101,7 +87,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
if (log.isDebugEnabled()) {
log.debug("Realm public keys successfully retrieved for client " + deployment.getResourceName() + ". New kids: " + publicKeys.keySet().toString());
log.debugf("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
}
// Update current keys
@@ -34,11 +34,4 @@ public interface PublicKeyLocator {
*/
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
/**
* Reset the state of locator (eg. clear the cached keys)
*
* @param deployment
*/
void reset(KeycloakDeployment deployment);
}
@@ -69,7 +69,6 @@ public class KeycloakDeploymentBuilderTest {
assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl());
}
@Test
@@ -79,7 +78,6 @@ public class KeycloakDeploymentBuilderTest {
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
assertEquals(86400, deployment.getPublicKeyCacheTtl());
}
@Test
@@ -30,6 +30,5 @@
"token-store": "cookie",
"principal-attribute": "email",
"token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20,
"public-key-cache-ttl": 120
"min-time-between-jwks-requests": 20
}
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -86,7 +86,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
//TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
list.add(addRealm);
@@ -117,7 +117,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
* TODO need to check realm-ref first.
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
//TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
*/
@@ -46,7 +46,7 @@ public final class RealmAddHandler extends AbstractAddStepHandler {
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
//TODO: externalize message
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
}
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -66,11 +66,6 @@ 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();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Jetty Integration</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -118,11 +118,12 @@
/**
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
*/
this.entitlement = function (resourceSeververId, entitlementRequest ) {
this.entitlement = function (resourceSeververId) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.onreadystatechange = function () {
if (request.readyState == 4) {
@@ -148,19 +149,7 @@
}
};
var erJson = null
if(entitlementRequest) {
request.open('POST', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
request.setRequestHeader("Content-type", "application/json");
erJson = JSON.stringify(entitlementRequest)
} else {
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
}
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.send(erJson);
request.send(null);
};
return this;
+60 -61
View File
@@ -153,7 +153,7 @@
processCallback(callback, initPromise);
return;
} else if (initOptions) {
if (initOptions.refreshToken) {
if (initOptions.token || initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
if (loginIframe.enable) {
@@ -210,18 +210,11 @@
var nonce = createUUID();
var redirectUri = adapter.redirectUri(options);
var callbackState = {
state: state,
nonce: nonce,
redirectUri: encodeURIComponent(redirectUri),
}
if (options && options.prompt) {
callbackState.prompt = options.prompt;
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
}
callbackStorage.add(callbackState);
callbackStorage.add({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
var action = 'auth';
if (options && options.action == 'register') {
@@ -379,7 +372,7 @@
kc.updateToken = function(minValidity) {
var promise = createPromise();
if (!kc.refreshToken) {
if (!kc.tokenParsed || !kc.refreshToken) {
promise.setError();
return promise.promise;
}
@@ -388,10 +381,14 @@
var exec = function() {
var refreshToken = false;
if (minValidity == -1) {
if (kc.timeSkew == -1) {
console.info('Skew ' + kc.timeSkew);
refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: time skew not set');
} else if (minValidity == -1) {
refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: forced refresh');
} else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
} else if (kc.isTokenExpired(minValidity)) {
refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: token expired');
}
@@ -634,6 +631,49 @@
kc.tokenTimeoutHandle = null;
}
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
if (kc.tokenParsed.session_state) {
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
}
kc.sessionId = sessionId;
kc.authenticated = true;
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (timeLocal) {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
} else {
kc.timeSkew = -1;
}
if (kc.onTokenExpired) {
if (kc.timeSkew == -1) {
kc.onTokenExpired();
} else {
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
if (expiresIn <= 0) {
kc.onTokenExpired();
} else {
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
}
}
}
} else {
delete kc.token;
delete kc.tokenParsed;
delete kc.subject;
delete kc.realmAccess;
delete kc.resourceAccess;
kc.authenticated = false;
}
if (refreshToken) {
kc.refreshToken = refreshToken;
kc.refreshTokenParsed = decodeToken(refreshToken);
@@ -649,48 +689,6 @@
delete kc.idToken;
delete kc.idTokenParsed;
}
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
if (kc.tokenParsed.session_state) {
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
}
kc.sessionId = sessionId;
kc.authenticated = true;
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (timeLocal) {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
if (kc.onTokenExpired) {
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
if (expiresIn <= 0) {
kc.onTokenExpired();
} else {
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
}
}
} else {
kc.updateToken(-1);
}
} else if (refreshToken) {
kc.updateToken(-1);
} else {
delete kc.token;
delete kc.tokenParsed;
delete kc.subject;
delete kc.realmAccess;
delete kc.resourceAccess;
kc.authenticated = false;
}
}
function decodeToken(str) {
@@ -749,7 +747,6 @@
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
oauth.redirectUri = oauthState.redirectUri;
oauth.storedNonce = oauthState.nonce;
oauth.prompt = oauthState.prompt;
if (oauth.fragment) {
oauth.newUrl += '#' + oauth.fragment;
@@ -841,15 +838,14 @@
kc.clearToken();
}
var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length);
for (var i = callbacks.length - 1; i >= 0; --i) {
var promise = callbacks[i];
for (var i = loginIframe.callbackList.length - 1; i >= 0; --i) {
var promise = loginIframe.callbackList[i];
if (event.data == "unchanged") {
promise.setSuccess();
} else {
promise.setError();
}
loginIframe.callbackList.splice(i, 1);
}
};
@@ -1045,7 +1041,7 @@
function clearExpired() {
var time = new Date().getTime();
for (var i = 0; i < localStorage.length; i++) {
for (var i = 1; i <= localStorage.length; i++) {
var key = localStorage.key(i);
if (key && key.indexOf('kc-callback-') == 0) {
var value = localStorage.getItem(key);
@@ -1222,6 +1218,9 @@
case 'redirect_fragment':
oauth.fragment = queryParams[param];
break;
case 'prompt':
oauth.prompt = queryParams[param];
break;
default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
@@ -1,49 +0,0 @@
<!--
~ 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.
-->
<script>
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return null;
}
function receiveMessage(event)
{
if (event.origin !== "ORIGIN") {
console.log(event.origin + " does not match built origin");
return;
}
var data = JSON.parse(event.data);
data.loggedIn = false;
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
data.loggedIn = true;
data.session = cookie;
}
event.source.postMessage(JSON.stringify(data),
event.origin);
}
window.addEventListener("message", receiveMessage, false);
</script>
@@ -21,28 +21,25 @@
var init;
function checkState(clientId, origin, sessionState, callback) {
var cookie = getCookie();
if (!cookie) {
callback('changed');
} else if (!init) {
if (!init) {
var req = new XMLHttpRequest();
var url = location.href + "/init";
url += "?client_id=" + encodeURIComponent(clientId);
url += "&origin=" + encodeURIComponent(origin);
url += "&session_state=" + encodeURIComponent(sessionState);
req.open('GET', url, true);
req.onreadystatechange = function () {
if (req.readyState === 4) {
if (req.status === 204) {
if (req.readyState == 4) {
if (req.status == 204) {
init = {
clientId: clientId,
origin: origin
}
callback('unchanged');
} else if (req.status === 404) {
} else if (req.status = 404) {
callback('changed');
} else {
callback('error');
@@ -52,8 +49,9 @@
req.send();
} else {
if (clientId === init.clientId && origin === init.origin) {
if (sessionState === cookie) {
if (clientId == init.clientId && origin == init.origin) {
var cookie = getCookie();
if (sessionState == cookie) {
callback('unchanged');
} else {
callback('changed');
@@ -71,7 +69,7 @@
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)===0) return c.substring(name.length,c.length);
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return null;
}
@@ -95,4 +93,4 @@
window.addEventListener("message", receiveMessage, false);
</script>
</body>
</html>
</html>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<name>Keycloak OIDC Client Adapter Modules</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -218,11 +218,6 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
return servletRequest.getRequestURL().toString();
}
@Override
public String getRelativePath() {
return servletRequest.getServletPath();
}
@Override
public boolean isSecure() {
return servletRequest.isSecure();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -215,7 +215,7 @@ public class KeycloakSpringBootConfiguration {
securityHandler.setConstraintMappings(jettyConstraintMappings);
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
webAppContext.setSecurityHandler(securityHandler);
webAppContext.setHandler(securityHandler);
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -30,12 +30,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}. Uses a
@@ -62,15 +56,12 @@ public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoin
private final RequestMatcher apiRequestMatcher;
private String loginUri = DEFAULT_LOGIN_URI;
private String realm = DEFAULT_REALM;
private AdapterDeploymentContext adapterDeploymentContext;
/**
* Creates a new Keycloak authentication entry point.
*/
public KeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext) {
public KeycloakAuthenticationEntryPoint() {
this(DEFAULT_API_REQUEST_MATCHER);
this.adapterDeploymentContext = adapterDeploymentContext;
}
/**
@@ -88,8 +79,7 @@ public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoin
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
{
HttpFacade facade = new SimpleHttpFacade(request, response);
if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
if (apiRequestMatcher.matches(request)) {
commenceUnauthorizedResponse(request, response);
} else {
commenceLoginRedirect(request, response);
@@ -71,8 +71,8 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
return factoryBean.getObject();
}
protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception {
return new KeycloakAuthenticationEntryPoint(adapterDeploymentContext());
protected AuthenticationEntryPoint authenticationEntryPoint() {
return new KeycloakAuthenticationEntryPoint();
}
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
@@ -70,11 +70,6 @@ class WrappedHttpServletRequest implements Request {
return buf.toString();
}
@Override
public String getRelativePath() {
return request.getServletPath();
}
@Override
public boolean isSecure() {
return request.isSecure();
@@ -1,90 +0,0 @@
/*
* 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.adapters.springsecurity.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.filter.GenericFilterBean;
/**
* Exposes a Keycloak adapter {@link AuthenticatedActionsHandler} as a Spring Security filter.
*
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatedActionsFilter extends GenericFilterBean implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticatedActionsFilter.class);
private final NodesRegistrationManagement management = new NodesRegistrationManagement();
private ApplicationContext applicationContext;
private AdapterDeploymentContext deploymentContext;
public KeycloakAuthenticatedActionsFilter() {
super();
}
@Override
protected void initFilterBean() throws ServletException {
deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
}
@Override
public void destroy() {
log.debug("Unregistering deployment");
management.stop();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deploymentContext.resolveDeployment(facade), (OIDCHttpFacade)facade);
boolean handled = handler.handledRequest();
if (handled) {
log.debug("Authenticated filter handled request: {}", ((HttpServletRequest) request).getRequestURI());
} else {
chain.doFilter(request, response);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
@@ -142,7 +142,6 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
if (AuthOutcome.FAILED.equals(result)) {
throw new KeycloakAuthenticationException("Auth outcome: " + result);
}
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
@@ -26,15 +26,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.spi.HttpFacade;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import org.mockito.Mock;
import static org.mockito.Mockito.when;
import org.mockito.MockitoAnnotations;
import org.springframework.context.ApplicationContext;
/**
* Keycloak authentication entry point tests.
@@ -44,24 +35,12 @@ public class KeycloakAuthenticationEntryPointTest {
private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Mock
private ApplicationContext applicationContext;
@Mock
private AdapterDeploymentContext adapterDeploymentContext;
@Mock
private KeycloakDeployment keycloakDeployment;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint(adapterDeploymentContext);
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
}
@Test
@@ -154,7 +154,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
when(keycloakDeployment.getResourceName()).thenReturn("resource-name");
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
filter.attemptAuthentication(request, response);
verify(response).setStatus(302);
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Tomcat Integration</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -127,7 +127,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
InputStream configInputStream = getConfigInputStream(context);
KeycloakDeployment kd;
if (configInputStream == null) {
log.warning("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
kd = new KeycloakDeployment();
} else {
kd = KeycloakDeploymentBuilder.build(configInputStream);
@@ -196,8 +196,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
//needed for the EAP6/AS7 adapter relying on the tomcat core adapter
facade.getResponse().sendError(401);
return false;
}
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -39,20 +39,9 @@ import java.util.List;
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
/**
* Method called by Tomcat < 8.5.5
*/
public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
return authenticateInternal(request, response, request.getContext().getLoginConfig());
}
/**
* Method called by Tomcat >= 8.5.5
*/
protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException {
return this.authenticate(request, response);
}
@Override
protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -92,7 +92,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
}
AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
@@ -173,15 +173,10 @@ public class KeycloakServletExtension implements ServletExtension {
}
});
ServletSessionConfig cookieConfig = deploymentInfo.getServletSessionConfig();
if (cookieConfig == null) {
cookieConfig = new ServletSessionConfig();
}
if (cookieConfig.getPath() == null) {
log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
cookieConfig.setPath(deploymentInfo.getContextPath());
deploymentInfo.setServletSessionConfig(cookieConfig);
}
log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
ServletSessionConfig cookieConfig = new ServletSessionConfig();
cookieConfig.setPath(deploymentInfo.getContextPath());
deploymentInfo.setServletSessionConfig(cookieConfig);
ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo);
deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationManagementWrapper.class, new InstanceFactory<UndertowNodesRegistrationManagementWrapper>() {
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak WildFly Integration</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
@@ -87,7 +87,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
//TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
list.add(addRealm);
@@ -118,7 +118,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
* TODO need to check realm-ref first.
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
//TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
*/
@@ -54,7 +54,7 @@ public final class RealmAddHandler extends AbstractAddStepHandler {
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
//TODO: externalize message
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
}
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
+2 -2
View File
@@ -20,10 +20,10 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Adapters</name>
<name>Keycloak Integration</name>
<description/>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML EAP Integration</name>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../pom.xml</relativePath>
</parent>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -105,7 +105,7 @@ public class SamlPrincipal implements Serializable, Principal {
* @return
*/
public List<String> getFriendlyAttributes(String friendlyName) {
List<String> list = friendlyAttributes.get(friendlyName);
List<String> list = friendlyAttributes.get(name);
if (list != null) {
return Collections.unmodifiableList(list);
} else {
Whitespace-only changes.
+1 -7
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>2.5.3.Final</version>
<version>2.3.0.CR1</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -34,7 +34,6 @@
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
@@ -71,11 +70,6 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
@@ -1,75 +0,0 @@
/*
* 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.adapters.cloned;
/**
* Configuration options relevant for configuring http client that can be used by adapter.
*
* NOTE: keep in sync with core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java until unified.
*
* @author hmlnarik
*/
public interface AdapterHttpClientConfig {
/**
* Returns truststore filename.
*/
public String getTruststore();
/**
* Returns truststore password.
*/
public String getTruststorePassword();
/**
* Returns keystore with client keys.
*/
public String getClientKeystore();
/**
* Returns keystore password.
*/
public String getClientKeystorePassword();
/**
* Returns boolean flag whether any hostname verification is done on the server's
* certificate, {@code true} means that verification is not done.
* @return
*/
public boolean isAllowAnyHostname();
/**
* Returns boolean flag whether any trust management and hostname verification is done.
* <p>
* <i>NOTE</i> Disabling trust manager is a security hole, so only set this option
* if you cannot or do not want to verify the identity of the
* host you are communicating with.
*/
public boolean isDisableTrustManager();
/**
* Returns size of connection pool.
*/
public int getConnectionPoolSize();
/**
* Returns URL of HTTP proxy.
*/
public String getProxyUrl();
}
@@ -1,78 +0,0 @@
/*
* 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.adapters.cloned;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.keycloak.adapters.saml.descriptor.parsers.SamlDescriptorIDPKeysExtractor;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.saml.common.exceptions.ParsingException;
/**
* @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
*/
public class HttpAdapterUtils {
public static MultivaluedHashMap<String, KeyInfo> downloadKeysFromSamlDescriptor(HttpClient client, String descriptorUrl) throws HttpClientAdapterException {
try {
HttpGet httpRequest = new HttpGet(descriptorUrl);
HttpResponse response = client.execute(httpRequest);
int status = response.getStatusLine().getStatusCode();
if (status != HttpStatus.SC_OK) {
EntityUtils.consumeQuietly(response.getEntity());
throw new HttpClientAdapterException("Unexpected status = " + status);
}
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new HttpClientAdapterException("There was no entity.");
}
MultivaluedHashMap<String, KeyInfo> res;
try (InputStream is = entity.getContent()) {
res = extractKeysFromSamlDescriptor(is);
}
EntityUtils.consumeQuietly(entity);
return res;
} catch (IOException | ParsingException e) {
throw new HttpClientAdapterException("IO error", e);
}
}
/**
* Parses SAML descriptor and extracts keys from it.
* @param xmlStream
* @return List of KeyInfo objects containing keys from the descriptor.
* @throws IOException
*/
public static MultivaluedHashMap<String, KeyInfo> extractKeysFromSamlDescriptor(InputStream xmlStream) throws ParsingException {
Object res = new SamlDescriptorIDPKeysExtractor().parse(xmlStream);
return (MultivaluedHashMap<String, KeyInfo>) res;
}
}
@@ -1,396 +0,0 @@
/*
* 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.adapters.cloned;
import org.apache.http.HttpHost;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.URI;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Abstraction for creating HttpClients. Allows SSL configuration.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HttpClientBuilder {
public static enum HostnameVerificationPolicy {
/**
* Hostname verification is not done on the server's certificate
*/
ANY,
/**
* Allows wildcards in subdomain names i.e. *.foo.com
*/
WILDCARD,
/**
* CN must match hostname connecting to
*/
STRICT
}
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
private static class PassthroughTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
protected KeyStore truststore;
protected KeyStore clientKeyStore;
protected String clientPrivateKeyPassword;
protected boolean disableTrustManager;
protected boolean disableCookieCache = true;
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
protected SSLContext sslContext;
protected int connectionPoolSize = 100;
protected int maxPooledPerRoute = 0;
protected long connectionTTL = -1;
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
protected HostnameVerifier verifier = null;
protected long socketTimeout = -1;
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
protected long establishConnectionTimeout = -1;
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
protected HttpHost proxyHost;
/**
* Socket inactivity timeout
*
* @param timeout
* @param unit
* @return
*/
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
this.socketTimeout = timeout;
this.socketTimeoutUnits = unit;
return this;
}
/**
* When trying to make an initial socket connection, what is the timeout?
*
* @param timeout
* @param unit
* @return
*/
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
this.establishConnectionTimeout = timeout;
this.establishConnectionTimeoutUnits = unit;
return this;
}
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
this.connectionTTL = ttl;
this.connectionTTLUnit = unit;
return this;
}
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
this.maxPooledPerRoute = maxPooledPerRoute;
return this;
}
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
return this;
}
/**
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
* hole, so only set this option if you cannot or do not want to verify the identity of the
* host you are communicating with.
*/
public HttpClientBuilder disableTrustManager() {
this.disableTrustManager = true;
return this;
}
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
return this;
}
/**
* SSL policy used to verify hostnames
*
* @param policy
* @return
*/
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
this.policy = policy;
return this;
}
public HttpClientBuilder sslContext(SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}
public HttpClientBuilder trustStore(KeyStore truststore) {
this.truststore = truststore;
return this;
}
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = password;
return this;
}
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = new String(password);
return this;
}
static class VerifierWrapper implements X509HostnameVerifier {
protected HostnameVerifier verifier;
VerifierWrapper(HostnameVerifier verifier) {
this.verifier = verifier;
}
@Override
public void verify(String host, SSLSocket ssl) throws IOException {
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
}
@Override
public void verify(String host, X509Certificate cert) throws SSLException {
throw new SSLException("This verification path not implemented");
}
@Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
throw new SSLException("This verification path not implemented");
}
@Override
public boolean verify(String s, SSLSession sslSession) {
return verifier.verify(s, sslSession);
}
}
public HttpClient build() {
X509HostnameVerifier verifier = null;
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
else {
switch (policy) {
case ANY:
verifier = new AllowAllHostnameVerifier();
break;
case WILDCARD:
verifier = new BrowserCompatHostnameVerifier();
break;
case STRICT:
verifier = new StrictHostnameVerifier();
break;
}
}
try {
SSLSocketFactory sslsf = null;
SSLContext theContext = sslContext;
if (disableTrustManager) {
theContext = SSLContext.getInstance("SSL");
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
new SecureRandom());
verifier = new AllowAllHostnameVerifier();
sslsf = new SniSSLSocketFactory(theContext, verifier);
} else if (theContext != null) {
sslsf = new SniSSLSocketFactory(theContext, verifier);
} else if (clientKeyStore != null || truststore != null) {
sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
} else {
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
tlsContext.init(null, null, null);
sslsf = new SniSSLSocketFactory(tlsContext, verifier);
}
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
Scheme httpsScheme = new Scheme("https", 443, sslsf);
registry.register(httpsScheme);
ClientConnectionManager cm = null;
if (connectionPoolSize > 0) {
ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
tcm.setMaxTotal(connectionPoolSize);
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
cm = tcm;
} else {
cm = new SingleClientConnManager(registry);
}
BasicHttpParams params = new BasicHttpParams();
if (proxyHost != null) {
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
}
if (socketTimeout > -1) {
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
}
if (establishConnectionTimeout > -1) {
HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
}
DefaultHttpClient client = new DefaultHttpClient(cm, params);
if (disableCookieCache) {
client.setCookieStore(new CookieStore() {
@Override
public void addCookie(Cookie cookie) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public List<Cookie> getCookies() {
return Collections.emptyList();
}
@Override
public boolean clearExpired(Date date) {
return false; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void clear() {
//To change body of implemented methods use File | Settings | File Templates.
}
});
}
return client;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {
truststorePath = EnvUtil.replace(truststorePath);
String truststorePassword = adapterConfig.getTruststorePassword();
try {
this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore", e);
}
}
String clientKeystore = adapterConfig.getClientKeystore();
if (clientKeystore != null) {
clientKeystore = EnvUtil.replace(clientKeystore);
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
try {
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
keyStore(clientCertKeystore, clientKeystorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load keystore", e);
}
}
int size = 10;
if (adapterConfig.getConnectionPoolSize() > 0)
size = adapterConfig.getConnectionPoolSize();
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
if (adapterConfig.isAllowAnyHostname())
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
connectionPoolSize(size);
hostnameVerification(policy);
if (adapterConfig.isDisableTrustManager()) {
disableTrustManager();
} else {
trustStore(truststore);
}
configureProxyForAuthServerIfProvided(adapterConfig);
return build();
}
/**
* Configures a the proxy to use for auth-server requests if provided.
* <p>
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored.
* </p>
*
* @param adapterConfig
*/
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return;
}
URI uri = URI.create(adapterConfig.getProxyUrl());
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
}
}
@@ -1,143 +0,0 @@
/*
* 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.adapters.cloned;
import org.apache.http.HttpHost;
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.protocol.HttpContext;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.AccessController;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* SSLSocketFactory that uses Server Name Indication (SNI) TLS extension.
*
* <p>
* Originally copied from <b>keycloak-adapter-core</b> project.
*
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
* @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
*/
public class SniSSLSocketFactory extends SSLSocketFactory {
private static final Logger LOG = Logger.getLogger(SniSSLSocketFactory.class.getName());
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, HostNameResolver nameResolver) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, nameResolver);
}
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, trustStrategy, hostnameVerifier);
}
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, hostnameVerifier);
}
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(keystore, keystorePassword, truststore);
}
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(keystore, keystorePassword);
}
public SniSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
}
public SniSSLSocketFactory(TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(trustStrategy, hostnameVerifier);
}
public SniSSLSocketFactory(TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(trustStrategy);
}
public SniSSLSocketFactory(SSLContext sslContext) {
super(sslContext);
}
public SniSSLSocketFactory(SSLContext sslContext, HostNameResolver nameResolver) {
super(sslContext, nameResolver);
}
public SniSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}
public SniSSLSocketFactory(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) {
super(socketfactory, hostnameVerifier);
}
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
super(socketfactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
@Override
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
return super.connectSocket(connectTimeout, applySNI(socket, host.getHostName()), host, remoteAddress, localAddress, context);
}
@Override
public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
return super.createLayeredSocket(applySNI(socket, target), target, port, context);
}
private Socket applySNI(final Socket socket, String hostname) {
if (socket instanceof SSLSocket) {
try {
Method setHostMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
@Override
public Method run() throws NoSuchMethodException {
return socket.getClass().getMethod("setHost", String.class);
}
});
setHostMethod.invoke(socket, hostname);
LOG.log(Level.FINEST, "Applied SNI to socket for host {0}", hostname);
} catch (PrivilegedActionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOG.log(Level.WARNING, "Failed to apply SNI to SSLSocket", e);
}
}
return socket;
}
}
@@ -79,9 +79,7 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
}
binding.signWith(null, keypair);
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
binding.signWith(keypair);
binding.signDocument();
}
return binding;
@@ -23,14 +23,7 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.http.client.HttpClient;
import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
import org.keycloak.rotation.CompositeKeyLocator;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -186,15 +179,10 @@ public class DefaultSamlDeployment implements SamlDeployment {
public static class DefaultIDP implements IDP {
private static final int DEFAULT_CACHE_TTL = 24 * 60 * 60;
private String entityID;
private final CompositeKeyLocator signatureValidationKeyLocator = new CompositeKeyLocator();
private PublicKey signatureValidationKey;
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
private final List<PublicKey> signatureValidationKeys = new LinkedList<>();
private int minTimeBetweenDescriptorRequests;
private HttpClient client;
@Override
public String getEntityID() {
@@ -212,25 +200,16 @@ public class DefaultSamlDeployment implements SamlDeployment {
}
@Override
public KeyLocator getSignatureValidationKeyLocator() {
return this.signatureValidationKeyLocator;
}
@Override
public int getMinTimeBetweenDescriptorRequests() {
return minTimeBetweenDescriptorRequests;
}
public void setMinTimeBetweenDescriptorRequests(int minTimeBetweenDescriptorRequests) {
this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests;
public PublicKey getSignatureValidationKey() {
return signatureValidationKey;
}
public void setEntityID(String entityID) {
this.entityID = entityID;
}
public void addSignatureValidationKey(PublicKey signatureValidationKey) {
this.signatureValidationKeys.add(signatureValidationKey);
public void setSignatureValidationKey(PublicKey signatureValidationKey) {
this.signatureValidationKey = signatureValidationKey;
}
public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
@@ -240,31 +219,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
this.singleLogoutService = singleLogoutService;
}
public void refreshKeyLocatorConfiguration() {
this.signatureValidationKeyLocator.clear();
// When key is set, use that (and only that), otherwise configure dynamic key locator
if (! this.signatureValidationKeys.isEmpty()) {
this.signatureValidationKeyLocator.add(new HardcodedKeyLocator(this.signatureValidationKeys));
} else if (this.singleSignOnService != null) {
String samlDescriptorUrl = singleSignOnService.getRequestBindingUrl() + "/descriptor";
HttpClient httpClient = getClient();
SamlDescriptorPublicKeyLocator samlDescriptorPublicKeyLocator =
new SamlDescriptorPublicKeyLocator(
samlDescriptorUrl, this.minTimeBetweenDescriptorRequests, DEFAULT_CACHE_TTL, httpClient);
this.signatureValidationKeyLocator.add(samlDescriptorPublicKeyLocator);
}
}
@Override
public HttpClient getClient() {
return this.client;
}
public void setClient(HttpClient client) {
this.client = client;
}
}
private IDP idp;
@@ -22,18 +22,14 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Set;
import org.apache.http.client.HttpClient;
import org.keycloak.rotation.KeyLocator;
/**
* Represents SAML deployment configuration.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface SamlDeployment {
enum Binding {
POST,
REDIRECT;
@@ -45,68 +41,20 @@ public interface SamlDeployment {
}
public interface IDP {
/**
* Returns entity identifier of this IdP.
* @return see description.
*/
String getEntityID();
/**
* Returns Single sign on service configuration for this IdP.
* @return see description.
*/
SingleSignOnService getSingleSignOnService();
/**
* Returns Single logout service configuration for this IdP.
* @return see description.
*/
SingleLogoutService getSingleLogoutService();
/**
* Returns {@link KeyLocator} looking up public keys used for validation of IdP signatures.
* @return see description.
*/
KeyLocator getSignatureValidationKeyLocator();
/**
* Returns minimum time (in seconds) between issuing requests to IdP SAML descriptor.
* Used e.g. by {@link KeyLocator} looking up public keys for validation of IdP signatures
* to prevent too frequent requests.
*
* @return see description.
*/
int getMinTimeBetweenDescriptorRequests();
/**
* Returns {@link HttpClient} instance that will be used for http communication with this IdP.
* @return see description
*/
HttpClient getClient();
PublicKey getSignatureValidationKey();
public interface SingleSignOnService {
/**
* Returns {@code true} if the requests to IdP need to be signed by SP key.
* @return see dscription
*/
boolean signRequest();
/**
* Returns {@code true} if the complete response message from IdP should
* be checked for valid signature.
* @return see dscription
*/
boolean validateResponseSignature();
/**
* Returns {@code true} if individual assertions in response from IdP should
* be checked for valid signature.
* @return see dscription
*/
boolean validateAssertionSignature();
Binding getRequestBinding();
Binding getResponseBinding();
String getRequestBindingUrl();
}
public interface SingleLogoutService {
boolean validateRequestSignature();
boolean validateResponseSignature();
@@ -119,19 +67,10 @@ public interface SamlDeployment {
}
}
/**
* Returns Identity Provider configuration for this SAML deployment.
* @return see description.
*/
public IDP getIDP();
public boolean isConfigured();
SslRequired getSslRequired();
/**
* Returns entity identifier of this SP.
* @return see description.
*/
String getEntityID();
String getNameIDPolicyFormat();
boolean isForceAuthentication();
@@ -40,7 +40,7 @@ public class SamlUtil {
httpFacade.getResponse().setHeader("Content-Type", "text/html");
httpFacade.getResponse().setHeader("Pragma", "no-cache");
httpFacade.getResponse().setHeader("Cache-Control", "no-cache, no-store");
httpFacade.getResponse().getOutputStream().write(html.getBytes(GeneralConstants.SAML_CHARSET));
httpFacade.getResponse().getOutputStream().write(html.getBytes());
httpFacade.getResponse().end();
} else {
String uri = asRequest ? binding.redirectBinding(document).requestURI(actionUrl).toString() : binding.redirectBinding(document).responseURI(actionUrl).toString();
@@ -19,7 +19,6 @@ package org.keycloak.adapters.saml.config;
import java.io.Serializable;
import java.util.List;
import org.keycloak.adapters.cloned.AdapterHttpClientConfig;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -158,97 +157,12 @@ public class IDP implements Serializable {
}
}
public static class HttpClientConfig implements AdapterHttpClientConfig {
private String truststore;
private String truststorePassword;
private String clientKeystore;
private String clientKeystorePassword;
private boolean allowAnyHostname;
private boolean disableTrustManager;
private int connectionPoolSize;
private String proxyUrl;
@Override
public String getTruststore() {
return truststore;
}
public void setTruststore(String truststore) {
this.truststore = truststore;
}
@Override
public String getTruststorePassword() {
return truststorePassword;
}
public void setTruststorePassword(String truststorePassword) {
this.truststorePassword = truststorePassword;
}
@Override
public String getClientKeystore() {
return clientKeystore;
}
public void setClientKeystore(String clientKeystore) {
this.clientKeystore = clientKeystore;
}
@Override
public String getClientKeystorePassword() {
return clientKeystorePassword;
}
public void setClientKeystorePassword(String clientKeystorePassword) {
this.clientKeystorePassword = clientKeystorePassword;
}
@Override
public boolean isAllowAnyHostname() {
return allowAnyHostname;
}
public void setAllowAnyHostname(boolean allowAnyHostname) {
this.allowAnyHostname = allowAnyHostname;
}
@Override
public boolean isDisableTrustManager() {
return disableTrustManager;
}
public void setDisableTrustManager(boolean disableTrustManager) {
this.disableTrustManager = disableTrustManager;
}
@Override
public int getConnectionPoolSize() {
return connectionPoolSize;
}
public void setConnectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
@Override
public String getProxyUrl() {
return proxyUrl;
}
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
}
private String entityID;
private String signatureAlgorithm;
private String signatureCanonicalizationMethod;
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
private List<Key> keys;
private AdapterHttpClientConfig httpClientConfig = new HttpClientConfig();
public String getEntityID() {
return entityID;
@@ -298,12 +212,4 @@ public class IDP implements Serializable {
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
}
public AdapterHttpClientConfig getHttpClientConfig() {
return httpClientConfig;
}
public void setHttpClientConfig(AdapterHttpClientConfig httpClientConfig) {
this.httpClientConfig = httpClientConfig;
}
}
@@ -72,15 +72,4 @@ public class ConfigXmlConstants {
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";
public static final String CLIENT_KEYSTORE_ATTR = "clientKeystore";
public static final String CLIENT_KEYSTORE_PASSWORD_ATTR = "clientKeystorePassword";
public static final String CONNECTION_POOL_SIZE_ATTR = "connectionPoolSize";
public static final String DISABLE_TRUST_MANAGER_ATTR = "disableTrustManager";
public static final String PROXY_URL_ATTR = "proxyUrl";
public static final String TRUSTSTORE_ATTR = "truststore";
public static final String TRUSTSTORE_PASSWORD_ATTR = "truststorePassword";
}
@@ -40,7 +40,6 @@ import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.Set;
import org.keycloak.adapters.cloned.HttpClientBuilder;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -179,39 +178,35 @@ public class DeploymentBuilder {
if (sp.getIdp().getKeys() != null) {
for (Key key : sp.getIdp().getKeys()) {
if (key.isSigning()) {
processSigningKey(idp, key, resourceLoader);
if (key.getKeystore() != null) {
KeyStore keyStore = loadKeystore(resourceLoader, key);
Certificate cert = null;
try {
cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
idp.setSignatureValidationKey(cert.getPublicKey());
} else {
if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
}
try {
PublicKey publicKey = getPublicKeyFromPem(key);
idp.setSignatureValidationKey(publicKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
idp.setClient(new HttpClientBuilder().build(sp.getIdp().getHttpClientConfig()));
idp.refreshKeyLocatorConfiguration();
return deployment;
}
private void processSigningKey(DefaultSamlDeployment.DefaultIDP idp, Key key, ResourceLoader resourceLoader) throws RuntimeException {
PublicKey publicKey;
if (key.getKeystore() != null) {
KeyStore keyStore = loadKeystore(resourceLoader, key);
Certificate cert = null;
try {
cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
publicKey = cert.getPublicKey();
} else {
if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
}
publicKey = getPublicKeyFromPem(key);
}
idp.addSignatureValidationKey(publicKey);
}
protected static PublicKey getPublicKeyFromPem(Key key) {
protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
PublicKey publicKey;
if (key.getPublicKeyPem() != null) {
publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
@@ -29,10 +29,6 @@ import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.util.List;
import org.keycloak.adapters.saml.config.IDP.HttpClientConfig;
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getAttributeValue;
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getBooleanAttributeValue;
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getIntegerAttributeValue;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -45,16 +41,16 @@ public class IDPXmlParser extends AbstractParser {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
IDP idp = new IDP();
String entityID = getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
String entityID = SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
if (entityID == null) {
throw new ParsingException("entityID must be set on IDP");
}
idp.setEntityID(entityID);
boolean signaturesRequired = getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
idp.setSignatureCanonicalizationMethod(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
idp.setSignatureAlgorithm(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
boolean signaturesRequired = SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
idp.setSignatureCanonicalizationMethod(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
idp.setSignatureAlgorithm(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null)
@@ -79,10 +75,6 @@ public class IDPXmlParser extends AbstractParser {
IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
idp.setSingleLogoutService(slo);
} else if (tag.equals(ConfigXmlConstants.HTTP_CLIENT_ELEMENT)) {
HttpClientConfig config = parseHttpClientElement(xmlEventReader);
idp.setHttpClientConfig(config);
} else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
KeysXmlParser parser = new KeysXmlParser();
List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
@@ -98,63 +90,29 @@ public class IDPXmlParser extends AbstractParser {
protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
slo.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
slo.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
slo.setValidateRequestSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
slo.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
slo.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
slo.setSignResponse(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
slo.setPostBindingUrl(getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
slo.setRedirectBindingUrl(getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
slo.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
slo.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
slo.setValidateRequestSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
slo.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
slo.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
slo.setSignResponse(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
slo.setPostBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
slo.setRedirectBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
return slo;
}
protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
sso.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
sso.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
sso.setValidateAssertionSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
sso.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
sso.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
sso.setValidateAssertionSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
sso.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
sso.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
sso.setBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
return sso;
}
private HttpClientConfig parseHttpClientElement(XMLEventReader xmlEventReader) throws ParsingException {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.HTTP_CLIENT_ELEMENT);
HttpClientConfig config = new HttpClientConfig();
config.setAllowAnyHostname(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
config.setClientKeystore(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_ATTR));
config.setClientKeystorePassword(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_PASSWORD_ATTR));
config.setConnectionPoolSize(getIntegerAttributeValue(startElement, ConfigXmlConstants.CONNECTION_POOL_SIZE_ATTR, 0));
config.setDisableTrustManager(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
config.setProxyUrl(getAttributeValue(startElement, ConfigXmlConstants.PROXY_URL_ATTR));
config.setTruststore(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_ATTR));
config.setTruststorePassword(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_PASSWORD_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null)
break;
if (xmlEvent instanceof EndElement) {
EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
String endElementName = StaxParserUtil.getEndElementName(endElement);
if (endElementName.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT))
break;
else
continue;
}
String tag = StaxParserUtil.getStartElementName(startElement);
StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
}
return config;
}
@Override
public boolean supports(QName qname) {
return false;
@@ -48,13 +48,6 @@ public class SPXmlParser extends AbstractParser {
return str;
}
public static int getIntegerAttributeValue(StartElement startElement, String tag, int defaultValue) {
String result = getAttributeValue(startElement, tag);
if (result == null)
return defaultValue;
return Integer.valueOf(result);
}
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
String result = getAttributeValue(startElement, tag);
if (result == null)
@@ -1,101 +0,0 @@
/*
* 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.adapters.saml.descriptor.parsers;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.processing.core.util.NamespaceContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Goes through the given XML file and extracts names, certificates and keys from the KeyInfo elements.
* @author hmlnarik
*/
public class SamlDescriptorIDPKeysExtractor {
private static final NamespaceContext NS_CONTEXT = new NamespaceContext();
static {
NS_CONTEXT.addNsUriPair("m", JBossSAMLURIConstants.METADATA_NSURI.get());
NS_CONTEXT.addNsUriPair("dsig", JBossSAMLURIConstants.XMLDSIG_NSURI.get());
}
private final KeyInfoFactory kif = KeyInfoFactory.getInstance();
private final XPathFactory xPathfactory = XPathFactory.newInstance();
private final XPath xpath = xPathfactory.newXPath();
{
xpath.setNamespaceContext(NS_CONTEXT);
}
public MultivaluedHashMap<String, KeyInfo> parse(InputStream stream) throws ParsingException {
MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(stream);
XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");
NodeList keyDescriptors = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < keyDescriptors.getLength(); i ++) {
Node keyDescriptor = keyDescriptors.item(i);
Element keyDescriptorEl = (Element) keyDescriptor;
KeyInfo ki = processKeyDescriptor(keyDescriptorEl);
if (ki != null) {
String use = keyDescriptorEl.getAttribute(JBossSAMLConstants.USE.get());
res.add(use, ki);
}
}
} catch (SAXException | IOException | ParserConfigurationException | MarshalException | XPathExpressionException e) {
throw new ParsingException("Error parsing SAML descriptor", e);
}
return res;
}
private KeyInfo processKeyDescriptor(Element keyDescriptor) throws MarshalException {
NodeList childNodes = keyDescriptor.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), JBossSAMLConstants.KEY_INFO.get());
if (childNodes.getLength() == 0) {
return null;
}
Node keyInfoNode = childNodes.item(0);
return (keyInfoNode == null) ? null : kif.unmarshalKeyInfo(new DOMStructure(keyInfoNode));
}
}
@@ -60,22 +60,15 @@ 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;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
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 java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
@@ -194,7 +187,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, statusResponse);
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
@@ -264,44 +257,13 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
KeyLocator signatureValidationKey = deployment.getIDP().getSignatureValidationKeyLocator();
if (postBinding) {
verifyPostBindingSignature(holder.getSamlDocument(), signatureValidationKey);
verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
} else {
String keyId = getMessageSigningKeyId(holder.getSamlObject());
verifyRedirectBindingSignature(paramKey, signatureValidationKey, keyId);
verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
}
}
private String getMessageSigningKeyId(SAML2Object doc) {
final ExtensionsType extensions;
if (doc instanceof RequestAbstractType) {
extensions = ((RequestAbstractType) doc).getExtensions();
} else if (doc instanceof StatusResponseType) {
extensions = ((StatusResponseType) doc).getExtensions();
} else {
return null;
}
if (extensions == null) {
return null;
}
for (Object ext : extensions.getAny()) {
if (! (ext instanceof Element)) {
continue;
}
String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
if (res != null) {
return res;
}
}
return null;
}
private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
if(statusCode != null && statusCode.getValue()!=null){
String v = statusCode.getValue().toString();
@@ -310,25 +272,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
protected AuthOutcome handleLoginResponse(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)) {
@@ -350,7 +295,6 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return 403;
}
};
return AuthOutcome.FAILED;
}
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
@@ -362,7 +306,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
@@ -465,14 +409,6 @@ 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) {
@@ -537,10 +473,10 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
public void verifyPostBindingSignature(Document document, KeyLocator keyLocator) throws VerificationException {
public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
SAML2Signature saml2Signature = new SAML2Signature();
try {
if (!saml2Signature.validate(document, keyLocator)) {
if (!saml2Signature.validate(document, publicKey)) {
throw new VerificationException("Invalid signature on document");
}
} catch (ProcessingException e) {
@@ -548,7 +484,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
}
private void verifyRedirectBindingSignature(String paramKey, KeyLocator keyLocator, String keyId) throws VerificationException {
public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
String request = facade.getRequest().getQueryParamValue(paramKey);
String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
@@ -575,76 +511,16 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
try {
//byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
byte[] decodedSignature = Base64.decode(signature);
byte[] rawQueryBytes = rawQuery.getBytes("UTF-8");
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
if (! validateRedirectBindingSignature(signatureAlgorithm, rawQueryBytes, decodedSignature, keyLocator, keyId)) {
Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
validator.initVerify(publicKey);
validator.update(rawQuery.getBytes("UTF-8"));
if (!validator.verify(decodedSignature)) {
throw new VerificationException("Invalid query param signature");
}
} catch (Exception e) {
throw new VerificationException(e);
}
}
private boolean validateRedirectBindingSignature(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, KeyLocator locator, String keyId)
throws KeyManagementException, VerificationException {
try {
Key key;
try {
key = locator.getKey(keyId);
boolean keyLocated = key != null;
if (keyLocated) {
return validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key);
}
} catch (KeyManagementException ex) {
}
} catch (SignatureException ex) {
log.debug("Verification failed for key %s: %s", keyId, ex);
log.trace(ex);
}
if (locator instanceof Iterable) {
Iterable<Key> availableKeys = (Iterable<Key>) locator;
log.trace("Trying hard to validate XML signature using all available keys.");
for (Key key : availableKeys) {
try {
if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
return true;
}
} catch (SignatureException ex) {
log.debug("Verification failed: %s", ex);
}
}
}
return false;
}
private boolean validateRedirectBindingSignatureForKey(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, Key key)
throws SignatureException {
if (key == null) {
return false;
}
if (! (key instanceof PublicKey)) {
log.warnf("Unusable key for signature validation: %s", key);
return false;
}
Signature signature = sigAlg.createSignature(); // todo plugin signature alg
try {
signature.initVerify((PublicKey) key);
} catch (InvalidKeyException ex) {
log.warnf(ex, "Unusable key for signature validation: %s", key);
return false;
}
signature.update(rawQueryBytes);
return signature.verify(decodedSignature);
}
}
@@ -82,10 +82,8 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
if (deployment.getSignatureCanonicalizationMethod() != null)
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
.signWith(null, deployment.getSigningKeyPair())
.signWith(deployment.getSigningKeyPair())
.signDocument();
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
}
@@ -115,10 +113,8 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
if (deployment.getSignatureCanonicalizationMethod() != null)
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
binding.signatureAlgorithm(deployment.getSignatureAlgorithm());
binding.signWith(null, deployment.getSigningKeyPair())
binding.signWith(deployment.getSigningKeyPair())
.signDocument();
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
}
binding.relayState("logout");
@@ -1,175 +0,0 @@
/*
* 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.adapters.saml.rotation;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyName;
import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.cloned.HttpAdapterUtils;
import org.keycloak.adapters.cloned.HttpClientAdapterException;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.api.util.KeyInfoTools;
/**
* This class defines a {@link KeyLocator} that looks up public keys and certificates in IdP's
* SAML descriptor (i.e. http://{host}/auth/realms/{realm}/protocol/saml/descriptor).
*
* Based on {@code JWKPublicKeyLocator}.
*
* @author hmlnarik
*/
public class SamlDescriptorPublicKeyLocator implements KeyLocator, Iterable<PublicKey> {
private static final Logger LOG = Logger.getLogger(SamlDescriptorPublicKeyLocator.class);
/**
* Time between two subsequent requests (in seconds).
*/
private final int minTimeBetweenDescriptorRequests;
/**
* Time to live for cache entries (in seconds).
*/
private final int cacheEntryTtl;
/**
* Target descriptor URL.
*/
private final String descriptorUrl;
private final Map<String, PublicKey> publicKeyCache = new ConcurrentHashMap<>();
private final HttpClient client;
private volatile int lastRequestTime = 0;
public SamlDescriptorPublicKeyLocator(String descriptorUrl, int minTimeBetweenDescriptorRequests, int cacheEntryTtl, HttpClient httpClient) {
this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests <= 0
? 20
: minTimeBetweenDescriptorRequests;
this.descriptorUrl = descriptorUrl;
this.cacheEntryTtl = cacheEntryTtl;
this.client = httpClient;
}
@Override
public Key getKey(String kid) throws KeyManagementException {
if (kid == null) {
LOG.debugf("Invalid key id: %s", kid);
return null;
}
LOG.tracef("Requested key id: %s", kid);
int currentTime = Time.currentTime();
PublicKey res;
if (currentTime > this.lastRequestTime + this.cacheEntryTtl) {
LOG.debugf("Performing regular cache cleanup.");
res = refreshCertificateCacheAndGet(kid);
} else {
res = publicKeyCache.get(kid);
if (res == null) {
if (currentTime > this.lastRequestTime + this.minTimeBetweenDescriptorRequests) {
res = refreshCertificateCacheAndGet(kid);
} else {
LOG.debugf("Won't send request to realm SAML descriptor url, timeout not expired. Last request time was %d", lastRequestTime);
}
}
}
return res;
}
@Override
public synchronized void refreshKeyCache() {
LOG.info("Forcing key cache cleanup and refresh.");
this.publicKeyCache.clear();
refreshCertificateCacheAndGet(null);
}
private synchronized PublicKey refreshCertificateCacheAndGet(String kid) {
if (this.descriptorUrl == null) {
return null;
}
this.lastRequestTime = Time.currentTime();
LOG.debugf("Refreshing public key cache from %s", this.descriptorUrl);
List<KeyInfo> signingCerts;
try {
MultivaluedHashMap<String, KeyInfo> certs = HttpAdapterUtils.downloadKeysFromSamlDescriptor(client, this.descriptorUrl);
signingCerts = certs.get(KeyTypes.SIGNING.value());
} catch (HttpClientAdapterException ex) {
LOG.error("Could not refresh certificates from the server", ex);
return null;
}
if (signingCerts == null) {
return null;
}
LOG.debugf("Certificates retrieved from server, filling public key cache");
// Only clear cache after it is certain that the SAML descriptor has been read successfully
this.publicKeyCache.clear();
for (KeyInfo ki : signingCerts) {
KeyName keyName = KeyInfoTools.getKeyName(ki);
X509Certificate x509certificate = KeyInfoTools.getX509Certificate(ki);
if (x509certificate != null && keyName != null) {
LOG.tracef("Registering signing certificate %s", keyName.getName());
this.publicKeyCache.put(keyName.getName(), x509certificate.getPublicKey());
} else {
LOG.tracef("Ignoring certificate %s: %s", keyName, x509certificate);
}
}
return (kid == null ? null : this.publicKeyCache.get(kid));
}
@Override
public String toString() {
return "Keys retrieved from SAML descriptor at " + descriptorUrl;
}
@Override
public Iterator<PublicKey> iterator() {
if (this.publicKeyCache.isEmpty()) {
refreshCertificateCacheAndGet(null);
}
return this.publicKeyCache.values().iterator();
}
}
Loaded 100 of 2206 files, more files were not shown because too many files have changed in this diff. Show more