diff --git a/docs/Reference-Adapter.md b/docs/Reference-Adapter.md index a041ac1..f4e3d45 100644 --- a/docs/Reference-Adapter.md +++ b/docs/Reference-Adapter.md @@ -11,7 +11,7 @@ Configuration of adapter properties in the Share-tier `share-config-custom.xml` ```xml - + http://localhost:8180/auth alfresco alfresco-share @@ -30,7 +30,8 @@ Note: This listing does not include the common property key prefix `keycloak.ada | Property | Default Value | Description | | --- | ---: | --- | | `auth-server-url` | `http://localhost:8180/auth` | Publically resolvable base URL to the Keycloak server to be used in redirect URLs and remote calls | -| `proxy-url` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved or round-trips via a public gateway / proxy should be avoided | +| `forced-route-url` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved or round-trips via a public gateway / proxy should be avoided | +| `proxy-url` | | URL for proxy server to use for calls from Alfresco to Keycloak | | `realm` | `alfresco` | Technical name of the Keycloak realm | | `realm-public-key` | | Fixed public key of the realm (PEM string) - if not set, the public key(s) will be dynamically loaded and automatically refreshed after a configurable amount of times between JSON Web Key Store requests | | `resource` | `alfresco` / `alfresco-share` | Technical name of the client set up in the realm | diff --git a/docs/Reference-Share.md b/docs/Reference-Share.md index b92d87a..b9acd3a 100644 --- a/docs/Reference-Share.md +++ b/docs/Reference-Share.md @@ -22,6 +22,7 @@ The configuration options for the `keycloak-adapter-config` sub-element are [doc | `enable-sso-filter` | `true` | Flag determining whether the SSO authentication handling logic is enabled - only if this is enabled (and `external-auth` configured for the main `alfresco` remote connector) will any of the functionality of the Share addon work. | | `enhance-login-form` | `true` | Flag determining whether an additional "Log in via SSO" button is to be included in the Share login form | | `force-keycloak-sso` | `false` | Flag determining whether SSO authentication should be forced, meaning users are automatically redirected for authentication to Keycloak and the login form is only accessible by using a direct URL access bypass | +| `remember-keycloak-sso` | `false` | Flag determining whether SSO authentication should be remembered, meaning users are automatically redirected for authentication to Keycloak if they last logged in via Keycloak, when `force-keycloak-sso` is not enabled (if enabled, this will set an additional cookie in the client's browser) | | `body-buffer-limit` | `10485760` | Size limit for request bodies that can be cached / stored if a request needs to be redirected to Keycloak for SSO authentication - requests larger than this limit will fail and require that the client first authenticate in a simple request, and use either authentication tickets or HTTP session cookies to perform the payload request re-using the established authentication | | `session-mapper-limit` | `10000` | Size limit (in number of sessions) of the in-memory mapper of HTTP and SSO session IDs in order to allow back-channel logout requests to be properly handled. As HTTP sessions are not replicated in default Alfresco Share, the session mapper only handles local sessions for an individual Share node. | | `ignore-default-filter` | `true` | Flag determining whether the default SSO filter should be ignored / skipped when `enable-sso-filter` is enabled, in order to avoid functionality conflicts, e.g. via redundant handling of `Authorization` HTTP headers. | diff --git a/docs/Simple-Configuration.md b/docs/Simple-Configuration.md index f1c4069..721bd64 100644 --- a/docs/Simple-Configuration.md +++ b/docs/Simple-Configuration.md @@ -98,7 +98,7 @@ The following core configuration properties can be set (more extensive list in t | `...groupFilter.containedInGroup.property.groupPaths` | | Comma-separated list of group paths (e.g. `/Group A/Group B,/Group A/Group C`) to use in filtering which groups are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | | `...groupFilter.containedInGroup.property.groupIds` | | Comma-separated list of group IDs to use in filtering which groups are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | | `keycloak.adapter.auth-server-url` | `http://localhost:8180/auth` | Publically resolvable base URL to the Keycloak server to be used in redirect URLs and remote calls | -| `...proxy-url` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved by the Alfresco Repository host or round-trips via a public gateway / proxy should be avoided | +| `...forced-route-url` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved by the Alfresco Repository host or round-trips via a public gateway / proxy should be avoided | | `...realm` | `alfresco` | Technical name of the Keycloak realm | | `...resource` | `alfresco` | Technical name of the client set up for the Alfresco Repository in the realm | | `...credentials.secret` | | Shared secret for validation of authorisation codes / access tokens | @@ -118,7 +118,7 @@ The following showcases an example configuration block: true - + http://localhost:8180/auth alfresco alfresco-share diff --git a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties index 365b73c..3a2a50c 100644 --- a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties +++ b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties @@ -16,6 +16,7 @@ keycloak.authentication.silentRemoteUserValidationFailure=true keycloak.authentication.bodyBufferLimit=10485760 keycloak.adapter.auth-server-url=http://localhost:8180/auth +keycloak.adapter.forced-route-url= keycloak.adapter.proxy-url= keycloak.adapter.realm=alfresco keycloak.adapter.resource=alfresco diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/ExtendedAdapterConfig.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/ExtendedAdapterConfig.java new file mode 100644 index 0000000..87525f9 --- /dev/null +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/ExtendedAdapterConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 - 2021 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.spring; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import org.keycloak.representations.adapters.config.AdapterConfig; + +/** + * Minorly extended configuration for Java based adapters + * + * @author Axel Faust + */ +@JsonPropertyOrder({ "realm", "realm-public-key", "auth-server-url", "ssl-required", "resource", "public-client", "credentials", + "use-resource-role-mappings", "enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers", "expose-token", + "bearer-only", "autodetect-bearer-only", "connection-pool-size", "socket-timeout-millis", "connection-ttl-millis", + "connection-timeout-millis", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "client-keystore", + "client-keystore-password", "client-key-password", "always-refresh-token", "register-node-at-startup", "register-node-period", + "token-store", "adapter-state-cookie-path", "principal-attribute", "proxy-url", "forced-route-url", + "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests", "public-key-cache-ttl", + "policy-enforcer", "ignore-oauth-query-parameter", "verify-token-audience" }) +public class ExtendedAdapterConfig extends AdapterConfig +{ + + @JsonProperty("forced-route-url") + protected String forcedRouteUrl; + + /** + * @return the forcedRouteUrl + */ + public String getForcedRouteUrl() + { + return this.forcedRouteUrl; + } + + /** + * @param forcedRouteUrl + * the forcedRouteUrl to set + */ + public void setForcedRouteUrl(final String forcedRouteUrl) + { + this.forcedRouteUrl = forcedRouteUrl; + } + +} diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakAdapterConfigBeanFactory.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakAdapterConfigBeanFactory.java index 2541d2b..3c8c6f6 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakAdapterConfigBeanFactory.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakAdapterConfigBeanFactory.java @@ -33,7 +33,6 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.util.PropertyCheck; -import org.keycloak.representations.adapters.config.AdapterConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.FactoryBean; @@ -44,7 +43,7 @@ import org.springframework.util.PropertyPlaceholderHelper; /** * @author Axel Faust */ -public class KeycloakAdapterConfigBeanFactory implements FactoryBean, InitializingBean +public class KeycloakAdapterConfigBeanFactory implements FactoryBean, InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakAdapterConfigBeanFactory.class); @@ -74,7 +73,7 @@ public class KeycloakAdapterConfigBeanFactory implements FactoryBean cls = AdapterConfig.class; + Class cls = ExtendedAdapterConfig.class; while (cls != null && !Object.class.equals(cls)) { final Field[] fields = cls.getDeclaredFields(); @@ -152,7 +151,7 @@ public class KeycloakAdapterConfigBeanFactory implements FactoryBean { final Class valueType = VALUE_TYPE_BY_CONFIG_NAME.get(configFieldName); @@ -248,7 +247,7 @@ public class KeycloakAdapterConfigBeanFactory implements FactoryBean getObjectType() { - return AdapterConfig.class; + return ExtendedAdapterConfig.class; } protected Object loadConfigValue(final String configFieldName, final Class valueType) diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakDeploymentBeanFactory.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakDeploymentBeanFactory.java index 3462f96..a489e0d 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakDeploymentBeanFactory.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/spring/KeycloakDeploymentBeanFactory.java @@ -15,18 +15,27 @@ */ package de.acosix.alfresco.keycloak.repo.spring; +import java.net.InetAddress; + import org.alfresco.httpclient.HttpClientFactory.NonBlockingHttpParamsFactory; import org.alfresco.util.PropertyCheck; import org.apache.commons.httpclient.params.DefaultHttpParams; +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.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeploymentBuilder; -import org.keycloak.representations.adapters.config.AdapterConfig; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; /** * @author Axel Faust */ +@SuppressWarnings("deprecation") public class KeycloakDeploymentBeanFactory implements FactoryBean, InitializingBean { @@ -36,7 +45,7 @@ public class KeycloakDeploymentBeanFactory implements FactoryBeantrue true false + false 10485760 10000 true diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/ExtendedAdapterConfig.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/ExtendedAdapterConfig.java new file mode 100644 index 0000000..65e4a97 --- /dev/null +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/ExtendedAdapterConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 - 2021 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.share.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import org.keycloak.representations.adapters.config.AdapterConfig; + +/** + * Minorly extended configuration for Java based adapters + * + * @author Axel Faust + */ +@JsonPropertyOrder({ "realm", "realm-public-key", "auth-server-url", "ssl-required", "resource", "public-client", "credentials", + "use-resource-role-mappings", "enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers", "expose-token", + "bearer-only", "autodetect-bearer-only", "connection-pool-size", "socket-timeout-millis", "connection-ttl-millis", + "connection-timeout-millis", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "client-keystore", + "client-keystore-password", "client-key-password", "always-refresh-token", "register-node-at-startup", "register-node-period", + "token-store", "adapter-state-cookie-path", "principal-attribute", "proxy-url", "forced-route-url", + "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests", "public-key-cache-ttl", + "policy-enforcer", "ignore-oauth-query-parameter", "verify-token-audience" }) +public class ExtendedAdapterConfig extends AdapterConfig +{ + + @JsonProperty("forced-route-url") + protected String forcedRouteUrl; + + /** + * @return the forcedRouteUrl + */ + public String getForcedRouteUrl() + { + return this.forcedRouteUrl; + } + + /** + * @param forcedRouteUrl + * the forcedRouteUrl to set + */ + public void setForcedRouteUrl(final String forcedRouteUrl) + { + this.forcedRouteUrl = forcedRouteUrl; + } + +} diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigElement.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigElement.java index 19b9c03..dde8f2f 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigElement.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigElement.java @@ -34,13 +34,11 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.util.EqualsHelper; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyCheck; -import org.keycloak.representations.adapters.config.AdapterConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.extensions.config.ConfigElement; import de.acosix.alfresco.utility.share.config.BaseCustomConfigElement; -import de.acosix.alfresco.utility.share.config.ConfigValueHolder; /** * @author Axel Faust @@ -79,7 +77,7 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement primitiveWrapperTypeMap.put(primitiveTypes[i], wrapperTypes[i]); } - Class cls = AdapterConfig.class; + Class cls = ExtendedAdapterConfig.class; while (cls != null && !Object.class.equals(cls)) { final Field[] fields = cls.getDeclaredFields(); @@ -152,8 +150,7 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement public boolean isFieldSupported(final String fieldName) { ParameterCheck.mandatoryString("fieldName", fieldName); - final boolean supported = CONFIG_NAMES.contains(fieldName); - return supported; + return CONFIG_NAMES.contains(fieldName); } /** @@ -170,22 +167,21 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement { throw new IllegalArgumentException(fieldName + " is not a supported field"); } - final Class valueType = VALUE_TYPE_BY_CONFIG_NAME.get(fieldName); - return valueType; + return VALUE_TYPE_BY_CONFIG_NAME.get(fieldName); } /** - * Retrieves the configured value for a specific field. Default values inherent in the {@link AdapterConfig Keycloak classes} are not + * Retrieves the configured value for a specific field. Default values inherent in the {@link ExtendedAdapterConfig Keycloak classes} + * are not * considered by this operation. * * @param fieldName - * the name of the field for which to retrieve the value + * the name of the field for which to retrieve the value * @return the currently configured value for the field, or {@code null} if no value has been configured */ public Object getFieldValue(final String fieldName) { - final Object value = this.configValueByField.get(fieldName); - return value; + return this.configValueByField.get(fieldName); } /** @@ -252,8 +248,7 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement { throw new IllegalArgumentException(fieldName + " is not a supported field"); } - final boolean unset = this.markedAsUnset.contains(fieldName); - return unset; + return this.markedAsUnset.contains(fieldName); } /** @@ -261,9 +256,9 @@ public class KeycloakAdapterConfigElement extends BaseCustomConfigElement * * @return the adapter configuration instance */ - public AdapterConfig buildAdapterConfiguration() + public ExtendedAdapterConfig buildAdapterConfiguration() { - final AdapterConfig adapterConfig = new AdapterConfig(); + final ExtendedAdapterConfig adapterConfig = new ExtendedAdapterConfig(); try { diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java index 22c941c..3057770 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java @@ -37,6 +37,8 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement protected final ConfigValueHolder forceKeycloakSso = new ConfigValueHolder<>(); + protected final ConfigValueHolder rememberKeycloakSso = new ConfigValueHolder<>(); + protected final ConfigValueHolder bodyBufferLimit = new ConfigValueHolder<>(); protected final ConfigValueHolder sessionMapperLimit = new ConfigValueHolder<>(); @@ -57,7 +59,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param enhanceLoginForm - * the enhanceLoginForm to set + * the enhanceLoginForm to set */ public void setEnhanceLoginForm(final Boolean enhanceLoginForm) { @@ -74,7 +76,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param enableSsoFilter - * the enableSsoFilter to set + * the enableSsoFilter to set */ public void setEnableSsoFilter(final Boolean enableSsoFilter) { @@ -91,7 +93,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param forceKeycloakSso - * the forceKeycloakSso to set + * the forceKeycloakSso to set */ public void setForceKeycloakSso(final Boolean forceKeycloakSso) { @@ -106,9 +108,26 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement return this.forceKeycloakSso.getValue(); } + /** + * @param rememberKeycloakSso + * the rememberKeycloakSso to set + */ + public void setRememberKeycloakSso(final Boolean rememberKeycloakSso) + { + this.rememberKeycloakSso.setValue(rememberKeycloakSso); + } + + /** + * @return the rememberKeycloakSso + */ + public Boolean getRememberKeycloakSso() + { + return this.rememberKeycloakSso.getValue(); + } + /** * @param bodyBufferLimit - * the bodyBufferLimit to set + * the bodyBufferLimit to set */ public void setBodyBufferLimit(final Integer bodyBufferLimit) { @@ -125,7 +144,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param sessionMapperLimit - * the sessionMapperLimit to set + * the sessionMapperLimit to set */ public void setSessionMapperLimit(final Integer sessionMapperLimit) { @@ -142,7 +161,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param ignoreDefaultFilter - * the ignoreDefaultFilter to set + * the ignoreDefaultFilter to set */ public void setIgnoreDefaultFilter(final Boolean ignoreDefaultFilter) { @@ -159,7 +178,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param performTokenExchange - * the performTokenExchange to set + * the performTokenExchange to set */ public void setPerformTokenExchange(final Boolean performTokenExchange) { @@ -176,7 +195,7 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement /** * @param alfrescoResourceName - * the alfrescoResourceName to set + * the alfrescoResourceName to set */ public void setAlfrescoResourceName(final String alfrescoResourceName) { @@ -236,6 +255,17 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement : this.getForceKeycloakSso()); } + if (otherConfigElement.rememberKeycloakSso.isUnset()) + { + combined.rememberKeycloakSso.unset(); + } + else + { + combined.setRememberKeycloakSso( + otherConfigElement.getRememberKeycloakSso() != null ? otherConfigElement.getRememberKeycloakSso() + : this.getRememberKeycloakSso()); + } + if (otherConfigElement.bodyBufferLimit.isUnset()) { combined.bodyBufferLimit.unset(); @@ -309,6 +339,9 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement builder.append("forceKeycloakSso="); builder.append(this.forceKeycloakSso); builder.append(", "); + builder.append("rememberKeycloakSso="); + builder.append(this.rememberKeycloakSso); + builder.append(", "); builder.append("bodyBufferLimit="); builder.append(this.bodyBufferLimit); builder.append(", "); diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java index e05d445..0ad06a4 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java @@ -58,6 +58,13 @@ public class KeycloakAuthenticationConfigElementReader implements ConfigElementR configElement.setForceKeycloakSso(value.isEmpty() ? null : Boolean.valueOf(value)); } + final Element rememberKeycloakSso = element.element("remember-keycloak-sso"); + if (rememberKeycloakSso != null) + { + final String value = rememberKeycloakSso.getTextTrim(); + configElement.setRememberKeycloakSso(value.isEmpty() ? null : Boolean.valueOf(value)); + } + final Element bodyBufferLimit = element.element("body-buffer-limit"); if (bodyBufferLimit != null) { diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java index fdb22f4..877d12e 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -48,12 +49,17 @@ import org.alfresco.util.EqualsHelper; import org.alfresco.util.PropertyCheck; import org.alfresco.web.site.servlet.SSOAuthenticationFilter; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; 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.conn.params.ConnRoutePNames; +import org.apache.http.conn.params.ConnRouteParams; +import org.apache.http.conn.routing.HttpRoute; import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; import org.keycloak.KeycloakSecurityContext; import org.keycloak.OAuth2Constants; @@ -84,7 +90,6 @@ import org.keycloak.common.util.Time; import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; -import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.util.JsonSerialization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,6 +120,7 @@ import org.springframework.extensions.webscripts.servlet.DependencyInjectedFilte import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import de.acosix.alfresco.keycloak.share.config.ExtendedAdapterConfig; import de.acosix.alfresco.keycloak.share.config.KeycloakAdapterConfigElement; import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement; import de.acosix.alfresco.keycloak.share.config.KeycloakConfigConstants; @@ -127,6 +133,7 @@ import de.acosix.alfresco.keycloak.share.util.RefreshableAccessTokenHolder; * * @author Axel Faust */ +@SuppressWarnings("deprecation") public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, InitializingBean, ApplicationContextAware { @@ -227,6 +234,8 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I protected boolean forceSso = false; + protected boolean rememberSso = false; + protected boolean ignoreDefaultFilter = false; protected KeycloakDeployment keycloakDeployment; @@ -316,6 +325,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I this.filterEnabled = Boolean.TRUE.equals(keycloakAuthConfig.getEnableSsoFilter()); this.loginFormEnhancementEnabled = Boolean.TRUE.equals(keycloakAuthConfig.getEnhanceLoginForm()); this.forceSso = Boolean.TRUE.equals(keycloakAuthConfig.getForceKeycloakSso()); + this.rememberSso = Boolean.TRUE.equals(keycloakAuthConfig.getRememberKeycloakSso()); this.ignoreDefaultFilter = Boolean.TRUE.equals(keycloakAuthConfig.getIgnoreDefaultFilter()); } else @@ -503,8 +513,15 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I protected void initFromAdapterConfig(final KeycloakAdapterConfigElement keycloakAdapterConfig) { - final AdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration(); + 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); + } this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment); } @@ -531,12 +548,15 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I tokenStore.logout(); - final Cookie keycloakCookie = new Cookie(KEYCLOAK_AUTHENTICATED_COOKIE, "false"); - keycloakCookie.setPath(context.getContextPath()); - keycloakCookie.setMaxAge(0); - keycloakCookie.setHttpOnly(true); - keycloakCookie.setSecure(req.isSecure()); - res.addCookie(keycloakCookie); + if (this.rememberSso) + { + final Cookie keycloakCookie = new Cookie(KEYCLOAK_AUTHENTICATED_COOKIE, "false"); + keycloakCookie.setPath(context.getContextPath()); + keycloakCookie.setMaxAge(0); + keycloakCookie.setHttpOnly(true); + keycloakCookie.setSecure(req.isSecure()); + res.addCookie(keycloakCookie); + } chain.doFilter(req, res); } @@ -1010,7 +1030,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I { final boolean hasKeycloakCookie = this.hasKeycloakCookie(req); - if (!hasKeycloakCookie) + if (!hasKeycloakCookie && this.rememberSso) { final Cookie keycloakCookie = new Cookie(KEYCLOAK_AUTHENTICATED_COOKIE, "true"); keycloakCookie.setPath(req.getServletContext().getContextPath()); @@ -1025,7 +1045,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I { final Cookie[] cookies = req.getCookies(); boolean hasKeycloakCookie = false; - if (cookies != null) + if (cookies != null && this.rememberSso) { for (final Cookie cookie : cookies) { @@ -1827,4 +1847,24 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I } return sslPort; } + + protected void configureForcedRouteIfNecessary(final HttpClient client, final String forcedRoute) + { + final HttpHost forcedRouteHost = HttpHost.create(forcedRoute); + final HttpParams params = client.getParams(); + final InetAddress local = ConnRouteParams.getLocalAddress(params); + final HttpHost defaultProxy = ConnRouteParams.getDefaultProxy(params); + final boolean secure = forcedRouteHost.getSchemeName().equalsIgnoreCase("https"); + + HttpRoute route; + if (defaultProxy == null) + { + route = new HttpRoute(forcedRouteHost, local, secure); + } + else + { + route = new HttpRoute(forcedRouteHost, local, defaultProxy, secure); + } + params.setParameter(ConnRoutePNames.FORCED_ROUTE, route); + } } diff --git a/share/src/test/docker/alfresco/extension/alfresco-global.addition.properties b/share/src/test/docker/alfresco/extension/alfresco-global.addition.properties index a8e6a9b..4313109 100644 --- a/share/src/test/docker/alfresco/extension/alfresco-global.addition.properties +++ b/share/src/test/docker/alfresco/extension/alfresco-global.addition.properties @@ -25,7 +25,7 @@ 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.adapter.proxy-url=http://keycloak:8080 +keycloak.adapter.forced-route-url=http://keycloak:8080 keycloak.roles.requiredClientScopes=alfresco-role-service diff --git a/share/src/test/docker/alfresco/web-extension/share-config-custom.xml b/share/src/test/docker/alfresco/web-extension/share-config-custom.xml index bfbe289..35d902e 100644 --- a/share/src/test/docker/alfresco/web-extension/share-config-custom.xml +++ b/share/src/test/docker/alfresco/web-extension/share-config-custom.xml @@ -75,7 +75,7 @@ true - http://keycloak:8080 + http://keycloak:8080 http://localhost:${docker.tests.keycloakPort}/auth test alfresco-share diff --git a/share/src/test/resources/default-config.xml b/share/src/test/resources/default-config.xml index 096c14b..e4fd6ce 100644 --- a/share/src/test/resources/default-config.xml +++ b/share/src/test/resources/default-config.xml @@ -40,7 +40,7 @@ alfresco - http://keycloak:8080 + http://keycloak:8080 http://localhost:8180/auth alfresco