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