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