From e9a5d1c670766dd1cc5846d6a4e91ceb099a3d00 Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Sat, 31 Jan 2015 11:06:54 +0000 Subject: [PATCH] Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud) 90918: MNT-12764 - The X-Alfresco-Remote-User (SsoUserHeader) SSO code path executes x2 requests and is stateful when it does not need to be Merged PROPERTY_GROUP_PROTOTYPING (5.0/Cloud) to HEAD-BUG-FIX (5.0/Cloud) 90558: Refactoring of SSO paths - Added webscripts.authenticator.remoteuser to allow stateless auth of X-Alfresco-Remote-User via the /service servlet mapping. Falls back to basic auth as usual. - Refactored PublicApiAuthenticatorFactory to extend new RemoteUserAuthenticatorFactory to allow external auth to work correctly with Public API endpoint. - BasicHttpAuthenticatorFactory tweaked to allow sub-classes to use more of its beans and methods. - Related code clean up git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@94741 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/public-rest-context.xml | 1 + .../web-scripts-application-context.xml | 12 +- .../BasicHttpAuthenticatorFactory.java | 14 +- .../RemoteUserAuthenticatorFactory.java | 129 ++++++++++++++++ .../api/PublicApiAuthenticatorFactory.java | 141 +++++++----------- 5 files changed, 197 insertions(+), 100 deletions(-) create mode 100644 source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 7beda3861d..e4602e6d8c 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -75,6 +75,7 @@ + diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 9b39c36c58..2a0d2e296f 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -183,13 +183,21 @@ - + - + + + + + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/servlet/BasicHttpAuthenticatorFactory.java b/source/java/org/alfresco/repo/web/scripts/servlet/BasicHttpAuthenticatorFactory.java index 5ff955d679..934382739f 100644 --- a/source/java/org/alfresco/repo/web/scripts/servlet/BasicHttpAuthenticatorFactory.java +++ b/source/java/org/alfresco/repo/web/scripts/servlet/BasicHttpAuthenticatorFactory.java @@ -50,8 +50,8 @@ public class BasicHttpAuthenticatorFactory implements ServletAuthenticatorFactor private static Log logger = LogFactory.getLog(BasicHttpAuthenticator.class); // Component dependencies - private AuthenticationService authenticationService; - private AuthenticationListener listener; + protected AuthenticationService authenticationService; + protected AuthenticationListener listener; /** @@ -103,12 +103,12 @@ public class BasicHttpAuthenticatorFactory implements ServletAuthenticatorFactor public class BasicHttpAuthenticator implements Authenticator { // dependencies - private WebScriptServletRequest servletReq; - private WebScriptServletResponse servletRes; + protected WebScriptServletRequest servletReq; + protected WebScriptServletResponse servletRes; - private String authorization; - private String ticket; - private AuthenticationListener listener; + protected String authorization; + protected String ticket; + protected AuthenticationListener listener; /** * Construct diff --git a/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java b/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java new file mode 100644 index 0000000000..a3987e7033 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.web.scripts.servlet; + +import org.alfresco.repo.management.subsystems.ActivateableBean; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.external.RemoteUserMapper; +import org.alfresco.repo.web.auth.AuthenticationListener; +import org.alfresco.repo.web.auth.TicketCredentials; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.webscripts.Authenticator; +import org.springframework.extensions.webscripts.Description.RequiredAuthentication; +import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest; +import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse; + +/** + * Authenticator to provide Remote User based Header authentication dropping back to Basic Auth otherwise. + * Statelessly authenticating via a secure header now does not require a Session so can be used with + * request-level load balancers which was not previously possible. + *

+ * @see web-scripts-application-context.xml and web.xml - bean id 'webscripts.authenticator.remoteuser' + *

