org.alfresco.repo.security.authentication.ntlm.NTLMAuthenticationProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.security.authentication.ntlm.NTLMAuthenticationProvider.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.security.authentication.ntlm;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.Enumeration;
import java.util.Hashtable;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.jlan.server.auth.PasswordEncryptor;
import org.alfresco.jlan.server.auth.passthru.AuthenticateSession;
import org.alfresco.jlan.server.auth.passthru.PassthruServers;
import org.alfresco.jlan.smb.SMBException;
import org.alfresco.jlan.smb.SMBStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.acegisecurity.*;
import net.sf.acegisecurity.providers.*;

/**
 * NTLM Authentication Provider
 * 
 * @author GKSpencer
 */
public class NTLMAuthenticationProvider implements AuthenticationProvider {
    private static final Log logger = LogFactory.getLog("org.alfresco.acegi");

    // Constants
    //
    // Standard authorities

    public static final String NTLMAuthorityGuest = "Guest";
    public static final String NTLMAuthorityAdministrator = "Administrator";

    // Active session timeout

    private static final long DefaultSessionTimeout = 60000L; // 1 minute
    private static final long MinimumSessionTimeout = 5000L; // 5 seconds

    // Passthru authentication servers

    private PassthruServers m_passthruServers;

    // Password encryptor for generating password hash for local authentication

    private PasswordEncryptor m_encryptor;

    // Allow guest access

    private boolean m_allowGuest;

    // Table of currently active passthru authentications and the associated authentication session
    //
    // If the two authentication stages are not completed within a reasonable time the authentication
    // session will be closed by the reaper thread.

    private Hashtable<NTLMPassthruToken, AuthenticateSession> m_passthruSessions;

    // Active authentication session timeout, in milliseconds

    private long m_passthruSessTmo = DefaultSessionTimeout;

    // Authentication session reaper thread

    private PassthruReaperThread m_reaperThread;

    /**
     * Passthru Session Repear Thread
     */
    class PassthruReaperThread extends Thread {
        // Thread shutdown request flag

        private boolean m_ishutdown;

        // Reaper wakeup interval, in milliseconds

        private long m_wakeupInterval = m_passthruSessTmo / 2;

        /**
         * Default constructor
         */
        PassthruReaperThread() {
            setDaemon(true);
            setName("PassthruReaper");
            start();
        }

        /**
         * Set the wakeup interval
         * 
         * @param wakeup long
         */
        public final void setWakeup(long wakeup) {
            m_wakeupInterval = wakeup;
        }

        /**
         * Main thread code
         */
        public void run() {
            // Loop until shutdown

            m_ishutdown = false;

            while (m_ishutdown == false) {
                // Sleep for a while

                try {
                    sleep(m_wakeupInterval);
                } catch (InterruptedException ex) {
                }

                // Check if there are any active sessions to check

                if (m_passthruSessions.size() > 0) {
                    // Enumerate the active sessions

                    Enumeration<NTLMPassthruToken> tokenEnum = m_passthruSessions.keys();
                    long timeNow = System.currentTimeMillis();

                    while (tokenEnum.hasMoreElements()) {
                        // Get the current NTLM token and check if it has expired

                        NTLMPassthruToken ntlmToken = tokenEnum.nextElement();

                        if (ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow) {
                            // Authentication token has expired, close the associated authentication session

                            AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
                            if (authSess != null) {
                                try {
                                    // Close the authentication session

                                    authSess.CloseSession();
                                } catch (Exception ex) {
                                    // Debug

                                    if (logger.isDebugEnabled())
                                        logger.debug("Error closing expired authentication session", ex);
                                }
                            }

                            // Remove the expired token from the active list

                            m_passthruSessions.remove(ntlmToken);

                            // Debug

                            if (logger.isDebugEnabled())
                                logger.debug("Removed expired NTLM token " + ntlmToken);
                        }
                    }
                }
            }

            // Debug

            if (logger.isDebugEnabled())
                logger.debug("Passthru reaper thread shutdown");
        }

