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>
<groupId>de.acosix.alfresco.maven</groupId>
<artifactId>de.acosix.alfresco.maven.project.parent-6.0.7</artifactId>
<version>1.4.1</version>
<artifactId>de.acosix.alfresco.maven.project.parent-23.1.0</artifactId>
<version>1.5.0</version>
</parent>
<groupId>de.acosix.alfresco.keycloak</groupId>
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
<version>1.1.0-rc7</version>
<version>1.2.0-rc1</version>
<packaging>pom</packaging>
<name>Acosix Alfresco Keycloak - Parent</name>
@@ -61,6 +61,15 @@
<twitter>twitter.com/ReluctantBird83</twitter>
</properties>
</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>
<properties>
@@ -68,18 +77,18 @@
<messages.packageId>acosix.keycloak</messages.packageId>
<moduleId>acosix-keycloak</moduleId>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.version>3.13.0</maven.compiler.version>
<maven.shade.version>3.6.0</maven.shade.version>
<maven.shade.version>3.2.4</maven.shade.version>
<keycloak.version>16.1.0</keycloak.version>
<keycloak.version>22.0.3</keycloak.version>
<resteasy.version>3.15.1.Final</resteasy.version>
<!-- lowest common denominator of Repository / Share in 6.0 -->
<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>
<docker.tests.repositoryImageBuilder.preRun></docker.tests.repositoryImageBuilder.preRun>
@@ -101,6 +110,7 @@
<docker.tests.repositoryImageBuilder.postRun>USER alfresco</docker.tests.repositoryImageBuilder.postRun>
<acosix.utility.version>1.3.0</acosix.utility.version>
-->
</properties>
<dependencyManagement>
@@ -132,13 +142,13 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
<artifactId>keycloak-jakarta-servlet-adapter-spi</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
<version>${keycloak.version}</version>
</dependency>
@@ -150,7 +160,7 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<artifactId>keycloak-admin-client-jakarta</artifactId>
<version>${keycloak.version}</version>
</dependency>
@@ -187,6 +197,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.alfresco.surf</groupId>
<artifactId>spring-surf</artifactId>
<version>${surf.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>de.acosix.alfresco.utility</groupId>
<artifactId>de.acosix.alfresco.utility.common</artifactId>
@@ -298,6 +315,14 @@
<build>
<pluginManagement>
<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>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.version}</version>

View File

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

View File

@@ -6,7 +6,7 @@ cache.${moduleId}.ssoToSessionCache.maxIdleSeconds=0
cache.${moduleId}.ssoToSessionCache.cluster.type=fully-distributed
cache.${moduleId}.ssoToSessionCache.backup-count=1
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
# explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.ssoToSessionCache.clearable=false
@@ -19,7 +19,7 @@ cache.${moduleId}.sessionToSsoCache.maxIdleSeconds=0
cache.${moduleId}.sessionToSsoCache.cluster.type=fully-distributed
cache.${moduleId}.sessionToSsoCache.backup-count=1
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
# explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.sessionToSsoCache.clearable=false
@@ -32,7 +32,7 @@ cache.${moduleId}.principalToSessionCache.maxIdleSeconds=0
cache.${moduleId}.principalToSessionCache.cluster.type=fully-distributed
cache.${moduleId}.principalToSessionCache.backup-count=1
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
# explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.principalToSessionCache.clearable=false
@@ -45,7 +45,7 @@ cache.${moduleId}.sessionToPrincipalCache.maxIdleSeconds=0
cache.${moduleId}.sessionToPrincipalCache.cluster.type=fully-distributed
cache.${moduleId}.sessionToPrincipalCache.backup-count=1
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
# explicitly not clearable - should be cleared via Keycloak back-channel action
cache.${moduleId}.sessionToPrincipalCache.clearable=false
@@ -58,7 +58,7 @@ cache.${moduleId}.ticketTokenCache.maxIdleSeconds=0
cache.${moduleId}.ticketTokenCache.cluster.type=fully-distributed
cache.${moduleId}.ticketTokenCache.backup-count=1
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
# dangerous to be cleared, as roles / claims can no longer be mapped
# 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.Locale;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.alfresco.repo.SessionUser;
import org.alfresco.repo.cache.SimpleCache;

View File

