diff --git a/source/java/org/alfresco/web/app/servlet/AbstractAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/AbstractAuthenticationFilter.java index 919b83411e..f8117dc41b 100644 --- a/source/java/org/alfresco/web/app/servlet/AbstractAuthenticationFilter.java +++ b/source/java/org/alfresco/web/app/servlet/AbstractAuthenticationFilter.java @@ -43,6 +43,9 @@ public abstract class AbstractAuthenticationFilter implements Filter { private static Log logger = LogFactory.getLog(AbstractAuthenticationFilter.class); + /** Request level marker to indicate that authentication should not be processed */ + final static String NO_AUTH_REQUIRED = "alfNoAuthRequired"; + /** * Parse the Accept-Lanaguage HTTP header value diff --git a/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java index e837a2be5d..1c3f7e250f 100644 --- a/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java +++ b/source/java/org/alfresco/web/app/servlet/KerberosAuthenticationFilter.java @@ -64,10 +64,12 @@ import org.alfresco.jlan.server.auth.spnego.NegTokenTarg; import org.alfresco.jlan.server.auth.spnego.OID; import org.alfresco.jlan.server.auth.spnego.SPNEGO; import org.alfresco.model.ContentModel; +import org.alfresco.repo.SessionUser; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.NTLMMode; +import org.alfresco.repo.webdav.auth.BaseKerberosAuthenticationFilter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -90,88 +92,26 @@ import org.springframework.web.context.support.WebApplicationContextUtils; * * @author GKSpencer */ -public class KerberosAuthenticationFilter extends AbstractAuthenticationFilter implements Filter, CallbackHandler +public class KerberosAuthenticationFilter extends BaseKerberosAuthenticationFilter { + // Debug logging + + private static Log logger = LogFactory.getLog(KerberosAuthenticationFilter.class); + // Constants // - // Default login configuration entry name - - private static final String LoginConfigEntry = "AlfrescoHTTP"; - // Locale object stored in the session private static final String LOCALE = "locale"; public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; - // Debug logging - - private static Log logger = LogFactory.getLog(KerberosAuthenticationFilter.class); - - // Servlet context, required to get authentication service - - private ServletContext m_context; - - // File server configuration - - private ServerConfigurationBean m_srvConfig; - - // Various services required by the Kerberos authenticator - - private AuthenticationService m_authService; - private AuthenticationComponent m_authComponent; - private PersonService m_personService; - private NodeService m_nodeService; - private TransactionService m_transactionService; + // Various services required by Kerberos authenticator private ConfigService m_configService; - // Allow guest access - - private boolean m_allowGuest; - - // Login page address - - private String m_loginPage; - - // Random number generator used to generate challenge keys - - private Random m_random = new Random(System.currentTimeMillis()); - - // Local server name, from either the file servers config or DNS host name - - private String m_srvName; - // List of available locales (from the web-client configuration) private List m_languages; - // Kerberos settings - // - // Account name and password for server ticket - // - // The account name must be built from the HTTP server name, in the format :- - // - // HTTP/@ - - private String m_accountName; - private String m_password; - - // Kerberos realm and KDC address - - private String m_krbRealm; - private String m_krbKDC; - - // Login configuration entry name - - private String m_loginEntryName = LoginConfigEntry; - - // Server login context - - private LoginContext m_loginContext; - - // SPNEGO NegTokenInit blob, sent to the client in the SMB negotiate response - - private byte[] m_negTokenInit; - /** * Initialize the filter * @@ -180,707 +120,103 @@ public class KerberosAuthenticationFilter extends AbstractAuthenticationFilter i */ public void init(FilterConfig args) throws ServletException { - // Save the servlet context, needed to get hold of the authentication service + // Call the base Kerberos filter initialization + + super.init( args); + - m_context = args.getServletContext(); - // Setup the authentication context - + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); + m_configService = (ConfigService)ctx.getBean("webClientConfigService"); - ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - m_nodeService = serviceRegistry.getNodeService(); - m_transactionService = serviceRegistry.getTransactionService(); - - m_authService = (AuthenticationService) ctx.getBean("AuthenticationService"); - m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); - m_personService = (PersonService) ctx.getBean("personService"); - m_configService = (ConfigService) ctx.getBean("webClientConfigService"); - - m_srvConfig = (ServerConfigurationBean) ctx.getBean(ServerConfigurationBean.SERVER_CONFIGURATION); - - // Check that the authentication component supports the required mode - - if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && - m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) - { - throw new ServletException("Required authentication mode not available"); - } - - // Get the local server name, try the file server config first - - if ( m_srvConfig != null) - { - m_srvName = m_srvConfig.getServerName(); - - if ( m_srvName == null) - { - // CIFS server may not be running so the local server name has not been set, generate - // a server name - - m_srvName = m_srvConfig.getLocalServerName(true) + "_A"; - } - } - else - { - // Get the host name - - try - { - // Get the local host name - - m_srvName = InetAddress.getLocalHost().getHostName(); - - // Strip any domain name - - int pos = m_srvName.indexOf("."); - if ( pos != -1) - m_srvName = m_srvName.substring(0, pos - 1); - } - catch (UnknownHostException ex) - { - // Log the error - - if ( logger.isErrorEnabled()) - logger.error("Kerberos filter, error getting local host name", ex); - } - - } - - // Check if the server name is valid - - if ( m_srvName == null || m_srvName.length() == 0) - throw new ServletException("Failed to get local server name"); - // Get a list of the available locales LanguagesConfigElement config = (LanguagesConfigElement) m_configService. getConfig("Languages").getConfigElement(LanguagesConfigElement.CONFIG_ELEMENT_ID); - + m_languages = config.getLanguages(); - // Check if Kerberos is enabled, get the Kerberos KDC address - - String kdcAddress = args.getInitParameter("KDC"); + // Set hte login page address - if (kdcAddress != null && kdcAddress.length() > 0) - { - // Set the Kerberos KDC address - - m_krbKDC = kdcAddress; - - // Get the Kerberos realm - - String krbRealm = args.getInitParameter("Realm"); - if ( krbRealm != null && krbRealm.length() > 0) - { - // Set the Kerberos realm - - m_krbRealm = krbRealm; - } - else - throw new ServletException("Kerberos realm not specified"); - - // Get the HTTP service account password - - String srvPassword = args.getInitParameter("Password"); - if ( srvPassword != null && srvPassword.length() > 0) - { - // Set the HTTP service account password - - m_password = srvPassword; - } - else - throw new ServletException("HTTP service account password not specified"); - - // Get the login configuration entry name - - String loginEntry = args.getInitParameter("LoginEntry"); - - if ( loginEntry != null) - { - if ( loginEntry.length() > 0) - { - // Set the login configuration entry name to use - - m_loginEntryName = loginEntry; - } - else - throw new ServletException("Invalid login entry specified"); - } - - // Get the local host name - - String localName = null; - - try - { - localName = InetAddress.getLocalHost().getCanonicalHostName(); - } - catch ( UnknownHostException ex) - { - throw new ServletException( "Failed to get local host name"); - } - - // Create a login context for the HTTP server service - - try - { - // Login the HTTP server service - - m_loginContext = new LoginContext( m_loginEntryName, this); - m_loginContext.login(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( "HTTP Kerberos login successful"); - } - catch ( LoginException ex) - { - // Debug - - if ( logger.isErrorEnabled()) - logger.error("HTTP Kerberos web filter error", ex); - - throw new ServletException("Failed to login HTTP server service"); - } - - // Get the HTTP service account name from the subject - - Subject subj = m_loginContext.getSubject(); - Principal princ = subj.getPrincipals().iterator().next(); - - m_accountName = princ.getName(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Logged on using principal " + m_accountName); - - // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback - - Vector mechTypes = new Vector(); - - mechTypes.add(OID.KERBEROS5); - mechTypes.add(OID.MSKERBEROS5); - - // Build the SPNEGO NegTokenInit blob - - try - { - // Build the mechListMIC principle - // - // Note: This field is not as specified - - String mecListMIC = null; - - StringBuilder mic = new StringBuilder(); - mic.append( localName); - mic.append("$@"); - mic.append( m_krbRealm); - - mecListMIC = mic.toString(); - - // Build the SPNEGO NegTokenInit that contains the authentication types that the HTTP server accepts - - NegTokenInit negTokenInit = new NegTokenInit(mechTypes, mecListMIC); - - // Encode the NegTokenInit blob - - m_negTokenInit = negTokenInit.encode(); - } - catch (IOException ex) - { - // Debug - - if ( logger.isErrorEnabled()) - logger.error("Error creating SPNEGO NegTokenInit blob", ex); - - throw new ServletException("Failed to create SPNEGO NegTokenInit blob"); - } - } + setLoginPage( Application.getLoginPage(m_context)); } - /** - * Run the filter - * - * @param sreq ServletRequest - * @param sresp ServletResponse - * @param chain FilterChain - * @exception IOException - * @exception ServletException + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#createUserObject(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + @Override + protected SessionUser createUserObject(String userName, String ticket, NodeRef personNode, String homeSpace) { + + // Create a web client user object + + User user = new User( userName, ticket, personNode); + user.setHomeSpaceId( homeSpace); + + return user; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpSession) */ - public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException, - ServletException + @Override + protected void onValidate(HttpServletRequest req, HttpSession session) { - // Get the HTTP request/response/session - - HttpServletRequest req = (HttpServletRequest) sreq; - HttpServletResponse resp = (HttpServletResponse) sresp; - - HttpSession httpSess = req.getSession(true); + // Set the current locale from the Accept-Lanaguage header if available - // Check if there is an authorization header with an SPNEGO security blob - - String authHdr = req.getHeader("Authorization"); - boolean reqAuth = false; - - if ( authHdr != null && authHdr.startsWith("Negotiate")) - reqAuth = true; - - // Check if the user is already authenticated - - User user = (User) httpSess.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); - - if ( user != null && reqAuth == false) + Locale userLocale = AbstractAuthenticationFilter.parseAcceptLanguageHeader(req, m_languages); + if (userLocale != null) { - try - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("User " + user.getUserName() + " validate ticket"); - - // Validate the user ticket - - m_authService.validate( user.getTicket()); - reqAuth = false; - - // Set the current locale - - I18NUtil.setLocale(Application.getLanguage(httpSess)); - } - catch (AuthenticationException ex) - { - if ( logger.isErrorEnabled()) - logger.error("Failed to validate user " + user.getUserName(), ex); - - reqAuth = true; - } + session.setAttribute(LOCALE, userLocale); + session.removeAttribute(MESSAGE_BUNDLE); } - // If the user has been validated and we do not require re-authentication then continue to - // the next filter - - if ( reqAuth == false && user != null) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Authentication not required, chaining ..."); - - // Chain to the next filter - - chain.doFilter(sreq, sresp); - return; - } + // Set the locale using the session - // Check if the login page is being accessed, do not intercept the login page - - if ( req.getRequestURI().endsWith(getLoginPage()) == true) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Login page requested, chaining ..."); - - // Chain to the next filter - - chain.doFilter( sreq, sresp); - return; - } - - // Check if the browser is Opera, if so then display the login page as Opera does not - // support SPNEGO - - String userAgent = req.getHeader("user-agent"); - - if ( userAgent != null && userAgent.indexOf("Opera ") != -1) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Opera detected, redirecting to login page"); + I18NUtil.setLocale(Application.getLanguage(session)); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession) + */ + @Override + protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session) + throws IOException + { + // Redirect to the login page if user validation fails - // Redirect to the login page - - resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); - return; - } - - // Check the authorization header - - if ( authHdr == null) { + redirectToLoginPage(req, res); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onLoginComplete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + protected boolean onLoginComplete(HttpServletRequest req, HttpServletResponse res) + throws IOException + { + // If the original URL requested was the login page then redirect to the browse view + if (req.getRequestURI().endsWith(getLoginPage()) == true) + { + if (logger.isDebugEnabled()) + logger.debug("Login page requested, redirecting to browse page"); - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("New Kerberos auth request from " + req.getRemoteHost() + " (" + - req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); - - // Send back a request for SPNEGO authentication - - resp.setHeader("WWW-Authenticate", "Negotiate"); - resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - - resp.flushBuffer(); + // Redirect to the browse view + res.sendRedirect(req.getContextPath() + "/faces/jsp/browse/browse.jsp"); + return false; } else { - // Decode the received SPNEGO blob and validate - - final byte[] spnegoByts = Base64.decodeBase64( authHdr.substring(10).getBytes()); - - // Check the received SPNEGO token type - - int tokType = -1; - - try - { - tokType = SPNEGO.checkTokenType( spnegoByts, 0, spnegoByts.length); - } - catch ( IOException ex) - { - } - - // Check for a NegTokenInit blob - - if ( tokType == SPNEGO.NegTokenInit) - { - // Parse the SPNEGO security blob to get the Kerberos ticket - - NegTokenInit negToken = new NegTokenInit(); - - try - { - // Decode the security blob - - negToken.decode( spnegoByts, 0, spnegoByts.length); - - // Determine the authentication mechanism the client is using and logon - - String oidStr = null; - if ( negToken.numberOfOids() > 0) - oidStr = negToken.getOidAt( 0).toString(); - - if ( oidStr != null && (oidStr.equals( OID.ID_MSKERBEROS5) || oidStr.equals(OID.ID_KERBEROS5))) - { - // Kerberos logon - - if ( doKerberosLogon( negToken, req, resp, httpSess) != null) - { - // If the original URL requested was the login page then redirect to the browse view - - if (req.getRequestURI().endsWith(getLoginPage()) == true) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Login page requested, redirecting to browse page"); - - // Redirect to the browse view - - resp.sendRedirect(req.getContextPath() + "/faces/jsp/browse/browse.jsp"); - } - else - { - // Allow the user to access the requested page - - chain.doFilter( req, resp); - } - } - else - { - // Send back a request for SPNEGO authentication - - resp.setHeader("WWW-Authenticate", "Negotiate"); - resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - - resp.flushBuffer(); - } - } - } - catch ( IOException ex) - { - // Log the error - - if ( logger.isDebugEnabled()) - logger.debug(ex); - } - } - else - { - // Unknown SPNEGO token type - - if ( logger.isDebugEnabled()) - logger.debug( "Unknown SPNEGO token type"); - - // Redirect to the login page - - resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); - } + return true; } } - /** - * Return the login page address - * - * @return String + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getLogger() */ - private String getLoginPage() + @Override + final protected Log getLogger() { - if (m_loginPage == null) - { - m_loginPage = Application.getLoginPage(m_context); - } - - return m_loginPage; - } - - /** - * Delete the servlet filter - */ - public void destroy() - { - } - - /** - * JAAS callback handler - * - * @param callbacks Callback[] - * @exception IOException - * @exception UnsupportedCallbackException - */ - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException - { - // Process the callback list - - for (int i = 0; i < callbacks.length; i++) - { - // Request for user name - - if (callbacks[i] instanceof NameCallback) - { - NameCallback cb = (NameCallback) callbacks[i]; - cb.setName( ""); - } - - // Request for password - else if (callbacks[i] instanceof PasswordCallback) - { - PasswordCallback cb = (PasswordCallback) callbacks[i]; - cb.setPassword(m_password.toCharArray()); - } - - // Request for realm - - else if (callbacks[i] instanceof RealmCallback) - { - RealmCallback cb = (RealmCallback) callbacks[i]; - cb.setText(m_krbRealm); - } - else - { - throw new UnsupportedCallbackException(callbacks[i]); - } - } - } - - /** - * Perform a Kerberos login and return an SPNEGO response - * - * @param negToken NegTokenInit - * @param req HttpServletRequest - * @param resp HttpServletResponse - * @param httpSess HttpSession - * @return NegTokenTarg - */ - private final NegTokenTarg doKerberosLogon( NegTokenInit negToken, HttpServletRequest req, HttpServletResponse resp, HttpSession httpSess) - { - // Authenticate the user - - KerberosDetails krbDetails = null; - NegTokenTarg negTokenTarg = null; - - UserTransaction tx = null; - - try - { - // Run the session setup as a privileged action - - SessionSetupPrivilegedAction sessSetupAction = new SessionSetupPrivilegedAction( m_accountName, negToken.getMechtoken()); - Object result = Subject.doAs( m_loginContext.getSubject(), sessSetupAction); - - if ( result != null) - { - // Access the Kerberos response - - krbDetails = (KerberosDetails) result; - - // Create the NegTokenTarg response blob - - negTokenTarg = new NegTokenTarg( SPNEGO.AcceptCompleted, OID.KERBEROS5, krbDetails.getResponseToken()); - - // Check if the user has been authenticated, if so then setup the user environment - - if ( negTokenTarg != null) - { - // Create a read transaction - - tx = m_transactionService.getUserTransaction(); - - NodeRef homeSpaceRef = null; - User user = null; - String userName = null; - - try - { - // Start the transaction - - tx.begin(); - - // Setup User object and Home space ID etc. - - NodeRef personNodeRef = m_personService.getPerson( krbDetails.getUserName()); - - // Use the system user to do the user name lookup - - m_authComponent.setSystemUserAsCurrentUser(); - - // User name should match the uid in the person entry found - - userName = (String) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - AuthenticationUtil.setCurrentUser( userName); - String currentTicket = m_authService.getCurrentTicket(); - user = new User(userName, currentTicket, personNodeRef); - - homeSpaceRef = (NodeRef) m_nodeService.getProperty( personNodeRef, ContentModel.PROP_HOMEFOLDER); - user.setHomeSpaceId(homeSpaceRef.getId()); - - // Commit - - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Exception ex2) - { - logger.error("Failed to rollback transaction", ex2); - } - if(ex instanceof RuntimeException) - { - throw (RuntimeException)ex; - } - else if(ex instanceof IOException) - { - throw (IOException)ex; - } - else if(ex instanceof ServletException) - { - throw (ServletException)ex; - } - else - { - throw new RuntimeException("Authentication setup failed", ex); - } - } - - // Store the user - - httpSess.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user); - httpSess.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE); - - // Set the current locale from the Accept-Lanaguage header if available - - Locale userLocale = parseAcceptLanguageHeader(req, m_languages); - - if ( userLocale != null) - { - httpSess.setAttribute(LOCALE, userLocale); - httpSess.removeAttribute(MESSAGE_BUNDLE); - } - - // Set the locale using the session - - I18NUtil.setLocale(Application.getLanguage(httpSess)); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("User " + userName + " logged on via Kerberos"); - - } - } - else - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "No SPNEGO response, Kerberos logon failed"); - } - } - catch (Exception ex) - { - // Log the error - - if ( logger.isDebugEnabled()) - logger.debug("Kerberos logon error", ex); - } - - // Return the response SPNEGO blob - - return negTokenTarg; - } - - /** - * Map the case insensitive logon name to the internal person object user name - * - * @param userName String - * @return String - */ - protected final String mapUserNameToPerson(String userName) - { - // Get the home folder for the user - - UserTransaction tx = m_transactionService.getUserTransaction(); - String personName = null; - - try - { - tx.begin(); - personName = m_personService.getUserIdentifier( userName); - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Throwable ex2) - { - logger.error("Failed to rollback transaction", ex2); - } - - // Re-throw the exception - - if (ex instanceof RuntimeException) - { - throw (RuntimeException) ex; - } - else - { - throw new RuntimeException("Error during execution of transaction.", ex); - } - } - - // Return the person name - - return personName; + return logger; } } diff --git a/source/java/org/alfresco/web/app/servlet/NTLMAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/NTLMAuthenticationFilter.java index da1f7b411d..589969f632 100644 --- a/source/java/org/alfresco/web/app/servlet/NTLMAuthenticationFilter.java +++ b/source/java/org/alfresco/web/app/servlet/NTLMAuthenticationFilter.java @@ -28,33 +28,20 @@ import java.io.IOException; import java.util.List; import java.util.Locale; -import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import javax.transaction.UserTransaction; import org.alfresco.config.ConfigService; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; -import org.alfresco.jlan.server.auth.ntlm.NTLM; -import org.alfresco.jlan.server.auth.ntlm.NTLMMessage; -import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage; -import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage; -import org.alfresco.model.ContentModel; import org.alfresco.repo.SessionUser; -import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.web.app.Application; -import org.alfresco.web.bean.LoginBean; import org.alfresco.web.bean.repository.User; import org.alfresco.web.config.LanguagesConfigElement; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.web.context.WebApplicationContext; @@ -77,9 +64,6 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter // Various services required by NTLM authenticator private ConfigService m_configService; - // Login page address - private String m_loginPage; - // List of available locales (from the web-client configuration) private List m_languages; @@ -95,150 +79,32 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter super.init(args); // Setup the authentication context + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); m_configService = (ConfigService)ctx.getBean("webClientConfigService"); // Get a list of the available locales + LanguagesConfigElement config = (LanguagesConfigElement) m_configService. getConfig("Languages").getConfigElement(LanguagesConfigElement.CONFIG_ELEMENT_ID); m_languages = config.getLanguages(); + setLoginPage( Application.getLoginPage(m_context)); } - /** - * Run the filter - * - * @param sreq ServletRequest - * @param sresp ServletResponse - * @param chain FilterChain - * @exception IOException - * @exception ServletException - */ - public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException, - ServletException - { - // Get the HTTP request/response/session - HttpServletRequest req = (HttpServletRequest) sreq; - HttpServletResponse resp = (HttpServletResponse) sresp; - HttpSession httpSess = req.getSession(true); - - // Check if there is an authorization header with an NTLM security blob - String authHdr = req.getHeader(AUTHORIZATION); - boolean reqAuth = (authHdr != null && authHdr.startsWith(AUTH_NTLM)); - - // Check if the user is already authenticated - User user = (User) httpSess.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); - if (user != null && reqAuth == false) - { - try - { - if (logger.isDebugEnabled()) - logger.debug("User " + user.getUserName() + " validate ticket"); - - // Validate the user ticket - m_authService.validate(user.getTicket()); - reqAuth = false; - - // Set the current locale - I18NUtil.setLocale(Application.getLanguage(httpSess)); - } - catch (AuthenticationException ex) - { - if (logger.isErrorEnabled()) - logger.error("Failed to validate user " + user.getUserName(), ex); - - reqAuth = true; - } - } - - // If the user has been validated and we do not require re-authentication then continue to - // the next filter - if (reqAuth == false && user != null) - { - if (logger.isDebugEnabled()) - logger.debug("Authentication not required, chaining ..."); - - // Chain to the next filter - chain.doFilter(sreq, sresp); - return; - } - - // Check if the login page is being accessed, do not intercept the login page - if (req.getRequestURI().endsWith(getLoginPage()) == true) - { - if (logger.isDebugEnabled()) - logger.debug("Login page requested, chaining ..."); - - // Chain to the next filter - chain.doFilter( sreq, sresp); - return; - } - - // Check if the browser is Opera, if so then display the login page as Opera does not - // support NTLM and displays an error page if a request to use NTLM is sent to it - String userAgent = req.getHeader("user-agent"); - if (userAgent != null && userAgent.indexOf("Opera ") != -1) - { - if (logger.isDebugEnabled()) - logger.debug("Opera detected, redirecting to login page"); - - redirectToLoginPage(req, resp); - return; - } - - // Check the authorization header - if (authHdr == null) - { - if (logger.isDebugEnabled()) - logger.debug("New NTLM auth request from " + req.getRemoteHost() + " (" + - req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); - - // Send back a request for NTLM authentication - restartLoginChallenge(resp, httpSess); - } - else - { - // Decode the received NTLM blob and validate - final byte[] ntlmByts = Base64.decodeBase64(authHdr.substring(5).getBytes()); - int ntlmTyp = NTLMMessage.isNTLMType(ntlmByts); - if (ntlmTyp == NTLM.Type1) - { - // Process the type 1 NTLM message - Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts); - processType1(type1Msg, req, resp, httpSess); - } - else if (ntlmTyp == NTLM.Type3) - { - // Process the type 3 NTLM message - Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts); - processType3(type3Msg, req, resp, httpSess, chain); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("NTLM blob not handled, redirecting to login page."); - - redirectToLoginPage(req, resp); - } - } - } - - /** - * Redirect to the login page - */ - protected void redirectToLoginPage(HttpServletRequest req, HttpServletResponse res) throws IOException - { - res.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getSessionUser(javax.servlet.http.HttpSession) - */ - @Override - protected SessionUser getSessionUser(HttpSession session) - { - return (SessionUser)session.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); - } + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#createUserObject(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + @Override + protected SessionUser createUserObject(String userName, String ticket, NodeRef personNode, String homeSpace) { + + // Create a web client user object + + User user = new User( userName, ticket, personNode); + user.setHomeSpaceId( homeSpace); + + return user; + } /* (non-Javadoc) * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpSession) @@ -247,7 +113,8 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter protected void onValidate(HttpServletRequest req, HttpSession session) { // Set the current locale from the Accept-Lanaguage header if available - Locale userLocale = AbstractAuthenticationFilter.parseAcceptLanguageHeader(req, m_languages); + + Locale userLocale = AbstractAuthenticationFilter.parseAcceptLanguageHeader(req, m_languages); if (userLocale != null) { session.setAttribute(LOCALE, userLocale); @@ -255,6 +122,7 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter } // Set the locale using the session + I18NUtil.setLocale(Application.getLanguage(session)); } @@ -266,76 +134,8 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter throws IOException { // Redirect to the login page if user validation fails - redirectToLoginPage(req, res); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#createUserEnvironment(javax.servlet.http.HttpSession, java.lang.String) - */ - @Override - protected SessionUser createUserEnvironment(HttpSession session, String userName) - throws IOException, ServletException - { - SessionUser user = null; - - UserTransaction tx = m_transactionService.getUserTransaction(); - - try - { - tx.begin(); - - // Setup User object and Home space ID etc. - NodeRef personNodeRef = m_personService.getPerson(userName); - - // Use the system user context to do the user lookup - m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); - - // User name should match the uid in the person entry found - m_authComponent.setSystemUserAsCurrentUser(); - userName = (String) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - - m_authComponent.setCurrentUser(userName); - String currentTicket = m_authService.getCurrentTicket(); - user = new User(userName, currentTicket, personNodeRef); - - NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); - ((User)user).setHomeSpaceId(homeSpaceRef.getId()); - - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Exception err) - { - logger.error("Failed to rollback transaction", err); - } - if (ex instanceof RuntimeException) - { - throw (RuntimeException)ex; - } - else if (ex instanceof IOException) - { - throw (IOException)ex; - } - else if (ex instanceof ServletException) - { - throw (ServletException)ex; - } - else - { - throw new RuntimeException("Authentication setup failed", ex); - } - } - - // Store the user on the session - session.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user); - session.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE); - - return user; + + redirectToLoginPage(req, res); } /* (non-Javadoc) @@ -361,21 +161,6 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter } } - /** - * Return the login page address - * - * @return String - */ - private String getLoginPage() - { - if (m_loginPage == null) - { - m_loginPage = Application.getLoginPage(m_context); - } - - return m_loginPage; - } - /* (non-Javadoc) * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getLogger() */ diff --git a/source/java/org/alfresco/web/app/servlet/WebScriptNTLMAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/WebScriptNTLMAuthenticationFilter.java new file mode 100644 index 0000000000..cd3936a18f --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/WebScriptNTLMAuthenticationFilter.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2007 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 recieved 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.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.config.ConfigService; +import org.alfresco.util.URLDecoder; +import org.alfresco.web.scripts.Match; +import org.alfresco.web.scripts.RuntimeContainer; +import org.alfresco.web.scripts.Description.RequiredAuthentication; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * WebScript aware NTLM Authentication Filter Class. + * + * Takes into account the authentication setting in the descriptor for the webscript. + * If authentication is not required then simply chains. Otherwise will delegate + * back to the usual web-client NTLM filter code path. + * + * @author Kevin Roast + */ +public class WebScriptNTLMAuthenticationFilter extends NTLMAuthenticationFilter +{ + private RuntimeContainer container; + + + /** + * Initialize the filter + * + * @param args FilterConfig + * @exception ServletException + */ + public void init(FilterConfig args) throws ServletException + { + super.init(args); + + ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(args.getServletContext()); + ConfigService configService = (ConfigService)context.getBean("web.config"); + String containerName = args.getInitParameter("container"); + if (containerName == null) + { + containerName = "webscripts.container"; + } + container = (RuntimeContainer)context.getBean(containerName); + } + + /** + * Run the filter + * + * @param sreq ServletRequest + * @param sresp ServletResponse + * @param chain FilterChain + * + * @exception IOException + * @exception ServletException + */ + public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain) + throws IOException, ServletException + { + // Get the HTTP request/response + HttpServletRequest req = (HttpServletRequest)sreq; + HttpServletResponse res = (HttpServletResponse)sres; + + // find a webscript match for the requested URI + String requestURI = req.getRequestURI(); + String pathInfo = requestURI.substring((req.getContextPath() + req.getServletPath()).length()); + + if (getLogger().isDebugEnabled()) + getLogger().debug("Processing request: " + requestURI + " SID:" + req.getSession().getId()); + + Match match = container.getRegistry().findWebScript(req.getMethod(), URLDecoder.decode(pathInfo)); + if (match != null && match.getWebScript() != null) + { + // check the authentication required - if none then we don't want any of + // the filters down the chain to require any authentication checks + if (RequiredAuthentication.none == match.getWebScript().getDescription().getRequiredAuthentication()) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Found webscript with no authentication - set NO_AUTH_REQUIRED flag."); + req.setAttribute(AbstractAuthenticationFilter.NO_AUTH_REQUIRED, Boolean.TRUE); + } + } + + super.doFilter(sreq, sres, chain); + } +} diff --git a/source/java/org/alfresco/web/bean/admin/AdminNodeBrowseBean.java b/source/java/org/alfresco/web/bean/admin/AdminNodeBrowseBean.java index 175782a29d..fe12a0b53b 100644 --- a/source/java/org/alfresco/web/bean/admin/AdminNodeBrowseBean.java +++ b/source/java/org/alfresco/web/bean/admin/AdminNodeBrowseBean.java @@ -27,6 +27,7 @@ package org.alfresco.web.bean.admin; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,6 +40,7 @@ import javax.faces.model.SelectItem; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -57,6 +59,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ISO9075; import org.alfresco.web.app.servlet.DownloadContentServlet; import org.alfresco.web.bean.repository.Repository; @@ -105,6 +108,7 @@ public class AdminNodeBrowseBean implements Serializable transient private DataModel avmStoreProps = null; // supporting repository services + transient private TransactionService transactionService; transient private NodeService nodeService; transient private DictionaryService dictionaryService; transient private SearchService searchService; @@ -112,6 +116,23 @@ public class AdminNodeBrowseBean implements Serializable transient private PermissionService permissionService; transient private AVMService avmService; + /** + * @param transactionService transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + private TransactionService getTransactionService() + { + if (transactionService == null) + { + transactionService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getTransactionService(); + } + return transactionService; + } + /** * @param nodeService node service */ @@ -529,7 +550,7 @@ public class AdminNodeBrowseBean implements Serializable * * @return query languages */ - public List getQueryLanguages() + public List getQueryLanguages() { return queryLanguages; } @@ -667,30 +688,38 @@ public class AdminNodeBrowseBean implements Serializable */ public String submitSearch() { - try + RetryingTransactionCallback searchCallback = new RetryingTransactionCallback() { - if (queryLanguage.equals("noderef")) + public String execute() throws Throwable { - // ensure node exists - NodeRef nodeRef = new NodeRef(query); - boolean exists = getNodeService().exists(nodeRef); - if (!exists) + if (queryLanguage.equals("noderef")) { - throw new AlfrescoRuntimeException("Node " + nodeRef + " does not exist."); + // ensure node exists + NodeRef nodeRef = new NodeRef(query); + boolean exists = getNodeService().exists(nodeRef); + if (!exists) + { + throw new AlfrescoRuntimeException("Node " + nodeRef + " does not exist."); + } + setNodeRef(nodeRef); + return "node"; } - setNodeRef(nodeRef); - return "node"; - } - else if (queryLanguage.equals("selectnodes")) - { - List nodes = getSearchService().selectNodes(getNodeRef(), query, null, getNamespaceService(), false); - searchResults = new SearchResults(nodes); + else if (queryLanguage.equals("selectnodes")) + { + List nodes = getSearchService().selectNodes(getNodeRef(), query, null, getNamespaceService(), false); + searchResults = new SearchResults(nodes); + return "search"; + } + + // perform search + searchResults = new SearchResults(getSearchService().query(getNodeRef().getStoreRef(), queryLanguage, query)); return "search"; } + }; - // perform search - searchResults = new SearchResults(getSearchService().query(getNodeRef().getStoreRef(), queryLanguage, query)); - return "search"; + try + { + return getTransactionService().getRetryingTransactionHelper().doInTransaction(searchCallback, true); } catch (Throwable e) { @@ -724,6 +753,7 @@ public class AdminNodeBrowseBean implements Serializable * @param name property name * @param value property values */ + @SuppressWarnings("unchecked") public Property(QName name, Serializable value) { this.name = name; @@ -740,19 +770,20 @@ public class AdminNodeBrowseBean implements Serializable } // handle multi/single values - // TODO: perhaps this is not the most efficient way - lots of list creations - List values = new ArrayList(); + final List values; if (value instanceof Collection) { + Collection oldValues = (Collection) value; + values = new ArrayList(oldValues.size()); isCollection = true; - for (Serializable multiValue : (Collection) value) + for (Serializable multiValue : oldValues) { values.add(new Value(multiValue)); } } else { - values.add(new Value(value)); + values = Collections.singletonList(new Value(value)); } this.values = new ListDataModel(values); } diff --git a/source/web/WEB-INF/faces-config-beans.xml b/source/web/WEB-INF/faces-config-beans.xml index f599b4d639..6889513643 100644 --- a/source/web/WEB-INF/faces-config-beans.xml +++ b/source/web/WEB-INF/faces-config-beans.xml @@ -1863,6 +1863,10 @@ AdminNodeBrowseBean org.alfresco.web.bean.admin.AdminNodeBrowseBean session + + transactionService + #{TransactionService} + nodeService #{NodeService} diff --git a/source/web/WEB-INF/web.xml b/source/web/WEB-INF/web.xml index e88f8cac98..83fa7f6449 100644 --- a/source/web/WEB-INF/web.xml +++ b/source/web/WEB-INF/web.xml @@ -99,6 +99,14 @@ org.alfresco.web.app.servlet.NovellIChainsHTTPRequestAuthenticationFilter --> + + + WebDAV Authentication Filter @@ -177,12 +185,12 @@ - Authentication Filter + WebScript NTLM Authentication Filter /wcservice/* - Authentication Filter + WebScript NTLM Authentication Filter /wcs/*