Fix Share working with recent Repository improvements

This commit is contained in:
AFaust
2020-02-15 01:33:49 +01:00
parent 1a5c3400f6
commit 0f974c9f1d
32 changed files with 890 additions and 2861 deletions

View File

@@ -33,6 +33,11 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
@@ -48,6 +53,11 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
@@ -56,6 +66,11 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>*</artifactId>
</exclusion>
<!-- use default from Alfresco Repository -->
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
</exclusion>
</exclusions>
</dependency>
@@ -63,6 +78,11 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>

View File

@@ -1,2 +1,4 @@
log4j.logger.${project.artifactId}=INFO
log4j.logger.${project.artifactId}.deps=ERROR
log4j.logger.${project.artifactId}.deps.keycloak=ERROR
log4j.logger.${project.artifactId}.deps.jboss=ERROR

View File

@@ -75,7 +75,7 @@
<constructor-arg value="cache.${moduleId}.ticketTokenCache" />
</bean>
<bean id="webscript.de.acosix.${moduleId}.roles.get" class="${project.artifactId}.web.scripts.RolesGet" parent="webscript">
<bean id="webscript.de.acosix.keycloak.roles.get" class="${project.artifactId}.web.scripts.RolesGet" parent="webscript">
<property name="roleService" ref="${moduleId}.RoleService" />
</bean>

View File

@@ -109,14 +109,21 @@
<property name="personService" ref="PersonService" />
</bean>
<bean id="webscriptAuthenticationFilter" class="org.alfresco.web.app.servlet.WebScriptSSOAuthenticationFilter">
<bean id="webscriptAuthenticationFilter" class="${project.artifactId}.authentication.KeycloakWebScriptSSOAuthenticationFilter">
<property name="active" value="${keycloak.authentication.enabled}" />
<property name="container" ref="webscripts.container" />
<!-- via inheritance, filter has way more fields, but only the above are actually needed -->
</bean>
<!-- need to override this to align userAttributeName in session with other SSO filters -->
<!-- for some reason, Alfresco is really inconsistent here between BaseAuthenticationFilter.AUTHENTICATION_USER and AuthenticationDriver.AUTHENTICATION_USER -->
<bean id="cookieBasedAuthenticationFilter" class="${project.artifactId}.authentication.KeycloakWebScriptCookieAuthenticationFilter">
<property name="authenticationService" ref="AuthenticationService" />
<property name="authenticationComponent" ref="AuthenticationComponent" />
<property name="personService" ref="personService" />
<property name="personService" ref="PersonService" />
<property name="nodeService" ref="NodeService" />
<property name="transactionService" ref="TransactionService" />
<property name="container" ref="webscripts.container" />
<property name="authenticationComponent" ref="AuthenticationComponent" />
<property name="remoteUserMapper" ref="RemoteUserMapper" />
</bean>
<bean id="globalAuthenticationFilter" class="${project.artifactId}.authentication.KeycloakAuthenticationFilter">

View File