        /**
         * Shutdown the reaper thread
         */
        public final void shutdownRequest() {
            m_ishutdown = true;
            this.interrupt();
        }
    }

    /**
     * Class constructor
     */
    public NTLMAuthenticationProvider() {

        // Create the passthru authentication server list

        m_passthruServers = new PassthruServers();

        // Create the password encryptor for local password hashing

        m_encryptor = new PasswordEncryptor();

        // Create the active session list and reaper thread

        m_passthruSessions = new Hashtable<NTLMPassthruToken, AuthenticateSession>();
        m_reaperThread = new PassthruReaperThread();
    }

    /**
     * Authenticate a user
     * 
     * @param auth Authentication
     * @return Authentication
     * @exception AuthenticationException
     */
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        // DEBUG

        if (logger.isDebugEnabled())
            logger.debug("Authenticate " + auth);

        // Check if the token is for passthru authentication

        if (auth instanceof NTLMPassthruToken) {
            // Access the NTLM passthru token

            NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth;

            // Authenticate using passthru

            authenticatePassthru(ntlmToken);
        }

        // Check for a local authentication token

        else if (auth instanceof NTLMLocalToken) {
            AuthenticateSession authSess = null;

            try {

                // Access the NTLM token

                NTLMLocalToken ntlmToken = (NTLMLocalToken) auth;

                // Open a session to an authentication server

                authSess = m_passthruServers.openSession();

                // Authenticate using the credentials supplied

                authenticateLocal(ntlmToken, authSess);
            } finally {
                // Make sure the authentication session is closed

                if (authSess != null) {
                    try {
                        authSess.CloseSession();
                    } catch (Exception ex) {
                    }
                }
            }
        }

        // Return the updated authentication token

