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

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.security.authentication.ntlm.NTLMAuthenticationComponentImpl.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.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;

import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.CredentialsExpiredException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.auth.PassthruServerFactory;
import org.alfresco.jlan.server.auth.PasswordEncryptor;
import org.alfresco.jlan.server.auth.passthru.AuthSessionFactory;
import org.alfresco.jlan.server.auth.passthru.AuthenticateSession;
import org.alfresco.jlan.server.auth.passthru.PassthruServers;
import org.alfresco.jlan.smb.Protocol;
import org.alfresco.jlan.smb.SMBException;
import org.alfresco.jlan.smb.SMBStatus;
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.NTLMMode;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;

/**
 * NTLM Authentication Component Class
 * 
 * <p>Provides authentication using passthru to a Windows server(s)/domain controller(s) using the accounts
 * defined on the passthru server to validate users.
 * 
 * @author GKSpencer
 */
public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationComponent
        implements NLTMAuthenticator, InitializingBean {
    // Logging

    private static final Log logger = LogFactory.getLog(NTLMAuthenticationComponentImpl.class);

    // 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 PassthruServerFactory m_passthruServerFactory = new PassthruServerFactory();
    private PassthruServers m_passthruServers;

    // Password encryptor for generating password hash for local authentication

    private PasswordEncryptor m_encryptor;

    // Allow guest access

    private boolean m_allowGuest;

    // Allow authenticated users that do not have an Alfresco person to logon as guest

    private boolean m_allowAuthUserAsGuest;

    // 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;

    // Null domain uses any available server option

    private boolean m_nullDomainUseAnyServer;

    /**
     * Passthru Session Reaper 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 NTLMAuthenticationComponentImpl() {

        // 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();
    }

    public void afterPropertiesSet() throws Exception {
        if (m_passthruServers == null) {
            // Create the passthru authentication server list
            m_passthruServerFactory.afterPropertiesSet();

            m_passthruServers = (PassthruServers) m_passthruServerFactory.getObject();
        }
    }

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

    /**
     * Directly sets the passthru server list.
     * 
     * @param servers
     *            a passthru server list, usually created by {@link org.alfresco.filesys.auth.PassthruServerFactory}
     */
    public void setPassthruServers(PassthruServers servers) {
        m_passthruServers = servers;
    }

    /**
     * Set the domain to authenticate against
     * 
     * @param domain String
     */
    public void setDomain(String domain) {
        if (domain.length() > 0) {
            m_passthruServerFactory.setDomain(domain);
        }
    }

    /**
     * Set the server(s) to authenticate against
     * 
     * @param servers String
     */
    public void setServers(String servers) {
        if (servers.length() > 0) {
            m_passthruServerFactory.setServer(servers);
        }
    }

    /**
     * Use the local server as the authentication server
     * 
     * @param useLocal String
     */
    public void setUseLocalServer(String useLocal) {
        m_passthruServerFactory.setLocalServer(Boolean.parseBoolean(useLocal));
    }

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

    /**
     * Allow authenticated users with no alfresco person record to logon with guest access
     * 
     * @param auth String
     */
    public void setAllowAuthUserAsGuest(String auth) {
        m_allowAuthUserAsGuest = Boolean.parseBoolean(auth);
    }

    /**
     * Allow null domain passthru logons to use the first available passthru server
     * 
     * @param nullDomain String
     */
    public void setNullDomainUseAnyServer(String nullDomain) {
        m_nullDomainUseAnyServer = Boolean.parseBoolean(nullDomain);

        // Push the setting to the passthru server component

        m_passthruServers.setNullDomainUseAnyServer(m_nullDomainUseAnyServer);
    }

    /**
     * Set the JCE provider
     * 
     * @param providerClass String
     */
    public 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:" + providerClass);
            }
        } 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 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");
        }
    }

    /**
     * Set the protocol order for passthru connections
     * 
     * @param protoOrder String
     */
    public void setProtocolOrder(String protoOrder) {
        // Parse the protocol order list

        StringTokenizer tokens = new StringTokenizer(protoOrder, ",");
        int primaryProto = Protocol.None;
        int secondaryProto = Protocol.None;

        // There should only be one or two tokens

        if (tokens.countTokens() > 2)
            throw new AlfrescoRuntimeException("Invalid protocol order list, " + protoOrder);

        // Get the primary protocol

        if (tokens.hasMoreTokens()) {
            // Parse the primary protocol

            String primaryStr = tokens.nextToken();

            if (primaryStr.equalsIgnoreCase("TCPIP"))
                primaryProto = Protocol.NativeSMB;
            else if (primaryStr.equalsIgnoreCase("NetBIOS"))
                primaryProto = Protocol.TCPNetBIOS;
            else
                throw new AlfrescoRuntimeException("Invalid protocol type, " + primaryStr);

            // Check if there is a secondary protocol, and validate

            if (tokens.hasMoreTokens()) {
                // Parse the secondary protocol

                String secondaryStr = tokens.nextToken();

                if (secondaryStr.equalsIgnoreCase("TCPIP") && primaryProto != Protocol.NativeSMB)
                    secondaryProto = Protocol.NativeSMB;
                else if (secondaryStr.equalsIgnoreCase("NetBIOS") && primaryProto != Protocol.TCPNetBIOS)
                    secondaryProto = Protocol.TCPNetBIOS;
                else
                    throw new AlfrescoRuntimeException("Invalid secondary protocol, " + secondaryStr);
            }
        }

        // Set the protocol order used for passthru authentication sessions

        AuthSessionFactory.setProtocolOrder(primaryProto, secondaryProto);

        // DEBUG

        if (logger.isDebugEnabled()) {
            logger.debug("Protocol order primary=" + Protocol.asString(primaryProto) + ", secondary="
                    + Protocol.asString(secondaryProto));
        }
    }

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

    /**
     * Authenticate
     * 
     * @param userName String
     * @param password char[]
     * @throws AuthenticationException
     */
    protected void authenticateImpl(String userName, char[] password) throws AuthenticationException {
        // Debug

        if (logger.isDebugEnabled()) {
            logger.debug("Authenticate user=" + userName + " via local credentials");
        }

        // Create a local authentication token

        NTLMLocalToken authToken = new NTLMLocalToken(userName, new String(password));

        // Authenticate using the token

        authenticate(authToken);

    }

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

        if (logger.isDebugEnabled()) {
            logger.debug("Authenticate " + auth + " via token");
        }

        // 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();

                // Check fi the passthru session is valid

                if (authSess == null) {
                    // DEBUG

                    if (logger.isDebugEnabled()) {
                        logger.debug("Failed to open passthru session, or no valid passthru server available for "
                                + ntlmToken);
                    }

                    throw new AuthenticationException("authentication.err.connection.passthru.server");
                }

                // 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) {
                    }
                }
            }
        } else {
            // Unsupported authentication token

            throw new AuthenticationException("authentication.err.passthru.token.unsupported");
        }

        // Return the updated authentication token

        return getCurrentAuthentication();
    }

    /**
     * Get the enum that describes NTLM integration
     * 
     * @return NTLMMode
     */
    public NTLMMode getNTLMMode() {
        return NTLMMode.PASS_THROUGH;
    }

    /**
     * Get the MD4 password hash, as required by NTLM based authentication methods.
     * 
     * @param userName String
     * @return String
     */
    public String getMD4HashedPassword(String userName) {
        // Do not support MD4 hashed password

        throw new AlfrescoRuntimeException("MD4 passwords not supported");
    }

    /**
     * Authenticate a user using local credentials
     * 
     * @param ntlmToken NTLMLocalToken
     * @param authSess AuthenticateSession
     * @throws org.alfresco.repo.security.authentication.AuthenticationException
     */
    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[2];
                    authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
                    authorities[1] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");

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

                    throw new AuthenticationException("authentication.err.passthru.guest.notenabled");
                }
            } else {
                // Set authorities

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

                ntlmToken.setAuthorities(authorities);
            }

            // Indicate that the token is authenticated

            ntlmToken.setAuthenticated(true);

            // Map the passthru username to an Alfresco person

            clearCurrentSecurityContext();
            setCurrentUser(username);

            // Debug

            if (logger.isDebugEnabled()) {
                logger.debug("Authenticated token=" + ntlmToken);
            }
        } catch (NoSuchAlgorithmException ex) {
            // JCE provider does not have the required encryption/hashing algorithms

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

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

            throw new AuthenticationException("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 AuthenticationException("Logon failure");
                    break;
                case SMBStatus.NTAccountDisabled:
                    authEx = new AuthenticationException("authentication.err.passthru.user.disabled");
                    break;
                default:
                    authEx = new AuthenticationException("Logon failure");
                    break;
                }

                throw authEx;
            } else {
                throw new AuthenticationException("Logon failure");
            }
        }
    }

    /**
     * Authenticate using passthru authentication with a client
     * 
     * @param ntlmToken NTLMPassthruToken
     * @throws org.alfresco.repo.security.authentication.AuthenticationException
     */
    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 AuthenticationException("Authentication session expired");
            }

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

            authSess = m_passthruServers.openSession(false, ntlmToken.getClientDomain());

            // Check if the session was opened to the passthru server

            if (authSess == null) {
                throw new AuthenticationException("authentication.err.connection.passthru.server");
            }

            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 AuthenticationException("authentication.err.passthru.guest.notenabled");
                    }
                }

                // Indicate that the token is authenticated

                ntlmToken.setAuthenticated(true);

                // Wrap the service calls in a transaction

                RetryingTransactionHelper helper = getTransactionService().getRetryingTransactionHelper();

                final String currentUser = username;

                helper.doInTransaction(new RetryingTransactionCallback<Void>() {
                    public Void execute() throws AuthenticationException {
                        clearCurrentSecurityContext();
                        setCurrentUser(currentUser);
                        return null;
                    }
                });
            } catch (NoSuchPersonException ex) {
                // Check if authenticated users are allowed on as guest when there is no Alfresco person record

                if (m_allowAuthUserAsGuest == true) {
                    // Set the guest authority

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

                    ntlmToken.setAuthorities(authorities);

                    // DEBUG

                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "Allow passthru authenticated user to logon as guest, user=" + ntlmToken.getName());
                    }
                } else {
                    // Logon failure, no matching person record
                    throw new AuthenticationException("authentication.err.passthru.user.notfound", ex);
                }
            } catch (IOException ex) {
                // Error connecting to the authentication server
                throw new AuthenticationException("Unable to connect to the authentication server", ex);
            } catch (SMBException ex) {
                // Debug

                if (logger.isDebugEnabled()) {
                    logger.debug("Passthru exception, " + 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 AuthenticationException("Logon failure");
                        break;
                    case SMBStatus.NTAccountDisabled:
                        authEx = new AuthenticationException("authentication.err.passthru.user.disabled");
                        break;
                    default:
                        authEx = new AuthenticationException("Logon failure");
                        break;
                    }

                    throw authEx;
                } else {
                    throw new AuthenticationException("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) {
                        logger.debug("unable to close session", ex);
                    }
                }
            }
        }
    }

    /**
     * Check if the user exists
     * 
     * @param userName String
     * @return boolean
     */
    public boolean exists(String userName) {
        throw new UnsupportedOperationException();
    }

    @Override
    protected boolean implementationAllowsGuestLogin() {
        return allowsGuest();
    }

}