/*
* 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;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AdapterDeploymentContext {
private static final Logger log = Logger.getLogger(AdapterDeploymentContext.class);
protected KeycloakDeployment deployment;
protected KeycloakConfigResolver configResolver;
public AdapterDeploymentContext() {
}
/**
* For single-tenant deployments, this constructor is to be used, as a
* full KeycloakDeployment is known at deployment time and won't change
* during the application deployment's life cycle.
*
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
* Server URL
*/
public AdapterDeploymentContext(KeycloakDeployment deployment) {
this.deployment = deployment;
}
/**
* For multi-tenant deployments, this constructor is to be used, as a
* KeycloakDeployment is not known at deployment time. It defers the
* resolution of a KeycloakDeployment to a KeycloakConfigResolver,
* to be implemented by the target application.
*
* @param configResolver A KeycloakConfigResolver that will be used
* to resolve a KeycloakDeployment
*/
public AdapterDeploymentContext(KeycloakConfigResolver configResolver) {
this.configResolver = configResolver;
}
/**
* For single-tenant deployments, it complements KeycloakDeployment
* by resolving a relative Auth Server's URL based on the current request
*
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
* to the KeycloakConfigResolver .
*
* @param facade the Request/Response Façade , used to either determine
* the Auth Server URL (single tenant) or pass thru to the
* KeycloakConfigResolver.
* @return
*/
public KeycloakDeployment resolveDeployment(HttpFacade facade) {
if (null != configResolver) {
return configResolver.resolve(facade.getRequest());
}
if (deployment == null) return null;
if (deployment.getAuthServerBaseUrl() == null) return deployment;
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
if (resolvedDeployment.getPublicKeyLocator() == null) {
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
}
return resolvedDeployment;
}
protected KeycloakDeployment resolveUrls(KeycloakDeployment deployment, HttpFacade facade) {
if (deployment.relativeUrls == RelativeUrlsUsed.NEVER) {
// Absolute URI are already set to everything
return deployment;
} else {
DeploymentDelegate delegate = new DeploymentDelegate(this.deployment);
delegate.setAuthServerBaseUrl(getBaseBuilder(facade, this.deployment.getAuthServerBaseUrl()).build().toString());
return delegate;
}
}
/**
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
* Ever method is delegated except URL get methods and isConfigured()
*
*/
protected static class DeploymentDelegate extends KeycloakDeployment {
protected KeycloakDeployment delegate;
public DeploymentDelegate(KeycloakDeployment delegate) {
this.delegate = delegate;
}
public void setAuthServerBaseUrl(String authServerBaseUrl) {
this.authServerBaseUrl = authServerBaseUrl;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
resolveUrls(serverBuilder);
}
@Override
public RelativeUrlsUsed getRelativeUrls() {
return delegate.getRelativeUrls();
}
@Override
public String getRealmInfoUrl() {
return (this.realmInfoUrl != null) ? this.realmInfoUrl : delegate.getRealmInfoUrl();
}
@Override
public String getTokenUrl() {
return (this.tokenUrl != null) ? this.tokenUrl : delegate.getTokenUrl();
}
@Override
public KeycloakUriBuilder getLogoutUrl() {
return (this.logoutUrl != null) ? this.logoutUrl : delegate.getLogoutUrl();
}
@Override
public String getAccountUrl() {
return (this.accountUrl != null) ? this.accountUrl : delegate.getAccountUrl();
}
@Override
public String getRegisterNodeUrl() {
return (this.registerNodeUrl != null) ? this.registerNodeUrl : delegate.getRegisterNodeUrl();
}
@Override
public String getUnregisterNodeUrl() {
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
}
@Override
public String getJwksUrl() {
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
}
@Override
public String getResourceName() {
return delegate.getResourceName();
}
@Override
public String getRealm() {
return delegate.getRealm();
}
@Override
public void setRealm(String realm) {
delegate.setRealm(realm);
}
@Override
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
delegate.setPublicKeyLocator(publicKeyLocator);
}
@Override
public PublicKeyLocator getPublicKeyLocator() {
return delegate.getPublicKeyLocator();
}
@Override
public void setResourceName(String resourceName) {
delegate.setResourceName(resourceName);
}
@Override
public boolean isBearerOnly() {
return delegate.isBearerOnly();
}
@Override
public void setBearerOnly(boolean bearerOnly) {
delegate.setBearerOnly(bearerOnly);
}
@Override
public boolean isEnableBasicAuth() {
return delegate.isEnableBasicAuth();
}
@Override
public void setEnableBasicAuth(boolean enableBasicAuth) {
delegate.setEnableBasicAuth(enableBasicAuth);
}
@Override
public boolean isPublicClient() {
return delegate.isPublicClient();
}
@Override
public void setPublicClient(boolean publicClient) {
delegate.setPublicClient(publicClient);
}
@Override
public Map<String, Object> getResourceCredentials() {
return delegate.getResourceCredentials();
}
@Override
public void setResourceCredentials(Map<String, Object> resourceCredentials) {
delegate.setResourceCredentials(resourceCredentials);
}
@Override
public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
delegate.setClientAuthenticator(clientAuthenticator);
}
@Override
public ClientCredentialsProvider getClientAuthenticator() {
return delegate.getClientAuthenticator();
}
@Override
public HttpClient getClient() {
return delegate.getClient();
}
@Override
public void setClient(HttpClient client) {
delegate.setClient(client);
}
@Override
public String getScope() {
return delegate.getScope();
}
@Override
public void setScope(String scope) {
delegate.setScope(scope);
}
@Override
public SslRequired getSslRequired() {
return delegate.getSslRequired();
}
@Override
public void setSslRequired(SslRequired sslRequired) {
delegate.setSslRequired(sslRequired);
}
@Override
public TokenStore getTokenStore() {
return delegate.getTokenStore();
}
@Override
public void setTokenStore(TokenStore tokenStore) {
delegate.setTokenStore(tokenStore);
}
@Override
public String getStateCookieName() {
return delegate.getStateCookieName();
}
@Override
public void setStateCookieName(String stateCookieName) {
delegate.setStateCookieName(stateCookieName);
}
@Override
public boolean isUseResourceRoleMappings() {
return delegate.isUseResourceRoleMappings();
}
@Override
public void setUseResourceRoleMappings(boolean useResourceRoleMappings) {
delegate.setUseResourceRoleMappings(useResourceRoleMappings);
}
@Override
public boolean isCors() {
return delegate.isCors();
}
@Override
public void setCors(boolean cors) {
delegate.setCors(cors);
}
@Override
public int getCorsMaxAge() {
return delegate.getCorsMaxAge();
}
@Override
public void setCorsMaxAge(int corsMaxAge) {
delegate.setCorsMaxAge(corsMaxAge);
}
@Override
public String getCorsAllowedHeaders() {
return delegate.getCorsAllowedHeaders();
}
@Override
public void setNotBefore(int notBefore) {
delegate.setNotBefore(notBefore);
}
@Override
public int getNotBefore() {
return delegate.getNotBefore();
}
@Override
public void updateNotBefore(int notBefore) {
delegate.setNotBefore(notBefore);
getPublicKeyLocator().reset(this);
}
@Override
public void setExposeToken(boolean exposeToken) {
delegate.setExposeToken(exposeToken);
}
@Override
public boolean isExposeToken() {
return delegate.isExposeToken();
}
@Override
public void setCorsAllowedMethods(String corsAllowedMethods) {
delegate.setCorsAllowedMethods(corsAllowedMethods);
}
@Override
public String getCorsAllowedMethods() {
return delegate.getCorsAllowedMethods();
}
@Override
public void setCorsAllowedHeaders(String corsAllowedHeaders) {
delegate.setCorsAllowedHeaders(corsAllowedHeaders);
}
@Override
public boolean isAlwaysRefreshToken() {
return delegate.isAlwaysRefreshToken();
}
@Override
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
delegate.setAlwaysRefreshToken(alwaysRefreshToken);
}
@Override
public int getRegisterNodePeriod() {
return delegate.getRegisterNodePeriod();
}
@Override
public void setRegisterNodePeriod(int registerNodePeriod) {
delegate.setRegisterNodePeriod(registerNodePeriod);
}
@Override
public void setRegisterNodeAtStartup(boolean registerNodeAtStartup) {
delegate.setRegisterNodeAtStartup(registerNodeAtStartup);
}
@Override
public boolean isRegisterNodeAtStartup() {
return delegate.isRegisterNodeAtStartup();
}
@Override
public String getPrincipalAttribute() {
return delegate.getPrincipalAttribute();
}
@Override
public void setPrincipalAttribute(String principalAttribute) {
delegate.setPrincipalAttribute(principalAttribute);
}
@Override
public boolean isTurnOffChangeSessionIdOnLogin() {
return delegate.isTurnOffChangeSessionIdOnLogin();
}
@Override
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
}
@Override
public int getTokenMinimumTimeToLive() {
return delegate.getTokenMinimumTimeToLive();
}
@Override
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
}
@Override
public PolicyEnforcer getPolicyEnforcer() {
return delegate.getPolicyEnforcer();
}
@Override
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
delegate.setPolicyEnforcer(policyEnforcer);
}
@Override
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
}
@Override
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) {
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(base);
URI request = URI.create(facade.getRequest().getURI());
String scheme = request.getScheme();
if (deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
scheme = "https";
if (!request.getScheme().equals(scheme) && request.getPort() != -1) {
log.error("request scheme: " + request.getScheme() + " ssl required");
throw new RuntimeException("Can't resolve relative url from adapter config.");
}
}
builder.scheme(scheme);
builder.host(request.getHost());
if (request.getPort() != -1) {
builder.port(request.getPort());
}
return builder;
}
protected void close(HttpResponse response) {
if (response.getEntity() != null) {
try {
response.getEntity().getContent().close();
} catch (IOException e) {
}
}
}
public void updateDeployment(AdapterConfig config) {
if (null != configResolver) {
throw new IllegalStateException("Cannot parse an adapter config and build an updated deployment when on a multi-tenant scenario.");
}
deployment = KeycloakDeploymentBuilder.build(config);
}
}