/*
* $Id: LDAPCitizen.java,v 1.14 2002/09/16 08:05:06 jkl Exp $
*
* Copyright (c) 2002 Njet Communications Ltd. All Rights Reserved.
*
* Use is subject to license terms, as defined in
* Anvil Sofware License, Version 1.1. See LICENSE
* file, or http://njet.org/license-1.1.txt
*/
package anvil.server.ldap;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Connection;
import java.io.IOException;
import java.util.Map;
import java.util.Hashtable;
import java.util.List;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.Iterator;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Permission;
import anvil.java.security.PermissionCollectionCombiner;
import anvil.java.util.BindingEnumeration;
import anvil.java.util.EnumerationToIterator;
import anvil.core.Any;
import anvil.core.Array;
import anvil.core.AnyString;
import anvil.core.Serialization;
import anvil.Log;
import anvil.core.UnserializationException;
import anvil.core.Serialization;
import anvil.database.ConnectionManager;
import anvil.database.PooledConnection;
import anvil.core.AnyString;
import anvil.server.Citizen;
import anvil.server.Realm;
import anvil.server.Tribe;
import anvil.server.Zone;
import anvil.server.OperationFailedException;
import anvil.server.CitizenNotFoundException;
import javax.naming.*;
import javax.naming.directory.*;
/**
* class LDAPCitizen
* Citizen is based on inetorgperson ldap object schema. It has special use for following
* inetorgperson attributes:
* <li>description - used to save user permission information
* <li>physicalDeliveryOfficeName - used to save user attributes
*
* One shouldn't alter these values unless he's familar with the format.
*
* User attributes that are specified in inetorgperson schema has "ctz." prefix when
* accessed through getAttribute() - except userPassword of course (use verifyCredentials() and
* setCredentials()).
*
* (Anvil script examle:)<br>
* <code>
* realm = context.getRealm("myRealm");
* citizen = realm.getCitizen("foo@njet.org");
* println "Common name: ", citizen["ctz.cn"];
* println "Shoe size: ", citizen["shoeSize"];
* </code>
*
* Note. Because Citizen implements Namespace inferface you can access user attributes as they were
* member fields of citizen object.
*
* @author: Simo Tuokko
*/
public class LDAPCitizen implements Citizen
{
public static final String OTHERS_ATTR = "physicalDeliveryOfficeName";
private String username = null;
private String dn = null;
private String fullDN = null;
private String password = null;
private Log log;
private LDAPRealm realm;
private PermissionCollection _permissions = null;
private PermissionCollection _combined = null;
private boolean hasPermissions = false;
private Tribe[] _groups = null;
private boolean _modified = false;
private Array _toBeDeleted = new Array();
private Array _cache = new Array();
private Array _cacheOrig = null;
final static AttributeMapping ATTRMAP_C = new AttributeMapping();
final static AttributeMapping ATTRMAP_L = new AttributeMapping(true);
public LDAPCitizen(LDAPRealm realm, String username)
throws CitizenNotFoundException, OperationFailedException
{
this.realm = realm;
this.log = realm.getLog();
this.username = username;
this.dn = "uid="+username+",ou=users";
this.fullDN = realm.createUserDN(username);
//get password and id
PooledConnection connImpl = null;
DirContext ctx = null;
boolean citizenFound = false;
try {
connImpl = realm.getConnection();
ctx = (DirContext)connImpl.getConnection();
//todo: use ctx.getAttributes()
NamingEnumeration enu = ctx.search("ou=users", "(uid="+username+")", new SearchControls());
if (enu.hasMore()) {
SearchResult s = (SearchResult)enu.next();
Attributes attrs = s.getAttributes();
Attribute passAt = attrs.get("facsimileTelephoneNumber");
if (passAt != null) {
password = (String)passAt.get();
}
NamingEnumeration attrEnu = attrs.getAll();
for (; attrEnu.hasMoreElements(); ) {
Attribute a = (Attribute)attrEnu.nextElement();
String id = a.getID();
if (ATTRMAP_L.containsKey(a.getID())) {
_cache.put( new AnyString((String)ATTRMAP_L.get(id)), new AnyString(a.get().toString()) );
} else if (OTHERS_ATTR.equals(id)) {
try {
Array others = (Array)Serialization.unserialize(null, a.get().toString());
_cache.union(others);
} catch(UnserializationException ioe) {
throw new OperationFailedException("Data unserialization failed: "+ioe);
}
} else {
//System.err.println("Load: Unknown attr: "+id+" = "+a.get());
}
}
citizenFound = true;
_cacheOrig = (Array)_cache.clone();
}
} catch (Exception e) {
throw new OperationFailedException("Creating of citizen failed", e);
} finally {
LDAPRealm.cleanupContext(connImpl);
}
if (!citizenFound) {
throw new CitizenNotFoundException(username);
}
}
public Any setVariable(String name, Any value) {
if (name != null && value != null && !name.equals("ctz.username")) {
Any anyName = Any.create(name);
if (_toBeDeleted.containsKey(anyName)) {
_toBeDeleted.remove(anyName);
}
_cache.put(Any.create(name), value);
_modified = true;
}
return value;
}
public boolean deleteVariable(String name) {
if (_cache.get(Any.create(name)) != null &&
!name.equals("ctz.username") &&
!name.equals("ctz.surName")) {
Any anyName = Any.create(name);
_toBeDeleted.put(anyName, Any.TRUE);
_cache.remove(anyName);
_modified = true;
return true;
}
return false;
}
public Any getVariable(String name) {
Any value = _cache.get(Any.create(name));
return (value != null) ? value : Any.UNDEFINED;
}
public Any checkVariable(String name) {
return getVariable(name);
}
public BindingEnumeration getVariables() {
return _cache.keysAndElements();
}
public Realm getRealm() {
return realm;
}
public String getName() {
return username;
}
public boolean verifyCredentials(String credential) {
/*log.error("--in verify..");
try {
Hashtable probs = new Hashtable();
probs.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
probs.put("java.naming.provider.url", "ldap://nuhapumppu:389/o=deftit.com");
probs.put("java.naming.authentication", "simple");
probs.put("java.naming.principal", dn);
probs.put("java.naming.credentials", credential);
Context c = new InitialDirContext(probs);
log.error("--c: "+c);
} catch(NamingException e) {
log.error("LDAPCitizen.verifyCredential() failed: "+e);
return false;
}
return true;*/
return credential.equals(password);
}
public void setCredentials(String newCredentials) throws OperationFailedException {
password = newCredentials;
setVariable("ctz.fax", Any.create(newCredentials));
commit();
}
public String getPassword() {
return password;
}
public Tribe[] getParents() {
if (_groups == null) {
synchronized(this) {
if (_groups == null) {
_groups = realm.getMemberGroups(fullDN);
}
}
}
return _groups;
}
public void commit() throws OperationFailedException {
if (!_modified) {
return;
}
Any anyKey;
String key;
Any value;
boolean ldapAttr;
List mods = new ArrayList();
Array others = new Array();
boolean othersModified = false;
//to be deleted
if (_toBeDeleted.size() > 0) {
for (BindingEnumeration de=_toBeDeleted.keysAndElements(); de.hasMoreElements(); ) {
anyKey = (Any)de.nextKey();
de.nextElement();
key = anyKey.toString();
ldapAttr = LDAPCitizen.ATTRMAP_C.containsKey(key);
if (ldapAttr) {
key = (String)LDAPCitizen.ATTRMAP_C.get(key);
}
if (!ldapAttr) {
othersModified = true;
} else if (!key.equals("uid") && !key.equals("sn")) {
mods.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(key)));
}
}
_toBeDeleted.clear();
}
for (BindingEnumeration enum=_cache.keysAndElements(); enum.hasMoreElements(); ) {
anyKey = (Any)enum.nextKey();
key = anyKey.toString();
value = (Any)enum.nextElement();
ldapAttr = LDAPCitizen.ATTRMAP_C.containsKey(key);
if (ldapAttr) {
key = (String)LDAPCitizen.ATTRMAP_C.get(key);
}
//compare value to original
Any orig = (Any)_cacheOrig.get(anyKey);
if (orig == null || !orig.equals(value)) {
if (!ldapAttr) {
othersModified = true;
others.put(anyKey, value);
} else if (!key.equals("uid")) {
int modType = (orig == null)? DirContext.ADD_ATTRIBUTE : DirContext.REPLACE_ATTRIBUTE;
mods.add(new ModificationItem(modType, new BasicAttribute(key, value.toString())));
}
continue;
}
if (!ldapAttr) {
others.put(anyKey, value);
}
} //for cache
if (othersModified) {
try {
if (others.size() > 0) {
mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute(OTHERS_ATTR, Serialization.serialize(null, others)))
);
} else {
mods.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(OTHERS_ATTR)));
}
} catch(IOException ioe) {
throw new OperationFailedException("Data serialization failed: "+ioe);
}
}
PooledConnection connImpl = null;
DirContext ctx = null;
if (mods.size() > 0) {
try {
connImpl = realm.getConnection();
ctx = (DirContext)connImpl.getConnection();
ctx.modifyAttributes(dn, (ModificationItem[])mods.toArray(new ModificationItem[mods.size()]));
} catch (Exception e) {
log.error("LDAPCitizen: Data commit failed", e);
} finally {
LDAPRealm.cleanupContext(connImpl);
}
_cacheOrig = (Array)_cache.clone();
}
_modified = false;
}
public PermissionCollection getCombinedPermissions() {
if (!hasPermissions) {
synchronized(this) {
if (!hasPermissions) {
hasPermissions = true;
getParents();
Permissions perms = new Permissions();
for (int i=0; i<_groups.length; i++) {
PermissionCollection groupCol = _groups[i].getCombinedPermissions();
if (groupCol != null) {
for (Enumeration enu = groupCol.elements(); enu.hasMoreElements(); ) {
perms.add((Permission)enu.nextElement());
}
}
}
//combine own permissions
for (Enumeration enu = getPermissions().elements(); enu.hasMoreElements(); ) {
perms.add((Permission)enu.nextElement());
}
_combined = perms;
}
}
}
return _combined;
}
public PermissionCollection getPermissions() {
if (_permissions == null) {
synchronized(this) {
if (_permissions == null) {
_permissions = realm.loadPermissions(dn);
}
}
}
return _permissions;
}
public void addPermission(Permission perm) throws OperationFailedException {
_permissions = realm.addPermission(perm, dn);
}
public void removePermission(Permission perm) throws OperationFailedException {
_permissions = realm.removePermission(perm, dn);
}
public Iterator listPermissions() {
if (_permissions != null) {
return new EnumerationToIterator(_permissions.elements());
} else {
return BindingEnumeration.EMPTY;
}
}
public void remove() throws OperationFailedException {
PooledConnection connImpl = null;
DirContext ctx = null;
try {
connImpl = realm.getConnection();
ctx = (DirContext)connImpl.getConnection();
ctx.unbind(dn);
realm.removeCitizen(username);
//this citizen no longer exists!
} catch (NameNotFoundException e) {
throw new OperationFailedException("User '"+username+"' not found!");
} catch (Exception e) {
throw new OperationFailedException(e.getMessage());
} finally {
LDAPRealm.cleanupContext(connImpl);
}
}
String getDN() {
return dn;
}
String getFullDN() {
return fullDN;
}
void refreshPermissions() {
hasPermissions = false;
_groups = null;
}
public boolean equals(Object o) {
if (o instanceof LDAPCitizen) {
LDAPCitizen c = (LDAPCitizen)o;
return (c.username.equals(username) && c.realm.equals(realm));
}
return false;
}
public String toString() {
return "LDAPCitizen ("+dn+")";
}
}