Upgrade to ACS v23.x, Jakarta, jdk17

This commit is contained in:
2024-08-22 14:21:39 -04:00
committed by Axel Faust
parent 0044ce3f6b
commit 6f7910aa93
22 changed files with 1052 additions and 108 deletions

51
pom.xml
View File

@@ -20,13 +20,13 @@
<parent> <parent>
<groupId>de.acosix.alfresco.maven</groupId> <groupId>de.acosix.alfresco.maven</groupId>
<artifactId>de.acosix.alfresco.maven.project.parent-6.0.7</artifactId> <artifactId>de.acosix.alfresco.maven.project.parent-23.1.0</artifactId>
<version>1.4.1</version> <version>1.5.0</version>
</parent> </parent>
<groupId>de.acosix.alfresco.keycloak</groupId> <groupId>de.acosix.alfresco.keycloak</groupId>
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId> <artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
<version>1.1.0-rc7</version> <version>1.2.0-rc1</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Acosix Alfresco Keycloak - Parent</name> <name>Acosix Alfresco Keycloak - Parent</name>
@@ -61,6 +61,15 @@
<twitter>twitter.com/ReluctantBird83</twitter> <twitter>twitter.com/ReluctantBird83</twitter>
</properties> </properties>
</developer> </developer>
<developer>
<id>blong</id>
<name>Brian Long</name>
<email>brian@inteligr8.com</email>
<organization>Inteligr8 LLC</organization>
<properties>
<twitter>twitter.com/brian_m_long</twitter>
</properties>
</developer>
</developers> </developers>
<properties> <properties>
@@ -68,18 +77,18 @@
<messages.packageId>acosix.keycloak</messages.packageId> <messages.packageId>acosix.keycloak</messages.packageId>
<moduleId>acosix-keycloak</moduleId> <moduleId>acosix-keycloak</moduleId>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.version>3.13.0</maven.compiler.version>
<maven.compiler.target>1.8</maven.compiler.target> <maven.shade.version>3.6.0</maven.shade.version>
<maven.shade.version>3.2.4</maven.shade.version> <keycloak.version>22.0.3</keycloak.version>
<keycloak.version>16.1.0</keycloak.version>
<resteasy.version>3.15.1.Final</resteasy.version> <resteasy.version>3.15.1.Final</resteasy.version>
<!-- lowest common denominator of Repository / Share in 6.0 --> <!-- lowest common denominator of Repository / Share in 6.0 -->
<apache.httpclient.version>4.5.13</apache.httpclient.version> <apache.httpclient.version>4.5.13</apache.httpclient.version>
<apache.httpcore.version>4.4.14</apache.httpcore.version> <apache.httpcore.version>4.4.16</apache.httpcore.version>
<!-- parent is including 6.11 erroneously -->
<surf.version>9.0</surf.version>
<acosix.utility.version>1.2.5</acosix.utility.version> <acosix.utility.version>1.4.3</acosix.utility.version>
<ootbee.support-tools.version>1.1.0.0</ootbee.support-tools.version> <ootbee.support-tools.version>1.1.0.0</ootbee.support-tools.version>
<docker.tests.repositoryImageBuilder.preRun></docker.tests.repositoryImageBuilder.preRun> <docker.tests.repositoryImageBuilder.preRun></docker.tests.repositoryImageBuilder.preRun>
@@ -101,6 +110,7 @@
<docker.tests.repositoryImageBuilder.postRun>USER alfresco</docker.tests.repositoryImageBuilder.postRun> <docker.tests.repositoryImageBuilder.postRun>USER alfresco</docker.tests.repositoryImageBuilder.postRun>
<acosix.utility.version>1.3.0</acosix.utility.version> <acosix.utility.version>1.3.0</acosix.utility.version>
--> -->
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -132,13 +142,13 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId> <artifactId>keycloak-jakarta-servlet-adapter-spi</artifactId>
<version>${keycloak.version}</version> <version>${keycloak.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId> <artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
<version>${keycloak.version}</version> <version>${keycloak.version}</version>
</dependency> </dependency>
@@ -150,7 +160,7 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId> <artifactId>keycloak-admin-client-jakarta</artifactId>
<version>${keycloak.version}</version> <version>${keycloak.version}</version>
</dependency> </dependency>
@@ -187,6 +197,13 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.alfresco.surf</groupId>
<artifactId>spring-surf</artifactId>
<version>${surf.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>de.acosix.alfresco.utility</groupId> <groupId>de.acosix.alfresco.utility</groupId>
<artifactId>de.acosix.alfresco.utility.common</artifactId> <artifactId>de.acosix.alfresco.utility.common</artifactId>
@@ -298,6 +315,14 @@
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>${maven.source.version}</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
</plugin>
<plugin> <plugin>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.version}</version> <version>${maven.shade.version}</version>

View File

@@ -21,7 +21,7 @@
<parent> <parent>
<groupId>de.acosix.alfresco.keycloak</groupId> <groupId>de.acosix.alfresco.keycloak</groupId>
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId> <artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
<version>1.1.0-rc7</version> <version>1.2.0-rc1</version>
</parent> </parent>
<artifactId>de.acosix.alfresco.keycloak.repo</artifactId> <artifactId>de.acosix.alfresco.keycloak.repo</artifactId>
@@ -44,12 +44,12 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId> <artifactId>keycloak-adapter-core</artifactId>
@@ -71,12 +71,17 @@
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>*</artifactId> <artifactId>*</artifactId>
</exclusion> </exclusion>
<!-- use default from Alfresco Repository -->
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId> <artifactId>keycloak-jakarta-servlet-adapter-spi</artifactId>
<exclusions> <exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 --> <!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion> <exclusion>
@@ -105,7 +110,7 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId> <artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
<exclusions> <exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 --> <!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion> <exclusion>
@@ -233,7 +238,8 @@
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<createSourcesJar>true</createSourcesJar> <!-- generating using `sources` classifier, which conflicts with the main `sources` -->
<createSourcesJar>false</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent> <shadeSourcesContent>true</shadeSourcesContent>
<keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope> <keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
<artifactSet> <artifactSet>

View File

@@ -6,7 +6,7 @@ cache.${moduleId}.ssoToSessionCache.maxIdleSeconds=0
cache.${moduleId}.ssoToSessionCache.cluster.type=fully-distributed cache.${moduleId}.ssoToSessionCache.cluster.type=fully-distributed
cache.${moduleId}.ssoToSessionCache.backup-count=1 cache.${moduleId}.ssoToSessionCache.backup-count=1
cache.${moduleId}.ssoToSessionCache.eviction-policy=LRU cache.${moduleId}.ssoToSessionCache.eviction-policy=LRU
cache.${moduleId}.ssoToSessionCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy cache.${moduleId}.ssoToSessionCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
cache.${moduleId}.ssoToSessionCache.readBackupData=false cache.${moduleId}.ssoToSessionCache.readBackupData=false
# explicitly not clearable - should be cleared via Keycloak back-channel action # explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.ssoToSessionCache.clearable=false cache.${moduleId}.ssoToSessionCache.clearable=false
@@ -19,7 +19,7 @@ cache.${moduleId}.sessionToSsoCache.maxIdleSeconds=0
cache.${moduleId}.sessionToSsoCache.cluster.type=fully-distributed cache.${moduleId}.sessionToSsoCache.cluster.type=fully-distributed
cache.${moduleId}.sessionToSsoCache.backup-count=1 cache.${moduleId}.sessionToSsoCache.backup-count=1
cache.${moduleId}.sessionToSsoCache.eviction-policy=LRU cache.${moduleId}.sessionToSsoCache.eviction-policy=LRU
cache.${moduleId}.sessionToSsoCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy cache.${moduleId}.sessionToSsoCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
cache.${moduleId}.sessionToSsoCache.readBackupData=false cache.${moduleId}.sessionToSsoCache.readBackupData=false
# explicitly not clearable - should be cleared via Keycloak back-channel action # explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.sessionToSsoCache.clearable=false cache.${moduleId}.sessionToSsoCache.clearable=false
@@ -32,7 +32,7 @@ cache.${moduleId}.principalToSessionCache.maxIdleSeconds=0
cache.${moduleId}.principalToSessionCache.cluster.type=fully-distributed cache.${moduleId}.principalToSessionCache.cluster.type=fully-distributed
cache.${moduleId}.principalToSessionCache.backup-count=1 cache.${moduleId}.principalToSessionCache.backup-count=1
cache.${moduleId}.principalToSessionCache.eviction-policy=LRU cache.${moduleId}.principalToSessionCache.eviction-policy=LRU
cache.${moduleId}.principalToSessionCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy cache.${moduleId}.principalToSessionCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
cache.${moduleId}.principalToSessionCache.readBackupData=false cache.${moduleId}.principalToSessionCache.readBackupData=false
# explicitly not clearable - should be cleared via Keycloak back-channel action # explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.principalToSessionCache.clearable=false cache.${moduleId}.principalToSessionCache.clearable=false
@@ -45,7 +45,7 @@ cache.${moduleId}.sessionToPrincipalCache.maxIdleSeconds=0
cache.${moduleId}.sessionToPrincipalCache.cluster.type=fully-distributed cache.${moduleId}.sessionToPrincipalCache.cluster.type=fully-distributed
cache.${moduleId}.sessionToPrincipalCache.backup-count=1 cache.${moduleId}.sessionToPrincipalCache.backup-count=1
cache.${moduleId}.sessionToPrincipalCache.eviction-policy=LRU cache.${moduleId}.sessionToPrincipalCache.eviction-policy=LRU
cache.${moduleId}.sessionToPrincipalCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy cache.${moduleId}.sessionToPrincipalCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
cache.${moduleId}.sessionToPrincipalCache.readBackupData=false cache.${moduleId}.sessionToPrincipalCache.readBackupData=false
# explicitly not clearable - should be cleared via Keycloak back-channel action # explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.sessionToPrincipalCache.clearable=false cache.${moduleId}.sessionToPrincipalCache.clearable=false
@@ -58,7 +58,7 @@ cache.${moduleId}.ticketTokenCache.maxIdleSeconds=0
cache.${moduleId}.ticketTokenCache.cluster.type=fully-distributed cache.${moduleId}.ticketTokenCache.cluster.type=fully-distributed
cache.${moduleId}.ticketTokenCache.backup-count=1 cache.${moduleId}.ticketTokenCache.backup-count=1
cache.${moduleId}.ticketTokenCache.eviction-policy=LRU cache.${moduleId}.ticketTokenCache.eviction-policy=LRU
cache.${moduleId}.ticketTokenCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy cache.${moduleId}.ticketTokenCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
cache.${moduleId}.ticketTokenCache.readBackupData=false cache.${moduleId}.ticketTokenCache.readBackupData=false
# dangerous to be cleared, as roles / claims can no longer be mapped # dangerous to be cleared, as roles / claims can no longer be mapped
# would always be better to just invalidate the tickets themselves # would always be better to just invalidate the tickets themselves

View File

@@ -0,0 +1,12 @@
logger.acosix-alfresco-keycloak.name=${project.artifactId}
logger.acosix-alfresco-keycloak.level=INFO
logger.acosix-alfresco-keycloak-deps.name=${project.artifactId}.deps
logger.acosix-alfresco-keycloak-deps.level=ERROR
logger.acosix-alfresco-keycloak-deps-keycloak.name=${project.artifactId}.deps.keycloak
logger.acosix-alfresco-keycloak-deps-keycloak.level=ERROR
logger.acosix-alfresco-keycloak-deps-jboss.name=${project.artifactId}.deps.jboss
logger.acosix-alfresco-keycloak-deps-jboss.level=ERROR

View File

@@ -21,16 +21,16 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletContext; import jakarta.servlet.ServletContext;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.repo.SessionUser; import org.alfresco.repo.SessionUser;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;

View File

@@ -17,8 +17,8 @@ package de.acosix.alfresco.keycloak.repo.authentication;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;

View File

@@ -17,13 +17,13 @@ package de.acosix.alfresco.keycloak.repo.authentication;
import java.io.IOException; import java.io.IOException;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletContext; import jakarta.servlet.ServletContext;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.repo.SessionUser; import org.alfresco.repo.SessionUser;
import org.alfresco.repo.web.scripts.bean.LoginPost; import org.alfresco.repo.web.scripts.bean.LoginPost;

View File

@@ -17,12 +17,12 @@ package de.acosix.alfresco.keycloak.repo.authentication;
import java.io.IOException; import java.io.IOException;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletContext; import jakarta.servlet.ServletContext;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.web.filter.beans.DependencyInjectedFilter; import org.alfresco.repo.web.filter.beans.DependencyInjectedFilter;

View File

@@ -23,7 +23,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.keycloak.adapters.servlet.ServletHttpFacade; import org.keycloak.adapters.servlet.ServletHttpFacade;
@@ -39,7 +39,7 @@ import org.keycloak.adapters.spi.HttpFacade;
public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFacade public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFacade
{ {
protected final Map<Pair<String, String>, javax.servlet.http.Cookie> cookies = new HashMap<>(); protected final Map<Pair<String, String>, jakarta.servlet.http.Cookie> cookies = new HashMap<>();
protected final Map<String, List<String>> headers = new HashMap<>(); protected final Map<String, List<String>> headers = new HashMap<>();
@@ -71,7 +71,7 @@ public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFac
/** /**
* @return the cookies * @return the cookies
*/ */
public List<javax.servlet.http.Cookie> getCookies() public List<jakarta.servlet.http.Cookie> getCookies()
{ {
return new ArrayList<>(this.cookies.values()); return new ArrayList<>(this.cookies.values());
} }
@@ -157,7 +157,7 @@ public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFac
public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge,
final boolean secure, final boolean httpOnly) final boolean secure, final boolean httpOnly)
{ {
final javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value); final jakarta.servlet.http.Cookie cookie = new jakarta.servlet.http.Cookie(name, value);
cookie.setPath(path); cookie.setPath(path);
if (domain != null) if (domain != null)
{ {

View File

@@ -4,37 +4,40 @@ import com.fasterxml.jackson.core.JsonParseException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.alfresco.util.ParameterCheck; import org.alfresco.util.ParameterCheck;
import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.TokenVerifier; import org.keycloak.TokenVerifier;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.ServerRequest.HttpFailure; import org.keycloak.adapters.ServerRequest.HttpFailure;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.rotation.AdapterTokenVerifier; import org.keycloak.adapters.rotation.AdapterTokenVerifier;
import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens; import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.acosix.alfresco.keycloak.repo.util.NameValueMapAdapter;
import de.acosix.alfresco.keycloak.repo.util.RefreshableAccessTokenHolder; import de.acosix.alfresco.keycloak.repo.util.RefreshableAccessTokenHolder;
/** /**
@@ -282,12 +285,20 @@ public class AccessTokenClient
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl()) final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl())
.path(ServiceUrlConstants.TOKEN_PATH).build(this.deployment.getRealm())); .path(ServiceUrlConstants.TOKEN_PATH).build(this.deployment.getRealm()));
final List<NameValuePair> formParams = new ArrayList<>(); final List<NameValuePair> formParams = new LinkedList<>();
postParamProvider.accept(formParams); postParamProvider.accept(formParams);
final List<Header> headers = new LinkedList<>();
ClientCredentialsProviderUtils.setClientCredentials(this.deployment, post, formParams); ClientCredentialsProviderUtils.setClientCredentials(
this.deployment.getAdapterConfig(),
this.deployment.getClientAuthenticator(),
new NameValueMapAdapter<>(headers, BasicHeader.class),
new NameValueMapAdapter<>(formParams, BasicNameValuePair.class));
for (Header header : headers)
post.addHeader(header);
final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8"); final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8");
post.setEntity(form); post.setEntity(form);

View File

@@ -0,0 +1,151 @@
package de.acosix.alfresco.keycloak.repo.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.http.NameValuePair;
public class NameValueMapAdapter<T extends NameValuePair> implements Map<String, String> {
private final List<? extends NameValuePair> pairs;
private final Class<T> type;
public NameValueMapAdapter(List<? extends NameValuePair> pairs, Class<T> type) {
this.pairs = pairs;
this.type = type;
}
@Override
public void clear() {
this.pairs.clear();
}
@Override
public boolean containsKey(Object key) {
for (NameValuePair pair : this.pairs)
if (pair.getName().equals(key))
return true;
return false;
}
@Override
public boolean containsValue(Object value) {
for (NameValuePair pair : this.pairs)
if (pair.getValue().equals(value))
return true;
return false;
}
@Override
public Set<Entry<String, String>> entrySet() {
Set<Entry<String, String>> set = new HashSet<Entry<String, String>>();
for (NameValuePair pair : this.pairs) {
set.add(new Entry<String, String>() {
@Override
public String getKey() {
return pair.getName();
}
@Override
public String getValue() {
return pair.getValue();
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException();
}
});
}
return set;
}
@Override
public String get(Object key) {
for (NameValuePair pair : this.pairs)
if (pair.getName().equals(key))
return pair.getValue();
return null;
}
@Override
public boolean isEmpty() {
return this.pairs.isEmpty();
}
@Override
public Set<String> keySet() {
Set<String> set = new HashSet<>();
for (NameValuePair pair : this.pairs)
set.add(pair.getName());
return set;
}
@Override
public String put(String key, String value) {
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
while (i.hasNext()) {
NameValuePair pair = i.next();
if (pair.getName().equals(key)) {
i.remove();
i.add(this.newNameValuePair(key, value));
return pair.getValue();
}
}
i.add(this.newNameValuePair(key, value));
return null;
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
for (Entry<? extends String, ? extends String> e : m.entrySet())
this.put(e.getKey(), e.getValue());
}
@Override
public String remove(Object key) {
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
while (i.hasNext()) {
NameValuePair pair = i.next();
if (pair.getName().equals(key)) {
i.remove();
return pair.getValue();
}
}
return null;
}
@Override
public int size() {
return this.pairs.size();
}
@Override
public Collection<String> values() {
List<String> list = new ArrayList<>(this.pairs.size());
for (NameValuePair pair : this.pairs)
list.add(pair.getValue());
return list;
}
private T newNameValuePair(String key, String value) {
try {
Constructor<T> constructor = this.type.getConstructor(String.class, String.class);
return constructor.newInstance(key, value);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new AlfrescoRuntimeException(e.getMessage(), e);
}
}
}

View File

@@ -21,7 +21,7 @@
<parent> <parent>
<groupId>de.acosix.alfresco.keycloak</groupId> <groupId>de.acosix.alfresco.keycloak</groupId>
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId> <artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
<version>1.1.0-rc7</version> <version>1.2.0-rc1</version>
</parent> </parent>
<artifactId>de.acosix.alfresco.keycloak.share</artifactId> <artifactId>de.acosix.alfresco.keycloak.share</artifactId>
@@ -45,8 +45,8 @@
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId> <artifactId>bcpkix-jdk18on</artifactId>
<version>1.68</version> <version>1.77</version>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -61,8 +61,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@@ -83,7 +83,7 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId> <artifactId>keycloak-jakarta-servlet-adapter-spi</artifactId>
<exclusions> <exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 --> <!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion> <exclusion>
@@ -104,7 +104,7 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId> <artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
<exclusions> <exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 --> <!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion> <exclusion>
@@ -132,7 +132,7 @@
<!-- BouncyCastle cannot be made part of an uber-JAR --> <!-- BouncyCastle cannot be made part of an uber-JAR -->
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId> <artifactId>bcpkix-jdk18on</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@@ -284,7 +284,8 @@
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<createSourcesJar>true</createSourcesJar> <!-- generating using `sources` classifier, which conflicts with the main `sources` -->
<createSourcesJar>false</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent> <shadeSourcesContent>true</shadeSourcesContent>
<keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope> <keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
<artifactSet> <artifactSet>

View File

@@ -0,0 +1,15 @@
logger.acosix-alfresco-keycloak.name=${project.artifactId}
logger.acosix-alfresco-keycloak.level=INFO
logger.acosix-alfresco-keycloak-deps.name=${project.artifactId}.deps
logger.acosix-alfresco-keycloak-deps.level=ERROR
logger.acosix-alfresco-keycloak-deps-keycloak.name=${project.artifactId}.deps.keycloak
logger.acosix-alfresco-keycloak-deps-keycloak.level=ERROR
logger.acosix-alfresco-keycloak-deps-jackson.name=${project.artifactId}.deps.jackson
logger.acosix-alfresco-keycloak-deps-jackson.level=ERROR
logger.acosix-alfresco-keycloak-deps-jboss.name=${project.artifactId}.deps.jboss
logger.acosix-alfresco-keycloak-deps-jboss.level=ERROR

View File

@@ -15,7 +15,7 @@
*/ */
package de.acosix.alfresco.keycloak.share.remote; package de.acosix.alfresco.keycloak.share.remote;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.keycloak.adapters.OidcKeycloakAccount; import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.spi.KeycloakAccount; import org.keycloak.adapters.spi.KeycloakAccount;

View File

@@ -17,7 +17,7 @@ package de.acosix.alfresco.keycloak.share.remote;
import java.util.Collections; import java.util.Collections;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.web.site.servlet.SlingshotAlfrescoConnector; import org.alfresco.web.site.servlet.SlingshotAlfrescoConnector;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;

View File

@@ -0,0 +1,496 @@
/*
* 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.
*/
/**
* Copied from Keycloak source: https://raw.githubusercontent.com/keycloak/keycloak/24.0.6/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
*
* The original is not extensible enough to configure the HttpClient with a
* custom HttpRoutePlanner. This copy adds that capability.
*/
package de.acosix.alfresco.keycloak.share.util;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
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.cookie.CookieSpecProvider;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CookieSpecRegistries;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.DefaultCookieSpecProvider;
import org.keycloak.adapters.SniSSLSocketFactory;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
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.Principal;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
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;
private SPNegoSchemeFactory spNegoSchemeFactory;
private boolean useSpNego;
private HttpRoutePlanner routePlanner;
/**
* 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(boolean disable) {
this.disableCookieCache = disable;
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;
}
public HttpClientBuilder routePlanner(HttpRoutePlanner routePlanner) {
this.routePlanner = routePlanner;
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 HttpClientBuilder spNegoSchemeFactory(SPNegoSchemeFactory spnegoSchemeFactory) {
this.spNegoSchemeFactory = spnegoSchemeFactory;
return this;
}
public HttpClientBuilder useSPNego(boolean useSpnego) {
this.useSpNego = useSpnego;
return this;
}
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 {
ConnectionSocketFactory sslsf;
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);
}
RegistryBuilder<ConnectionSocketFactory> sf = RegistryBuilder.create();
sf.register("http", PlainConnectionSocketFactory.getSocketFactory());
sf.register("https", sslsf);
HttpClientConnectionManager cm;
if (connectionPoolSize > 0) {
PoolingHttpClientConnectionManager tcm = new PoolingHttpClientConnectionManager(sf.build(), null, null, null, connectionTTL, connectionTTLUnit);
tcm.setMaxTotal(connectionPoolSize);
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
cm = tcm;
} else {
cm = new BasicHttpClientConnectionManager(sf.build());
}
SocketConfig.Builder socketConfig = SocketConfig.copy(SocketConfig.DEFAULT);
ConnectionConfig.Builder connConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT);
RequestConfig.Builder requestConfig = RequestConfig.copy(RequestConfig.DEFAULT);
if (proxyHost != null) {
requestConfig.setProxy(new HttpHost(proxyHost));
}
if (socketTimeout > -1) {
requestConfig.setSocketTimeout((int) socketTimeoutUnits.toMillis(socketTimeout));
}
if (establishConnectionTimeout > -1) {
requestConfig.setConnectTimeout((int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
}
Registry<CookieSpecProvider> cookieSpecs = CookieSpecRegistries.createDefaultBuilder()
.register(CookieSpecs.DEFAULT, new DefaultCookieSpecProvider()).build();
if (useSpNego) {
requestConfig.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.SPNEGO));
}
org.apache.http.impl.client.HttpClientBuilder clientBuilder = org.apache.http.impl.client.HttpClientBuilder.create()
.setDefaultSocketConfig(socketConfig.build())
.setDefaultConnectionConfig(connConfig.build())
.setDefaultRequestConfig(requestConfig.build())
.setDefaultCookieSpecRegistry(cookieSpecs)
.setConnectionManager(cm);
if (spNegoSchemeFactory != null) {
RegistryBuilder<AuthSchemeProvider> authSchemes = RegistryBuilder.create();
authSchemes.register(AuthSchemes.SPNEGO, spNegoSchemeFactory);
clientBuilder.setDefaultAuthSchemeRegistry(authSchemes.build());
}
if (useSpNego) {
Credentials fake = new Credentials() {
@Override
public String getPassword() {
return null;
}
@Override
public Principal getUserPrincipal() {
return null;
}
};
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, fake);
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
if (disableCookieCache) {
clientBuilder.setDefaultCookieStore(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.
}
});
}
if (this.routePlanner != null) {
clientBuilder.setRoutePlanner(this.routePlanner);
}
return clientBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(true); // 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);
}
}
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
if (adapterConfig.isAllowAnyHostname())
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
connectionPoolSize(adapterConfig.getConnectionPoolSize());
hostnameVerification(policy);
if (adapterConfig.isDisableTrustManager()) {
disableTrustManager();
} else {
trustStore(truststore);
}
configureProxyForAuthServerIfProvided(adapterConfig);
if (socketTimeout == -1 && adapterConfig.getSocketTimeout() > 0) {
socketTimeout(adapterConfig.getSocketTimeout(), TimeUnit.MILLISECONDS);
}
if (establishConnectionTimeout == -1 && adapterConfig.getConnectionTimeout() > 0) {
establishConnectionTimeout(adapterConfig.getConnectionTimeout(), TimeUnit.MILLISECONDS);
}
if (connectionTTL == -1 && adapterConfig.getConnectionTTL() > 0) {
connectionTTL(adapterConfig.getConnectionTTL(), TimeUnit.MILLISECONDS);
}
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());
}
}

View File

@@ -0,0 +1,151 @@
package de.acosix.alfresco.keycloak.share.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.http.NameValuePair;
public class NameValueMapAdapter<T extends NameValuePair> implements Map<String, String> {
private final List<? extends NameValuePair> pairs;
private final Class<T> type;
public NameValueMapAdapter(List<? extends NameValuePair> pairs, Class<T> type) {
this.pairs = pairs;
this.type = type;
}
@Override
public void clear() {
this.pairs.clear();
}
@Override
public boolean containsKey(Object key) {
for (NameValuePair pair : this.pairs)
if (pair.getName().equals(key))
return true;
return false;
}
@Override
public boolean containsValue(Object value) {
for (NameValuePair pair : this.pairs)
if (pair.getValue().equals(value))
return true;
return false;
}
@Override
public Set<Entry<String, String>> entrySet() {
Set<Entry<String, String>> set = new HashSet<Entry<String, String>>();
for (NameValuePair pair : this.pairs) {
set.add(new Entry<String, String>() {
@Override
public String getKey() {
return pair.getName();
}
@Override
public String getValue() {
return pair.getValue();
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException();
}
});
}
return set;
}
@Override
public String get(Object key) {
for (NameValuePair pair : this.pairs)
if (pair.getName().equals(key))
return pair.getValue();
return null;
}
@Override
public boolean isEmpty() {
return this.pairs.isEmpty();
}
@Override
public Set<String> keySet() {
Set<String> set = new HashSet<>();
for (NameValuePair pair : this.pairs)
set.add(pair.getName());
return set;
}
@Override
public String put(String key, String value) {
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
while (i.hasNext()) {
NameValuePair pair = i.next();
if (pair.getName().equals(key)) {
i.remove();
i.add(this.newNameValuePair(key, value));
return pair.getValue();
}
}
i.add(this.newNameValuePair(key, value));
return null;
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
for (Entry<? extends String, ? extends String> e : m.entrySet())
this.put(e.getKey(), e.getValue());
}
@Override
public String remove(Object key) {
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
while (i.hasNext()) {
NameValuePair pair = i.next();
if (pair.getName().equals(key)) {
i.remove();
return pair.getValue();
}
}
return null;
}
@Override
public int size() {
return this.pairs.size();
}
@Override
public Collection<String> values() {
List<String> list = new ArrayList<>(this.pairs.size());
for (NameValuePair pair : this.pairs)
list.add(pair.getValue());
return list;
}
private T newNameValuePair(String key, String value) {
try {
Constructor<T> constructor = this.type.getConstructor(String.class, String.class);
return constructor.newInstance(key, value);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new AlfrescoRuntimeException(e.getMessage(), e);
}
}
}

View File

@@ -23,33 +23,41 @@ import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletContext; import jakarta.servlet.ServletContext;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.EqualsHelper; import org.alfresco.util.EqualsHelper;
import org.alfresco.util.PropertyCheck; import org.alfresco.util.PropertyCheck;
import org.alfresco.web.site.servlet.SSOAuthenticationFilter; import org.alfresco.web.site.servlet.SSOAuthenticationFilter;
import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
@@ -58,8 +66,11 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.params.ConnRouteParams; import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
@@ -73,7 +84,6 @@ import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.OIDCAuthenticationError; import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.adapters.OidcKeycloakAccount; import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.PreAuthActionsHandler; import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.rotation.AdapterTokenVerifier; import org.keycloak.adapters.rotation.AdapterTokenVerifier;
import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens; import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens;
import org.keycloak.adapters.servlet.FilterRequestAuthenticator; import org.keycloak.adapters.servlet.FilterRequestAuthenticator;
@@ -88,6 +98,7 @@ import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@@ -125,6 +136,8 @@ import de.acosix.alfresco.keycloak.share.config.KeycloakAdapterConfigElement;
import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement; import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement;
import de.acosix.alfresco.keycloak.share.config.KeycloakConfigConstants; import de.acosix.alfresco.keycloak.share.config.KeycloakConfigConstants;
import de.acosix.alfresco.keycloak.share.remote.AccessTokenAwareSlingshotAlfrescoConnector; import de.acosix.alfresco.keycloak.share.remote.AccessTokenAwareSlingshotAlfrescoConnector;
import de.acosix.alfresco.keycloak.share.util.HttpClientBuilder;
import de.acosix.alfresco.keycloak.share.util.NameValueMapAdapter;
import de.acosix.alfresco.keycloak.share.util.RefreshableAccessTokenHolder; import de.acosix.alfresco.keycloak.share.util.RefreshableAccessTokenHolder;
/** /**
@@ -515,13 +528,25 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
{ {
final ExtendedAdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration(); final ExtendedAdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration();
this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration); this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration);
final String forcedRouteUrl = adapterConfiguration.getForcedRouteUrl();
if (forcedRouteUrl != null && !forcedRouteUrl.isEmpty()) // we need to recreate the HttpClient to configure the forced route URL
{ this.keycloakDeployment.setClient(new Callable<HttpClient>() {
final HttpClient client = this.keycloakDeployment.getClient(); private HttpClient client;
this.configureForcedRouteIfNecessary(client, forcedRouteUrl); @Override
this.keycloakDeployment.setClient(client); public HttpClient call() throws Exception {
} if (client == null) {
synchronized (this) {
if (client == null) {
client = new HttpClientBuilder()
.routePlanner(createForcedRoutePlanner(adapterConfiguration))
.build(adapterConfiguration);
}
}
}
return client;
}
});
this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment); this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment);
} }
@@ -1726,7 +1751,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.keycloakDeployment.getAuthServerBaseUrl()) final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.keycloakDeployment.getAuthServerBaseUrl())
.path(ServiceUrlConstants.TOKEN_PATH).build(this.keycloakDeployment.getRealm())); .path(ServiceUrlConstants.TOKEN_PATH).build(this.keycloakDeployment.getRealm()));
final List<NameValuePair> formParams = new ArrayList<>(); final List<NameValuePair> formParams = new LinkedList<>();
formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)); formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE));
formParams.add(new BasicNameValuePair(OAuth2Constants.AUDIENCE, alfrescoResourceName)); formParams.add(new BasicNameValuePair(OAuth2Constants.AUDIENCE, alfrescoResourceName));
@@ -1748,9 +1773,17 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
throw new IllegalStateException( throw new IllegalStateException(
"Either an active security context or access token should be present in the session, or previous validations have caught their non-existence and prevented this operation form being called"); "Either an active security context or access token should be present in the session, or previous validations have caught their non-existence and prevented this operation form being called");
} }
final List<Header> headers = new LinkedList<>();
ClientCredentialsProviderUtils.setClientCredentials(this.keycloakDeployment, post, formParams); ClientCredentialsProviderUtils.setClientCredentials(
this.keycloakDeployment.getAdapterConfig(),
this.keycloakDeployment.getClientAuthenticator(),
new NameValueMapAdapter<>(headers, BasicHeader.class),
new NameValueMapAdapter<>(formParams, BasicNameValuePair.class));
for (Header header : headers)
post.addHeader(header);
final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8"); final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8");
post.setEntity(form); post.setEntity(form);
@@ -1867,4 +1900,47 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
} }
params.setParameter(ConnRoutePNames.FORCED_ROUTE, route); params.setParameter(ConnRoutePNames.FORCED_ROUTE, route);
} }
protected HttpRoute createRoute(ExtendedAdapterConfig adapterConfig, HttpHost routeHost) throws UnknownHostException, MalformedURLException {
boolean secure = "https".equalsIgnoreCase(routeHost.getSchemeName());
if (adapterConfig.getProxyUrl() != null) {
// useful in parsing the URL for just what is needed for HttpHost
URL proxyUrl = new URL(adapterConfig.getProxyUrl());
HttpHost proxyHost = new HttpHost(proxyUrl.getHost(), proxyUrl.getPort(), proxyUrl.getProtocol());
return new HttpRoute(routeHost, InetAddress.getLocalHost(), proxyHost, secure);
} else {
return new HttpRoute(routeHost, InetAddress.getLocalHost(), secure);
}
}
protected HttpRoute createForcedRoute(ExtendedAdapterConfig adapterConfig) throws UnknownHostException, MalformedURLException {
// useful in parsing the URL for just what is needed for HttpHost
URL forcedRouteUrl = new URL(adapterConfig.getForcedRouteUrl());
HttpHost forcedRouteHost = new HttpHost(forcedRouteUrl.getHost(), forcedRouteUrl.getPort(), forcedRouteUrl.getProtocol());
return this.createRoute(adapterConfig, forcedRouteHost);
}
protected HttpRoutePlanner createForcedRoutePlanner(ExtendedAdapterConfig adapterConfig) throws MalformedURLException {
URL authServerUrl = new URL(adapterConfig.getAuthServerUrl());
final HttpHost authServerHost = new HttpHost(authServerUrl.getHost(), authServerUrl.getPort(), authServerUrl.getProtocol());
return new HttpRoutePlanner() {
@Override
public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
try {
if (authServerHost.equals(target)) {
LOGGER.trace("Rerouting to forced route");
HttpRoute route = createForcedRoute(adapterConfig);
LOGGER.trace("Rerouting to forced route: {}", route);
return route;
} else {
return createRoute(adapterConfig, target);
}
} catch (IOException ie) {
throw new HttpException(ie.getMessage(), ie);
}
}
};
}
} }

View File

@@ -15,7 +15,7 @@
*/ */
package de.acosix.alfresco.keycloak.share.web; package de.acosix.alfresco.keycloak.share.web;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.extensions.surf.RequestContext; import org.springframework.extensions.surf.RequestContext;
import org.springframework.extensions.surf.RequestContextUtil; import org.springframework.extensions.surf.RequestContextUtil;
@@ -27,7 +27,7 @@ import org.springframework.web.context.request.WebRequest;
/** /**
* This specialisation of the request context interceptor exists only to ensure that a newly created request context is properly * This specialisation of the request context interceptor exists only to ensure that a newly created request context is properly
* {@link RequestContextUtil#populateRequestContext(org.springframework.extensions.surf.RequestContext, javax.servlet.http.HttpServletRequest) * {@link RequestContextUtil#populateRequestContext(org.springframework.extensions.surf.RequestContext, jakarta.servlet.http.HttpServletRequest)
* populated} as to ensure that somewhat important data, such as the user object, is properly initialised. * populated} as to ensure that somewhat important data, such as the user object, is properly initialised.
* *
* @author Axel Faust * @author Axel Faust

View File

@@ -23,7 +23,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.keycloak.adapters.servlet.ServletHttpFacade; import org.keycloak.adapters.servlet.ServletHttpFacade;
@@ -39,7 +39,7 @@ import org.keycloak.adapters.spi.HttpFacade;
public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFacade public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFacade
{ {
protected final Map<Pair<String, String>, javax.servlet.http.Cookie> cookies = new HashMap<>(); protected final Map<Pair<String, String>, jakarta.servlet.http.Cookie> cookies = new HashMap<>();
protected final Map<String, List<String>> headers = new HashMap<>(); protected final Map<String, List<String>> headers = new HashMap<>();
@@ -67,7 +67,7 @@ public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFac
/** /**
* @return the cookies * @return the cookies
*/ */
public List<javax.servlet.http.Cookie> getCookies() public List<jakarta.servlet.http.Cookie> getCookies()
{ {
return new ArrayList<>(this.cookies.values()); return new ArrayList<>(this.cookies.values());
} }
@@ -137,7 +137,7 @@ public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFac
public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge,
final boolean secure, final boolean httpOnly) final boolean secure, final boolean httpOnly)
{ {
final javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value); final jakarta.servlet.http.Cookie cookie = new jakarta.servlet.http.Cookie(name, value);
cookie.setPath(path); cookie.setPath(path);
if (domain != null) if (domain != null)
{ {

View File

@@ -18,13 +18,13 @@ package de.acosix.alfresco.keycloak.share.web;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletContext; import jakarta.servlet.ServletContext;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.util.PropertyCheck; import org.alfresco.util.PropertyCheck;
import org.alfresco.web.site.SlingshotUserFactory; import org.alfresco.web.site.SlingshotUserFactory;

View File

@@ -18,10 +18,10 @@ package de.acosix.alfresco.keycloak.share.web;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.alfresco.util.PropertyCheck; import org.alfresco.util.PropertyCheck;
import org.alfresco.web.site.servlet.SlingshotLoginController; import org.alfresco.web.site.servlet.SlingshotLoginController;