@@ -17,8 +17,8 @@ package de.acosix.alfresco.keycloak.repo.authentication;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.alfresco.repo.management.subsystems.ActivateableBean;
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 javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.alfresco.repo.SessionUser;
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 javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.repo.management.subsystems.ActivateableBean;
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.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.util.Pair;
import org.keycloak.adapters.servlet.ServletHttpFacade;
@@ -39,7 +39,7 @@ import org.keycloak.adapters.spi.HttpFacade;
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<>();
@@ -71,7 +71,7 @@ public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFac
/**
* @return the cookies
*/
public List<javax.servlet.http.Cookie> getCookies()
public List<jakarta.servlet.http.Cookie> getCookies()
{
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,
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);
if (domain != null)
{

View File

@@ -4,37 +4,40 @@ import com.fasterxml.jackson.core.JsonParseException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import org.alfresco.util.ParameterCheck;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.TokenVerifier;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.ServerRequest.HttpFailure;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.acosix.alfresco.keycloak.repo.util.NameValueMapAdapter;
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())
.path(ServiceUrlConstants.TOKEN_PATH).build(this.deployment.getRealm()));
final List<NameValuePair> formParams = new ArrayList<>();
final List<NameValuePair> formParams = new LinkedList<>();
postParamProvider.accept(formParams);
ClientCredentialsProviderUtils.setClientCredentials(this.deployment, post, formParams);
final List<Header> headers = new LinkedList<>();
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");
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>
<groupId>de.acosix.alfresco.keycloak</groupId>
<artifactId>de.acosix.alfresco.keycloak.parent</artifactId>
<version>1.1.0-rc7</version>
<version>1.2.0-rc1</version>
</parent>
<artifactId>de.acosix.alfresco.keycloak.share</artifactId>
@@ -45,8 +45,8 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.77</version>
</dependency>
</dependencies>
@@ -61,8 +61,8 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
@@ -83,7 +83,7 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
<artifactId>keycloak-jakarta-servlet-adapter-spi</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
@@ -104,7 +104,7 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
@@ -132,7 +132,7 @@
<!-- BouncyCastle cannot be made part of an uber-JAR -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<dependency>
@@ -284,7 +284,8 @@
<goal>shade</goal>
</goals>
<configuration>
<createSourcesJar>true</createSourcesJar>
<!-- generating using `sources` classifier, which conflicts with the main `sources` -->
<createSourcesJar>false</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent>
<keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
<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;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.spi.KeycloakAccount;

View File

@@ -17,7 +17,7 @@ package de.acosix.alfresco.keycloak.share.remote;
import java.util.Collections;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;
import org.alfresco.web.site.servlet.SlingshotAlfrescoConnector;
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.InvocationTargetException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.PropertyCheck;
import org.alfresco.web.site.servlet.SSOAuthenticationFilter;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
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.ConnRouteParams;
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.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.OAuth2Constants;
@@ -73,7 +84,6 @@ import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
import org.keycloak.adapters.rotation.AdapterTokenVerifier.VerifiedTokens;
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.Time;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.protocol.oidc.client.authentication.ClientCredentialsProviderUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
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.KeycloakConfigConstants;
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;
/**
@@ -515,13 +528,25 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
{
final ExtendedAdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration();
this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration);
final String forcedRouteUrl = adapterConfiguration.getForcedRouteUrl();
if (forcedRouteUrl != null && !forcedRouteUrl.isEmpty())
{
final HttpClient client = this.keycloakDeployment.getClient();
this.configureForcedRouteIfNecessary(client, forcedRouteUrl);
this.keycloakDeployment.setClient(client);
// we need to recreate the HttpClient to configure the forced route URL
this.keycloakDeployment.setClient(new Callable<HttpClient>() {
private HttpClient client;
@Override
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);
}
@@ -1726,7 +1751,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.keycloakDeployment.getAuthServerBaseUrl())
.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.AUDIENCE, alfrescoResourceName));
@@ -1749,8 +1774,16 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
"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");
}
ClientCredentialsProviderUtils.setClientCredentials(this.keycloakDeployment, post, formParams);
final List<Header> headers = new LinkedList<>();
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");
post.setEntity(form);
@@ -1867,4 +1900,47 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
}
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;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.extensions.surf.RequestContext;
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
* {@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.
*
* @author Axel Faust

View File

@@ -23,7 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.util.Pair;
import org.keycloak.adapters.servlet.ServletHttpFacade;
@@ -39,7 +39,7 @@ import org.keycloak.adapters.spi.HttpFacade;
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<>();
@@ -67,7 +67,7 @@ public class ResponseHeaderCookieCaptureServletHttpFacade extends ServletHttpFac
/**
* @return the cookies
*/
public List<javax.servlet.http.Cookie> getCookies()
public List<jakarta.servlet.http.Cookie> getCookies()
{
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,
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);
if (domain != null)
{

View File

@@ -18,13 +18,13 @@ package de.acosix.alfresco.keycloak.share.web;
import java.io.IOException;
import java.util.Date;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.alfresco.util.PropertyCheck;
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.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.alfresco.util.PropertyCheck;
import org.alfresco.web.site.servlet.SlingshotLoginController;