diff --git a/config/alfresco/subsystems/Authentication/external/external-filter-context.xml b/config/alfresco/subsystems/Authentication/external/external-filter-context.xml new file mode 100644 index 0000000000..2a89b9e61a --- /dev/null +++ b/config/alfresco/subsystems/Authentication/external/external-filter-context.xml @@ -0,0 +1,21 @@ + + + + + + + + ${external.authentication.proxyUserName} + + + ${external.authentication.proxyHeader} + + + ${external.authentication.enabled} + + + ${external.authentication.userIdPattern} + + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/Authentication/external/external-filter.properties b/config/alfresco/subsystems/Authentication/external/external-filter.properties new file mode 100644 index 0000000000..476a605f81 --- /dev/null +++ b/config/alfresco/subsystems/Authentication/external/external-filter.properties @@ -0,0 +1,4 @@ +external.authentication.proxyUserName=alfresco-system +external.authentication.proxyHeader=X-Alfresco-Remote-User +external.authentication.enabled=true +external.authentication.userIdPattern= diff --git a/config/alfresco/web-client-application-context.xml b/config/alfresco/web-client-application-context.xml index e66fbcb8b2..0bea82dff8 100644 --- a/config/alfresco/web-client-application-context.xml +++ b/config/alfresco/web-client-application-context.xml @@ -170,6 +170,21 @@ + + + + + + + org.alfresco.web.app.servlet.RemoteUserMapper + org.alfresco.repo.management.subsystems.ActivateableBean + + + + remoteUserMapper + + + diff --git a/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java index 02f19889ec..fc44391a46 100644 --- a/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java +++ b/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java @@ -48,6 +48,8 @@ import org.apache.commons.logging.LogFactory; public class AdminAuthenticationFilter implements Filter { private static final Log logger = LogFactory.getLog(AdminAuthenticationFilter.class); + + private FilterConfig config; /** * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) @@ -66,7 +68,7 @@ public class AdminAuthenticationFilter implements Filter logger.debug("Authorising request for protected resource: " + httpRequest.getRequestURI()); // there should be a user at this point so retrieve it - User user = AuthenticationHelper.getUser(httpRequest, httpResponse); + User user = AuthenticationHelper.getUser(this.config.getServletContext(), httpRequest, httpResponse); // if the user is present check to see whether it is an admin user boolean isAdmin = (user != null && user.isAdmin()); @@ -105,7 +107,7 @@ public class AdminAuthenticationFilter implements Filter */ public void init(FilterConfig config) throws ServletException { - // nothing to do + this.config = config; } /** diff --git a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java index 3457f69e4f..063724e47b 100644 --- a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java +++ b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java @@ -39,6 +39,8 @@ import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; +import org.alfresco.repo.management.subsystems.ActivateableBean; +import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.ServiceRegistry; @@ -46,7 +48,6 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.web.app.Application; import org.alfresco.web.bean.LoginBean; @@ -82,6 +83,8 @@ public final class AuthenticationHelper /** public service bean IDs **/ private static final String AUTHENTICATION_SERVICE = "AuthenticationService"; + private static final String AUTHENTICATION_COMPONENT = "AuthenticationComponent"; + private static final String REMOTE_USER_MAPPER = "remoteUserMapper"; private static final String UNPROTECTED_AUTH_SERVICE = "authenticationService"; private static final String PERSON_SERVICE = "personService"; @@ -172,7 +175,7 @@ public final class AuthenticationHelper HttpSession session = req.getSession(); // retrieve the User object - User user = getUser(req, res); + User user = getUser(sc, req, res); // get the login bean if we're not in the portal LoginBean loginBean = null; @@ -462,15 +465,25 @@ public final class AuthenticationHelper * @param httpResponse The HTTP response * @return The User object representing the current user or null if it could not be found */ - public static User getUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse) + public static User getUser(ServletContext sc, HttpServletRequest httpRequest, HttpServletResponse httpResponse) { + String userId = null; + + // If the remote user mapper is configured, we may be able to map in an externally authenticated user + WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(sc); + RemoteUserMapper remoteUserMapper = (RemoteUserMapper) wc.getBean(REMOTE_USER_MAPPER); + if (!(remoteUserMapper instanceof ActivateableBean) || ((ActivateableBean) remoteUserMapper).isActive()) + { + userId = remoteUserMapper.getRemoteUser(httpRequest); + } + HttpSession session = httpRequest.getSession(); User user = null; - + // examine the appropriate session to try and find the User object if (Application.inPortalServer() == false) { - user = (User)session.getAttribute(AUTHENTICATION_USER); + user = (User) session.getAttribute(AUTHENTICATION_USER); } else { @@ -483,12 +496,33 @@ public final class AuthenticationHelper String name = (String)enumNames.nextElement(); if (name.endsWith(AUTHENTICATION_USER)) { - user = (User)session.getAttribute(name); + user = (User) session.getAttribute(name); break; } } } - + + // If the remote user mapper is configured, we may be able to map in an externally authenticated user + if (userId != null) + { + // We have a previously-cached user with the wrong identity - replace them + if (user != null && !user.getUserName().equals(userId)) + { + user = null; + } + + if (user == null) + { + // If we have been authenticated by other means, just propagate through the user identity + if (userId != null) + { + AuthenticationComponent authenticationComponent = (AuthenticationComponent) wc + .getBean(AUTHENTICATION_COMPONENT); + authenticationComponent.setCurrentUser(userId); + user = setUser(sc, httpRequest, userId, true); + } + } + } return user; } diff --git a/source/java/org/alfresco/web/app/servlet/DefaultRemoteUserMapper.java b/source/java/org/alfresco/web/app/servlet/DefaultRemoteUserMapper.java new file mode 100644 index 0000000000..274579c2c3 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/DefaultRemoteUserMapper.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.app.servlet; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.alfresco.repo.management.subsystems.ActivateableBean; + +/** + * A default {@link RemoteUserMapper} implementation. Extracts the user ID using + * {@link HttpServletRequest#getRemoteUser()}. If it matches the configured proxy user name or the configured proxy user + * name is null, it extracts the user ID from the configured proxy request header. Otherwise returns the remote user + * name. An optional regular expression defining how to convert the header to a user ID can be configured using + * {@link #setUserIdPattern(String)}. This allows for the secure proxying of requests from a Surf client such as + * Alfresco Share using SSL client certificates. + * + * @author dward + */ +public class DefaultRemoteUserMapper implements RemoteUserMapper, ActivateableBean +{ + /** The remote identity used to 'proxy' requests securely in the name of another user. */ + private String proxyUserName = "alfresco-system"; + + /** The header containing the ID of a proxied user. */ + private String proxyHeader = "X-Alfresco-Remote-User"; + + /** Is this mapper enabled? */ + private boolean isEnabled; + + /** Regular expression for extracting a user ID from the header. */ + private Pattern userIdPattern; + + /** + * Sets the name of the remote user used to 'proxy' requests securely in the name of another user. Typically this + * remote identity will be protected by an SSL client certificate. + * + * @param proxyUserName + * the proxy user name. If null or empty, then the header will be checked regardless of + * remote user identity. + */ + public void setProxyUserName(String proxyUserName) + { + this.proxyUserName = proxyUserName == null || proxyUserName.length() == 0 ? null : proxyUserName; + } + + /** + * Sets the name of the header containing the ID of a proxied user. + * + * @param proxyHeader + * the proxy header name + */ + public void setProxyHeader(String proxyHeader) + { + this.proxyHeader = proxyHeader; + } + + /** + * Controls whether the mapper is enabled. When disabled {@link #getRemoteUser(HttpServletRequest)} will always + * return null + * + * @param isEnabled + * Is this mapper enabled? + */ + public void setActive(boolean isEnabled) + { + this.isEnabled = isEnabled; + } + + /** + * Sets a regular expression for extracting a user ID from the header. If this is not set, then the entire contents + * of the header will be used as the user ID. + * + * @param userIdPattern + * the regular expression + */ + public void setUserIdPattern(String userIdPattern) + { + this.userIdPattern = userIdPattern == null || userIdPattern.length() == 0 ? null : Pattern + .compile(userIdPattern); + } + + /* + * (non-Javadoc) + * @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(javax.servlet.http.HttpServletRequest) + */ + public String getRemoteUser(HttpServletRequest request) + { + if (!this.isEnabled) + { + return null; + } + if (this.proxyUserName == null) + { + return extractUserFromProxyHeader(request); + } + else + { + String userId = request.getRemoteUser(); + if (userId == null) + { + return null; + } + if (userId.equals(this.proxyUserName)) + { + userId = extractUserFromProxyHeader(request); + } + return userId; + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() + */ + public boolean isActive() + { + return this.isEnabled; + } + + /** + * Extracts a user ID from the proxy header. If a user ID pattern has been configured returns the contents of the + * first matching regular expression group or null. Otherwise returns the trimmed header contents or + * null. + * + * @param request + * the request + * @return the user ID + */ + private String extractUserFromProxyHeader(HttpServletRequest request) + { + String userId = request.getHeader(this.proxyHeader); + if (userId == null) + { + return null; + } + if (this.userIdPattern == null) + { + userId = userId.trim(); + } + else + { + Matcher matcher = this.userIdPattern.matcher(userId); + if (matcher.matches()) + { + userId = matcher.group().trim(); + } + } + return userId.length() == 0 ? null : userId; + } + +} diff --git a/source/java/org/alfresco/web/app/servlet/RemoteUserMapper.java b/source/java/org/alfresco/web/app/servlet/RemoteUserMapper.java new file mode 100644 index 0000000000..1c20ed5294 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/RemoteUserMapper.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.app.servlet; + +import javax.servlet.http.HttpServletRequest; + +/** + * An interface for objects capable of extracting an externally authenticated user ID from an HTTP request. + * + * @author dward + */ +public interface RemoteUserMapper +{ + /** + * Gets an externally authenticated user ID from an HTTP request. + * + * @param request + * the request + * @return the user ID or null if the user is unauthenticated + */ + public String getRemoteUser(HttpServletRequest request); +}