org.pegadi.server.user.LDAPUserServerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.pegadi.server.user.LDAPUserServerImpl.java

Source

/**
 * Copyright 1999-2009 The Pegadi Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * A redsys sub-server for user information, using LDAP as backend.
 *
 * This sub-server is for organizations which want to store their users in a
 * LDAP server.
 *
 * If this sub-server is used together with other @see PasswordChecker than LDAPPasswordChecker,
 * then care must be taken to ensure that the username exists in both systems.
 *
 *
 * @author Erlend Hamnaberg <erlenha@underdusken.no>
 * TODO: extract the connection into an interface similar to the javax.sql.DataSource
 * IS THE CONNECTION EVER CLOSED????
 */

//package redsys.server.user;
package org.pegadi.server.user;

import no.dusken.common.model.Person;
import org.apache.commons.lang.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.*;

public class LDAPUserServerImpl extends AbstractUserServer {

    /**
     * the hostname of the LDAP server
     */
    protected String url;

    /**
     * the login DN of the LDAP user
     */
    protected String ldapLoginDN;

    /**
     * the password of the LDAP user
     */
    protected String ldapPassword;

    /**
     * the base DN
     */
    protected String ldapBaseDN;

    /**
     * properties
     */
    protected Hashtable env = new Hashtable();

    /**
     * connection
     */
    protected DirContext ctx;

    /**
     * Method of authentication
     */
    protected String auth;

    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Creates a new LDAP user server.
     */
    public LDAPUserServerImpl() { //throws LDAPException {
        super();
    }

    public void init() {
        env.put("java.naming.ldap.version", "3");
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, url + "/" + ldapBaseDN);
        env.put(Context.SECURITY_AUTHENTICATION, auth);
        env.put(Context.SECURITY_PRINCIPAL, ldapLoginDN);
        env.put(Context.SECURITY_CREDENTIALS, ldapPassword);

