From df3a2630abaa0be03b11fe7d2b25d68a3a78233a Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Sat, 31 Jan 2015 11:07:31 +0000 Subject: [PATCH] Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud) 90921: MNT-12765 - No endpoints can be configured in Share that use external-auth and a different URL - as they will be redirected down the URL for 'alfresco' endpoint. Merged PROPERTY_GROUP_PROTOTYPING (5.0/Cloud) to HEAD-BUG-FIX (5.0/Cloud) 90742: Refactoring of SSO paths - Added Session User authentication support to RemoteUserAuthenticatorFactory - so can use cookie based auth for example with Public API route. - Tidy up of common duplicated code constants e.g. _alfAuthTicket - Added Global Authentication Filter around the /api/* endpoint to allow SSO active over Public API git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@94744 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../RemoteUserAuthenticatorFactory.java | 46 ++- .../webdav/auth/AuthenticationDriver.java | 2 + .../webdav/auth/BaseAuthenticationFilter.java | 2 +- .../auth/BaseSSOAuthenticationFilter.java | 267 +++++++++--------- .../SSOFallbackBasicAuthenticationDriver.java | 4 +- 5 files changed, 180 insertions(+), 141 deletions(-) diff --git a/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java b/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java index a3987e7033..c7bd0f0b50 100644 --- a/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java +++ b/source/java/org/alfresco/repo/web/scripts/servlet/RemoteUserAuthenticatorFactory.java @@ -18,11 +18,17 @@ */ package org.alfresco.repo.web.scripts.servlet; +import javax.servlet.http.HttpSession; + +import org.alfresco.repo.SessionUser; 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.authentication.external.RemoteUserMapper; import org.alfresco.repo.web.auth.AuthenticationListener; import org.alfresco.repo.web.auth.TicketCredentials; +import org.alfresco.repo.web.auth.WebCredentials; +import org.alfresco.repo.webdav.auth.AuthenticationDriver; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.webscripts.Authenticator; @@ -80,18 +86,54 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor @Override public boolean authenticate(RequiredAuthentication required, boolean isGuest) { + boolean authenticated = false; + // 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; + authenticated = true; } else { - return super.authenticate(required, isGuest); + // is there a Session which might contain a valid user ticket? + HttpSession session = servletReq.getHttpServletRequest().getSession(false); + if (session != null) + { + try + { + SessionUser user = (SessionUser)session.getAttribute(AuthenticationDriver.AUTHENTICATION_USER); + if (user != null) + { + // Validate the ticket for the current SessionUser + authenticationService.validate(user.getTicket()); + if (logger.isDebugEnabled()) + logger.debug("Ticket is valid; retaining cached user in session."); + listener.userAuthenticated(new TicketCredentials(user.getTicket())); + authenticated = true; + } + else + { + authenticated = super.authenticate(required, isGuest); + } + } + catch (AuthenticationException authErr) + { + if (logger.isDebugEnabled()) + logger.debug("An Authentication error occur, removing User session: ", authErr); + session.removeAttribute(AuthenticationDriver.AUTHENTICATION_USER); + session.invalidate(); + listener.authenticationFailed(new WebCredentials() {}); + } + } + else + { + authenticated = super.authenticate(required, isGuest); + } } + return authenticated; } /** diff --git a/source/java/org/alfresco/repo/webdav/auth/AuthenticationDriver.java b/source/java/org/alfresco/repo/webdav/auth/AuthenticationDriver.java index eb22c3bdcb..044a63e1d3 100644 --- a/source/java/org/alfresco/repo/webdav/auth/AuthenticationDriver.java +++ b/source/java/org/alfresco/repo/webdav/auth/AuthenticationDriver.java @@ -33,6 +33,8 @@ import javax.servlet.http.HttpServletResponse; */ public interface AuthenticationDriver { + public static final String AUTHENTICATION_USER = "_alfAuthTicket"; + /** * Authenticate user based on information in http request such as Authorization header or cached session * information. diff --git a/source/java/org/alfresco/repo/webdav/auth/BaseAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/BaseAuthenticationFilter.java index e809bae8db..31775c8d2c 100644 --- a/source/java/org/alfresco/repo/webdav/auth/BaseAuthenticationFilter.java +++ b/source/java/org/alfresco/repo/webdav/auth/BaseAuthenticationFilter.java @@ -362,7 +362,7 @@ public abstract class BaseAuthenticationFilter }); // Store the user on the session - session.setAttribute(getUserAttributeName(), user); + session.setAttribute(getUserAttributeName(), user); setExternalAuth(session, externalAuth); return user; } diff --git a/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java index 34d160fa53..3e407c4dfc 100644 --- a/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java +++ b/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java @@ -1,25 +1,25 @@ /* - * Copyright (C) 2005-2013 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 + * 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.webdav.auth; import java.io.IOException; -import java.io.PrintWriter; +import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; @@ -85,7 +85,6 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt this.serverConfiguration = serverConfiguration; } - /** * Activates or deactivates the bean * @@ -166,8 +165,6 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt { } - - /** * Callback executed on successful ticket validation during Type3 Message processing. * @@ -211,7 +208,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt protected boolean onLoginComplete(ServletContext sc, HttpServletRequest req, HttpServletResponse res, boolean userInit) throws IOException { - return true; + return true; } /** @@ -223,7 +220,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt protected final String mapClientAddressToDomain(String clientIP) { // Check if there are any domain mappings - SecurityConfigSection securityConfigSection = getSecurityConfigSection(); + SecurityConfigSection securityConfigSection = getSecurityConfigSection(); if (securityConfigSection != null && securityConfigSection.hasDomainMappings() == false) { return null; @@ -232,7 +229,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt if (securityConfigSection != null) { // Convert the client IP address to an integer value - + int clientAddr = IPAddress.parseNumericAddress(clientIP); for (DomainMapping domainMap : securityConfigSection.getDomainMappings()) { @@ -267,75 +264,75 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ protected boolean checkForTicketParameter(ServletContext servletContext, HttpServletRequest req, HttpServletResponse resp) { - // Check if the request includes an authentication ticket + // Check if the request includes an authentication ticket - boolean ticketValid = false; - String ticket = req.getParameter(ARG_TICKET); - - if (ticket != null && ticket.length() != 0) - { - if (getLogger().isDebugEnabled()) - getLogger().debug( + boolean ticketValid = false; + String ticket = req.getParameter(ARG_TICKET); + + if (ticket != null && ticket.length() != 0) + { + if (getLogger().isDebugEnabled()) + getLogger().debug( "Logon via ticket from " + req.getRemoteHost() + " (" + req.getRemoteAddr() + ":" + req.getRemotePort() + ")" + " ticket=" + ticket); - - UserTransaction tx = null; - try - { - // Get a cached user with a valid ticket - SessionUser user = getSessionUser(servletContext, req, resp, true); - - // If this isn't the same ticket, invalidate the session - if (user != null && !ticket.equals(user.getTicket())) + + UserTransaction tx = null; + try { - if (getLogger().isDebugEnabled()) - getLogger().debug("The ticket doesn't match, invalidate the session."); - invalidateSession(req); - user = null; + // Get a cached user with a valid ticket + SessionUser user = getSessionUser(servletContext, req, resp, true); + + // If this isn't the same ticket, invalidate the session + if (user != null && !ticket.equals(user.getTicket())) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("The ticket doesn't match, invalidate the session."); + invalidateSession(req); + user = null; + } + + // If we don't yet have a valid cached user, validate the ticket and create one + if (user == null) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("There is no valid cached user, validate the ticket and create one."); + authenticationService.validate(ticket); + user = createUserEnvironment(req.getSession(), authenticationService.getCurrentUserName(), + authenticationService.getCurrentTicket(), true); + } + + // Indicate the ticket parameter was specified, and valid + + ticketValid = true; } - - // If we don't yet have a valid cached user, validate the ticket and create one - if (user == null) + catch (AuthenticationException authErr) { - if (getLogger().isDebugEnabled()) - getLogger().debug("There is no valid cached user, validate the ticket and create one."); - authenticationService.validate(ticket); - user = createUserEnvironment(req.getSession(), authenticationService.getCurrentUserName(), - authenticationService.getCurrentTicket(), true); + if (getLogger().isDebugEnabled()) + getLogger().debug("Failed to authenticate user ticket: " + authErr.getMessage(), authErr); } - - // Indicate the ticket parameter was specified, and valid - - ticketValid = true; - } - catch (AuthenticationException authErr) - { - if (getLogger().isDebugEnabled()) - getLogger().debug("Failed to authenticate user ticket: " + authErr.getMessage(), authErr); - } - catch (Throwable e) - { - if (getLogger().isDebugEnabled()) + catch (Throwable e) + { + if (getLogger().isDebugEnabled()) getLogger().debug("Error during ticket validation and user creation: " + e.getMessage(), e); - } - finally - { - try - { - if (tx != null) - { - tx.rollback(); - } - } - catch (Exception tex) - { - } - } - } - - // Return the ticket parameter status - - return ticketValid; + } + finally + { + try + { + if (tx != null) + { + tx.rollback(); + } + } + catch (Exception tex) + { + } + } + } + + // Return the ticket parameter status + + return ticketValid; } /** @@ -346,12 +343,12 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt * @exception IOException */ protected void redirectToLoginPage(HttpServletRequest req, HttpServletResponse res) - throws IOException + throws IOException { if (getLogger().isDebugEnabled()) getLogger().debug("redirectToLoginPage..."); - if (hasLoginPage()) - res.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); + if (hasLoginPage()) + res.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); } /** @@ -361,7 +358,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ protected final boolean hasLoginPage() { - return m_loginPage != null ? true : false; + return m_loginPage != null ? true : false; } /** @@ -371,7 +368,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ protected final String getLoginPage() { - return m_loginPage; + return m_loginPage; } /** @@ -381,7 +378,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ protected final void setLoginPage( String loginPage) { - m_loginPage = loginPage; + m_loginPage = loginPage; } /** @@ -391,7 +388,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ protected final boolean allowsTicketLogons() { - return m_ticketLogons; + return m_ticketLogons; } /** @@ -401,7 +398,7 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ public final void setTicketLogons( boolean ticketsAllowed) { - m_ticketLogons = ticketsAllowed; + m_ticketLogons = ticketsAllowed; } /** @@ -413,23 +410,23 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt */ protected final boolean isNTLMSSPBlob( byte[] byts, int offset) { - // Check if the blob has the NTLMSSP signature + // Check if the blob has the NTLMSSP signature - boolean isNTLMSSP = false; - - if (( byts.length - offset) >= NTLM.Signature.length) { - - // Check for the NTLMSSP signature - - int idx = 0; - while ( idx < NTLM.Signature.length && byts[offset + idx] == NTLM.Signature[ idx]) - idx++; - - if ( idx == NTLM.Signature.length) - isNTLMSSP = true; - } - - return isNTLMSSP; + boolean isNTLMSSP = false; + + if (( byts.length - offset) >= NTLM.Signature.length) { + + // Check for the NTLMSSP signature + + int idx = 0; + while ( idx < NTLM.Signature.length && byts[offset + idx] == NTLM.Signature[ idx]) + idx++; + + if ( idx == NTLM.Signature.length) + isNTLMSSP = true; + } + + return isNTLMSSP; } /** @@ -542,35 +539,35 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt { return serverConfiguration == null ? null : (SecurityConfigSection) serverConfiguration.getConfigSection(SecurityConfigSection.SectionName); } - - /** - * Writes link to login page and refresh tag which cause user - * to be redirected to the login page. + + /** + * Writes link to login page and refresh tag which cause user + * to be redirected to the login page. * - * @param context ServletContext - * @param resp HttpServletResponse - * @param httpSess HttpSession - * @throws IOException - */ - protected void writeLoginPageLink(ServletContext context, HttpServletRequest req, HttpServletResponse resp) throws IOException - { - if ( hasLoginPage()) - { + * @param context ServletContext + * @param resp HttpServletResponse + * @param httpSess HttpSession + * @throws IOException + */ + protected void writeLoginPageLink(ServletContext context, HttpServletRequest req, HttpServletResponse resp) throws IOException + { + if ( hasLoginPage()) + { resp.setContentType(MIME_HTML_TEXT); - final PrintWriter out = resp.getWriter(); - out.println(""); - out.println(""); - out.println("

Please log in.

"); - out.println(""); - out.close(); - } - } - + final PrintWriter out = resp.getWriter(); + out.println(""); + out.println(""); + out.println("

Please log in.

"); + out.println(""); + out.close(); + } + } + /** * Include into response authentication method that is supported by fallback mechanism * @@ -606,13 +603,13 @@ public abstract class BaseSSOAuthenticationFilter extends BaseAuthenticationFilt if (!fallbackSuccess) { restartLoginChallenge(context, req, resp); - + if (getLogger().isDebugEnabled()) { getLogger().debug("Fallback authentication failed. Restarting login..."); } } - + if (fallbackSuccess && getLogger().isDebugEnabled()) { getLogger().debug("Fallback authentication succeeded."); diff --git a/source/java/org/alfresco/repo/webdav/auth/SSOFallbackBasicAuthenticationDriver.java b/source/java/org/alfresco/repo/webdav/auth/SSOFallbackBasicAuthenticationDriver.java index b41081045a..4852326a38 100644 --- a/source/java/org/alfresco/repo/webdav/auth/SSOFallbackBasicAuthenticationDriver.java +++ b/source/java/org/alfresco/repo/webdav/auth/SSOFallbackBasicAuthenticationDriver.java @@ -49,8 +49,6 @@ import org.apache.commons.logging.LogFactory; */ public class SSOFallbackBasicAuthenticationDriver implements AuthenticationDriver { - public static final String AUTHENTICATION_USER = "_alfAuthTicket"; - private Log logger = LogFactory.getLog(SSOFallbackBasicAuthenticationDriver.class); private AuthenticationService authenticationService; @@ -58,7 +56,7 @@ public class SSOFallbackBasicAuthenticationDriver implements AuthenticationDrive private NodeService nodeService; private TransactionService transactionService; - private String userAttributeName = AUTHENTICATION_USER; + private String userAttributeName = AuthenticationDriver.AUTHENTICATION_USER; public void setAuthenticationService(AuthenticationService authenticationService) {