/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.fw.security;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;

import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.context.SecurityContextHolder;

import gov.va.med.fw.model.UserPrincipalImpl;

/**
 * Helper class to provide SecurityContext related methods. 
 * This class provides methods to load the
 * SecurityContext from a "well known place". For web, well known place is
 * HttpSession, it returns the SecurityContext stored in the HttpSession at the
 * time of logon. For non-web, it creates the SecurityContext from
 * Authentication Object stored in a ThreadLocal bound variable.
 * 
 * @author DNS   MANSOG
 * @date May 5, 2005
 */
public class SecurityContextHelper {

    /**
     * Find and return the SecurityContext from a well known location, 
     * a ThreadLocal bound variable in this case.
     * 
     * @return SecurityContext if exists
     * @throws IllegalStateException
     *             if user is not logged in and there is no information stored
     *             in a well know location.
     */
    public static SecurityContext getSecurityContext() {
        if (SecurityContextHolder.getContext() == null
                || !(SecurityContextHolder.getContext() instanceof org.acegisecurity.context.SecurityContext))
            throw new IllegalStateException("SecurityContext invalid: ["
                    + SecurityContextHolder.getContext()+" context]. Probably not yet logged in.");

        org.acegisecurity.context.SecurityContext sc = (org.acegisecurity.context.SecurityContext)SecurityContextHolder.getContext();
        Authentication authToken = sc.getAuthentication();
        if (authToken != null) {
            if (authToken instanceof AuthenticationObject) {
                AuthenticationObject auth = (AuthenticationObject) authToken;
                if (!auth.isLoggedIn()) {
                    throw new IllegalStateException(
                            "SecurityContext invalid: Not yet logged in");
                }
                auth.getUserPrincipal().getUserCredentials().setVerified(true);
                return new SecurityContext(auth.getUserPrincipal(), auth.getAuthorities(), auth.getDetails());
            }
            
            //seems like anonymous authentication.  make sure check the getPrincipal() since it can be:
            // 1) String if acegi created the Authentication object for us
            // 2) UserPrincipal if ESR creates the Authentication object via LoginManager.loginAnonymous()
            Object prin = authToken.getPrincipal();
            if(prin instanceof String) {
                prin = new UserPrincipalImpl((String) prin);
            }
            return new SecurityContext((UserPrincipal) prin, authToken.getAuthorities(), null);
        }
        
        // if get here, throw exception
        throw new IllegalStateException("SecurityContext invalid: ["
                + authToken+" Authentication Object]. Not yet logged in.");
    }

    /**
     * Get the SecurityContext from HttpServletRequest.
     * @param request
     * @return SecurityContext
     */
    public static SecurityContext getSecurityContext(HttpServletRequest request) {
        // as getSecurityContext should always return the same result, 
        // therefore not loading from the request.
        // To get from request, may be need to refactor SecureAction class
        // and move the code to put/get from session in the common place. 
        return getSecurityContext();
    }
    
    public static UserPrincipal getCurrentUser() {
        if (SecurityContextHolder.getContext() == null
                || !(SecurityContextHolder.getContext() instanceof org.acegisecurity.context.SecurityContext))
            throw new IllegalStateException("SecurityContext invalid: ["
                    + SecurityContextHolder.getContext()+" context]. Probably not yet logged in.");

        org.acegisecurity.context.SecurityContext sc = (org.acegisecurity.context.SecurityContext)SecurityContextHolder.getContext();
        Authentication authToken = sc.getAuthentication();
        if (authToken != null) {
            if (authToken instanceof AuthenticationObject) {
                AuthenticationObject auth = (AuthenticationObject) authToken;
                if (!auth.isLoggedIn()) {
                    throw new IllegalStateException(
                            "SecurityContext invalid: Not yet logged in");
                }
                auth.getUserPrincipal().getUserCredentials().setVerified(true);
                return auth.getUserPrincipal();
            }
            
            //seems like anonymous authentication.  make sure check the getPrincipal() since it can be:
            // 1) String if acegi created the Authentication object for us
            // 2) UserPrincipal if ESR creates the Authentication object via LoginManager.loginAnonymous()
            Object prin = authToken.getPrincipal();
            if(prin instanceof String) {
                prin = new UserPrincipalImpl((String) prin);
            }
            return (UserPrincipal) prin;
        }
        
        // if get here, throw exception
        throw new IllegalStateException("SecurityContext invalid: ["
                + authToken+" Authentication Object]. Not yet logged in.");
    }
    
    /**
     * Return the user name or logical id of the given user.
     * if logical id exists, its returned otherwise username is returned.
     * 
     * @param userPrincipal
     * @return
     */
    public static String getUserName(UserPrincipal userPrincipal) {
        if (userPrincipal != null) {
            return getUserName(userPrincipal.getUserCredentials());
        }
        return null;
    }

    
    public static String getUserName(UserCredentials cred) {
        if (cred != null) {
            return cred.getLogicalID() != null ? cred.getLogicalID() : cred.getUserID();
        }
        return null;
    }
    
    public static String getUserName() {
    	SecurityContext context = getSecurityContext();
    	UserPrincipal prin = context != null ? context.getUserPrincipal() : null;
    	return getUserName(prin);
    }
    
    /**
     * Get the list of permissins grated for the current user
     * @return
     */
    public static List getGrantedPermissions() {
 	   org.acegisecurity.context.SecurityContext context = (org.acegisecurity.context.SecurityContext)SecurityContextHolder.getContext();
 	   
       if(null == context)      
    	   return Collections.EMPTY_LIST;
       
       Authentication currentUser = context.getAuthentication();
       
       if (currentUser == null || currentUser.getAuthorities() == null || currentUser.getAuthorities().length < 1)
    	   return Collections.EMPTY_LIST;
       
       GrantedAuthority[] authorities = currentUser.getAuthorities();
       int count = authorities.length;
       
       List permissions = new ArrayList ();
       for (int i=0; i<count; i++){
    	   permissions.add(authorities[i].getAuthority());
       }
       
       return permissions;
    }
    
    public static boolean isAnyPermissionGranted(String[] permissions) {
        List list = new ArrayList(permissions.length);
        for (int i=0; i<permissions.length; i++) 
            list.add(permissions[i]);
        return isAnyPermissionGranted(list);     
    } 
    
    public static boolean isAnyPermissionGranted(List permissions) {
        List grantedPermissions = getGrantedPermissions();
        grantedPermissions.retainAll(permissions);
        return grantedPermissions.size() > 0 ? true : false;     
    }
    
    public static void initSecurityContextOnThread(LoginManager loginManager, UserCredentials cred) throws LoginException {
		if (cred != null && cred.isAnonymous()) {
			loginManager.loginAnonymous(cred.getLogicalID());
		}
		else {
			// ensure Context is non-null for this Thread (needed since flushing occurs during below login)
			loginManager.loginAnonymous();
		}

		// now that context is non-null for this Thread, if cred exists, use it
		if (cred != null && !cred.isAnonymous()) {
			loginManager.login(cred);
		}    	
    }
}