/*
* $Id: LDAPRealm.java,v 1.12 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 anvil.Log;
import anvil.java.util.BindingEnumeration;
import anvil.java.security.PermissionCollectionCombiner;
import anvil.core.Any;
import anvil.core.Serialization;
import anvil.java.util.Hashlist;
import anvil.core.Array;
import anvil.database.ConnectionManager;
import anvil.database.PooledConnection;
import anvil.server.PolicyPreferences;
import anvil.server.RealmPreferences;
import anvil.server.Citizen;
import anvil.server.Realm;
import anvil.server.Tribe;
import anvil.server.Zone;
import anvil.server.Realm;
import anvil.server.Tribe;
import anvil.server.Citizen;
import anvil.server.OperationFailedException;
import anvil.server.CitizenNotFoundException;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.Set;
import java.util.Map;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Enumeration;
import java.util.ArrayList;
import java.security.Permission;
import java.security.Permissions;
import java.security.PermissionCollection;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import org.apache.oro.text.regex.MalformedPatternException;
import javax.naming.*;
import javax.naming.directory.*;
/**
* class LDAPRealm
* Implements Anvil Realm architecture over LDAP.
*
* TODO:
* - tribe.attach(tribe) loop check
* - construct ctz.displayName from first+surname/username
*
* @author: Simo Tuokko
*/
public class LDAPRealm implements Realm
{
private Map _users = new HashMap();
private Map _groups = new HashMap();
private Zone _zone;
private String _name;
private String _prefix = null;
private String _contextPool = "authen";
private LDAPTribe _rootTribe = null;
private ConnectionManager _manager = null;
private static String ROOT_TRIBE = "Root Tribe";
private static Pattern EMAIL_PATTERN;
private static Attribute objectClassAttr;
private static Attribute groupObjectClass;
static {
try {
EMAIL_PATTERN = new Perl5Compiler().compile("^([a-z0-9_\\.\\-])+\\@(([a-z0-9\\-])+\\.)+([a-z0-9]){2,4}$");
} catch(MalformedPatternException e) {}
objectClassAttr = new BasicAttribute("objectclass");
objectClassAttr.add("top");
objectClassAttr.add("person");
objectClassAttr.add("organizationalPerson");
objectClassAttr.add("inetorgperson");
groupObjectClass = new BasicAttribute("objectclass");
groupObjectClass.add("top");
groupObjectClass.add("groupofuniquenames");
}
public LDAPRealm()
{
}
public String toString()
{
return "LDAPRealm("+_name+"@"+_contextPool+"@"+_prefix+")";
}
public void initialize(RealmPreferences prefs)
{
_name = prefs.getName();
_zone = prefs.getParent();
//get connection manager for contextPool
_contextPool = (String)prefs.getPreference("contextPool");
_prefix = (String)prefs.getPreference("prefix");
if (_prefix == null) {
_zone.log().error("LDAPRealm: 'prefix' not found from configuration!");
}
if (_contextPool == null) {
_zone.log().error("LDAPRealm: contextPool name not found from configuration, using default: authen");
}
try {
_manager = _zone.getManagerFor(_contextPool);
} catch(Exception e) {
_zone.log().error("LDAPRealm: error while getting ConnectionManager: "+e);
}
_zone.log().info("Realm " + this + " initialized");
}
public void stop()
{
_zone.log().info("Realm " + this + " stopped");
}
public Citizen getCitizen(String username)
{
Citizen c = null;
if (!_users.containsKey(username)) {
try {
c = new LDAPCitizen(this, username);
_users.put(username, c);
} catch(CitizenNotFoundException cnfe) {
//return null
} catch(OperationFailedException e) {
_zone.log().error("LDAPRealm.getCitizen() failed: "+e);
}
} else {
c = (Citizen)_users.get(username);
}
return c;
}
public Tribe getTribe(String name)
{
Tribe t = null;
if (!_groups.containsKey(name)) {
try {
t = new LDAPTribe(this, name);
_groups.put(name, t);
} catch(CitizenNotFoundException cnfe) {
_zone.log().error("LDAPRealm.getTribe() Tribe '"+name+"' not found!");
} catch(OperationFailedException e) {
_zone.log().error("LDAPRealm.getTribe() failed: "+e);
}
} else {
t = (Tribe)_groups.get(name);
}
return t;
}
public Citizen[] searchCitizenByVariable(String variable, String value)
{
if (variable == null || !LDAPCitizen.ATTRMAP_C.containsKey(variable)) {
_zone.log().error("LDAPRealm doesn't support search for: "+variable);
return null;
}
PooledConnection connImpl = null;
DirContext ctx = null;
ArrayList result = new ArrayList();
try {
connImpl = getConnection();
ctx = (DirContext)connImpl.getConnection();
String search = "("+LDAPCitizen.ATTRMAP_C.get(variable)+"="+value+")";
SearchControls sc = new SearchControls();
sc.setReturningAttributes(new String[] { "uid" });
NamingEnumeration enu = ctx.search("ou=users", search, sc);
while (enu.hasMore()) {
SearchResult s = (SearchResult)enu.next();
Attributes attrs = s.getAttributes();
Attribute uid = attrs.get("uid");
if (uid != null) {
result.add( getCitizen((String)uid.get()) );
}
}
return (Citizen[])result.toArray(new Citizen[result.size()]);
} catch (Exception e) {
_zone.log().error("LDAPRealm.searchCitizenByVariable() failed: "+e);
} finally {
cleanupContext(connImpl);
}
return null;
}
public Citizen getAnonymousCitizen()
{
return null;
}
public Tribe getRoot()
{
if (_rootTribe == null) {
synchronized(this) {
if (_rootTribe == null) {
try {
_rootTribe = new LDAPTribe(this, ROOT_TRIBE, true);
} catch(OperationFailedException e) {
_zone.log().error("LDAPRealm.getRoot(): "+e);
}
}
}
}
return _rootTribe;
}
Tribe[] getMemberGroups(String dn)
{
PooledConnection connImpl = null;
DirContext ctx = null;
List groups = new ArrayList();
try {
connImpl = _manager.acquire(_contextPool);
ctx = (DirContext)connImpl.getConnection();
NamingEnumeration enu = null;
SearchControls sc = new SearchControls();
sc.setReturningAttributes(new String[] {"cn"});
enu = ctx.search("ou=groups", "(uniqueMember="+ dn +")", sc);
while (enu.hasMore()) {
SearchResult s = (SearchResult)enu.next();
Attributes a = s.getAttributes();
Attribute cn = a.get("cn");
if (cn != null) {
groups.add( getTribe((String)cn.get()) );
}
}
return (Tribe[])groups.toArray(new Tribe[groups.size()]);
} catch (Exception e) {
_zone.log().error("LDAPRealm.getMemberGroups() error: "+e);
} finally {
cleanupContext(connImpl);
}
return null;
}
public Tribe createTribe(String name) throws OperationFailedException {
if (name == null) throw new OperationFailedException("Null tribe name!");
PooledConnection connImpl = null;
DirContext ctx = null;
try {
connImpl = _manager.acquire(_contextPool);
ctx = (DirContext)connImpl.getConnection();
Attributes at = new BasicAttributes();
at.put(groupObjectClass);
at.put("cn", name);
ctx.bind("cn="+name+",ou=groups", null, at);
//"refresh" root tribe
if (_rootTribe != null) {
_rootTribe.refreshCitizens();
}
return (Tribe)getTribe(name);
} catch (NameAlreadyBoundException e) {
throw new OperationFailedException("Group '"+name+"' already exists!");
} catch (Exception e) {
_zone.log().error("LDAPRealm.createTribe(): "+e);
} finally {
cleanupContext(connImpl);
}
return null;
}
public Citizen createCitizen(String username, String password) throws OperationFailedException {
return createCitizen(username, password, null);
}
public Citizen createCitizen(String username, String password, String[][] params) throws OperationFailedException
{
if (username == null) throw new OperationFailedException("Null username!");
username = username.trim().toLowerCase();
if (!checkEmail(username)) throw new OperationFailedException("Username isn't a valid email address!");
PooledConnection connImpl = null;
DirContext ctx = null;
try {
connImpl = _manager.acquire(_contextPool);
ctx = (DirContext)connImpl.getConnection();
Attributes at = new BasicAttributes();
at.put("facsimileTelephoneNumber", password);
at.put(objectClassAttr);
Array others = new Array();
if (params != null) {
for (int i=0,l=params.length; i<l; i++) {
String key = params[i][0];
String val = params[i][1];
if (LDAPCitizen.ATTRMAP_C.containsKey(key)) {
at.put((String)LDAPCitizen.ATTRMAP_C.get(key), val);
//System.err.println ("Save: OK attr: "+LDAPCitizen.ATTRMAP_C.get(key)+" = "+val);
} else {
others.put(Any.create(key), Any.create(val));
//System.err.println ("Save: Unknown attr: "+LDAPCitizen.ATTRMAP_C.get(key)+" = "+val);
}
}
}
at.put("cn", username);
if (at.get("sn") == null) {
at.put("sn", username);
}
if (others.size() > 0) {
try {
String sothers = Serialization.serialize(null, others);
at.put(LDAPCitizen.OTHERS_ATTR, sothers);
} catch(IOException ioe) {
throw new OperationFailedException("Data serialization failed: "+ioe);
}
}
ctx.bind("uid="+username+",ou=users", null, at);
//"refresh" root tribe
if (_rootTribe != null) {
_rootTribe.refreshCitizens();
}
return (Citizen)getCitizen(username);
} catch (NameAlreadyBoundException e) {
throw new OperationFailedException("User '"+username+"' already exists!");
} catch (Exception e) {
_zone.log().error("LDAPRealm.createCitizen(): "+e);
} finally {
cleanupContext(connImpl);
}
return null;
}
public void setRoot(Tribe tribe) throws OperationFailedException
{
throw new OperationFailedException("This implementation doesn't support setting of root tribe!");
}
void removeCitizen(String username)
{
if (_users.containsKey(username)) {
_users.remove(username);
if (_rootTribe != null) {
_rootTribe.refreshCitizens();
}
}
}
PooledConnection getConnection() throws Exception
{
return _manager.acquire(_contextPool);
}
Log getLog()
{
return _zone.log();
}
static void cleanupContext(PooledConnection connImpl)
{
if (connImpl != null) {
connImpl.release();
}
}
public String getPrefix() {
return _prefix;
}
public String createUserDN(String name) {
StringBuffer buf = new StringBuffer();
buf.append("uid=");
buf.append(name);
buf.append(",ou=users");
if (_prefix != null && _prefix.length() > 0) {
buf.append(",");
buf.append(_prefix);
}
return buf.toString();
}
public String createGroupDN(String name) {
StringBuffer buf = new StringBuffer();
buf.append("cn=");
buf.append(name);
buf.append(",ou=groups");
if (_prefix != null && _prefix.length() > 0) {
buf.append(",");
buf.append(_prefix);
}
return buf.toString();
}
private boolean checkEmail(String email)
{
return new Perl5Matcher().matches(email, EMAIL_PATTERN);
}
PermissionCollection loadPermissions(String dn) {
PooledConnection connImpl = null;
DirContext ctx = null;
try {
connImpl = getConnection();
ctx = (DirContext)connImpl.getConnection();
return loadPermissions(ctx, dn);
} catch (Exception e) {
_zone.log().error("LDAPRealm.loadPermissions(): "+e);
} finally {
cleanupContext(connImpl);
}
return null;
}
static PermissionCollection loadPermissions(DirContext ctx, String dn) throws Exception {
Permissions result = new Permissions();
Attributes attrs = ctx.getAttributes(dn, new String[] { "description" });
Attribute desc = attrs.get("description");
if (desc != null) {
for (int i=0, l=desc.size(); i<l; i++) {
try {
String[] permSt = parsePermission((String)desc.get(i));
result.add( PolicyPreferences.createPermission(permSt) );
} catch(Throwable t) {
throw new OperationFailedException("LDAPRealm.loadPermissions(): "+t.getMessage());
}
}
}
return result;
}
PermissionCollection addPermission(Permission perm, String dn) throws OperationFailedException {
PooledConnection connImpl = null;
DirContext ctx = null;
try {
connImpl = getConnection();
ctx = (DirContext)connImpl.getConnection();
PermissionCollection storage = LDAPRealm.loadPermissions(ctx, dn);
boolean found = false;
for (Enumeration enu=storage.elements(); enu.hasMoreElements(); ) {
if (perm.equals(enu.nextElement())) {
_zone.log().error("--addPermission() we already have: "+perm);
found = true;
}
}
storage.add(perm);
if (!found) {
LDAPRealm.savePermissions(ctx, dn, storage);
_zone.log().error("--addPermissions() perm added: "+perm);
}
return storage;
} catch (Exception e) {
throw new OperationFailedException(e.getMessage());
} finally {
cleanupContext(connImpl);
}
}
PermissionCollection removePermission(Permission perm, String dn) throws OperationFailedException {
PooledConnection connImpl = null;
DirContext ctx = null;
try {
connImpl = getConnection();
ctx = (DirContext)connImpl.getConnection();
PermissionCollection storage = LDAPRealm.loadPermissions(ctx, dn);
PermissionCollection altered = new Permissions();
boolean found = false;
for (Enumeration enu=storage.elements(); enu.hasMoreElements(); ) {
Permission p = (Permission)enu.nextElement();
if (perm.equals(p)) {
found = true;
} else {
altered.add(p);
}
}
if (found) {
LDAPRealm.savePermissions(ctx, dn, altered);
_zone.log().error("--removePermissions() perm removed: "+altered);
} else {
_zone.log().error("--removePermissions() kusee.."+altered);
}
return altered;
} catch (Exception e) {
throw new OperationFailedException(e.getMessage());
} finally {
cleanupContext(connImpl);
}
}
static void savePermissions(DirContext ctx, String dn, PermissionCollection perms) throws Exception {
Attribute attr = new BasicAttribute("description");
Attributes attrs = new BasicAttributes();
for (Enumeration enu=perms.elements(); enu.hasMoreElements(); ) {
attr.add( createPermissionString((Permission)enu.nextElement()) );
}
attrs.put(attr);
ctx.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
}
static String createPermissionString(Permission p) {
StringBuffer b = new StringBuffer();
String type = p.getClass().getName();
String tmp = null;
if (type.equals("anvil.core.ToolPermission")) {
b.append("tool");
} else if (type.equals("anvil.core.RuntimePermission")) {
b.append("runtime");
} else if (type.equals("java.io.FilePermission")) {
b.append("file");
} else if (type.equals("java.net.SocketPermission")) {
b.append("socket");
} else if (type.equals("anvil.java.security.JavaPermission")) {
b.append("java");
} else if (type.equals("anvil.database.ConnectionPoolPermission")) {
b.append("pool");
} else if (type.equals("anvil.script.ImportPermission")) {
b.append("import");
} else if (type.equals("anvil.server.RealmPermission")) {
b.append("realm");
} else if (type.equals("anvil.server.NamespacePermission")) {
b.append("namespace");
} else if (type.equals("java.security.AllPermission")) {
b.append("all");
} else {
b.append(type);
}
b.append("|");
if ((tmp = p.getName()) != null) {
b.append(tmp);
}
b.append("|");
if ((tmp = p.getActions()) != null) {
b.append(tmp);
}
return b.toString();
}
static String[] parsePermission(String s)
{
StringTokenizer st = new StringTokenizer(s, "|");
int len = st.countTokens();
if (len < 2 || len > 3) return null;
String[] res = new String[len];
for (int i=0; i<len; i++) {
res[i] = st.nextToken();
}
return res;
}
}