        try {
            ctx = new InitialDirContext(env);
            log.info("Successfully created a Context");
        } catch (NamingException e) {
            log.error("Unable to create a Context", e);
        } catch (Exception e) {
            log.error("This should never come", e);
        }
    }

    public void setLdapLoginDN(String ldapLoginDN) {
        this.ldapLoginDN = ldapLoginDN;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setLdapPassword(String ldapPassword) {
        this.ldapPassword = ldapPassword;
    }

    public void setLdapBaseDN(String ldapBaseDN) {
        this.ldapBaseDN = ldapBaseDN;
    }

    public void setAuth(String auth) {
        this.auth = auth;
    }

    /**
     * Find a user by ID.  This id may be a compound ID, like the
     * LDAP database's DN structure. Otherwise it might be an empoyeeNumber
     * like this implementation use.
     * <p/>
     * Tries first to get the user by pegadiID, which is the old method.
     *
     * @param id
     * @return the Userobject if found, or null if not.
     */
    public Person getUserById(String id) {
        if (id == null || id.equals(0))
            return null;
        Person user = null;
        String[] getThese = { "sn", "gn", "mail", "uid", "employeeNumber" };
        try {
            //int nr = Integer.parseInt(id); //only needed if we can get the dn.
            SearchControls sc = new SearchControls();
            sc.setReturningAttributes(getThese);
            NamingEnumeration e = ctx.search("ou=people", "employeeNumber=" + id, sc);
            if (e.hasMore()) {
                SearchResult sr = (SearchResult) e.next();
                user = this.createUser(sr.getAttributes());
            }
        } catch (NamingException e) {
            log.error("An error occured while trying to getUserById(" + id + ")", e);
            /*FIXME does not work.
             * try {
                
            Attributes attrs = ctx.getAttributes("dn=" + id,getThese);
            return createUser(attrs);
                
            } catch (NamingException e) {
            e.printStackTrace();
            }*/
        }
        return user;
    }

    /*TODO*/
    public boolean isActive(String userID) {
        return false;
    }

    /**
     * Can probably be done more elegant too.
     *
     * @param userDN   real dn to the user.
     * @param password the user's password
     * @return
     */
    public boolean checkAuthentication(String userDN, String password) {
        if (password.trim().equals(""))
            return false;
        DirContext ctx2 = null;
        try {
            // See if the user authenticates.
            Hashtable env = new Hashtable();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, url + "/" + ldapBaseDN);
            env.put(Context.SECURITY_AUTHENTICATION, auth);
            env.put(Context.SECURITY_PRINCIPAL, userDN);
            env.put(Context.SECURITY_CREDENTIALS, password);
            env.put("com.sun.jndi.ldap.connect.timeout", "10000");
            // Specify timeout to be 10 seconds, only on non SSL since SSL connections
            // break with a timeout.
            ctx2 = new InitialDirContext(env);
            log.info("Successfully logged in... " + userDN);
        } catch (Exception e) {
            log.error("Exception during login", e);
            return false;
        }

        finally {
            try {
                ctx2.close();
            } catch (NamingException ignore) {
            }
        }

        return true;
    }

    /**
     * Creates the actual User.
     * Compatible with both methods @see getUserById()
     *
     * @param attr the Attributes gotten from the SearchResult
     * @return a new User based on the info in the SearchResult
     */
    protected Person createUser(Attributes attr) {
        //NamingEnumeration e = attr.getAll();
        if (attr == null)
            return null;
        else {
            String initials = null;
            String fname;
            String lname;
            String mail;
            Long id;
            String uid;
            try {
                fname = (String) attr.get("givenName").get();
                lname = (String) attr.get("sn").get();
                mail = (String) attr.get("mail").get();
                uid = (String) attr.get("uid").get();
                if (fname != null && lname != null)
                    initials = createInitials(fname + " " + lname);
                id = Long.valueOf((String) attr.get("employeenumber").get());

                //Does not work.... use (getDN ???)
                /* if (id == null)
                id = (String) attr.get("dn").get();*/

                return new Person(id, fname, lname, uid, mail);
            } catch (NamingException er) {
                log.error("Error during creation of user", er);
            } catch (Exception e) {
                log.error("Something else", e);
            }
            return null;
        }
    }

    /**
     * Find a user by username.
     *
     * @param username The user name of the wanted user.  for an LDAP server,
     *                 this can be the CN (common name) of the user, or the uid, or whatever field you've chosen to use...
     * @return the User if found, null if not.
     */
    public Person getUserByUsername(String username) {
        if (username == null || username.length() == 0)
            return null; //no user is allowed with zero username.  die silently, as this happens for all articles where journalist/photographer is not set.
        try {
            String[] getThese = { "sn", "gn", "mail", "uid", "employeeNumber" };
            Attributes attrs = ctx.getAttributes("uid=" + username + ",ou=people", getThese);
            return this.createUser(attrs);
        } catch (NamingException ignored) {
            log.debug("Naming Exception", ignored);
        }
        return null;
    }

    @Override
    public Person getUserByLegacyId(Integer legacyId) {
        throw new NotImplementedException();
    }

    /**
     * Used to create the dn for the username.
     * FIXME: Should be done in a different way, because some people use cn=.. as username, not uid=..
     * FIXME: Introduce constants for searchitems, like cn, ou=people.
     *
     * @param username the users' username
     * @return the generated dn
     */
    public String getDN(String username) {
        return "uid=" + username + ",ou=people," + ldapBaseDN;
    }

    /**
     * Authenticates the given user name and password.
     *
     * @param username User name.
     * @return User ID if successful, <code>null</code> if failure.
     */
    public String login(String username) {
        log.info("Attempting login for user '" + username + "'");
        try {
            Person user = this.getUserByUsername(username);
            return username;
        } catch (Exception e) {
            log.error("error during login", e);
            return null;
        }
    }

    /**
     * Returns all active journalists.
     *
     * @return an array of  <code>User</code>s
     */
    public List<Person> getJournalists() {
        return this.getUsersByRole(journalistRoleId, 1);
    }

    /**
     * Returns all active photographers
     *
     * @return an array of <code>User</code>s
     */
    public List<Person> getPhotographers() {
        return this.getUsersByRole(photographerRoleId, 1);
    }

    /**
     * Returns an array of users having a given role. Either active or
     * inactive users are returned.
     *
     * @param rolename the name of the role (.
     * @param active   specifying whether we want the active or inactive users.
     * @return an array of <code>User</code>s.
     */
    protected List<Person> getUsersByRoleName(String rolename, boolean active) {
        return null;
    }

    /**
     * Returns an array of users.
     *
     * @param inactive <code>true</code> if inactive users should be included.
     * @return an array of <code>User</code>s.
     */
    public List<Person> getAllUsers(boolean inactive) {
        ArrayList<Person> users = new ArrayList<Person>();
        try {
            SearchControls sc = new SearchControls();
            String[] getThese = { "sn", "gn", "mail", "uid", "employeeNumber" };
            sc.setReturningAttributes(getThese);
            if (inactive) {
                Attributes attrs = ctx.getAttributes("ou=people", getThese);
                users.add(this.createUser(attrs));
            } else {
                NamingEnumeration e = ctx.search("ou=people", "(active=1)", sc);
                while (e.hasMore()) {
                    SearchResult sr = (SearchResult) e.next();
                    users.add(this.createUser(sr.getAttributes()));
                }
            }
            Collections.sort(users);
            return users;
        } catch (NamingException er) {
            log.error("Could not get users", er);
        } catch (Exception e) {
            log.error("Something else", e);
        }
        return null;
    }

    /**
     * Create a set of initials based on the first letter of each name.
     *
     * @param name The name to use as a base.
     * @return The initials.
     */
    protected String createInitials(String name) {
        StringBuffer res = new StringBuffer(10);
        StringTokenizer st = new StringTokenizer(name, " ");

        while (st.hasMoreTokens()) {
            res.append(st.nextToken().charAt(0));
        }

        return res.toString().toLowerCase();
    }

    /**
     * @param roleID the ID of a role
     * @param userID the ID of a user
     * @return <code>true</code> if the user has that role.
     */
    public boolean hasRole(Integer roleID, Long userID) {
        try {
            SearchControls sc = new SearchControls();
            NamingEnumeration e = ctx.search("ou=people",
                    "(&(employeeNumber=" + userID + ")(pegadiRole=" + roleID + ":*))", sc);
            if (e.hasMore())
                return true;

        } catch (NamingException er) {
            log.error("Error checking for role: " + roleID + "for userID " + userID, er);
        } catch (Exception e) {
            log.error("Something else", e);
        }
        return false;
    }

    /**
     * @param roleID the ID of a role
     * @param user   the user
     * @return <code>true</code> if the user has that role.
     */
    public boolean hasRole(int roleID, Person user) {
        String dn = this.getDN(user.getUsername());
        try {
            SearchControls sc = new SearchControls();
            NamingEnumeration e = ctx.search("ou=roles", "(&(roleID=" + roleID + ")(member=" + dn + "))", sc);
            if (e.hasMore())
                return true;

        } catch (NamingException er) {
            log.error("Error checking for role: " + roleID + "for user" + user.getUsername(), er);
        } catch (Exception e) {
            log.error("Something else", e);
        }
        return false;
    }

    /**
     * Return true if the user can publish an article. All users can publish,
     * so this will allways return <code>true</code>.
     *
     * @return Always <code>true</code>.
     */
    public boolean canPublish(Long userID) {
        return true;
    }

    /**
     * Placeholder for functions related to role-mapping
     */
    protected void createRoleMappings() {
        // we must define redsys.role.Role, and then:
        // redsys.role.Journalist
        // redsys.role.Photographer
        // redsys.role.Editor
        // redsys.role.Layout

        // and then provide a mechanism for mapping the roles into LDAP equivalent.
        // i think we shall assume that other newspapers use 'dynamic groups' as default
    }

    /**
     * Returns an array of users having a given role. Either active or
     * inactive users are returned.
     *
     * @param roleID the role of the users.
     * @param active specifying whether we want the active or inactive users.
     * @return an array of <code>User</code>s.
     */
    public List<Person> getUsersByRole(int roleID, int active) {
        if (roleID <= 0)
            return null;
        ArrayList<Person> users = new ArrayList<Person>();
        try {
            SearchControls sc = new SearchControls();
            String[] getThese = { "sn", "gn", "mail", "uid", "employeeNumber" };
            sc.setReturningAttributes(getThese);
            NamingEnumeration e = ctx.search("ou=people", "(&(active=" + active + ")(pegadiRole=" + roleID + "*))",
                    sc);
            while (e.hasMore()) {
                SearchResult sr = (SearchResult) e.next();
                users.add(this.createUser(sr.getAttributes()));
            }
            Collections.sort(users);
            return users;
        } catch (NamingException er) {
            log.error("Error, getUsersByRole(" + roleID + "," + active + ")", er);
        }
        return null;
    }

}