@@ -484,10 +484,6 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
final SessionUser sessionUser = this.createUserEnvironment(session, userId);
this.keycloakAuthenticationComponent.handleUserTokens(accessToken, keycloakSecurityContext.getIdToken(), true);
// need different attribute name than default for integration with web scripts framework
// default attribute name seems to be no longer used
session.setAttribute(AuthenticationDriver.AUTHENTICATION_USER, sessionUser);
this.authenticationListener.userAuthenticated(new KeycloakCredentials(accessToken));
// store tokens in cache as well for ticket validation
@@ -529,6 +525,41 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
chain.doFilter(requestWrapper, res);
}
/**
*
* {@inheritDoc}
*/
@Override
protected SessionUser createUserEnvironment(final HttpSession session, final String userName) throws IOException, ServletException
{
final SessionUser sessionUser = super.createUserEnvironment(session, userName);
// ensure all common attribute names are mapped
// Alfresco is really inconsistent with these attribute names
session.setAttribute(AuthenticationDriver.AUTHENTICATION_USER, sessionUser);
session.setAttribute(BaseAuthenticationFilter.AUTHENTICATION_USER, sessionUser);
return sessionUser;
}
/**
*
* {@inheritDoc}
*/
@Override
protected SessionUser createUserEnvironment(final HttpSession session, final String userName, final String ticket,
final boolean externalAuth) throws IOException, ServletException
{
final SessionUser sessionUser = super.createUserEnvironment(session, userName, ticket, externalAuth);
// ensure all common attribute names are mapped
// Alfresco is really inconsistent with these attribute names
session.setAttribute(AuthenticationDriver.AUTHENTICATION_USER, sessionUser);
session.setAttribute(BaseAuthenticationFilter.AUTHENTICATION_USER, sessionUser);
return sessionUser;
}
/**
* Processes a failed authentication via Keycloak.
*
@@ -607,10 +638,6 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
LOGGER.trace("Skipping doFilter as filter is not active");
skip = true;
}
else if (req.getAttribute(NO_AUTH_REQUIRED) != null)
{
LOGGER.trace("Skipping doFilter as filter higher up in chain determined authentication as not required");
}
else if (servletRequestUri.matches(KEYCLOAK_ACTION_URL_PATTERN))
{
LOGGER.trace("Explicitly not skipping doFilter as Keycloak action URL is being called");
@@ -636,6 +663,14 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter
else if (this.allowTicketLogon && this.checkForTicketParameter(context, req, res))
{
LOGGER.trace("Skipping doFilter as user was authenticated by ticket URL parameter");
skip = true;
}
// check no-auth flag (derived e.g. from checking if target web script requires authentication) only after all pre-emptive auth
// request details have been checked
else if (Boolean.TRUE.equals(req.getAttribute(NO_AUTH_REQUIRED)))
{
LOGGER.trace("Skipping doFilter as filter higher up in chain determined authentication as not required");
skip = true;
}
else if (sessionUser != null)
{

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2019 - 2020 Acosix GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.acosix.alfresco.keycloak.repo.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import org.alfresco.repo.SessionUser;
import org.alfresco.repo.webdav.auth.AuthenticationDriver;
import org.alfresco.repo.webdav.auth.BaseAuthenticationFilter;
import org.alfresco.web.app.servlet.WebscriptCookieAuthenticationFilter;
/**
* This sub-class of the default web script cookie filter only exists to ensure the proper session attribute names are used for mapping the
* authenticated session user.
*
* @author Axel Faust
*/
public class KeycloakWebScriptCookieAuthenticationFilter extends WebscriptCookieAuthenticationFilter
{
/**
*
* {@inheritDoc}
*/
@Override
protected SessionUser createUserEnvironment(final HttpSession session, final String userName) throws IOException, ServletException
{
final SessionUser sessionUser = super.createUserEnvironment(session, userName);
// ensure all common attribute names are mapped
// Alfresco is really inconsistent with these attribute names
session.setAttribute(AuthenticationDriver.AUTHENTICATION_USER, sessionUser);
session.setAttribute(BaseAuthenticationFilter.AUTHENTICATION_USER, sessionUser);
return sessionUser;
}
/**
*
* {@inheritDoc}
*/
@Override
protected SessionUser createUserEnvironment(final HttpSession session, final String userName, final String ticket,
final boolean externalAuth) throws IOException, ServletException
{
final SessionUser sessionUser = super.createUserEnvironment(session, userName, ticket, externalAuth);
// ensure all common attribute names are mapped
// Alfresco is really inconsistent with these attribute names
session.setAttribute(AuthenticationDriver.AUTHENTICATION_USER, sessionUser);
session.setAttribute(BaseAuthenticationFilter.AUTHENTICATION_USER, sessionUser);
return sessionUser;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2019 - 2020 Acosix GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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 org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.web.filter.beans.DependencyInjectedFilter;
import org.alfresco.repo.webdav.auth.BaseAuthenticationFilter;
import org.alfresco.util.PropertyCheck;
import org.alfresco.web.app.servlet.WebScriptSSOAuthenticationFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.surf.util.URLDecoder;
import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
import org.springframework.extensions.webscripts.Match;
import org.springframework.extensions.webscripts.RuntimeContainer;
/**
* This web script SSO authentication filter class is used instead of {@link WebScriptSSOAuthenticationFilter default Alfresco filter} in
* order to properly handle unauthenticated and guest access, especially since the later is performed by Alfresco Share to load edition
* details and potentially other data needed for determining which customisations are active, even before a user has had a chance to
* authenticate.
*
* @author Axel Faust
*/
public class KeycloakWebScriptSSOAuthenticationFilter extends BaseAuthenticationFilter
implements DependencyInjectedFilter, InitializingBean, ActivateableBean
{
// copied from WebScriptRequestImpl due to accessible constraints
private static final String ARG_GUEST = "guest";
private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakWebScriptSSOAuthenticationFilter.class);
protected RuntimeContainer container;
protected boolean isActive = true;
/**
*
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet()
{
PropertyCheck.mandatory(this, "container", this.container);
}
/**
* @param container
* the container to set
*/
public void setContainer(final RuntimeContainer container)
{
this.container = container;
}
/**
* Activates or deactivates the bean
*
* @param active
* <code>true</code> if the bean is active and initialisation should complete
*/
public final void setActive(final boolean active)
{
this.isActive = active;
}
/**
*
* {@inheritDoc}
*/
@Override
public final boolean isActive()
{
return this.isActive;
}
/**
*
* {@inheritDoc}
*/
@Override
public void doFilter(final ServletContext context, final ServletRequest sreq, final ServletResponse sresp, final FilterChain chain)
throws IOException, ServletException
{
final HttpServletRequest req = (HttpServletRequest) sreq;
final String requestURI = req.getRequestURI();
final String pathInfo = requestURI.substring((req.getContextPath() + req.getServletPath()).length());
LOGGER.trace("Processing request: {} SID: {}", requestURI, req.getSession(false) != null ? req.getSession().getId() : null);
final Match match = this.container.getRegistry().findWebScript(req.getMethod(), URLDecoder.decode(pathInfo));
if (match != null && match.getWebScript() != null)
{
final RequiredAuthentication reqAuth = match.getWebScript().getDescription().getRequiredAuthentication();
if (RequiredAuthentication.none == reqAuth)
{
LOGGER.debug("Found webscript with no authentication - set NO_AUTH_REQUIRED flag.");
req.setAttribute(NO_AUTH_REQUIRED, Boolean.TRUE);
}
else if (RequiredAuthentication.guest == reqAuth && Boolean.parseBoolean(sreq.getParameter(ARG_GUEST)))
{
LOGGER.debug("Found webscript with guest authentication and request with set guest parameter - set NO_AUTH_REQUIRED flag.");
req.setAttribute(NO_AUTH_REQUIRED, Boolean.TRUE);
}
}
chain.doFilter(sreq, sresp);
}
/**
*
* {@inheritDoc}
*/
@Override
// ugh - Commons Logging - why does base class not have a sensible default??
protected Log getLogger()
{
return LogFactory.getLog(this.getClass());
}
}

View File

@@ -312,7 +312,7 @@ public class RoleServiceImpl implements InitializingBean, RoleService
}
else
{
LOGGER.debug("No role mapper defined for resource {}", roleNameMapper);
LOGGER.debug("No role mapper defined for resource {}", resourceName);
roles = Collections.emptyList();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,19 +26,20 @@
<artifactId>de.acosix.alfresco.keycloak.share.deps</artifactId>
<name>Acosix Alfresco Keycloak - Share Dependencies Module</name>
<description>Aggregate (Uber-)JAR of all dependencies for the Acosix Alfresco Keycloak Share Module</description>
<description>Aggregate (Uber-)JAR of all dependencies for the Acosix Alfresco Keycloak Share Module (except BouncyCastle)</description>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
<groupId>org.bouncycastle</groupId>
<groupId>com.sun.activation</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
@@ -48,13 +49,19 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
</exclusion>
<!-- use default from Alfresco Share -->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>*</artifactId>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
</exclusion>
</exclusions>
</dependency>
@@ -63,12 +70,13 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<exclusions>
<!-- don't include activation standalone JAR - rely on JDK inclusion since Java 6 -->
<exclusion>
<groupId>org.bouncycastle</groupId>
<groupId>com.sun.activation</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
@@ -77,6 +85,12 @@
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
@@ -97,6 +111,10 @@
<pattern>org.keycloak</pattern>
<shadedPattern>de.acosix.alfresco.keycloak.share.deps.keycloak</shadedPattern>
</relocation>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>de.acosix.alfresco.keycloak.share.deps.jackson</shadedPattern>
</relocation>
<relocation>
<pattern>org.jboss.logging</pattern>
<shadedPattern>de.acosix.alfresco.keycloak.share.deps.jboss.logging</shadedPattern>

View File

@@ -27,6 +27,10 @@
<artifactId>de.acosix.alfresco.keycloak.share</artifactId>
<name>Acosix Alfresco Keycloak - Share Module</name>
<properties>
<docker.tests.keycloakPort>8380</docker.tests.keycloakPort>
</properties>
<dependencyManagement>
<dependencies>
@@ -38,6 +42,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.60</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -55,23 +65,21 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>de.acosix.alfresco.keycloak.share.deps</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- BouncyCastle cannot be made part of an uber-JAR -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
<dependency>
@@ -109,6 +117,16 @@
<classifier>installable</classifier>
</dependency>
<dependency>
<groupId>org.orderofthebee.support-tools</groupId>
<artifactId>support-tools-repo</artifactId>
</dependency>
<dependency>
<groupId>org.orderofthebee.support-tools</groupId>
<artifactId>support-tools-share</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -163,7 +181,13 @@
</run>
</image>
<image>
<!-- no change to Share image -->
<run>
<volumes>
<bind>
<volume>${project.build.directory}/docker/share-logs:/usr/local/tomcat/logs</volume>
</bind>
</volumes>
</run>
</image>
<image>
<!-- no change to Search image -->

View File

@@ -45,10 +45,8 @@
<dependencySet>
<outputDirectory>lib</outputDirectory>
<includes>
<include>org.keycloak:*</include>
<include>org.jboss.logging:*</include>
<include>org.bouncycastle:*</include>
<include>com.fasterxml.jackson.core:*</include>
<include>${project.groupId}:${project.artifactId}.deps:*</include>
</includes>
</dependencySet>
</dependencySets>

View File

@@ -25,7 +25,7 @@
</element-readers>
</plug-ins>
<!-- sensible default configuration (similar to Repository identity-service-authentication.properties -->
<!-- sensible default configuration -->
<config evaluator="string-compare" condition="Keycloak">
<keycloak-auth-config>
<enhance-login-form>true</enhance-login-form>
@@ -50,7 +50,7 @@
</config>
<!-- add to the global configuration -->
<config evaluator="string-compare">
<config>
<user>
<!-- make sure groups of a user are kept up-to-date in at least 60 seconds intervals (lazily refreshed on next request) -->
<cached-user-groups-timeout>60000</cached-user-groups-timeout>

View File

@@ -1 +1,5 @@
log4j.logger.${project.artifactId}=INFO
log4j.logger.${project.artifactId}.deps=ERROR
log4j.logger.${project.artifactId}.deps.keycloak=ERROR
log4j.logger.${project.artifactId}.deps.jackson=ERROR
log4j.logger.${project.artifactId}.deps.jboss=ERROR

View File

@@ -27,7 +27,6 @@
<targetPackageRoot>org.alfresco</targetPackageRoot>
<sourcePackageRoot>de.acosix.keycloak.customisations</sourcePackageRoot>
</customization>
</customizations>
<customization>
<targetPackageRoot>org.alfresco.share.pages</targetPackageRoot>
@@ -36,6 +35,7 @@
<webscript>share-header</webscript>
</alwaysApply>
</customization>
</customizations>
</module>
</modules>
</extension>

View File

@@ -31,13 +31,12 @@ import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.ParameterCheck;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.config.ConfigElement;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.acosix.alfresco.keycloak.share.deps.jackson.annotation.JsonProperty;
import de.acosix.alfresco.keycloak.share.deps.keycloak.representations.adapters.config.AdapterConfig;
import de.acosix.alfresco.utility.share.config.BaseCustomConfigElement;
import de.acosix.alfresco.utility.share.config.ConfigValueHolder;
@@ -137,6 +136,8 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement
protected final ConfigValueHolder<Long> socketTimeout = new ConfigValueHolder<>();
protected final ConfigValueHolder<String> directAuthHost = new ConfigValueHolder<>();
/**
* Creates a new instance of this class.
*/
@@ -179,6 +180,23 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement
this.socketTimeout.setValue(socketTimeout);
}
/**
* @return the directAuthHost
*/
public String getDirectAuthHost()
{
return this.directAuthHost.getValue();
}
/**
* @param directAuthHost
* the directAuthHost to set
*/
public void setDirectAuthHost(final String directAuthHost)
{
this.directAuthHost.setValue(directAuthHost);
}
/**
* Checks if a specific field is supported by this config element.
*
@@ -386,6 +404,16 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement
otherConfigElement.getSocketTimeout() != null ? otherConfigElement.getSocketTimeout() : this.getSocketTimeout());
}
if (otherConfigElement.directAuthHost.isUnset())
{
combined.directAuthHost.unset();
}
else
{
combined.setDirectAuthHost(
otherConfigElement.getDirectAuthHost() != null ? otherConfigElement.getDirectAuthHost() : this.getDirectAuthHost());
}
return combined;
}
@@ -401,16 +429,12 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement
builder.append(this.configValueByField);
builder.append(",markedAsUnset=");
builder.append(this.markedAsUnset);
if (this.connectionTimeout != null)
{
builder.append(",connectionTimeout=");
builder.append(this.connectionTimeout);
}
if (this.connectionTimeout != null)
{
builder.append(",socketTimeout=");
builder.append(this.socketTimeout);
}
builder.append(",directAuthHost=");
builder.append(this.directAuthHost);
builder.append("]");
return builder.toString();
}
@@ -432,8 +456,9 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement
result = prime * result + valueHash;
}
result = prime * result + (this.connectionTimeout != null ? this.connectionTimeout.hashCode() : 0);
result = prime * result + (this.socketTimeout != null ? this.socketTimeout.hashCode() : 0);
result = prime * result + this.connectionTimeout.hashCode();
result = prime * result + this.socketTimeout.hashCode();
result = prime * result + this.directAuthHost.hashCode();
return result;
}

View File

@@ -129,6 +129,10 @@ public class KeycloakAdapterConfigElementReader implements ConfigElementReader
final String prospectiveSocketTimeout = subElement.getTextTrim();
configElement.setSocketTimeout(prospectiveSocketTimeout.isEmpty() ? null : Long.valueOf(prospectiveSocketTimeout));
break;
case "directAuthHost":
final String prospectiveDirectAuthHost = subElement.getTextTrim();
configElement.setDirectAuthHost(prospectiveDirectAuthHost.isEmpty() ? null : prospectiveDirectAuthHost);
break;
default:
LOGGER.warn("Encountered unsupported Keycloak Adapter config element {}", subElementName);
}

View File

@@ -78,6 +78,8 @@ public class KeycloakAuthenticationFilterActivation implements BeanDefinitionReg
if (registry.containsBeanDefinition(keycloakFilterBeanName))
{
LOGGER.debug("Activating KeycloakAuthenticationFilter bean");
// re-register default filter under different name
final BeanDefinition defaultSsoAuthenticationFilter = registry.getBeanDefinition(DEFAULT_SSO_AUTHENTICATION_FILTER_NAME);
registry.removeBeanDefinition(DEFAULT_SSO_AUTHENTICATION_FILTER_NAME);
@@ -92,6 +94,8 @@ public class KeycloakAuthenticationFilterActivation implements BeanDefinitionReg
keycloakSsoAuthenticationFilter.getPropertyValues().add("defaultSsoFilter",
new RuntimeBeanReference(defaultSsoAuthenticationFilterReplacementName));
registry.registerBeanDefinition(DEFAULT_SSO_AUTHENTICATION_FILTER_NAME, keycloakSsoAuthenticationFilter);
LOGGER.debug("Activated KeycloakAuthenticationFilter bean");
}
else
{

View File

@@ -25,14 +25,14 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.config.ConfigService;
import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.InMemorySessionIdMapper;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.SessionIdMapper;
/**
* This implementation of a {@link SessionIdMapper Keycloak session ID mapper} is based on the {@link InMemorySessionIdMapper in-memory

View File

@@ -16,6 +16,7 @@
package de.acosix.alfresco.keycloak.share.web;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -38,25 +39,12 @@ import javax.servlet.http.HttpSession;
import org.alfresco.util.PropertyCheck;
import org.alfresco.web.site.servlet.SSOAuthenticationFilter;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.servlet.FilterRequestAuthenticator;
import org.keycloak.adapters.servlet.OIDCFilterSessionStore;
import org.keycloak.adapters.servlet.OIDCServletHttpFacade;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.KeycloakAccount;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
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.params.HttpParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
@@ -83,6 +71,25 @@ import org.springframework.extensions.webscripts.servlet.DependencyInjectedFilte
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.deps.keycloak.KeycloakSecurityContext;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.AdapterDeploymentContext;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.AuthenticatedActionsHandler;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.HttpClientBuilder;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.KeycloakDeployment;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.KeycloakDeploymentBuilder;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.OAuthRequestAuthenticator;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.OidcKeycloakAccount;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.PreAuthActionsHandler;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.servlet.FilterRequestAuthenticator;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.servlet.OIDCFilterSessionStore;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.servlet.OIDCServletHttpFacade;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.AuthOutcome;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.AuthenticationError;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.KeycloakAccount;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.SessionIdMapper;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.UserSessionManagement;
import de.acosix.alfresco.keycloak.share.deps.keycloak.representations.AccessToken;
import de.acosix.alfresco.keycloak.share.deps.keycloak.representations.adapters.config.AdapterConfig;
import de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector;
/**
@@ -259,7 +266,10 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
{
httpClientBuilder = httpClientBuilder.socketTimeout(socketTimeout.longValue(), TimeUnit.MILLISECONDS);
}
this.keycloakDeployment.setClient(httpClientBuilder.build(adapterConfiguration));
final HttpClient client = httpClientBuilder.build(adapterConfiguration);
this.configureForcedRouteIfNecessary(keycloakAdapterConfig, client);
this.keycloakDeployment.setClient(client);
}
this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment);
@@ -516,7 +526,6 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
}
else
{
if (authOutcome == AuthOutcome.NOT_ATTEMPTED)
{
LOGGER.debug("No authentication took place - continueing with filter chain processing");
@@ -1129,4 +1138,29 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
});
}
}
@SuppressWarnings("deprecation")
protected void configureForcedRouteIfNecessary(final KeycloakAdapterConfigElement configElement, final HttpClient client)
{
final String directAuthHost = configElement.getDirectAuthHost();
if (directAuthHost != null && !directAuthHost.isEmpty())
{
final HttpHost host = HttpHost.create(directAuthHost);
final HttpParams params = client.getParams();
final InetAddress local = ConnRouteParams.getLocalAddress(params);
final HttpHost proxy = ConnRouteParams.getDefaultProxy(params);
final boolean secure = host.getSchemeName().equalsIgnoreCase("https");
HttpRoute route;
if (proxy == null)
{
route = new HttpRoute(host, local, secure);
}
else
{
route = new HttpRoute(host, local, proxy, secure);
}
params.setParameter(ConnRoutePNames.FORCED_ROUTE, route);
}
}
}

View File

@@ -26,8 +26,9 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.util.Pair;
import org.keycloak.adapters.servlet.ServletHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.servlet.ServletHttpFacade;
import de.acosix.alfresco.keycloak.share.deps.keycloak.adapters.spi.HttpFacade;
/**
* This {@link HttpFacade} wraps servlet requests and responses in such a way that any response headers / cookies being set by Keycloak

View File

@@ -24,5 +24,8 @@ keycloak.adapter.resource=alfresco
keycloak.adapter.credentials.provider=secret
keycloak.adapter.credentials.secret=6f70a28f-98cd-41ca-8f2f-368a8797d708
# localhost in auth-server-url won't work for direct access in a Docker deployment
keycloak.authentication.directAuthHost=http://host.docker.internal:8380
keycloak.synchronization.userFilter.containedInGroup.property.groupPaths=/Test A
keycloak.synchronization.groupFilter.containedInGroup.property.groupPaths=/Test A

View File

@@ -0,0 +1,25 @@
#
# Copyright 2019 - 2020 Acosix GmbH
#
# 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.
log4j.rootLogger=error, File
log4j.appender.File=org.apache.log4j.DailyRollingFileAppender
log4j.appender.File.File=\${catalina.base}/logs/share.log
log4j.appender.File.Append=true
log4j.appender.File.DatePattern='.'yyyy-MM-dd
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=%d{yyyy-MM-dd} %d{ABSOLUTE} %-5p [%c] [%t] %m%n
log4j.logger.${project.artifactId}=DEBUG

View File

@@ -16,13 +16,76 @@
-->
<alfresco-config>
<config evaluator="string-compare" condition="Test">
<config evaluator="string-compare" condition="Remote">
<remote>
<connector>
<id>alfrescoCookie</id>
<name>Alfresco Connector</name>
<description>Connects to an Alfresco instance using cookie-based authentication</description>
<class>de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector</class>
</connector>
<connector>
<id>alfrescoHeader</id>
<name>Alfresco Connector</name>
<description>Connects to an Alfresco instance using header and cookie-based authentication</description>
<class>de.acosix.alfresco.keycloak.share.remote.BearerTokenAwareSlingshotAlfrescoConnector</class>
<userHeader>SsoUserHeader</userHeader>
</connector>
<endpoint>
<id>alfresco</id>
<name>Alfresco - user access</name>
<description>Access to Alfresco Repository WebScripts that require user authentication</description>
<connector-id>alfrescoCookie</connector-id>
<endpoint-url>http://repository:8080/alfresco/wcs</endpoint-url>
<identity>user</identity>
<external-auth>true</external-auth>
</endpoint>
<endpoint>
<id>alfresco-feed</id>
<name>Alfresco Feed</name>
<description>Alfresco Feed - supports basic HTTP authentication via the EndPointProxyServlet</description>
<connector-id>alfrescoHeader</connector-id>
<endpoint-url>http://repository:8080/alfresco/wcs</endpoint-url>
<basic-auth>true</basic-auth>
<identity>user</identity>
<external-auth>true</external-auth>
</endpoint>
<endpoint>
<id>alfresco-api</id>
<parent-id>alfresco</parent-id>
<name>Alfresco Public API - user access</name>
<description>Access to Alfresco Repository Public API that require user authentication.
This makes use of the authentication that is provided by parent 'alfresco' endpoint.
</description>
<connector-id>alfrescoHeader</connector-id>
<endpoint-url>http://repository:8080/alfresco/api</endpoint-url>
<identity>user</identity>
<external-auth>true</external-auth>
</endpoint>
</remote>
</config>
<config evaluator="string-compare" condition="Keycloak">
<keycloak-auth-config>
<enhance-login-form>true</enhance-login-form>
<enable-sso-filter>true</enable-sso-filter>
<force-keycloak-sso>false</force-keycloak-sso>
</keycloak-auth-config>
<keycloak-adapter-config>
<directAuthHost>http://host.docker.internal:8380</directAuthHost>
<auth-server-url>http://${docker.tests.host.name}:${docker.tests.keycloakPort}/auth</auth-server-url>
<realm>test</realm>
<resource>alfresco-share</resource>
<ssl-required>none</ssl-required>
<public-client>false</public-client>
<credentials>
<secret>secret</secret>
<provider>test</provider>
<provider>secret</provider>
<secret>a5b3e8bc-39cc-4ddd-8c8f-1c34e7a35975</secret>
</credentials>
<allow-any-hostname>true</allow-any-hostname>
</keycloak-adapter-config>
</config>

View File

@@ -59,10 +59,16 @@
<dependencySet>
<outputDirectory>WEB-INF/lib</outputDirectory>
<includes>
<include>${project.groupId}:de.acosix.alfresco.keycloak.repo.deps:*</include>
<!-- TODO: Report bug against Maven PatternIncludesArtifactFilter#matchAgainst for incorrect return false-->
<!-- when patterns with 5 tokens are listed in includes (like the installable JAR of Acosix Utility Core Repo), they may prevent evaluation of any additional patterns -->
<!-- this cost me half a day to track down when the following three patterns were sorted last -->
<include>com.cronutils:*</include>
<include>net.time4j:*</include>
<include>org.orderofthebee.support-tools:support-tools-repo:*</include>
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.common:*</include>
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz1:*</include>
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo.quartz2:*</include>
<include>${project.groupId}:de.acosix.alfresco.keycloak.repo.deps:*</include>
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.repo:jar:installable:*</include>
<include>${project.groupId}:de.acosix.alfresco.keycloak.repo:jar:installable:*</include>
</includes>

View File

@@ -23,13 +23,6 @@
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<files>
<file>
<source>${project.basedir}/src/test/docker/share-log4j.properties</source>
<outputDirectory>WEB-INF/classes</outputDirectory>
<destName>log4j.properties</destName>
</file>
</files>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
@@ -74,12 +67,14 @@
<outputDirectory>WEB-INF/lib</outputDirectory>
<includes>
<include>${project.groupId}:de.acosix.alfresco.keycloak.share.deps:*</include>
<include>org.bouncycastle:*</include>
</includes>
<scope>compile</scope>
</dependencySet>
<dependencySet>
<outputDirectory>WEB-INF/lib</outputDirectory>
<includes>
<include>org.orderofthebee.support-tools:support-tools-share:*</include>
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.common:*</include>
<include>de.acosix.alfresco.utility:de.acosix.alfresco.utility.core.share:jar:installable:*</include>
</includes>

View File

@@ -0,0 +1 @@
# only exists to ensure Maven creates path in project ./target

File diff suppressed because it is too large Load Diff

View File

@@ -20,12 +20,14 @@ import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.extensions.config.Config;
import org.springframework.extensions.config.ConfigElement;
import org.springframework.extensions.config.ConfigSource;
import org.springframework.extensions.config.source.UrlConfigSource;
import org.springframework.extensions.config.xml.XMLConfigService;
import de.acosix.alfresco.keycloak.share.deps.keycloak.representations.adapters.config.AdapterConfig;
/**
* @author Axel Faust
*/
@@ -43,8 +45,9 @@ public class KeycloakAdapterConfigTest
final Config keycloakConfigSection = configService.getConfig(KeycloakConfigConstants.KEYCLOAK_CONFIG_SECTION_NAME);
final KeycloakAuthenticationConfigElement keycloakAuthConfig = (KeycloakAuthenticationConfigElement) keycloakConfigSection
.getConfigElement(KeycloakAuthenticationConfigElement.NAME);
final ConfigElement keycloakAuthConfigEl = keycloakConfigSection.getConfigElement(KeycloakAuthenticationConfigElement.NAME);
Assert.assertTrue(keycloakAuthConfigEl instanceof KeycloakAuthenticationConfigElement);
final KeycloakAuthenticationConfigElement keycloakAuthConfig = (KeycloakAuthenticationConfigElement) keycloakAuthConfigEl;
Assert.assertTrue(keycloakAuthConfig.getEnhanceLoginForm());
Assert.assertTrue(keycloakAuthConfig.getEnableSsoFilter());
@@ -89,8 +92,9 @@ public class KeycloakAdapterConfigTest
final Config keycloakConfigSection = configService.getConfig(KeycloakConfigConstants.KEYCLOAK_CONFIG_SECTION_NAME);
final KeycloakAuthenticationConfigElement keycloakAuthConfig = (KeycloakAuthenticationConfigElement) keycloakConfigSection
.getConfigElement(KeycloakAuthenticationConfigElement.NAME);
final ConfigElement keycloakAuthConfigEl = keycloakConfigSection.getConfigElement(KeycloakAuthenticationConfigElement.NAME);
Assert.assertTrue(keycloakAuthConfigEl instanceof KeycloakAuthenticationConfigElement);
final KeycloakAuthenticationConfigElement keycloakAuthConfig = (KeycloakAuthenticationConfigElement) keycloakAuthConfigEl;
Assert.assertFalse(keycloakAuthConfig.getEnhanceLoginForm());
Assert.assertFalse(keycloakAuthConfig.getEnableSsoFilter());

View File

@@ -50,7 +50,7 @@
</config>
<!-- add to the global configuration -->
<config evaluator="string-compare">
<config>
<user>
<!-- make sure groups of a user are kept up-to-date in at least 60 seconds intervals (lazily refreshed on next request) -->
<cached-user-groups-timeout>60000</cached-user-groups-timeout>