        return auth;
    }

    /**
     * Determine if this provider supports the specified authentication token
     * 
     * @param authentication Class
     */
    public boolean supports(Class authentication) {
        // Check if the authentication is an NTLM authentication token

        if (NTLMPassthruToken.class.isAssignableFrom(authentication))
            return true;
        return NTLMLocalToken.class.isAssignableFrom(authentication);
    }

    /**
     * Determine if guest logons are allowed
     * 
     * @return boolean
     */
    public final boolean allowsGuest() {
        return m_allowGuest;
    }

    /**
     * Set the domain to authenticate against
     * 
     * @param domain String
     */
    public final void setDomain(String domain) {

        // Check if the passthru server list is already configured

        if (m_passthruServers.getTotalServerCount() > 0)
            throw new AlfrescoRuntimeException("Passthru server list already configured");

        // Configure the passthru authentication server list using the domain controllers

        try {
            m_passthruServers.setDomain(domain);
        } catch (IOException ex) {
            throw new AlfrescoRuntimeException("Failed to set passthru domain", ex);
        }
    }

    /**
     * Set the server(s) to authenticate against
     * 
     * @param servers String
     */
    public final void setServers(String servers) {

        // Check if the passthru server list is already configured

        if (m_passthruServers.getTotalServerCount() > 0)
            throw new AlfrescoRuntimeException("Passthru server list already configured");

        // Configure the passthru authenticaiton list using a list of server names/addresses

        m_passthruServers.setServerList(servers);
    }

    /**
     * Use the local server as the authentication server
     * 
     * @param useLocal String
     */
    public final void setUseLocalServer(String useLocal) {
        // Check if the local server should be used for authentication

        if (Boolean.parseBoolean(useLocal) == true) {
            // Check if the passthru server list is already configured

            if (m_passthruServers.getTotalServerCount() > 0)
                throw new AlfrescoRuntimeException("Passthru server list already configured");

            try {
                // Get the list of local network addresses

                InetAddress[] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());

                // Build the list of local addresses

                if (localAddrs != null && localAddrs.length > 0) {
                    StringBuilder addrStr = new StringBuilder();

                    for (InetAddress curAddr : localAddrs) {
                        if (curAddr.isLoopbackAddress() == false) {
                            addrStr.append(curAddr.getHostAddress());
                            addrStr.append(",");
                        }
                    }

                    if (addrStr.length() > 0)
                        addrStr.setLength(addrStr.length() - 1);

                    // Set the server list using the local address list

                    m_passthruServers.setServerList(addrStr.toString());
                } else
                    throw new AlfrescoRuntimeException("No local server address(es)");
            } catch (UnknownHostException ex) {
                throw new AlfrescoRuntimeException("Failed to get local address list");
            }
        }
    }

    /**
     * Allow guest access
     * 
     * @param guest String
     */
    public final void setGuestAccess(String guest) {
        m_allowGuest = Boolean.parseBoolean(guest);
    }

    /**
     * Set the JCE provider
     * 
     * @param providerClass String
     */
    public final void setJCEProvider(String providerClass) {
        // Set the JCE provider, required to provide various encryption/hashing algorithms not available
        // in the standard Sun JDK/JRE

        try {

            // Load the JCE provider class and validate

            Object jceObj = Class.forName(providerClass).newInstance();
            if (jceObj instanceof java.security.Provider) {

                // Inform listeners, validate the configuration change

                Provider jceProvider = (Provider) jceObj;

                // Add the JCE provider

                Security.addProvider(jceProvider);

                // Debug

                if (logger.isDebugEnabled())
                    logger.debug("Using JCE provider " + providerClass);
            } else {
                throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class");
            }
        } catch (ClassNotFoundException ex) {
            throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found");
        } catch (Exception ex) {
            throw new AlfrescoRuntimeException("JCE provider class error", ex);
        }
    }

    /**
     * Set the authentication session timeout, in seconds
     * 
     * @param sessTmo String
     */
    public final void setSessionTimeout(String sessTmo) {
        // Convert to an integer value and range check the timeout value

        try {
            // Convert to an integer value

            long sessTmoMilli = Long.parseLong(sessTmo) * 1000L;

            if (sessTmoMilli < MinimumSessionTimeout)
                throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo);

            // Set the authentication session timeout value

            m_passthruSessTmo = sessTmoMilli;

            // Set the reaper thread wakeup interval

            m_reaperThread.setWakeup(sessTmoMilli / 2);
        } catch (NumberFormatException ex) {
            throw new AlfrescoRuntimeException("Invalid authenication session timeout value");
        }
    }

    /**
     * Return the authentication session timeout, in milliseconds
     * 
     * @return long
     */
    private final long getSessionTimeout() {
        return m_passthruSessTmo;
    }

    /**
     * Authenticate a user using local credentials
     * 
     * @param ntlmToken NTLMLocalToken
     * @param authSess AuthenticateSession
     */
    private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess) {
        try {
            // Get the plaintext password and generate an NTLM1 password hash

            String username = (String) ntlmToken.getPrincipal();
            String plainPwd = (String) ntlmToken.getCredentials();
            byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword(plainPwd, authSess.getEncryptionKey(),
                    PasswordEncryptor.NTLM1, null, null);

            // Send the logon request to the authentication server
            //
            // Note: Only use the stronger NTLM hash, we do not send the LM hash

            authSess.doSessionSetup(username, null, ntlm1Pwd);

            // Check if the session has logged on as a guest

            if (authSess.isGuest() || username.equalsIgnoreCase("GUEST")) {
                // If guest access is enabled add a guest authority to the token

                if (allowsGuest()) {
                    // Set the guest authority

                    GrantedAuthority[] authorities = new GrantedAuthority[1];
                    authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);

                    ntlmToken.setAuthorities(authorities);
                } else {
                    // Guest access not allowed

                    throw new BadCredentialsException("Guest logons disabled");
                }
            }

            // Indicate that the token is authenticated

            ntlmToken.setAuthenticated(true);
        } catch (NoSuchAlgorithmException ex) {
            // JCE provider does not have the required encryption/hashing algorithms

            throw new AuthenticationServiceException("JCE provider error", ex);
        } catch (InvalidKeyException ex) {
            // Problem creating key during encryption

            throw new AuthenticationServiceException("Invalid key error", ex);
        } catch (IOException ex) {
            // Error connecting to the authentication server

            throw new AuthenticationServiceException("I/O error", ex);
        } catch (SMBException ex) {
            // Check the returned status code to determine why the logon failed and throw an appropriate exception

            if (ex.getErrorClass() == SMBStatus.NTErr) {
                AuthenticationException authEx = null;

                switch (ex.getErrorCode()) {
                case SMBStatus.NTLogonFailure:
                    authEx = new BadCredentialsException("Logon failure");
                    break;
                case SMBStatus.NTAccountDisabled:
                    authEx = new DisabledException("Account disabled");
                    break;
                default:
                    authEx = new BadCredentialsException("Logon failure");
                    break;
                }

                throw authEx;
            } else
                throw new BadCredentialsException("Logon failure");
        }
    }

    /**
     * Authenticate using passthru authentication with a client
     * 
     * @param ntlmToken NTLMPassthruToken
     */
    private void authenticatePassthru(NTLMPassthruToken ntlmToken) {
        // Check if the token has an authentication session, if not then it is either a new token
        // or the session has been timed out

        AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);

        if (authSess == null) {
            // Check if the token has a challenge, if it does then the associated session has been
            // timed out

            if (ntlmToken.getChallenge() != null)
                throw new CredentialsExpiredException("Authentication session expired");

            // Open an authentication session for the new token and add to the active session list

            authSess = m_passthruServers.openSession();

            ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout());

            // Get the challenge from the initial session negotiate stage

            ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey()));

            StringBuilder details = new StringBuilder();

            // Build a details string with the authentication session details

            details.append(authSess.getDomain());
            details.append("\\");
            details.append(authSess.getPCShare().getNodeName());
            details.append(",");
            details.append(authSess.getSession().getProtocolName());

            ntlmToken.setDetails(details.toString());

            // Put the token/session into the active session list

            m_passthruSessions.put(ntlmToken, authSess);

            // Debug

            if (logger.isDebugEnabled())
                logger.debug("Passthru stage 1 token " + ntlmToken);
        } else {
            try {
                // Stage two of the authentication, send the hashed password to the authentication server

                byte[] lmPwd = null;
                byte[] ntlmPwd = null;

                if (ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN)
                    lmPwd = ntlmToken.getHashedPassword();
                else if (ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1)
                    ntlmPwd = ntlmToken.getHashedPassword();

                String username = (String) ntlmToken.getPrincipal();

                authSess.doSessionSetup(username, lmPwd, ntlmPwd);

                // Check if the session has logged on as a guest

                if (authSess.isGuest() || username.equalsIgnoreCase("GUEST")) {
                    // If guest access is enabled add a guest authority to the token

                    if (allowsGuest()) {
                        // Set the guest authority

                        GrantedAuthority[] authorities = new GrantedAuthority[1];
                        authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);

                        ntlmToken.setAuthorities(authorities);
                    } else {
                        // Guest access not allowed

                        throw new BadCredentialsException("Guest logons disabled");
                    }
                }

                // Indicate that the token is authenticated

                ntlmToken.setAuthenticated(true);
            } catch (IOException ex) {
                // Error connecting to the authentication server

                throw new AuthenticationServiceException("I/O error", ex);
            } catch (SMBException ex) {
                // Check the returned status code to determine why the logon failed and throw an appropriate exception

                if (ex.getErrorClass() == SMBStatus.NTErr) {
                    AuthenticationException authEx = null;

                    switch (ex.getErrorCode()) {
                    case SMBStatus.NTLogonFailure:
                        authEx = new BadCredentialsException("Logon failure");
                        break;
                    case SMBStatus.NTAccountDisabled:
                        authEx = new DisabledException("Account disabled");
                        break;
                    default:
                        authEx = new BadCredentialsException("Logon failure");
                        break;
                    }

                    throw authEx;
                } else
                    throw new BadCredentialsException("Logon failure");
            } finally {
                // Make sure the authentication session is closed

                if (authSess != null) {
                    try {
                        // Remove the session from the active list

                        m_passthruSessions.remove(ntlmToken);

                        // Close the session to the authentication server

                        authSess.CloseSession();
                    } catch (Exception ex) {
                    }
                }
            }
        }
    }
}