package examples.security.delegatingrealm;
import examples.security.util.RealmProperties;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.security.acl.Acl;
import java.security.acl.Group;
import java.security.acl.NotOwnerException;
import java.security.acl.Permission;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import weblogic.logging.LogOutputStream;
import weblogic.security.acl.BasicRealm;
import weblogic.security.acl.DebuggableRealm;
import weblogic.security.acl.DelegatedRealm;
import weblogic.security.acl.ManageableRealm;
import weblogic.security.acl.User;
import weblogic.security.acl.UserInfo;
import weblogic.utils.UnsupportedOperationException;
/**
* This realm delegates calls to methods of other objects. This can
* be useful if, for example, you want to obtain user and group
* information from the LDAP realm, but maintain ACLs in the RDBMS
* realm. <p>
*
* To use this class, set its name as the value of your
* <tt>weblogic.security.realmClass</tt> property, and create a
* <tt>delegating.properties</tt> file. The file uses standard Java
* properties file syntax, and should contain entries such as the
* following:
*
*<pre>
getUser = weblogic.security.ldaprealm.LDAPRealm.getUser
getGroup = weblogic.security.ldaprealm.LDAPRealm.getGroup
authenticate.1 = weblogic.security.ldaprealm.LDAPRealm.authenticate
authenticate.2 = examples.security.rdbmsrealm.RDBMSRealm.authenticate
getAcl = examples.security.rdbmsrealm.RDBMSRealm.getAcl
</pre>
*
* The above properties delegate user and group calls to the LDAP
* realm. The LDAP realm is the primary authentication delegate and
* the RDBMS realm is the secondary authentication delegate, which is
* consulted if the primary returns null. ACL-related
* methods are delegated to the RDBMS realm. <p>
*
* You should examine the source of this class before using it, to
* make sure it behaves as you expect. It should be easy to tailor to
* your exact needs.
*
* @see #deleteUser
* @author Copyright (c) 2000 by BEA Systems, Inc. All Rights Reserved.
*/
public class DelegatingRealm
implements ManageableRealm, DebuggableRealm, DelegatedRealm
{
// A few implementation notes and caveats:
// You may not be able to fully "mix and match" objects from
// different realms without some care. For example, the default
// RDBMS realm implementation looks for groups and users in the
// database. If you delegate ACLs to the RDBMS realm and delegate
// users to, say, the LDAP realm, you won't be able to add an LDAP
// user to the RDBMS realm without making some modifications to both
// the RDBMS realm (to look back in the Delegating realm for users
// it can't find) and the Delegating realm (so it won't recursively
// call back into the RDBMS realm if that's also specified as a
// source of users - this would lead to a recursive loop and a stack
// overflow).
private static final String PROPS_FILE = "delegating.properties";
private static final String PROPS_PREFIX = "";
private static RealmProperties props;
private static Hashtable proxyInstances = new Hashtable();
static
{
try
{
props = new RealmProperties(DelegatingRealm.class, PROPS_FILE,
PROPS_PREFIX);
}
catch (IOException e)
{
throw new DelegatingException(PROPS_FILE + " not found", e);
}
}
private static Vector proxies = new Vector();
private static Vector realms = new Vector();
private String realmName = "delegating";
private Proxy[] getUserProxies =
getProxies("getUser", new Class[]{ String.class });
private Proxy[] authenticateProxies =
getProxies("authenticate", new Class[]{ UserInfo.class });
private Proxy[] getGroupProxies =
getProxies("getGroup", new Class[]{ String.class });
private Proxy[] getAclOwnerProxies =
getProxies("getAclOwner", new Class[]{ Object.class });
private Proxy[] getAclProxies =
getProxies("getAcl", new Class[]{ String.class });
private Proxy[] getAclSepProxies =
getProxies("getAclSep", new Class[]{ String.class, Character.class });
private Proxy[] getPermissionProxies =
getProxies("getPermission", new Class[]{ String.class });
private Proxy[] getUsersProxies =
getProxies("getUsers", new Class[0]);
private Proxy[] getGroupsProxies =
getProxies("getGroups", new Class[0]);
private Proxy[] getAclsProxies =
getProxies("getAcls", new Class[0]);
private Proxy[] getPermissionsProxies =
getProxies("getPermissions", new Class[0]);
private Proxy[] newUserProxies =
getProxies("newUser", new Class[]{ String.class, Object.class });
private Proxy[] newGroupProxies =
getProxies("newGroup", new Class[]{ String.class });
private Proxy[] newAclProxies =
getProxies("newAcl", new Class[]{ Principal.class, String.class });
private Proxy[] newPermissionProxies =
getProxies("newPermission", new Class[]{ String.class });
private Proxy[] deleteUserProxies =
getProxies("deleteUser", new Class[]{ User.class });
private Proxy[] deleteGroupProxies =
getProxies("deleteGroup", new Class[]{ Group.class });
private Proxy[] deleteAclProxies =
getProxies("deleteAcl", new Class[]{ Principal.class, Acl.class });
private Proxy[] deletePermissionProxies =
getProxies("deletePermission", new Class[]{ Permission.class });
private Proxy[] setPermissionProxies =
getProxies("setPermission",
new Class[]{ Acl.class, Principal.class,
Permission.class, Boolean.class });
private LogOutputStream log;
public DelegatingRealm()
{
super();
}
private static Proxy[] getProxies(String propName, Class[] paramTypes)
{
Vector mv = new Vector();
Proxy proxy = getProxy(propName, paramTypes);
if (proxy == null)
{
return null;
}
mv.addElement(proxy);
if ((proxy = getProxy(propName + ".0", paramTypes)) != null)
{
mv.addElement(proxy);
}
for (int i = 1; (proxy = getProxy(propName + "." + i,
paramTypes)) != null; i++)
{
mv.addElement(proxy);
}
Proxy[] prxs = new Proxy[mv.size()];
mv.copyInto(prxs);
return prxs;
}
private static Proxy getProxy(String propName, Class[] paramTypes)
{
String methodName = props.get(propName);
if (methodName == null)
{
return null;
}
int lastDot = methodName.lastIndexOf('.');
if (lastDot == -1)
{
throw new RuntimeException("internal property name error");
}
String className = methodName.substring(0, lastDot);
methodName = methodName.substring(lastDot + 1);
Class clazz = null;
// If we delegate several calls to methods on a single class, make
// sure we use just one instance of that class, so we don't create
// too many unnecessary objects.
Object obj = proxyInstances.get(className);
if (obj == null)
{
try
{
clazz = Class.forName(className);
}
catch (ClassNotFoundException e)
{
throw new DelegatingException(e);
}
} else {
clazz = obj.getClass();
}
try
{
boolean newlyCreated = false;
if (obj == null)
{
obj = clazz.newInstance();
proxyInstances.put(className, obj);
newlyCreated = true;
}
Proxy p = new Proxy(obj, clazz.getMethod(methodName, paramTypes));
if (newlyCreated && obj instanceof BasicRealm)
{
realms.addElement(obj);
}
proxies.addElement(p);
return p;
}
catch (NoSuchMethodException e)
{
throw new DelegatingException(e);
}
catch (InstantiationException e)
{
throw new DelegatingException(e);
}
catch (IllegalAccessException e)
{
throw new DelegatingException(e);
}
}
public void init(String name, Object ownerCredential)
throws NotOwnerException
{
Enumeration enum = realms.elements();
while (enum.hasMoreElements())
{
BasicRealm realm = (BasicRealm) enum.nextElement();
realm.init(name, ownerCredential);
}
}
public void load(String name, Object credential)
throws ClassNotFoundException, IOException, NotOwnerException
{
Enumeration enum = realms.elements();
while (enum.hasMoreElements())
{
BasicRealm realm = (BasicRealm) enum.nextElement();
realm.load(name, credential);
}
}
public void save(String name)
throws IOException
{
Enumeration enum = realms.elements();
while (enum.hasMoreElements())
{
BasicRealm realm = (BasicRealm) enum.nextElement();
realm.save(name);
}
}
public String getName()
{
return realmName;
}
public void setDebug(boolean enable)
{
if (enable && log == null)
{
log = new LogOutputStream("DelegatingRealm");
}
if (!enable)
{
log = null;
}
Enumeration enum = realms.elements();
while (enum.hasMoreElements())
{
Object realm = enum.nextElement();
if (realm instanceof DebuggableRealm)
{
((DebuggableRealm) realm).setDebug(enable);
}
}
}
public User getUser(String name)
{
return (User) callProxy(getUserProxies, new Object[]{ name });
}
public User getUser(UserInfo userInfo)
{
return authenticate(userInfo);
}
public User authenticate(UserInfo userInfo)
{
return (User) callProxy(authenticateProxies, new Object[]{ userInfo });
}
public Group getGroup(String name)
{
return (Group) callProxy(getGroupProxies, new Object[]{ name });
}
public Principal getAclOwner(Object credential)
{
return (Principal) callProxy(getAclOwnerProxies,
new Object[]{ credential });
}
public Acl getAcl(String name)
{
return (Acl) callProxy(getAclProxies, new Object[]{ name });
}
public Acl getAcl(String name, char separator)
{
return (Acl) callProxy(getAclSepProxies,
new Object[]{ name, new Character(separator) });
}
public Permission getPermission(String name)
{
return (Permission) callProxy(getPermissionProxies, new Object[]{ name });
}
public Enumeration getUsers()
{
return (Enumeration) callProxy(getUsersProxies, new Object[0]);
}
public Enumeration getGroups()
{
return (Enumeration) callProxy(getGroupsProxies, new Object[0]);
}
public Enumeration getAcls()
{
return (Enumeration) callProxy(getAclsProxies, new Object[0]);
}
public Enumeration getPermissions()
{
return (Enumeration) callProxy(getPermissionsProxies, new Object[0]);
}
public User newUser(String name, Object credential, Object constraints)
throws SecurityException
{
return (User) callProxy(newUserProxies,
new Object[]{ name, credential, constraints });
}
public Group newGroup(String name)
throws SecurityException
{
return (Group) callProxy(newGroupProxies, new Object[]{ name });
}
public Acl newAcl(Principal owner, String name)
throws SecurityException
{
return (Acl) callProxy(newAclProxies, new Object[]{ owner, name });
}
public Permission newPermission(String name)
throws SecurityException
{
return (Permission) callProxy(newPermissionProxies, new Object[]{ name });
}
/**
* This method has an implementation note that you should read. <p>
*
* For this and other methods that have return type <tt>void</tt>,
* if you specify multiple delegates in the properties file, only
* the first will be called. To change this behavior, edit the
* methods you are interested in so that they call
* <tt>callProxies</tt> instead.
*/
public void deleteUser(User user)
throws SecurityException
{
callProxy(deleteGroupProxies, new Object[]{ user });
}
public void deleteGroup(Group group)
throws SecurityException
{
callProxy(deleteGroupProxies, new Object[]{ group });
}
public void deleteAcl(Principal owner, Acl acl)
throws SecurityException
{
callProxy(deleteAclProxies, new Object[]{ owner, acl });
}
public void deletePermission(Permission perm)
throws SecurityException
{
callProxy(deletePermissionProxies, new Object[]{ perm });
}
public void setPermission(Acl acl, Principal principal,
Permission permission, boolean allow)
{
callProxy(setPermissionProxies, new Object[]{ acl, principal, permission,
new Boolean(allow) });
}
/**
* Call a method on every one of a set of proxies.
*
* @see callProxyEx
*/
private void callProxies(Proxy[] proxies, Object[] args)
{
callProxy(proxies, args, true);
}
/**
* Call a method on a set of proxies.
*
* @see callProxyEx
*/
private Object callProxy(Proxy[] proxies, Object[] args)
{
return callProxy(proxies, args, false);
}
/**
* Call a method on a set of proxies.
*
* @see callProxyEx
*/
private Object callProxy(Proxy[] proxies, Object[] args, boolean callAll)
{
try
{
return callProxyEx(proxies, args, callAll);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new DelegatingException("invocation failed", e);
}
}
/**
* Call a method on a set of proxies. Attempt to obtain in a
* sensible way any exception that may be thrown. If the callAll
* parameter is set to true, all proxies are called, and the return
* value of the last is returned. Otherwise, calling stops at the
* first method to return a non-null value, and its return value is
* returned.
*/
private Object callProxyEx(Proxy[] proxies, Object[] args, boolean callAll)
throws Exception
{
if (proxies == null)
{
throw new UnsupportedOperationException("operation not supported by delegates");
}
Object result = null;
try
{
for (int i = 0; i < proxies.length; i++)
{
if (callAll == false && (result = proxies[i].invoke(args)) != null)
{
break;
}
}
}
catch (InvocationTargetException e)
{
Throwable t = e.getTargetException();
if (t instanceof Exception)
{
throw (Exception) t;
} else {
throw new DelegatingException("invocation failed", t);
}
}
return result;
}
public LogOutputStream getDebugLog()
{
return log;
}
private static class Proxy
{
Object object;
Method method;
Proxy(Object obj, Method meth)
{
object = obj;
method = meth;
}
Object invoke(Object[] args)
throws InvocationTargetException
{
try
{
return method.invoke(object, args);
}
catch (IllegalAccessException e)
{
throw new DelegatingException("delegation failed", e);
}
}
}
private Object delegator;
/**
* Set the delegator of all of our delegated objects. We explicitly
* avoid setting ourselves up as the delegator, because we want to
* be as transparent as possible.
*/
public void setDelegator(Object obj)
{
delegator = obj;
Enumeration enum = proxyInstances.elements();
while (enum.hasMoreElements())
{
Object o = enum.nextElement();
if (o instanceof DelegatedRealm)
{
((DelegatedRealm) o).setDelegator(obj);
}
}
}
public Object getDelegator()
{
return delegator;
}
}