package edu.washington.cac.seraph;

import org.apache.log4j.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.atlassian.seraph.auth.DefaultAuthenticator;
import com.opensymphony.user.UserManager;
import com.opensymphony.user.EntityNotFoundException;
import com.opensymphony.user.User;
import com.opensymphony.user.Group;
import com.opensymphony.user.provider.ejb.util.Base64;

import java.security.Principal;

//ldap
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;


/**
 * Extension of DefaultAuthenticator that uses third-party code to determine if a user is logged in,
 * given a HTTPRequest object.
 * Third-party code will typically check for the existence of a special cookie.
 * 
 * In SSO scenarios where this authenticator is used, one typically configures Seraph to use an external login page
 * as well:
 * 
 *  <init-param>
 *    <param-name>login.url</param-name>
 *    <param-value>http://mycompany.com/globallogin?target=${originalurl}</param-value>
 *  </init-param>
 * 
 *
 */
public class UWjira2 extends DefaultAuthenticator {

    private static final Logger log = Logger.getLogger(UWjira2.class);
    
    private static final String   DEFAULT_ROLE = "confluence-users";
    private static final String   ADMIN_ROLE   = "confluence-administrators";
    private static final String[] ADMINS       = new String[] {"u:cac:sds-ds-apps","u:cac:sds-opm-support"};
    private static final String   WA           = "@washington.edu";
    
    private static final String   USER_LDAP_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    private static final String   USER_LDAP_URL     = "ldap://directory.washington.edu";
    private static final String[] USER_ATRIBS       = new String[] {"cn","sn","givenName","mail"};
    private static final String   USER_LDAP_BASE    = "'o=University of Washington, c=US'";
    
    private static final String   GRP_LDAP_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    private static final String   GRP_LDAP_URL     = "ldap://groups.u.washington.edu";
    private static final String   GRP_LDAP_AUTH    = "EXTERNAL";
    private static final String   GRP_LDAP_PROTO   = "ssl";
    private static final String[] GRP_ATRIBS       = new String[] {"cn","member"};
    private static final String   GRP_SEARCH       = "member=UWNETID=";
    private static final String   GRP_LDAP_BASE    = "ou=groups,dc=washington,dc=edu";

    public Principal getUser(HttpServletRequest request, HttpServletResponse response) {
    
        String remoteUser = null;
        Principal user = null;

        try {
            remoteUser = request.getRemoteUser();
        } catch (Exception e) { 
            log.error("UWjira  Cannot get remote user " + remoteUser);
        }

        // if we don't have the remote user, check basic, then we're done
        // *this could be done after the session check*
        if (remoteUser == null || remoteUser.length() < 1) {
            // check basic
            String auth = request.getHeader("AUthorization");
            if (auth != null && auth.toLowerCase().startsWith("basic ")) {
                String passEnc = auth.substring(6);
                try {
                    String pass = new String(Base64.decode(passEnc.getBytes()));
                    log.error("UWjira  basic got " + pass);
                } catch (Exception e) {
                    log.error("UWjira  failed to decode basic");
                }
            }
            return user;
        }

        // check the session
        HttpSession session = request.getSession(false);
        if (session != null &&
            session.getAttribute(DefaultAuthenticator.LOGGED_IN_KEY) != null) {
            try {
                log.debug("UWjira  fast track: " + request.getRequestURL());
                return (Principal)session.getAttribute(DefaultAuthenticator.LOGGED_IN_KEY);
            } catch (Exception e) {
                log.error("UWjira  failed to get user from session: " + e);
            }
        } else if (session == null) {
            return user;
        }

        try {
            // we have a user and session
            user = getUser(remoteUser);
            if (user == null) {
                // create them
                log.debug("UWjira  getUser: creating user " + remoteUser);
                user = createUser(remoteUser);
            }
            
            // update them
            log.debug("UWJira  getUser: found user " + remoteUser);
            user = checkUser(remoteUser);

            request.getSession().setAttribute(DefaultAuthenticator.LOGGED_IN_KEY, user);
            request.getSession().setAttribute(DefaultAuthenticator.LOGGED_OUT_KEY, null);
        } catch (Exception e) {
            log.error("UWjira  getUser(req,res) exception: " + e, e);
        }
        
        return user;
    }
    
