mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged V3.0 to HEAD
11829: Updated javadocs for RuntimeExec class 11830: Updated and wired in Spring source 11831: Fixed ETHREEOH-382: Can't run Lucene search via Node Browser 11832: Added unit test for V3.0 rev 11535 11834: Removed redundant TODO item 11835: ETHREEOH-798 Double clicking OK on most pop-up dialogs in Share causing multiple requests to be sent - and errors generated for the user 11836: Fix for a number of session based authentication and webscript authentication issues with NTLM from Share. Fixes ETHREEOH-806 and ETHREEOH-834 and first part of fix for ETHREEOH-789. 11838: Sharepoint Protocol Support 11843: Build fix 11846: Refactor of the SSO web filters (NTLM and Kerberos) for web-client and WebDAV. 11848: Added commented out entries for web-client and WebDAV Kerberos filter debugging. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12483 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -3,6 +3,6 @@
|
|||||||
<description>SSO authentication touch point - return a simple 200 OK status</description>
|
<description>SSO authentication touch point - return a simple 200 OK status</description>
|
||||||
<url>/touch</url>
|
<url>/touch</url>
|
||||||
<format default="">argument</format>
|
<format default="">argument</format>
|
||||||
<authentication>none</authentication>
|
<authentication>guest</authentication>
|
||||||
<transaction>none</transaction>
|
<transaction>none</transaction>
|
||||||
</webscript>
|
</webscript>
|
@@ -0,0 +1,612 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006-2008 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.repo.webdav.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import javax.security.auth.callback.Callback;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.callback.NameCallback;
|
||||||
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
|
import javax.security.auth.login.LoginContext;
|
||||||
|
import javax.security.auth.login.LoginException;
|
||||||
|
import javax.security.sasl.RealmCallback;
|
||||||
|
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 org.alfresco.jlan.server.auth.kerberos.KerberosDetails;
|
||||||
|
import org.alfresco.jlan.server.auth.kerberos.SessionSetupPrivilegedAction;
|
||||||
|
import org.alfresco.jlan.server.auth.spnego.NegTokenInit;
|
||||||
|
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.repo.SessionUser;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class with common code and initialisation for Kerberos authentication filters.
|
||||||
|
*
|
||||||
|
* @author gkspencer
|
||||||
|
*/
|
||||||
|
public abstract class BaseKerberosAuthenticationFilter extends BaseSSOAuthenticationFilter implements CallbackHandler {
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
//
|
||||||
|
// Default login configuration entry name
|
||||||
|
|
||||||
|
private static final String LoginConfigEntry = "AlfrescoHTTP";
|
||||||
|
|
||||||
|
// Kerberos settings
|
||||||
|
//
|
||||||
|
// Account name and password for server ticket
|
||||||
|
//
|
||||||
|
// The account name must be built from the HTTP server name, in the format :-
|
||||||
|
//
|
||||||
|
// HTTP/<server_name>@<realm>
|
||||||
|
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @param args FilterConfig
|
||||||
|
* @exception ServletException
|
||||||
|
*/
|
||||||
|
public void init(FilterConfig args) throws ServletException
|
||||||
|
{
|
||||||
|
// Call the base SSO filter initialization
|
||||||
|
|
||||||
|
super.init( args);
|
||||||
|
|
||||||
|
// Check if Kerberos is enabled, get the Kerberos KDC address
|
||||||
|
|
||||||
|
String kdcAddress = args.getInitParameter("KDC");
|
||||||
|
|
||||||
|
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 ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug( "HTTP Kerberos login successful");
|
||||||
|
}
|
||||||
|
catch ( LoginException ex)
|
||||||
|
{
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( getLogger().isErrorEnabled())
|
||||||
|
getLogger().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 ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Logged on using principal " + m_accountName);
|
||||||
|
|
||||||
|
// Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback
|
||||||
|
|
||||||
|
Vector<Oid> mechTypes = new Vector<Oid>();
|
||||||
|
|
||||||
|
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 ( getLogger().isErrorEnabled())
|
||||||
|
getLogger().error("Error creating SPNEGO NegTokenInit blob", ex);
|
||||||
|
|
||||||
|
throw new ServletException("Failed to create SPNEGO NegTokenInit blob");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
// If a filter up the chain has marked the request as not requiring auth then respect it
|
||||||
|
|
||||||
|
if (req.getAttribute( NO_AUTH_REQUIRED) != null)
|
||||||
|
{
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Authentication not required (filter), chaining ...");
|
||||||
|
|
||||||
|
// Chain to the next filter
|
||||||
|
chain.doFilter(sreq, sresp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is an authorization header with an SPNEGO security blob
|
||||||
|
|
||||||
|
String authHdr = req.getHeader("Authorization");
|
||||||
|
boolean reqAuth = false;
|
||||||
|
|
||||||
|
if ( authHdr != null)
|
||||||
|
{
|
||||||
|
// Check for a Kerberos/SPNEGO authorization header
|
||||||
|
|
||||||
|
if ( authHdr.startsWith( "Negotiate"))
|
||||||
|
reqAuth = true;
|
||||||
|
else if ( authHdr.startsWith( "NTLM"))
|
||||||
|
{
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Received NTLM logon from client");
|
||||||
|
|
||||||
|
// Restart the authentication
|
||||||
|
|
||||||
|
restartLoginChallenge(resp, httpSess);
|
||||||
|
|
||||||
|
chain.doFilter(sreq, sresp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user is already authenticated
|
||||||
|
|
||||||
|
SessionUser user = getSessionUser( httpSess);
|
||||||
|
|
||||||
|
if ( user != null && reqAuth == false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("User " + user.getUserName() + " validate ticket");
|
||||||
|
|
||||||
|
// Validate the user ticket
|
||||||
|
|
||||||
|
m_authService.validate( user.getTicket());
|
||||||
|
reqAuth = false;
|
||||||
|
|
||||||
|
// Filter validate hook
|
||||||
|
onValidate( req, httpSess);
|
||||||
|
}
|
||||||
|
catch (AuthenticationException ex)
|
||||||
|
{
|
||||||
|
if ( getLogger().isErrorEnabled())
|
||||||
|
getLogger().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)
|
||||||
|
{
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Authentication not required (user), chaining ...");
|
||||||
|
|
||||||
|
// Chain to the next filter
|
||||||
|
|
||||||
|
chain.doFilter(sreq, sresp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the authorization header
|
||||||
|
|
||||||
|
if ( authHdr == null) {
|
||||||
|
|
||||||
|
// If ticket based logons are allowed, check for a ticket parameter
|
||||||
|
|
||||||
|
if ( allowsTicketLogons())
|
||||||
|
{
|
||||||
|
// Check if a ticket parameter has been specified in the reuqest
|
||||||
|
|
||||||
|
if ( checkForTicketParameter( req, httpSess))
|
||||||
|
{
|
||||||
|
// Chain to the next filter
|
||||||
|
|
||||||
|
chain.doFilter(sreq, sresp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("New Kerberos auth request from " + req.getRemoteHost() + " (" +
|
||||||
|
req.getRemoteAddr() + ":" + req.getRemotePort() + ")");
|
||||||
|
|
||||||
|
// Send back a request for SPNEGO authentication
|
||||||
|
|
||||||
|
restartLoginChallenge( resp, httpSess);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Decode the received SPNEGO blob and validate
|
||||||
|
|
||||||
|
final byte[] spnegoByts = Base64.decodeBase64( authHdr.substring(10).getBytes());
|
||||||
|
|
||||||
|
// Check if the client sent an NTLMSSP blob
|
||||||
|
|
||||||
|
if ( isNTLMSSPBlob( spnegoByts, 0))
|
||||||
|
{
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug( "Client sent an NTLMSSP security blob");
|
||||||
|
|
||||||
|
// Restart the authentication
|
||||||
|
|
||||||
|
restartLoginChallenge(resp, httpSess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// Allow the user to access the requested page
|
||||||
|
|
||||||
|
chain.doFilter( req, resp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Send back a request for SPNEGO authentication
|
||||||
|
|
||||||
|
restartLoginChallenge( resp, httpSess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( IOException ex)
|
||||||
|
{
|
||||||
|
// Log the error
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Unknown SPNEGO token type
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug( "Unknown SPNEGO token type");
|
||||||
|
|
||||||
|
// Send back a request for SPNEGO authentication
|
||||||
|
|
||||||
|
restartLoginChallenge( resp, httpSess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(m_accountName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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 the user authentication context
|
||||||
|
|
||||||
|
SessionUser user = createUserEnvironment( httpSess, krbDetails.getUserName());
|
||||||
|
|
||||||
|
// Store the user
|
||||||
|
|
||||||
|
httpSess.setAttribute(AUTHENTICATION_USER, user);
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("User " + user.getUserName() + " logged on via Kerberos");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug( "No SPNEGO response, Kerberos logon failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the error
|
||||||
|
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Kerberos logon error", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the response SPNEGO blob
|
||||||
|
|
||||||
|
return negTokenTarg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restart the Kerberos logon process
|
||||||
|
*
|
||||||
|
* @param resp HttpServletResponse
|
||||||
|
* @param httpSess HttpSession
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void restartLoginChallenge(HttpServletResponse resp, HttpSession session) throws IOException
|
||||||
|
{
|
||||||
|
// Force the logon to start again
|
||||||
|
|
||||||
|
resp.setHeader("WWW-Authenticate", "Negotiate");
|
||||||
|
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
|
||||||
|
resp.flushBuffer();
|
||||||
|
}
|
||||||
|
}
|
@@ -25,19 +25,18 @@
|
|||||||
package org.alfresco.repo.webdav.auth;
|
package org.alfresco.repo.webdav.auth;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.FilterConfig;
|
import javax.servlet.FilterConfig;
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
@@ -46,40 +45,29 @@ import javax.transaction.UserTransaction;
|
|||||||
|
|
||||||
import net.sf.acegisecurity.BadCredentialsException;
|
import net.sf.acegisecurity.BadCredentialsException;
|
||||||
|
|
||||||
import org.alfresco.filesys.ServerConfigurationBean;
|
|
||||||
import org.alfresco.jlan.server.auth.PasswordEncryptor;
|
import org.alfresco.jlan.server.auth.PasswordEncryptor;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.NTLM;
|
import org.alfresco.jlan.server.auth.ntlm.NTLM;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.NTLMLogonDetails;
|
import org.alfresco.jlan.server.auth.ntlm.NTLMLogonDetails;
|
||||||
|
import org.alfresco.jlan.server.auth.ntlm.NTLMMessage;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.NTLMv2Blob;
|
import org.alfresco.jlan.server.auth.ntlm.NTLMv2Blob;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.TargetInfo;
|
import org.alfresco.jlan.server.auth.ntlm.TargetInfo;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage;
|
import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.Type2NTLMMessage;
|
import org.alfresco.jlan.server.auth.ntlm.Type2NTLMMessage;
|
||||||
import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage;
|
import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage;
|
||||||
import org.alfresco.jlan.server.auth.passthru.DomainMapping;
|
|
||||||
import org.alfresco.jlan.server.config.SecurityConfigSection;
|
|
||||||
import org.alfresco.jlan.util.DataPacker;
|
import org.alfresco.jlan.util.DataPacker;
|
||||||
import org.alfresco.jlan.util.IPAddress;
|
|
||||||
import org.alfresco.repo.SessionUser;
|
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.AuthenticationException;
|
||||||
import org.alfresco.repo.security.authentication.MD4PasswordEncoder;
|
import org.alfresco.repo.security.authentication.MD4PasswordEncoder;
|
||||||
import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl;
|
import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl;
|
||||||
import org.alfresco.repo.security.authentication.NTLMMode;
|
import org.alfresco.repo.security.authentication.NTLMMode;
|
||||||
import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken;
|
import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken;
|
||||||
import org.alfresco.service.ServiceRegistry;
|
|
||||||
import org.alfresco.service.cmr.repository.NodeService;
|
|
||||||
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
||||||
import org.alfresco.service.cmr.security.PersonService;
|
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
|
||||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class with common code and initialisation for NTLM authentication filters.
|
* Base class with common code and initialisation for NTLM authentication filters.
|
||||||
*/
|
*/
|
||||||
public abstract class BaseNTLMAuthenticationFilter implements Filter
|
public abstract class BaseNTLMAuthenticationFilter extends BaseSSOAuthenticationFilter
|
||||||
{
|
{
|
||||||
// NTLM authentication session object names
|
// NTLM authentication session object names
|
||||||
public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess";
|
public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess";
|
||||||
@@ -109,22 +97,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
// NTLM flags to send to the client with the allowed logon types
|
// NTLM flags to send to the client with the allowed logon types
|
||||||
private int m_ntlmFlags;
|
private int m_ntlmFlags;
|
||||||
|
|
||||||
// Servlet context, required to get authentication service
|
|
||||||
protected ServletContext m_context;
|
|
||||||
|
|
||||||
// File server configuration
|
|
||||||
private ServerConfigurationBean m_srvConfig;
|
|
||||||
|
|
||||||
// Security configuration section, for domain mappings
|
|
||||||
private SecurityConfigSection m_secConfig;
|
|
||||||
|
|
||||||
// Various services required by NTLM authenticator
|
|
||||||
protected AuthenticationService m_authService;
|
|
||||||
protected AuthenticationComponent m_authComponent;
|
|
||||||
protected PersonService m_personService;
|
|
||||||
protected NodeService m_nodeService;
|
|
||||||
protected TransactionService m_transactionService;
|
|
||||||
|
|
||||||
// Password encryptor
|
// Password encryptor
|
||||||
private PasswordEncryptor m_encryptor = new PasswordEncryptor();
|
private PasswordEncryptor m_encryptor = new PasswordEncryptor();
|
||||||
|
|
||||||
@@ -134,9 +106,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
// MD4 hash decoder
|
// MD4 hash decoder
|
||||||
private MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl();
|
private MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl();
|
||||||
|
|
||||||
// Local server name, from either the file servers config or DNS host name
|
|
||||||
protected String m_srvName;
|
|
||||||
|
|
||||||
// Allow guest access
|
// Allow guest access
|
||||||
private boolean m_allowGuest = false;
|
private boolean m_allowGuest = false;
|
||||||
|
|
||||||
@@ -155,88 +124,20 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
*/
|
*/
|
||||||
public void init(FilterConfig args) throws ServletException
|
public void init(FilterConfig args) throws ServletException
|
||||||
{
|
{
|
||||||
// Save the servlet context, needed to get hold of the authentication service
|
// Call the base SSO filter initialization
|
||||||
m_context = args.getServletContext();
|
|
||||||
|
super.init( args);
|
||||||
// Setup the authentication context
|
|
||||||
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context);
|
// Check that the authentication component supports the required mode
|
||||||
|
|
||||||
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
|
||||||
m_nodeService = serviceRegistry.getNodeService();
|
|
||||||
m_transactionService = serviceRegistry.getTransactionService();
|
|
||||||
m_authService = serviceRegistry.getAuthenticationService();
|
|
||||||
|
|
||||||
m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent");
|
|
||||||
m_personService = (PersonService) ctx.getBean("personService");
|
|
||||||
|
|
||||||
m_srvConfig = (ServerConfigurationBean) ctx.getBean(ServerConfigurationBean.SERVER_CONFIGURATION);
|
|
||||||
|
|
||||||
// Check that the authentication component supports the required mode
|
|
||||||
if (m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER &&
|
if (m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER &&
|
||||||
m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH)
|
m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH)
|
||||||
{
|
{
|
||||||
throw new ServletException("Required authentication mode not available");
|
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)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InetAddress resolved = InetAddress.getByName(m_srvName);
|
|
||||||
if (resolved == null)
|
|
||||||
{
|
|
||||||
// failed to resolve the configured name
|
|
||||||
m_srvName = m_srvConfig.getLocalServerName(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (UnknownHostException ex)
|
|
||||||
{
|
|
||||||
if (getLogger().isErrorEnabled())
|
|
||||||
getLogger().error("NTLM filter, error getting resolving host name", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_srvName = m_srvConfig.getLocalServerName(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the security configuration section
|
|
||||||
m_secConfig = (SecurityConfigSection)m_srvConfig.getConfigSection(SecurityConfigSection.SectionName);
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (getLogger().isErrorEnabled())
|
|
||||||
getLogger().error("NTLM 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if guest access is to be allowed
|
// Check if guest access is to be allowed
|
||||||
|
|
||||||
String guestAccess = args.getInitParameter("AllowGuest");
|
String guestAccess = args.getInitParameter("AllowGuest");
|
||||||
if (guestAccess != null)
|
if (guestAccess != null)
|
||||||
{
|
{
|
||||||
@@ -247,6 +148,7 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if unknown users should be mapped to guest access
|
// Check if unknown users should be mapped to guest access
|
||||||
|
|
||||||
String mapUnknownToGuest = args.getInitParameter("MapUnknownUserToGuest");
|
String mapUnknownToGuest = args.getInitParameter("MapUnknownUserToGuest");
|
||||||
if (mapUnknownToGuest != null)
|
if (mapUnknownToGuest != null)
|
||||||
{
|
{
|
||||||
@@ -258,6 +160,7 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
|
|
||||||
// Set the NTLM flags depending on the authentication component supporting MD4 passwords,
|
// Set the NTLM flags depending on the authentication component supporting MD4 passwords,
|
||||||
// or is using passthru auth
|
// or is using passthru auth
|
||||||
|
|
||||||
if (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER && m_disableNTLMv2 == false)
|
if (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER && m_disableNTLMv2 == false)
|
||||||
{
|
{
|
||||||
// Allow the client to use an NTLMv2 logon
|
// Allow the client to use an NTLMv2 logon
|
||||||
@@ -272,6 +175,179 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
// If a filter up the chain has marked the request as not requiring auth then respect it
|
||||||
|
|
||||||
|
if (req.getAttribute( NO_AUTH_REQUIRED) != null)
|
||||||
|
{
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Authentication not required (filter), chaining ...");
|
||||||
|
|
||||||
|
// Chain to the next filter
|
||||||
|
chain.doFilter(sreq, sresp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is an authorization header with an NTLM security blob
|
||||||
|
String authHdr = req.getHeader(AUTHORIZATION);
|
||||||
|
boolean reqAuth = false;
|
||||||
|
|
||||||
|
// Check if an NTLM authorization header was received
|
||||||
|
|
||||||
|
if ( authHdr != null)
|
||||||
|
{
|
||||||
|
// Check for an NTLM authorization header
|
||||||
|
|
||||||
|
if ( authHdr.startsWith(AUTH_NTLM))
|
||||||
|
reqAuth = true;
|
||||||
|
else if ( authHdr.startsWith( "Negotiate"))
|
||||||
|
{
|
||||||
|
if ( getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Received 'Negotiate' from client, may be SPNEGO/Kerberos logon");
|
||||||
|
|
||||||
|
// Restart the authentication
|
||||||
|
|
||||||
|
restartLoginChallenge(resp, httpSess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user is already authenticated
|
||||||
|
SessionUser user = getSessionUser( httpSess);
|
||||||
|
|
||||||
|
if (user != null && reqAuth == false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("User " + user.getUserName() + " validate ticket");
|
||||||
|
|
||||||
|
// Validate the user ticket
|
||||||
|
m_authService.validate(user.getTicket());
|
||||||
|
reqAuth = false;
|
||||||
|
|
||||||
|
// Filter validate hook
|
||||||
|
onValidate( req, httpSess);
|
||||||
|
}
|
||||||
|
catch (AuthenticationException ex)
|
||||||
|
{
|
||||||
|
if (getLogger().isErrorEnabled())
|
||||||
|
getLogger().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 (getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Authentication not required (user), 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 (hasLoginPage() && req.getRequestURI().endsWith(getLoginPage()) == true)
|
||||||
|
{
|
||||||
|
if (getLogger().isDebugEnabled())
|
||||||
|
getLogger().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 (getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Opera detected, redirecting to login page");
|
||||||
|
|
||||||
|
// If there is no login page configured (WebDAV) then just keep requesting the user details from the client
|
||||||
|
|
||||||
|
if ( hasLoginPage())
|
||||||
|
redirectToLoginPage(req, resp);
|
||||||
|
else
|
||||||
|
restartLoginChallenge(resp, httpSess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the authorization header
|
||||||
|
if (authHdr == null)
|
||||||
|
{
|
||||||
|
// Check for a ticket based logon, if enabled
|
||||||
|
|
||||||
|
if ( allowsTicketLogons())
|
||||||
|
{
|
||||||
|
// Check if the request includes an authentication ticket
|
||||||
|
|
||||||
|
if ( checkForTicketParameter(req, httpSess)) {
|
||||||
|
|
||||||
|
// Authentication was bypassed using a ticket parameter
|
||||||
|
|
||||||
|
chain.doFilter(sreq, sresp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
|
||||||
|
if (getLogger().isDebugEnabled())
|
||||||
|
getLogger().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 (getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("NTLM blob not handled, redirecting to login page.");
|
||||||
|
|
||||||
|
redirectToLoginPage(req, resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the servlet filter
|
* Delete the servlet filter
|
||||||
*/
|
*/
|
||||||
@@ -293,8 +369,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
{
|
{
|
||||||
Log logger = getLogger();
|
Log logger = getLogger();
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
logger.debug("Received type1 " + type1Msg);
|
getLogger().debug("Received type1 " + type1Msg);
|
||||||
|
|
||||||
// Get the existing NTLM details
|
// Get the existing NTLM details
|
||||||
NTLMLogonDetails ntlmDetails = null;
|
NTLMLogonDetails ntlmDetails = null;
|
||||||
@@ -314,8 +390,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
byte[] type2Bytes = cachedType2.getBytes();
|
byte[] type2Bytes = cachedType2.getBytes();
|
||||||
String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes));
|
String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes));
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
logger.debug("Sending cached NTLM type2 to client - " + cachedType2);
|
getLogger().debug("Sending cached NTLM type2 to client - " + cachedType2);
|
||||||
|
|
||||||
// Send back a request for NTLM authentication
|
// Send back a request for NTLM authentication
|
||||||
res.setHeader(WWW_AUTHENTICATE, ntlmBlob);
|
res.setHeader(WWW_AUTHENTICATE, ntlmBlob);
|
||||||
@@ -347,8 +423,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
domain = mapClientAddressToDomain(req.getRemoteAddr());
|
domain = mapClientAddressToDomain(req.getRemoteAddr());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
logger.debug("Client domain " + domain);
|
getLogger().debug("Client domain " + domain);
|
||||||
|
|
||||||
// Create an authentication token for the new logon
|
// Create an authentication token for the new logon
|
||||||
authToken = new NTLMPassthruToken(domain);
|
authToken = new NTLMPassthruToken(domain);
|
||||||
@@ -380,8 +456,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
|
|
||||||
session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails);
|
session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails);
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
logger.debug("Sending NTLM type2 to client - " + type2Msg);
|
getLogger().debug("Sending NTLM type2 to client - " + type2Msg);
|
||||||
|
|
||||||
// Send back a request for NTLM authentication
|
// Send back a request for NTLM authentication
|
||||||
byte[] type2Bytes = type2Msg.getBytes();
|
byte[] type2Bytes = type2Msg.getBytes();
|
||||||
@@ -439,15 +515,7 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
|
|
||||||
if (ntlmPwd != null)
|
if (ntlmPwd != null)
|
||||||
{
|
{
|
||||||
if (ntlmPwd.length == cachedPwd.length)
|
authenticated = Arrays.equals( cachedPwd, ntlmPwd);
|
||||||
{
|
|
||||||
authenticated = true;
|
|
||||||
for (int i = 0; i < ntlmPwd.length; i++)
|
|
||||||
{
|
|
||||||
if (ntlmPwd[i] != cachedPwd[i])
|
|
||||||
authenticated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
@@ -487,8 +555,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
// Indicate that the user has been authenticated
|
// Indicate that the user has been authenticated
|
||||||
authenticated = true;
|
authenticated = true;
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (getLogger().isDebugEnabled())
|
||||||
logger.debug("Guest logon");
|
getLogger().debug("Guest logon");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -635,46 +703,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback to get the specific impl of the Session User for a filter
|
|
||||||
*
|
|
||||||
* @return User from the session
|
|
||||||
*/
|
|
||||||
protected abstract SessionUser getSessionUser(HttpSession session);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback to create the User environment as appropriate for a filter impl
|
|
||||||
*
|
|
||||||
* @param session
|
|
||||||
* @param userName
|
|
||||||
*
|
|
||||||
* @return SessionUser impl
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
protected abstract SessionUser createUserEnvironment(HttpSession session, String userName)
|
|
||||||
throws IOException, ServletException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback executed on successful ticket validation during Type3 Message processing
|
|
||||||
*/
|
|
||||||
protected abstract void onValidate(HttpServletRequest req, HttpSession session);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback executed on failed authentication of a user ticket during Type3 Message processing
|
|
||||||
*/
|
|
||||||
protected abstract void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback executed on completion of NTLM login
|
|
||||||
*
|
|
||||||
* @return true to continue filter chaining, false otherwise
|
|
||||||
*/
|
|
||||||
protected abstract boolean onLoginComplete(HttpServletRequest req, HttpServletResponse res)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the MD4 hash against local password
|
* Validate the MD4 hash against local password
|
||||||
*
|
*
|
||||||
@@ -1030,6 +1058,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Restart the NTLM logon process
|
||||||
|
*
|
||||||
* @param resp
|
* @param resp
|
||||||
* @param httpSess
|
* @param httpSess
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
@@ -1046,50 +1076,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter
|
|||||||
res.flushBuffer();
|
res.flushBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Map a client IP address to a domain
|
|
||||||
*
|
|
||||||
* @param clientIP String
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
protected final String mapClientAddressToDomain(String clientIP)
|
|
||||||
{
|
|
||||||
// Check if there are any domain mappings
|
|
||||||
if (m_secConfig != null && m_secConfig.hasDomainMappings() == false)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_secConfig != null)
|
|
||||||
{
|
|
||||||
// convert the client IP address to an integer value
|
|
||||||
int clientAddr = IPAddress.parseNumericAddress(clientIP);
|
|
||||||
for (DomainMapping domainMap : m_secConfig.getDomainMappings())
|
|
||||||
{
|
|
||||||
if (domainMap.isMemberOfDomain(clientAddr))
|
|
||||||
{
|
|
||||||
if (getLogger().isDebugEnabled())
|
|
||||||
getLogger().debug("Mapped client IP " + clientIP + " to domain " + domainMap.getDomain());
|
|
||||||
|
|
||||||
return domainMap.getDomain();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getLogger().isDebugEnabled())
|
|
||||||
getLogger().debug("Failed to map client IP " + clientIP + " to a domain");
|
|
||||||
|
|
||||||
// No domain mapping for the client address
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the logger
|
|
||||||
*
|
|
||||||
* @return Log
|
|
||||||
*/
|
|
||||||
protected abstract Log getLogger();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable NTLMv2 support, must be called from the implementation constructor
|
* Disable NTLMv2 support, must be called from the implementation constructor
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,580 @@
|
|||||||
|
/*
|
||||||
|
* 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.repo.webdav.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.transaction.UserTransaction;
|
||||||
|
|
||||||
|
import org.alfresco.filesys.ServerConfigurationBean;
|
||||||
|
import org.alfresco.jlan.server.auth.ntlm.NTLM;
|
||||||
|
import org.alfresco.jlan.server.auth.passthru.DomainMapping;
|
||||||
|
import org.alfresco.jlan.server.config.SecurityConfigSection;
|
||||||
|
import org.alfresco.jlan.util.IPAddress;
|
||||||
|
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.service.ServiceRegistry;
|
||||||
|
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.PersonService;
|
||||||
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class with common code and initialisation for single signon authentication filters.
|
||||||
|
*
|
||||||
|
* @author gkspencer
|
||||||
|
* @author kroast
|
||||||
|
*/
|
||||||
|
public abstract class BaseSSOAuthenticationFilter implements Filter
|
||||||
|
{
|
||||||
|
// Constants
|
||||||
|
//
|
||||||
|
// Session value names
|
||||||
|
//
|
||||||
|
// Note: These values are copied from the AuthenticationHelper and LoginBean classes to avoid project dependencies
|
||||||
|
|
||||||
|
protected static final String AUTHENTICATION_USER = "_alfAuthTicket";
|
||||||
|
protected static final String LOGIN_EXTERNAL_AUTH = "_alfExternalAuth";
|
||||||
|
|
||||||
|
// Request level marker to indicate that authentication should not be processed
|
||||||
|
//
|
||||||
|
// Note: copied from the AbstractAuthenticationFilter to avoid project dependencies
|
||||||
|
|
||||||
|
protected static final String NO_AUTH_REQUIRED = "alfNoAuthRequired";
|
||||||
|
|
||||||
|
// Allow an authenitcation ticket to be passed as part of a request to bypass authentication
|
||||||
|
|
||||||
|
private static final String ARG_TICKET = "ticket";
|
||||||
|
|
||||||
|
// Servlet context, required to get authentication service
|
||||||
|
|
||||||
|
protected ServletContext m_context;
|
||||||
|
|
||||||
|
// File server configuration
|
||||||
|
|
||||||
|
private ServerConfigurationBean m_srvConfig;
|
||||||
|
|
||||||
|
// Security configuration section, for domain mappings
|
||||||
|
|
||||||
|
private SecurityConfigSection m_secConfig;
|
||||||
|
|
||||||
|
// Various services required by NTLM authenticator
|
||||||
|
|
||||||
|
protected AuthenticationService m_authService;
|
||||||
|
protected AuthenticationComponent m_authComponent;
|
||||||
|
protected PersonService m_personService;
|
||||||
|
protected NodeService m_nodeService;
|
||||||
|
protected TransactionService m_transactionService;
|
||||||
|
|
||||||
|
// Local server name, from either the file servers config or DNS host name
|
||||||
|
|
||||||
|
protected String m_srvName;
|
||||||
|
|
||||||
|
// Login page relative address, if null then login will loop until a valid login is received
|
||||||
|
|
||||||
|
private String m_loginPage;
|
||||||
|
|
||||||
|
// Indicate whether ticket based logons are supported
|
||||||
|
|
||||||
|
private boolean m_ticketLogons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the filter
|
||||||
|
*
|
||||||
|
* @param args FilterConfig
|
||||||
|
*
|
||||||
|
* @exception ServletException
|
||||||
|
*/
|
||||||
|
public void init(FilterConfig args) throws ServletException
|
||||||
|
{
|
||||||
|
// Save the servlet context, needed to get hold of the authentication service
|
||||||
|
|
||||||
|
m_context = args.getServletContext();
|
||||||
|
|
||||||
|
// Setup the authentication context
|
||||||
|
|
||||||
|
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context);
|
||||||
|
|
||||||
|
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||||
|
m_nodeService = serviceRegistry.getNodeService();
|
||||||
|
m_transactionService = serviceRegistry.getTransactionService();
|
||||||
|
m_authService = serviceRegistry.getAuthenticationService();
|
||||||
|
|
||||||
|
m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent");
|
||||||
|
m_personService = (PersonService) ctx.getBean("personService");
|
||||||
|
|
||||||
|
m_srvConfig = (ServerConfigurationBean) ctx.getBean(ServerConfigurationBean.SERVER_CONFIGURATION);
|
||||||
|
|
||||||
|
// Get the local server name, try the file server config first
|
||||||
|
|
||||||
|
if (m_srvConfig != null)
|
||||||
|
{
|
||||||
|
m_srvName = m_srvConfig.getServerName();
|
||||||
|
|
||||||
|
if (m_srvName != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InetAddress resolved = InetAddress.getByName(m_srvName);
|
||||||
|
if (resolved == null)
|
||||||
|
{
|
||||||
|
// Failed to resolve the configured name
|
||||||
|
|
||||||
|
m_srvName = m_srvConfig.getLocalServerName(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UnknownHostException ex)
|
||||||
|
{
|
||||||
|
if (getLogger().isErrorEnabled())
|
||||||
|
getLogger().error("NTLM filter, error resolving CIFS host name", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still do not have a name use the DNS name of the server, with the domain part removed
|
||||||
|
|
||||||
|
if ( m_srvName == null)
|
||||||
|
{
|
||||||
|
m_srvName = m_srvConfig.getLocalServerName(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the security configuration section
|
||||||
|
|
||||||
|
m_secConfig = (SecurityConfigSection)m_srvConfig.getConfigSection(SecurityConfigSection.SectionName);
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (getLogger().isErrorEnabled())
|
||||||
|
getLogger().error("NTLM 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the servlet filter
|
||||||
|
*/
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the user object that will be stored in the session
|
||||||
|
*
|
||||||
|
* @param userName String
|
||||||
|
* @param ticket String
|
||||||
|
* @param personNode NodeRef
|
||||||
|
* @param homeSpace String
|
||||||
|
* @return SessionUser
|
||||||
|
*/
|
||||||
|
protected abstract SessionUser createUserObject( String userName, String ticket, NodeRef personNode, String homeSpace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to get the specific impl of the Session User for a filter
|
||||||
|
*
|
||||||
|
* @return User from the session
|
||||||
|
*/
|
||||||
|
protected SessionUser getSessionUser(HttpSession session)
|
||||||
|
{
|
||||||
|
return (SessionUser)session.getAttribute( AUTHENTICATION_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to create the User environment as appropriate for a filter impl
|
||||||
|
*
|
||||||
|
* @param session HttpSession
|
||||||
|
* @param userName String
|
||||||
|
* @return SessionUser
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ServletException
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
|
||||||
|
NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
|
||||||
|
|
||||||
|
// Create the user object to be stored in the session
|
||||||
|
|
||||||
|
user = createUserObject( userName, currentTicket, personNodeRef, homeSpaceRef.getId());
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (Throwable ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tx.rollback();
|
||||||
|
}
|
||||||
|
catch (Exception err)
|
||||||
|
{
|
||||||
|
getLogger().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( AUTHENTICATION_USER, user);
|
||||||
|
session.setAttribute( LOGIN_EXTERNAL_AUTH, Boolean.TRUE);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback executed on successful ticket validation during Type3 Message processing
|
||||||
|
*
|
||||||
|
* @param req HttpServletReqeust
|
||||||
|
* @param session HttpSession
|
||||||
|
*/
|
||||||
|
protected void onValidate(HttpServletRequest req, HttpSession session)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback executed on failed authentication of a user ticket during Type3 Message processing
|
||||||
|
*
|
||||||
|
* @param req HttpServletRequest
|
||||||
|
* @param res HttpServletResponse
|
||||||
|
* @param session HttpSession
|
||||||
|
*/
|
||||||
|
protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback executed on completion of NTLM login
|
||||||
|
*
|
||||||
|
* @param req HttpServletRequest
|
||||||
|
* @param res HttpServletResponse
|
||||||
|
* @return true to continue filter chaining, false otherwise
|
||||||
|
*/
|
||||||
|
protected boolean onLoginComplete(HttpServletRequest req, HttpServletResponse res)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a client IP address to a domain
|
||||||
|
*
|
||||||
|
* @param clientIP String
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
protected final String mapClientAddressToDomain(String clientIP)
|
||||||
|
{
|
||||||
|
// Check if there are any domain mappings
|
||||||
|
|
||||||
|
if (m_secConfig != null && m_secConfig.hasDomainMappings() == false)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_secConfig != null)
|
||||||
|
{
|
||||||
|
// Convert the client IP address to an integer value
|
||||||
|
|
||||||
|
int clientAddr = IPAddress.parseNumericAddress(clientIP);
|
||||||
|
for (DomainMapping domainMap : m_secConfig.getDomainMappings())
|
||||||
|
{
|
||||||
|
if (domainMap.isMemberOfDomain(clientAddr))
|
||||||
|
{
|
||||||
|
if (getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Mapped client IP " + clientIP + " to domain " + domainMap.getDomain());
|
||||||
|
|
||||||
|
return domainMap.getDomain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getLogger().isDebugEnabled())
|
||||||
|
getLogger().debug("Failed to map client IP " + clientIP + " to a domain");
|
||||||
|
|
||||||
|
// No domain mapping for the client address
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the request has specified a ticket parameter to bypass the standard authentication
|
||||||
|
*
|
||||||
|
* @param req HttpServletRequest
|
||||||
|
* @param sess HttpSession
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
protected boolean checkForTicketParameter( HttpServletRequest req, HttpSession sess)
|
||||||
|
{
|
||||||
|
// 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("Logon via ticket from " + req.getRemoteHost() + " (" +
|
||||||
|
req.getRemoteAddr() + ":" + req.getRemotePort() + ")" + " ticket=" + ticket);
|
||||||
|
|
||||||
|
UserTransaction tx = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Validate the ticket
|
||||||
|
|
||||||
|
m_authService.validate(ticket);
|
||||||
|
|
||||||
|
SessionUser user = getSessionUser( sess);
|
||||||
|
|
||||||
|
if ( user == null)
|
||||||
|
{
|
||||||
|
// Start a transaction
|
||||||
|
|
||||||
|
tx = m_transactionService.getUserTransaction();
|
||||||
|
tx.begin();
|
||||||
|
|
||||||
|
// Need to create the User instance if not already available
|
||||||
|
|
||||||
|
String currentUsername = m_authService.getCurrentUserName();
|
||||||
|
|
||||||
|
NodeRef personRef = m_personService.getPerson(currentUsername);
|
||||||
|
user = createUserObject( currentUsername, m_authService.getCurrentTicket(), personRef, null);
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
tx = null;
|
||||||
|
|
||||||
|
// Store the User object in the Session - the authentication servlet will then proceed
|
||||||
|
|
||||||
|
req.getSession().setAttribute(AUTHENTICATION_USER, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the login page
|
||||||
|
*
|
||||||
|
* @param req HttpServletRequest
|
||||||
|
* @param req HttpServletResponse
|
||||||
|
* @exception IOException
|
||||||
|
*/
|
||||||
|
protected void redirectToLoginPage(HttpServletRequest req, HttpServletResponse res)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
if ( hasLoginPage())
|
||||||
|
res.sendRedirect(req.getContextPath() + "/faces" + getLoginPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the logger
|
||||||
|
*
|
||||||
|
* @return Log
|
||||||
|
*/
|
||||||
|
protected abstract Log getLogger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the login page is available
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
protected final boolean hasLoginPage()
|
||||||
|
{
|
||||||
|
return m_loginPage != null ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the login page address
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
protected final String getLoginPage()
|
||||||
|
{
|
||||||
|
return m_loginPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the login page address
|
||||||
|
*
|
||||||
|
* @param loginPage String
|
||||||
|
*/
|
||||||
|
protected final void setLoginPage( String loginPage)
|
||||||
|
{
|
||||||
|
m_loginPage = loginPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if ticket based logons are allowed
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
protected final boolean allowsTicketLogons()
|
||||||
|
{
|
||||||
|
return m_ticketLogons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ticket based logons allowed flag
|
||||||
|
*
|
||||||
|
* @param ticketsAllowed boolean
|
||||||
|
*/
|
||||||
|
protected final void setTicketLogons( boolean ticketsAllowed)
|
||||||
|
{
|
||||||
|
m_ticketLogons = ticketsAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a security blob starts with the NTLMSSP signature
|
||||||
|
*
|
||||||
|
* @param byts byte[]
|
||||||
|
* @param offset int
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
protected final boolean isNTLMSSPBlob( byte[] byts, int offset)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -25,135 +25,29 @@
|
|||||||
package org.alfresco.repo.webdav.auth;
|
package org.alfresco.repo.webdav.auth;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import javax.security.auth.Subject;
|
|
||||||
import javax.security.auth.callback.Callback;
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
|
||||||
import javax.security.auth.callback.NameCallback;
|
|
||||||
import javax.security.auth.callback.PasswordCallback;
|
|
||||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
|
||||||
import javax.security.auth.login.LoginContext;
|
|
||||||
import javax.security.auth.login.LoginException;
|
|
||||||
import javax.security.sasl.RealmCallback;
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.FilterConfig;
|
import javax.servlet.FilterConfig;
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import javax.transaction.UserTransaction;
|
|
||||||
|
|
||||||
import org.alfresco.filesys.ServerConfigurationBean;
|
import org.alfresco.repo.SessionUser;
|
||||||
import org.alfresco.jlan.server.auth.kerberos.KerberosDetails;
|
|
||||||
import org.alfresco.jlan.server.auth.kerberos.SessionSetupPrivilegedAction;
|
|
||||||
import org.alfresco.jlan.server.auth.spnego.NegTokenInit;
|
|
||||||
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.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.service.ServiceRegistry;
|
|
||||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
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.PersonService;
|
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.ietf.jgss.Oid;
|
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
|
||||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebDAV Kerberos Authentication Filter Class
|
* WebDAV Kerberos Authentication Filter Class
|
||||||
*
|
*
|
||||||
* @author GKSpencer
|
* @author GKSpencer
|
||||||
*/
|
*/
|
||||||
public class KerberosAuthenticationFilter implements Filter, CallbackHandler
|
public class KerberosAuthenticationFilter extends BaseKerberosAuthenticationFilter
|
||||||
{
|
{
|
||||||
// Constants
|
|
||||||
//
|
|
||||||
// Default login configuration entry name
|
|
||||||
|
|
||||||
private static final String LoginConfigEntry = "AlfrescoHTTP";
|
|
||||||
|
|
||||||
// Authenticated user session object name
|
|
||||||
|
|
||||||
public final static String AUTHENTICATION_USER = "_alfDAVAuthTicket";
|
|
||||||
|
|
||||||
// Allow an authenitcation ticket to be passed as part of a request to bypass authentication
|
|
||||||
|
|
||||||
private static final String ARG_TICKET = "ticket";
|
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
|
|
||||||
private static Log logger = LogFactory.getLog(KerberosAuthenticationFilter.class);
|
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;
|
|
||||||
|
|
||||||
// Login page address
|
|
||||||
|
|
||||||
private String m_loginPage;
|
|
||||||
|
|
||||||
// Local server name, from either the file servers config or DNS host name
|
|
||||||
|
|
||||||
private String m_srvName;
|
|
||||||
|
|
||||||
// Kerberos settings
|
|
||||||
//
|
|
||||||
// Account name and password for server ticket
|
|
||||||
//
|
|
||||||
// The account name must be built from the HTTP server name, in the format :-
|
|
||||||
//
|
|
||||||
// HTTP/<server_name>@<realm>
|
|
||||||
|
|
||||||
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
|
* Initialize the filter
|
||||||
*
|
*
|
||||||
@@ -162,698 +56,43 @@ public class KerberosAuthenticationFilter implements Filter, CallbackHandler
|
|||||||
*/
|
*/
|
||||||
public void init(FilterConfig args) throws ServletException
|
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
|
||||||
|
|
||||||
m_context = args.getServletContext();
|
super.init( args);
|
||||||
|
|
||||||
// Setup the authentication context
|
// Enable ticket based logons
|
||||||
|
|
||||||
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context);
|
setTicketLogons( true);
|
||||||
|
|
||||||
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_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");
|
|
||||||
|
|
||||||
// Check if Kerberos is enabled, get the Kerberos KDC address
|
|
||||||
|
|
||||||
String kdcAddress = args.getInitParameter("KDC");
|
|
||||||
|
|
||||||
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<Oid> mechTypes = new Vector<Oid>();
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* (non-Javadoc)
|
||||||
* Run the filter
|
* @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#createUserObject(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
|
||||||
*
|
*/
|
||||||
* @param sreq ServletRequest
|
@Override
|
||||||
* @param sresp ServletResponse
|
protected SessionUser createUserObject(String userName, String ticket, NodeRef personNode, String homeSpace) {
|
||||||
* @param chain FilterChain
|
|
||||||
* @exception IOException
|
// Create a WebDAV user object
|
||||||
* @exception ServletException
|
|
||||||
|
return new WebDAVUser( userName, ticket, personNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession)
|
||||||
*/
|
*/
|
||||||
public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException,
|
@Override
|
||||||
ServletException
|
protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session)
|
||||||
{
|
throws IOException
|
||||||
// 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 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
|
|
||||||
|
|
||||||
WebDAVUser user = (WebDAVUser) httpSess.getAttribute( AUTHENTICATION_USER);
|
|
||||||
|
|
||||||
if ( user != null && reqAuth == false)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Debug
|
|
||||||
|
|
||||||
if ( logger.isDebugEnabled())
|
|
||||||
logger.debug("User " + user.getUserName() + " validate ticket");
|
|
||||||
|
|
||||||
// Validate the user ticket
|
|
||||||
|
|
||||||
m_authService.validate( user.getTicket());
|
|
||||||
reqAuth = false;
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// Debug
|
|
||||||
|
|
||||||
if ( logger.isDebugEnabled())
|
|
||||||
logger.debug("Authentication not required, chaining ...");
|
|
||||||
|
|
||||||
// Chain to the next filter
|
|
||||||
|
|
||||||
chain.doFilter(sreq, sresp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the authorization header
|
|
||||||
|
|
||||||
if ( authHdr == null) {
|
|
||||||
|
|
||||||
// Check if the request includes an authentication ticket
|
|
||||||
|
|
||||||
String ticket = req.getParameter( ARG_TICKET);
|
|
||||||
|
|
||||||
if ( ticket != null && ticket.length() > 0)
|
|
||||||
{
|
|
||||||
// Debug
|
|
||||||
|
|
||||||
if ( logger.isDebugEnabled())
|
|
||||||
logger.debug("Logon via ticket from " + req.getRemoteHost() + " (" +
|
|
||||||
req.getRemoteAddr() + ":" + req.getRemotePort() + ")" + " ticket=" + ticket);
|
|
||||||
|
|
||||||
UserTransaction tx = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Validate the ticket
|
|
||||||
|
|
||||||
m_authService.validate(ticket);
|
|
||||||
|
|
||||||
// Need to create the User instance if not already available
|
|
||||||
|
|
||||||
String currentUsername = m_authService.getCurrentUserName();
|
|
||||||
|
|
||||||
// Start a transaction
|
|
||||||
|
|
||||||
tx = m_transactionService.getUserTransaction();
|
|
||||||
tx.begin();
|
|
||||||
|
|
||||||
NodeRef personRef = m_personService.getPerson(currentUsername);
|
|
||||||
user = new WebDAVUser( currentUsername, m_authService.getCurrentTicket(), personRef);
|
|
||||||
NodeRef homeRef = (NodeRef) m_nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER);
|
|
||||||
|
|
||||||
// Check that the home space node exists - else Login cannot proceed
|
|
||||||
|
|
||||||
if (m_nodeService.exists(homeRef) == false)
|
|
||||||
{
|
|
||||||
throw new InvalidNodeRefException(homeRef);
|
|
||||||
}
|
|
||||||
user.setHomeNode(homeRef);
|
|
||||||
|
|
||||||
tx.commit();
|
|
||||||
tx = null;
|
|
||||||
|
|
||||||
// Store the User object in the Session - the authentication servlet will then proceed
|
|
||||||
|
|
||||||
req.getSession().setAttribute( AUTHENTICATION_USER, user);
|
|
||||||
|
|
||||||
// Chain to the next filter
|
|
||||||
|
|
||||||
chain.doFilter(sreq, sresp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (AuthenticationException authErr)
|
|
||||||
{
|
|
||||||
// Clear the user object to signal authentication failure
|
|
||||||
|
|
||||||
user = null;
|
|
||||||
}
|
|
||||||
catch (Throwable e)
|
|
||||||
{
|
|
||||||
// Clear the user object to signal authentication failure
|
|
||||||
|
|
||||||
user = null;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (tx != null)
|
|
||||||
{
|
|
||||||
tx.rollback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception tex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
// Send back a request for SPNEGO authentication
|
|
||||||
|
|
||||||
resp.setHeader("WWW-Authenticate", "Negotiate");
|
|
||||||
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
|
|
||||||
resp.flushBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the servlet filter
|
|
||||||
*/
|
|
||||||
public void destroy()
|
|
||||||
{
|
{
|
||||||
|
// Restart the login challenge process if validation fails
|
||||||
|
|
||||||
|
restartLoginChallenge(res, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* (non-Javadoc)
|
||||||
* JAAS callback handler
|
* @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#getLogger()
|
||||||
*
|
*/
|
||||||
* @param callbacks Callback[]
|
@Override
|
||||||
* @exception IOException
|
protected Log getLogger() {
|
||||||
* @exception UnsupportedCallbackException
|
return logger;
|
||||||
*/
|
}
|
||||||
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(m_accountName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
WebDAVUser 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 WebDAVUser(userName, currentTicket, personNodeRef);
|
|
||||||
|
|
||||||
homeSpaceRef = (NodeRef) m_nodeService.getProperty( personNodeRef, ContentModel.PROP_HOMEFOLDER);
|
|
||||||
user.setHomeNode( homeSpaceRef);
|
|
||||||
|
|
||||||
// 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(AUTHENTICATION_USER, user);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -26,24 +26,14 @@ package org.alfresco.repo.webdav.auth;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterConfig;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import javax.transaction.UserTransaction;
|
|
||||||
|
|
||||||
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.SessionUser;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
@@ -54,190 +44,35 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
*/
|
*/
|
||||||
public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter
|
public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter
|
||||||
{
|
{
|
||||||
// Authenticated user session object name
|
|
||||||
public final static String AUTHENTICATION_USER = "_alfDAVAuthTicket";
|
|
||||||
|
|
||||||
// Allow an authenitcation ticket to be passed as part of a request to bypass authentication
|
|
||||||
private static final String ARG_TICKET = "ticket";
|
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
private static Log logger = LogFactory.getLog(NTLMAuthenticationFilter.class);
|
private static Log logger = LogFactory.getLog(NTLMAuthenticationFilter.class);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the filter
|
* Initialize the filter
|
||||||
*
|
*
|
||||||
* @param sreq ServletRequest
|
* @param args FilterConfig
|
||||||
* @param sresp ServletResponse
|
|
||||||
* @param chain FilterChain
|
|
||||||
* @exception IOException
|
|
||||||
* @exception ServletException
|
* @exception ServletException
|
||||||
*/
|
*/
|
||||||
public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException,
|
public void init(FilterConfig args) throws ServletException
|
||||||
ServletException
|
|
||||||
{
|
{
|
||||||
// Get the HTTP request/response/session
|
super.init(args);
|
||||||
HttpServletRequest req = (HttpServletRequest) sreq;
|
|
||||||
HttpServletResponse resp = (HttpServletResponse) sresp;
|
// Enable ticket based logons
|
||||||
HttpSession httpSess = req.getSession(true);
|
|
||||||
|
|
||||||
// Check if there is an authorization header with an NTLM security blob
|
setTicketLogons( true);
|
||||||
String authHdr = req.getHeader(AUTHORIZATION);
|
|
||||||
boolean reqAuth = (authHdr != null && authHdr.startsWith(AUTH_NTLM));
|
|
||||||
|
|
||||||
// Check if the user is already authenticated
|
|
||||||
WebDAVUser user = (WebDAVUser) httpSess.getAttribute(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;
|
|
||||||
}
|
|
||||||
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 the authorization header
|
|
||||||
if (authHdr == null)
|
|
||||||
{
|
|
||||||
// Check if the request includes an authentication ticket
|
|
||||||
String ticket = req.getParameter(ARG_TICKET);
|
|
||||||
if (ticket != null && ticket.length() != 0)
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
logger.debug("Logon via ticket from " + req.getRemoteHost() + " (" +
|
|
||||||
req.getRemoteAddr() + ":" + req.getRemotePort() + ")" + " ticket=" + ticket);
|
|
||||||
|
|
||||||
UserTransaction tx = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Validate the ticket
|
|
||||||
m_authService.validate(ticket);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
// Start a transaction
|
|
||||||
tx = m_transactionService.getUserTransaction();
|
|
||||||
tx.begin();
|
|
||||||
|
|
||||||
// Need to create the User instance if not already available
|
|
||||||
String currentUsername = m_authService.getCurrentUserName();
|
|
||||||
|
|
||||||
NodeRef personRef = m_personService.getPerson(currentUsername);
|
|
||||||
user = new WebDAVUser(currentUsername, m_authService.getCurrentTicket(), personRef);
|
|
||||||
NodeRef homeRef = (NodeRef)m_nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER);
|
|
||||||
user.setHomeNode(homeRef);
|
|
||||||
|
|
||||||
tx.commit();
|
|
||||||
tx = null;
|
|
||||||
|
|
||||||
// Store the User object in the Session - the authentication servlet will then proceed
|
|
||||||
req.getSession().setAttribute(AUTHENTICATION_USER, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chain to the next filter
|
|
||||||
chain.doFilter(sreq, sresp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (AuthenticationException authErr)
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
logger.debug("Failed to authenticate user ticket: " + authErr.getMessage(), authErr);
|
|
||||||
}
|
|
||||||
catch (Throwable e)
|
|
||||||
{
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
logger.debug("Error during ticket validation and user creation: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (tx != null)
|
|
||||||
{
|
|
||||||
tx.rollback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception tex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, restarting login challenge.");
|
|
||||||
|
|
||||||
restartLoginChallenge(resp, httpSess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getSessionUser(javax.servlet.http.HttpSession)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected SessionUser getSessionUser(HttpSession session)
|
|
||||||
{
|
|
||||||
return (SessionUser)session.getAttribute(AUTHENTICATION_USER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpSession)
|
* @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#createUserObject(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onValidate(HttpServletRequest req, HttpSession session)
|
protected SessionUser createUserObject(String userName, String ticket, NodeRef personNode, String homeSpace) {
|
||||||
{
|
|
||||||
// nothing to do for webdav filter
|
// Create a WebDAV user object
|
||||||
}
|
|
||||||
|
return new WebDAVUser( userName, ticket, personNode);
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession)
|
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession)
|
||||||
*/
|
*/
|
||||||
@@ -245,88 +80,11 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter
|
|||||||
protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session)
|
protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
// restart the login challenge process if validation fails
|
// Restart the login challenge process if validation fails
|
||||||
|
|
||||||
restartLoginChallenge(res, session);
|
restartLoginChallenge(res, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (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 WebDAVUser(userName, currentTicket, personNodeRef);
|
|
||||||
|
|
||||||
NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
|
|
||||||
((WebDAVUser)user).setHomeNode(homeSpaceRef);
|
|
||||||
|
|
||||||
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 on the session
|
|
||||||
session.setAttribute(AUTHENTICATION_USER, user);
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (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
|
|
||||||
{
|
|
||||||
// no futher processing to do, allow to complete
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getLogger()
|
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getLogger()
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user