+ * This authenticator can be bound to /service and does not require /wcservice (Session) mapping. + * + * @since 5.1 + * @author Kevin Roast + */ +public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactory +{ + private static Log logger = LogFactory.getLog(RemoteUserAuthenticatorFactory.class); + + protected RemoteUserMapper remoteUserMapper; + protected AuthenticationComponent authenticationComponent; + + public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper) + { + this.remoteUserMapper = remoteUserMapper; + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + @Override + public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res) + { + return new RemoteUserAuthenticator(req, res, this.listener); + } + + /** + * Remote User authenticator - adds header authentication onto Basic Auth. Stateless does not require Session. + * + * @author Kevin Roast + */ + public class RemoteUserAuthenticator extends BasicHttpAuthenticator + { + public RemoteUserAuthenticator(WebScriptServletRequest req, WebScriptServletResponse res, AuthenticationListener listener) + { + super(req, res, listener); + } + + @Override + public boolean authenticate(RequiredAuthentication required, boolean isGuest) + { + // retrieve the remote user if configured and available - authenticate that user directly + final String userId = getRemoteUser(); + if (userId != null) + { + authenticationComponent.setCurrentUser(userId); + listener.userAuthenticated(new TicketCredentials(authenticationService.getCurrentTicket())); + return true; + } + else + { + return super.authenticate(required, isGuest); + } + } + + /** + * Retrieve the remote user from servlet request header when using a secure connection. + * The RemoteUserMapper bean must be active and configured. + * + * @return remote user ID or null if not active or found + */ + protected String getRemoteUser() + { + String userId = null; + + // If the remote user mapper is configured, we may be able to map in an externally authenticated user + if (remoteUserMapper != null && + (!(remoteUserMapper instanceof ActivateableBean) || ((ActivateableBean) remoteUserMapper).isActive())) + { + userId = remoteUserMapper.getRemoteUser(this.servletReq.getHttpServletRequest()); + } + + if (logger.isDebugEnabled()) + { + if (userId == null) + { + logger.debug("No external user ID in request."); + } + else + { + logger.debug("Extracted external user ID from request: " + userId); + } + } + + return userId; + } + } +} diff --git a/source/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java b/source/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java index 7f60f4ecc7..994ae4ebbf 100644 --- a/source/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java +++ b/source/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ package org.alfresco.rest.api; import java.util.Collections; @@ -7,17 +25,15 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.external.RemoteUserMapper; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.web.auth.AuthenticationListener; import org.alfresco.repo.web.auth.TenantAuthentication; import org.alfresco.repo.web.auth.WebCredentials; import org.alfresco.repo.web.scripts.TenantWebScriptServletRequest; -import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory; +import org.alfresco.repo.web.scripts.servlet.RemoteUserAuthenticatorFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.webscripts.Authenticator; @@ -33,21 +49,16 @@ import org.springframework.extensions.webscripts.servlet.WebScriptServletRespons * * @author sglover */ -public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory +public class PublicApiAuthenticatorFactory extends RemoteUserAuthenticatorFactory { private static Log logger = LogFactory.getLog(PublicApiAuthenticatorFactory.class); public static final String DEFAULT_AUTHENTICATOR_KEY_HEADER = "X-Alfresco-Authenticator-Key"; - - public static final String DEFAULT_REMOTE_USER_HEADER = "X-Alfresco-Remote-User"; - - private String remoteUserHeader = DEFAULT_REMOTE_USER_HEADER; + private String authenticatorKeyHeader = DEFAULT_AUTHENTICATOR_KEY_HEADER; - private RemoteUserMapper remoteUserMapper; private RetryingTransactionHelper retryingTransactionHelper; private TenantAuthentication tenantAuthentication; private Set validAuthenticatorKeys = Collections.emptySet(); - private AuthenticationListener authenticationListener; private Set outboundHeaderNames; public void setAuthenticatorKeyHeader(String authenticatorKeyHeader) @@ -55,11 +66,6 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory this.authenticatorKeyHeader = authenticatorKeyHeader; } - public void setAuthenticationListener(AuthenticationListener authenticationListener) - { - this.authenticationListener = authenticationListener; - } - /** * Set the headers passed to the gateway for authentication. * @@ -80,11 +86,6 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory this.outboundHeaderNames = outboundHeaders; } - public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper) - { - this.remoteUserMapper = remoteUserMapper; - } - public void setTenantAuthentication(TenantAuthentication service) { this.tenantAuthentication = service; @@ -135,11 +136,10 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory /** * Public api authentication with additional tenant applicability check */ - public class PublicApiAuthenticator extends BasicHttpAuthenticator + public class PublicApiAuthenticator extends RemoteUserAuthenticator { // dependencies private TenantWebScriptServletRequest servletReq; - private WebScriptServletResponse servletRes; // Proxy listener used to receive initial authentication events from the base BasicHttpAuthenticator private ProxyListener proxyListener; @@ -159,41 +159,9 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory throw new WebScriptException("Request is not a tenant aware request"); } servletReq = (TenantWebScriptServletRequest)req; - servletRes = res; this.proxyListener = proxyListener; } - private String getRemoteUser() - { - String userId = null; - - // If the remote user mapper is configured, we may be able to map in an externally authenticated user - if (remoteUserMapper != null - && (!(remoteUserMapper instanceof ActivateableBean) || ((ActivateableBean) remoteUserMapper).isActive())) - { - userId = remoteUserMapper.getRemoteUser(this.servletReq.getHttpServletRequest()); - } - else - { - // fall back to extracting the header - userId = servletReq.getHeader(remoteUserHeader); - } - - if (logger.isDebugEnabled()) - { - if (userId == null) - { - logger.debug("No external user ID in request."); - } - else - { - logger.debug("Extracted external user ID from request: " + userId); - } - } - - return userId; - } - /* (non-Javadoc) * @see org.alfresco.web.scripts.Authenticator#authenticate(org.alfresco.web.scripts.Description.RequiredAuthentication, boolean) */ @@ -204,60 +172,53 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory { String authenticatorKey = servletReq.getHeader(authenticatorKeyHeader); String remoteUser = getRemoteUser(); - if (authenticatorKey != null && - remoteUser != null) + if (authenticatorKey != null && remoteUser != null) { // Trusted auth. Validate key and setup authentication context. authorized = authenticateViaGateway(required, isGuest, authenticatorKey, remoteUser); } else { - // Fallback to standard BasicHttpAutheticator + // Fallback to parent authenticator try { authorized = super.authenticate(required, isGuest); } catch (AuthenticationException ae) { - // eg. guest + // e.g. guest if (logger.isDebugEnabled()) - { logger.debug("TenantBasicHttpAuthenticator: required="+required+", isGuest="+isGuest+" - "+ae.getMessage()); - } } } - if (!authorized) + if (authorized) { - // not authorized, no point continuing - return authorized; - } - - // check tenant validity - final String tenant = servletReq.getTenant(); - final String email = AuthenticationUtil.getFullyAuthenticatedUser(); - try - { - authorized = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + // check tenant validity + final String tenant = servletReq.getTenant(); + final String email = AuthenticationUtil.getFullyAuthenticatedUser(); + try { - public Boolean execute() throws Exception + authorized = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() { - return tenantAuthentication.authenticateTenant(email, tenant); + public Boolean execute() throws Exception + { + return tenantAuthentication.authenticateTenant(email, tenant); + } + }, true, false); + } + finally + { + if (!authorized) + { + listener.authenticationFailed(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials())); + AuthenticationUtil.clearCurrentSecurityContext(); + } + else + { + listener.userAuthenticated(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials())); } - }, true, false); - } - finally - { - if (!authorized) - { - authenticationListener.authenticationFailed(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials())); - AuthenticationUtil.clearCurrentSecurityContext(); - } - else - { - authenticationListener.userAuthenticated(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials())); } } - return authorized; } finally @@ -286,7 +247,6 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory return false; } } - } private class ProxyListener implements AuthenticationListener @@ -302,19 +262,18 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory @Override public void authenticationFailed(WebCredentials credentials) { - authenticationListener.authenticationFailed(credentials); + listener.authenticationFailed(credentials); } @Override public void authenticationFailed(WebCredentials credentials, Exception ex) { - authenticationListener.authenticationFailed(credentials, ex); + listener.authenticationFailed(credentials, ex); } - public WebCredentials getOrignalCredentials() { - return originalCredentials; + return this.originalCredentials; } } }