    private User createUser(String user) {
        
        User u = null;
        
        try {
            u = UserManager.getInstance().createUser(user);
            u.setFullName(user);
            u.setEmail(user + "@u.washington.edu");
            Group g = UserManager.getInstance().getGroup("jira-users");
            if (! u.inGroup(g)) {
                u.addToGroup(g);
            }
            u.store();
        } catch (Exception e) {
            log.error("UWjira  createUser error " + e);
        }
        
        return u;
    }
    
    private User checkUser(String user) {
        
        User u = null;
        
        try {
            u = UserManager.getInstance().getUser(user);
            
            Group basegrp = UserManager.getInstance().getGroup("jira-users");
            if (! u.inGroup(basegrp)) {
                log.debug("UWjira add base group for " + user);
                u.addToGroup(basegrp);
            }
            
            Map<String,Set<String>> ldapgrps = groupInfo(user);
            List l = u.getGroups();
            
            if (ldapgrps != null && ldapgrps.size() > 0 &&
                l != null && l.size() > 0) {

                // clean current groups
                for (Iterator i = l.iterator(); i.hasNext();) {
                    String g = (String)i.next();
                    if (ldapgrps.containsKey(g)) {
                        log.debug("UWjira  has group " + g);
                        ldapgrps.remove(g);
                    } else if (g.startsWith("u:")) {
                        log.debug("UWjira  need to remove " + g + " for " + user);
                    }
                }
                
                // add any new ones
                for (String lg : ldapgrps.keySet()) {
                    Group n = null;
                    try {
                        n = UserManager.getInstance().getGroup(lg);
                    } catch(EntityNotFoundException enf) {
                        log.debug("UWjira  create ldap group " + lg);
                        n = UserManager.getInstance().createGroup(lg);
                    }
                    log.debug("UWjira  add ldap group " + n.getName());
                    u.addToGroup(n);
                }
            } else {
                log.warn("UWjira failed to get groups");
            }
            
            
            
            for (String adminldap : ADMINS) {
                if (u.inGroup(adminldap) && ! u.inGroup("jira-administrators") ) {
                    Group a = UserManager.getInstance().getGroup("jira-administrators");
                    u.addToGroup(a);
                }
            }
            
            
            Map<String,String> info = getInfo(user);
    
            if (info != null) {
    
                log.debug("UWjira ldap info for " + user + " " + info.get("cn"));
                u.setFullName(info.get("cn"));
                u.setEmail(info.get("mail"));
    
            } else {
    
                log.warn("UWjira no info for " + user);
                u.setFullName(user);
                u.setEmail(user + "@u.washington.edu");
    
            }
        
            u.store();
        } catch(Exception e) {
            log.error("UWjira  checkUser error " + e);
            log.error("        message: " + e.getMessage());
            e.printStackTrace();
        }
        
        return u;
    }
    
    private Map<String,String> getInfo(String user) {
    
        if (user.endsWith(WA)) {
            user = user.substring(0,user.indexOf(WA));
        } else if (user.contains("@")) {
            log.debug("UWjira fail user info not uw: " + user);
            return null;
        }

        // USER_ATRIBS "cn","sn","givenName","mail"
        Map<String,String> m = new HashMap<String,String>(USER_ATRIBS.length);
        m.put("cn",user);
        m.put("mail","");
        m.put("sn","");
        m.put("givenName","");
        
        try {
        
            // get the ldap connection
            LdapContext uctx = createUserContext();
            
            SearchControls ucons = createUserContraints();
            
            NamingEnumeration e = null;
            
            try {
                // give it a shot
                e = uctx.search(USER_LDAP_BASE, "uid=" + user, ucons);
                
            } catch (CommunicationException ce) {
            
                // try again with a new connection
                log.error("UWjira fail user info, calling create " + ce);
                uctx = createUserContext();
                e = uctx.search(USER_LDAP_BASE, "uid=" + user, ucons);
                
            }
                
            SearchResult res = (SearchResult)e.next();
            Attributes attrs = res.getAttributes();
            
            for (int i=0; i<USER_ATRIBS.length; i++) {
                m.put(USER_ATRIBS[i],
                      (String)attrs.get(USER_ATRIBS[i]).get() );
            }
            
        } catch (CommunicationException ce) {
            log.error("UWjira fail user info reconnect: " + ce);
            
        } catch (NamingException ne) {
            log.error("UWjira fail user info: " + ne);
            
        } catch (Exception e) {
            log.error("UWjira fail user info for user " + user);
        }

        return m;
    }
    
