/*
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package org.alfresco.repo.security.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.context.Context;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextImpl;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.User;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.GUID;
public class TestAuthenticationServiceImpl implements MutableAuthenticationService
{
    private Map userNamesAndPasswords = new HashMap();
    private Set disabledUsers = new HashSet();
    private Map userToTicket = new HashMap();
    String domain;
    boolean allowCreate;
    boolean allowDelete;
    boolean allowUpdate;
    boolean allowGuest;
    public TestAuthenticationServiceImpl(String domain, boolean allowCreate, boolean allowDelete, boolean allowUpdate, boolean allowGuest)
    {
        super();
        this.domain = domain;
        this.allowCreate = allowCreate;
        this.allowDelete = allowDelete;
        this.allowUpdate = allowUpdate;
        this.allowGuest = allowGuest;
    }
    public TestAuthenticationServiceImpl(String domain, boolean allowCreate, boolean allowDelete, boolean allowUpdate,  boolean allowGuest,
            Map users, Set disabled)
    {
        this(domain, allowCreate, allowDelete, allowUpdate, allowGuest);
        if (users != null)
        {
            userNamesAndPasswords.putAll(users);
        }
        if (disabled != null)
        {
            disabledUsers.addAll(disabled);
        }
    }
    public void createAuthentication(String userName, char[] password) throws AuthenticationException
    {
        if (!allowCreate)
        {
            throw new AuthenticationException("Create not allowed");
        }
        if (userNamesAndPasswords.containsKey(userName))
        {
            throw new AuthenticationException("User exists");
        }
        else
        {
            userNamesAndPasswords.put(userName, new String(password));
        }
    }
    public void updateAuthentication(String userName, char[] oldPassword, char[] newPassword)
            throws AuthenticationException
    {
        if (!allowUpdate)
        {
            throw new AuthenticationException("Update not allowed");
        }
        if (!userNamesAndPasswords.containsKey(userName))
        {
            throw new AuthenticationException("User does not exist");
        }
        else
        {
            if (userNamesAndPasswords.get(userName).equals(new String(oldPassword)))
            {
                userNamesAndPasswords.put(userName, new String(newPassword));
            }
            else
            {
                throw new AuthenticationException("Password does not match existing");
            }
        }
    }
    public void setAuthentication(String userName, char[] newPassword) throws AuthenticationException
    {
        if (!allowUpdate)
        {
            throw new AuthenticationException("Update not allowed");
        }
        if (!userNamesAndPasswords.containsKey(userName))
        {
            throw new AuthenticationException("User does not exist");
        }
        else
        {
            userNamesAndPasswords.put(userName, new String(newPassword));
        }
    }
    public void deleteAuthentication(String userName) throws AuthenticationException
    {
        if (!allowDelete)
        {
            throw new AuthenticationException("Delete not allowed");
        }
        if (!userNamesAndPasswords.containsKey(userName))
        {
            throw new AuthenticationException("User does not exist");
        }
        else
        {
            userNamesAndPasswords.remove(userName);
        }
    }
    public void setAuthenticationEnabled(String userName, boolean enabled) throws AuthenticationException
    {
        if (!allowUpdate)
        {
            throw new AuthenticationException("Update not allowed");
        }
        if (!userNamesAndPasswords.containsKey(userName))
        {
            throw new AuthenticationException("User does not exist");
        }
        else
        {
            if (enabled)
            {
                disabledUsers.remove(userName);
            }
            else
            {
                disabledUsers.add(userName);
            }
        }
    }
    public boolean getAuthenticationEnabled(String userName) throws AuthenticationException
    {
        if (!userNamesAndPasswords.containsKey(userName))
        {
            return false;
        }
        else
        {
            return !disabledUsers.contains(userName);
        }
    }
    public void authenticate(String userName, char[] password) throws AuthenticationException
    {
        if (!userNamesAndPasswords.containsKey(userName))
        {
            throw new AuthenticationException("User does not exist");
        }
        else if (disabledUsers.contains(userName))
        {
            throw new AuthenticationException("User disabled0");
        }
        else
        {
            if (userNamesAndPasswords.get(userName).equals(new String(password)))
            {
                setCurrentUser(userName);
            }
            else
            {
                throw new AuthenticationException("Unknown user/password");
            }
        }
    }
    public void authenticateAsGuest() throws AuthenticationException
    {
        if (allowGuest)
        {
            setCurrentUser(AuthenticationUtil.getGuestUserName());
        }
        else
        {
            throw new AuthenticationException("Guest access denied");
        }
    }
    
    public boolean guestUserAuthenticationAllowed()
    {
        return allowGuest;
    }
    public boolean authenticationExists(String userName)
    {
        return userNamesAndPasswords.containsKey(userName);
    }
        
    public boolean isAuthenticationMutable(String userName)
    {
        return authenticationExists(userName);
    }
    
    public boolean isAuthenticationCreationAllowed()
    {
        return allowCreate;
    }
    public String getCurrentUserName() throws AuthenticationException
    {
        Context context = ContextHolder.getContext();
        if ((context == null) || !(context instanceof SecureContext))
        {
            return null;
        }
        return getUserName(((SecureContext) context).getAuthentication());
    }
    private String getUserName(Authentication authentication)
    {
        String username = authentication.getPrincipal().toString();
        if (authentication.getPrincipal() instanceof UserDetails)
        {
            username = ((UserDetails) authentication.getPrincipal()).getUsername();
        }
        return username;
    }
    public void invalidateUserSession(String userName) throws AuthenticationException
    {
        userToTicket.remove(userName);
    }
    public void invalidateTicket(String ticket) throws AuthenticationException
    {
        String userToRemove = null;
        for (String user : userToTicket.keySet())
        {
            String currentTicket = userToTicket.get(user);
            if (EqualsHelper.nullSafeEquals(currentTicket, ticket))
            {
                userToRemove = user;
            }
        }
        if (userToRemove != null)
        {
            userToTicket.remove(userToRemove);
        }
    }
    public void validate(String ticket) throws AuthenticationException
    {
        String userToSet = null;
        for (String user : userToTicket.keySet())
        {
            String currentTicket = userToTicket.get(user);
            if (EqualsHelper.nullSafeEquals(currentTicket, ticket))
            {
                userToSet = user;
            }
        }
        if (userToSet != null)
        {
            setCurrentUser(userToSet);
        }
        else
        {
            throw new AuthenticationException("Invalid ticket");
        }
    }
    public String getCurrentTicket()
    {
        String currentUser = getCurrentUserName();
        String ticket = userToTicket.get(currentUser);
        if (ticket == null)
        {
            ticket = GUID.generate();
            userToTicket.put(currentUser, ticket);
        }
        return ticket;
    }
    
    public String getNewTicket()
    {
        String currentUser = getCurrentUserName();
        String ticket = userToTicket.get(currentUser);
        if (ticket == null)
        {
            ticket = GUID.generate();
            userToTicket.put(currentUser, ticket);
        }
        return ticket;
    }
    public void clearCurrentSecurityContext()
    {
        ContextHolder.setContext(null);
    }
    public boolean isCurrentUserTheSystemUser()
    {
        String userName = getCurrentUserName();
        if ((userName != null) && userName.equals(SYSTEM_USER_NAME))
        {
            return true;
        }
        return false;
    }
    public Set getDomains()
    {
        return Collections.singleton(domain);
    }
    public Set getDomainsThatAllowUserCreation()
    {
        if (allowCreate)
        {
            return Collections.singleton(domain);
        }
        else
        {
            return Collections. emptySet();
        }
    }
    public Set getDomainsThatAllowUserDeletion()
    {
        if (allowDelete)
        {
            return Collections.singleton(domain);
        }
        else
        {
            return Collections. emptySet();
        }
    }
    public Set getDomiansThatAllowUserPasswordChanges()
    {
        if (allowUpdate)
        {
            return Collections.singleton(domain);
        }
        else
        {
            return Collections. emptySet();
        }
    }
    /**
     * Explicitly set the current user to be authenticated.
     * 
     * @param userName
     *            String
     * @return Authentication
     */
    public Authentication setCurrentUser(String userName) throws AuthenticationException
    {
        if (userName == null)
        {
            throw new AuthenticationException("Null user name");
        }
        try
        {
            UserDetails ud = null;
            if (userName.equals(SYSTEM_USER_NAME))
            {
                GrantedAuthority[] gas = new GrantedAuthority[1];
                gas[0] = new GrantedAuthorityImpl("ROLE_SYSTEM");
                ud = new User(SYSTEM_USER_NAME, "", true, true, true, true, gas);
            }
            else if (userName.equalsIgnoreCase(AuthenticationUtil.getGuestUserName()))
            {
                GrantedAuthority[] gas = new GrantedAuthority[0];
                ud = new User(AuthenticationUtil.getGuestUserName().toLowerCase(), "", true, true, true, true, gas);
            }
            else
            {
                ud = getUserDetails(userName);
            }
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(ud, "", ud
                    .getAuthorities());
            auth.setDetails(ud);
            auth.setAuthenticated(true);
            return setCurrentAuthentication(auth);
        }
        catch (net.sf.acegisecurity.AuthenticationException ae)
        {
            throw new AuthenticationException(ae.getMessage(), ae);
        }
    }
    /**
     * Default implementation that makes an ACEGI object on the fly
     * 
     * @param userName
     * @return
     */
    protected UserDetails getUserDetails(String userName)
    {
        GrantedAuthority[] gas = new GrantedAuthority[1];
        gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
        UserDetails ud = new User(userName, "", true, true, true, true, gas);
        return ud;
    }
    public Authentication setCurrentAuthentication(Authentication authentication)
    {
        Context context = ContextHolder.getContext();
        SecureContext sc = null;
        if ((context == null) || !(context instanceof SecureContext))
        {
            sc = new SecureContextImpl();
            ContextHolder.setContext(sc);
        }
        else
        {
            sc = (SecureContext) context;
        }
        authentication.setAuthenticated(true);
        sc.setAuthentication(authentication);
        return authentication;
    }
    public Set getDefaultAdministratorUserNames()
    {
        return Collections.singleton(AuthenticationUtil.getAdminUserName());
    }
    public Set getDefaultGuestUserNames()
    {
        return Collections.singleton(AuthenticationUtil.getGuestUserName());
    }
    private static final String SYSTEM_USER_NAME = "System";
}