    private Map<String,Set<String>> groupInfo(String user) {
        
        log.info("UWjira groupInfo creating grps");
        Map<String,Set<String>> grps = new HashMap<String,Set<String>>();
        
        try {
        
            LdapContext    gctx  = createGroupContext();
            SearchControls gcons = createGroupContraints();
            
            String netid = user.split("@")[0];
            log.info("UWjira  about to do search with base" + GRP_LDAP_BASE + " search " + GRP_SEARCH + netid); 
            NamingEnumeration e = gctx.search(GRP_LDAP_BASE, GRP_SEARCH + netid, gcons);

            // build the group list with users
            while (e.hasMore()) {
                
                SearchResult sr = (SearchResult)e.next();
                Attributes attrs = sr.getAttributes();
                
                // for each group
                for (NamingEnumeration ecn = attrs.get(GRP_ATRIBS[0]).getAll(); ecn.hasMoreElements();) {
                    
                    // get the group name
                    String grp = (String)ecn.next ();
                    Set<String> users = new HashSet<String>();
                    if (grp.startsWith("u_")) {
                    	// for each user in the group
                        for (NamingEnumeration eu = attrs.get(GRP_ATRIBS[1]).getAll(); eu.hasMoreElements();) {
                            String u = (String)eu.next();
                            users.add( u.substring( u.indexOf("=")+1, u.length() ) );
                        }
                        // put the group with users in the hash
                        grps.put(grp,users);
                    }
                    
                }
            }
            
        } catch (CommunicationException ce) {
            log.error("UWjira fail group info reconnect: " + ce);
            
        } catch (NamingException ne) {
            log.error("UWjira fail group info: " + ne);
            
        } catch (Exception e) {
            log.error("UWjira fail group info catch all: " + e);
        }
        
        log.error("UWjira  " + grps.size());
        return grps;
    }
    
    private static final LdapContext createUserContext() {
        
        try {
            
            Hashtable<String,String> env = new Hashtable<String,String>(2);
            env.put(Context.INITIAL_CONTEXT_FACTORY,USER_LDAP_FACTORY);
            env.put(Context.PROVIDER_URL,USER_LDAP_URL);
            return new InitialLdapContext(env,null);
            
        } catch(Exception e) { }
        
        return null;
    }
    
    private static final SearchControls createUserContraints() {
        
        SearchControls c = new SearchControls();
        c.setSearchScope(SearchControls.SUBTREE_SCOPE);
        c.setReturningAttributes(USER_ATRIBS);
        return c;
    }
    
    private static final LdapContext createGroupContext() {
        
        try {
            
            Hashtable<String,String> env = new Hashtable<String,String>(2);
            env.put(Context.INITIAL_CONTEXT_FACTORY, GRP_LDAP_FACTORY);
            env.put(Context.PROVIDER_URL,            GRP_LDAP_URL);
            env.put(Context.SECURITY_AUTHENTICATION, GRP_LDAP_AUTH);
            env.put(Context.SECURITY_PROTOCOL,       GRP_LDAP_PROTO);
            return new InitialLdapContext(env,null);
            
        } catch(Exception e) { }
        
        return null;
    }
    
    private static final SearchControls createGroupContraints() {
        
        SearchControls c = new SearchControls();
        c.setSearchScope(SearchControls.SUBTREE_SCOPE);
        c.setReturningAttributes(GRP_ATRIBS);
        return c;
    }
}
