X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.security.core%2Fsrc%2Forg%2Fargeo%2Fosgi%2Fuseradmin%2FAbstractUserDirectory.java;h=3f5bf850d55a890093804df97993f27f1eb809e4;hb=8260f4470f514ea347ca53f5b4dfc632c4a4de66;hp=1d2e72759b479151ddf032d8a8f9a1ac19ef5bd2;hpb=137290df09ccfb49fcdfc72b611aa8d32182342c;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java index 1d2e72759..3f5bf850d 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -12,10 +12,10 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Dictionary; -import java.util.HashMap; +import java.util.Enumeration; +import java.util.Hashtable; import java.util.Iterator; import java.util.List; -import java.util.Map; import javax.naming.InvalidNameException; import javax.naming.directory.Attributes; @@ -26,9 +26,6 @@ import javax.naming.ldap.Rdn; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -36,41 +33,38 @@ import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; -abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { - private final static Log log = LogFactory - .getLog(AbstractUserDirectory.class); +/** Base class for a {@link UserDirectory}. */ +public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { + private final static Log log = LogFactory.getLog(AbstractUserDirectory.class); - private Dictionary properties; - private String baseDn = "dc=example,dc=com"; - private String userObjectClass; - private String groupObjectClass; + private final Hashtable properties; + private final LdapName baseDn; + private final String userObjectClass, userBase, groupObjectClass, groupBase; - private boolean isReadOnly; - private URI uri; + private final boolean readOnly; + private final URI uri; private UserAdmin externalRoles; - private List indexedUserProperties = Arrays.asList(new String[] { - LdifName.uid.name(), LdifName.mail.name(), LdifName.cn.name() }); + private List indexedUserProperties = Arrays + .asList(new String[] { LdifName.uid.name(), LdifName.mail.name(), LdifName.cn.name() }); private String memberAttributeId = "member"; - private List credentialAttributeIds = Arrays - .asList(new String[] { LdifName.userpassword.name() }); - - // private TransactionSynchronizationRegistry syncRegistry; - // private Object editingTransactionKey = null; + private List credentialAttributeIds = Arrays.asList(new String[] { LdifName.userPassword.name() }); + // JTA private TransactionManager transactionManager; - private ThreadLocal workingCopy = new ThreadLocal(); - private Xid editingTransactionXid = null; + private WcXaResource xaResource = new WcXaResource(this); - public AbstractUserDirectory(Dictionary properties) { - // TODO make a copy? - this.properties = properties; + public AbstractUserDirectory(Dictionary props) { + properties = new Hashtable(); + for (Enumeration keys = props.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + properties.put(key, props.get(key)); + } String uriStr = UserAdminConf.uri.getValue(properties); if (uriStr == null) @@ -79,29 +73,29 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { try { uri = new URI(uriStr); } catch (URISyntaxException e) { - throw new UserDirectoryException("Badly formatted URI", e); + throw new UserDirectoryException("Badly formatted URI " + uriStr, e); } - baseDn = UserAdminConf.baseDn.getValue(properties).toString(); - String isReadOnly = UserAdminConf.readOnly.getValue(properties); - if (isReadOnly == null) - this.isReadOnly = readOnlyDefault(uri); - else - this.isReadOnly = new Boolean(isReadOnly); + try { + baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties)); + } catch (InvalidNameException e) { + throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e); + } + String readOnlyStr = UserAdminConf.readOnly.getValue(properties); + if (readOnlyStr == null) { + readOnly = readOnlyDefault(uri); + properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly)); + } else + readOnly = new Boolean(readOnlyStr); - this.userObjectClass = UserAdminConf.userObjectClass - .getValue(properties); - this.groupObjectClass = UserAdminConf.groupObjectClass - .getValue(properties); + userObjectClass = UserAdminConf.userObjectClass.getValue(properties); + userBase = UserAdminConf.userBase.getValue(properties); + groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties); + groupBase = UserAdminConf.groupBase.getValue(properties); } - // public AbstractUserDirectory(URI uri, boolean isReadOnly) { - // this.uri = uri; - // this.isReadOnly = isReadOnly; - // } - - /** Returns the {@link Group}s this user is a direct member of. */ - protected abstract List getDirectGroups(User user); + /** Returns the groups this user is a direct member of. */ + protected abstract List getDirectGroups(LdapName dn); protected abstract Boolean daoHasRole(LdapName dn); @@ -109,9 +103,6 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { protected abstract List doGetRoles(Filter f); - protected abstract void doGetUser(String key, String value, - List collectedUsers); - public void init() { } @@ -120,28 +111,18 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { } - boolean isEditing() { - if (editingTransactionXid == null) - return false; - return workingCopy.get() != null; - // Object currentTrKey = syncRegistry.getTransactionKey(); - // if (currentTrKey == null) - // return false; - // return editingTransactionKey.equals(currentTrKey); + protected boolean isEditing() { + return xaResource.wc() != null; } - protected WorkingCopy getWorkingCopy() { - WorkingCopy wc = workingCopy.get(); + protected UserDirectoryWorkingCopy getWorkingCopy() { + UserDirectoryWorkingCopy wc = xaResource.wc(); if (wc == null) return null; - if (wc.xid == null) { - workingCopy.set(null); - return null; - } return wc; } - void checkEdit() { + protected void checkEdit() { Transaction transaction; try { transaction = transactionManager.getTransaction(); @@ -149,29 +130,18 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { throw new UserDirectoryException("Cannot get transaction", e); } if (transaction == null) - throw new UserDirectoryException( - "A transaction needs to be active in order to edit"); - if (editingTransactionXid == null) { - WorkingCopy wc = new WorkingCopy(); + throw new UserDirectoryException("A transaction needs to be active in order to edit"); + if (xaResource.wc() == null) { try { - transaction.enlistResource(wc); - editingTransactionXid = wc.getXid(); - workingCopy.set(wc); + transaction.enlistResource(xaResource); } catch (Exception e) { - throw new UserDirectoryException("Cannot enlist " + wc, e); + throw new UserDirectoryException("Cannot enlist " + xaResource, e); } } else { - if (workingCopy.get() == null) - throw new UserDirectoryException("Transaction " - + editingTransactionXid + " already editing"); - else if (!editingTransactionXid.equals(workingCopy.get().getXid())) - throw new UserDirectoryException("Working copy Xid " - + workingCopy.get().getXid() + " inconsistent with" - + editingTransactionXid); } } - List getAllRoles(User user) { + protected List getAllRoles(DirectoryUser user) { List allRoles = new ArrayList(); if (user != null) { collectRoles(user, allRoles); @@ -181,9 +151,10 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { return allRoles; } - private void collectRoles(User user, List allRoles) { - for (Group group : getDirectGroups(user)) { + private void collectRoles(DirectoryUser user, List allRoles) { + for (LdapName groupDn : getDirectGroups(user.getDn())) { // TODO check for loops + DirectoryUser group = doGetRole(groupDn); allRoles.add(group); collectRoles(group, allRoles); } @@ -196,13 +167,16 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { // USER ADMIN @Override public Role getRole(String name) { - LdapName key = toDn(name); - WorkingCopy wc = getWorkingCopy(); - DirectoryUser user = daoGetRole(key); + return doGetRole(toDn(name)); + } + + protected DirectoryUser doGetRole(LdapName dn) { + UserDirectoryWorkingCopy wc = getWorkingCopy(); + DirectoryUser user = daoGetRole(dn); if (wc != null) { - if (user == null && wc.getNewUsers().containsKey(key)) - user = wc.getNewUsers().get(key); - else if (wc.getDeletedUsers().containsKey(key)) + if (user == null && wc.getNewUsers().containsKey(dn)) + user = wc.getNewUsers().get(dn); + else if (wc.getDeletedUsers().containsKey(dn)) user = null; } return user; @@ -211,7 +185,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { @SuppressWarnings("unchecked") @Override public Role[] getRoles(String filter) throws InvalidSyntaxException { - WorkingCopy wc = getWorkingCopy(); + UserDirectoryWorkingCopy wc = getWorkingCopy(); Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null; List res = doGetRoles(f); if (wc != null) { @@ -234,8 +208,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { @Override public User getUser(String key, String value) { // TODO check value null or empty - List collectedUsers = new ArrayList( - getIndexedUserProperties().size()); + List collectedUsers = new ArrayList(getIndexedUserProperties().size()); if (key != null) { doGetUser(key, value, collectedUsers); } else { @@ -254,25 +227,35 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { } if (collectedUsers.size() == 1) return collectedUsers.get(0); + else if (collectedUsers.size() > 1) + log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" : "") + value); return null; } + protected void doGetUser(String key, String value, List collectedUsers) { + try { + Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")"); + List users = doGetRoles(f); + collectedUsers.addAll(users); + } catch (InvalidSyntaxException e) { + throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e); + } + } + @Override public Authorization getAuthorization(User user) { - return new LdifAuthorization((DirectoryUser) user, - getAllRoles((DirectoryUser) user)); + return new LdifAuthorization((DirectoryUser) user, getAllRoles((DirectoryUser) user)); } @Override public Role createRole(String name, int type) { checkEdit(); - WorkingCopy wc = getWorkingCopy(); + UserDirectoryWorkingCopy wc = getWorkingCopy(); LdapName dn = toDn(name); - if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) - || wc.getNewUsers().containsKey(dn)) + if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn)) throw new UserDirectoryException("Already a role " + name); - BasicAttributes attrs = new BasicAttributes(); - attrs.put("dn", dn.toString()); + BasicAttributes attrs = new BasicAttributes(true); + // attrs.put(LdifName.dn.name(), dn.toString()); Rdn nameRdn = dn.getRdn(dn.size() - 1); // TODO deal with multiple attr RDN attrs.put(nameRdn.getType(), nameRdn.getValue()); @@ -291,7 +274,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { LdifUser newRole; BasicAttribute objClass = new BasicAttribute(objectClass.name()); if (type == Role.USER) { - String userObjClass = getUserObjectClass(); + String userObjClass = newUserObjectClass(dn); objClass.add(userObjClass); if (inetOrgPerson.name().equals(userObjClass)) { objClass.add(organizationalPerson.name()); @@ -299,12 +282,14 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { } else if (organizationalPerson.name().equals(userObjClass)) { objClass.add(person.name()); } - objClass.add(top); + objClass.add(top.name()); attrs.put(objClass); newRole = new LdifUser(this, dn, attrs); } else if (type == Role.GROUP) { - objClass.add(getGroupObjectClass()); - objClass.add(top); + String groupObjClass = getGroupObjectClass(); + objClass.add(groupObjClass); + // objClass.add(LdifName.extensibleObject.name()); + objClass.add(top.name()); attrs.put(objClass); newRole = new LdifGroup(this, dn, attrs); } else @@ -315,30 +300,33 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { @Override public boolean removeRole(String name) { checkEdit(); - WorkingCopy wc = getWorkingCopy(); + UserDirectoryWorkingCopy wc = getWorkingCopy(); LdapName dn = toDn(name); - if (!daoHasRole(dn) && !wc.getNewUsers().containsKey(dn)) - return false; - DirectoryUser user = (DirectoryUser) getRole(name); - wc.getDeletedUsers().put(dn, user); - // FIXME clarify directgroups - for (DirectoryGroup group : getDirectGroups(user)) { - group.getAttributes().get(getMemberAttributeId()) - .remove(dn.toString()); + boolean actuallyDeleted; + if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) { + DirectoryUser user = (DirectoryUser) getRole(name); + wc.getDeletedUsers().put(dn, user); + actuallyDeleted = true; + } else {// just removing from groups (e.g. system roles) + actuallyDeleted = false; } - return true; + for (LdapName groupDn : getDirectGroups(dn)) { + DirectoryUser group = doGetRole(groupDn); + group.getAttributes().get(getMemberAttributeId()).remove(dn.toString()); + } + return actuallyDeleted; } // TRANSACTION - protected void prepare(WorkingCopy wc) { + protected void prepare(UserDirectoryWorkingCopy wc) { } - protected void commit(WorkingCopy wc) { + protected void commit(UserDirectoryWorkingCopy wc) { } - protected void rollback(WorkingCopy wc) { + protected void rollback(UserDirectoryWorkingCopy wc) { } @@ -352,12 +340,11 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { } // GETTERS - - String getMemberAttributeId() { + protected String getMemberAttributeId() { return memberAttributeId; } - List getCredentialAttributeIds() { + protected List getCredentialAttributeIds() { return credentialAttributeIds; } @@ -365,10 +352,6 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { return uri; } - protected void setUri(URI uri) { - this.uri = uri; - } - protected List getIndexedUserProperties() { return indexedUserProperties; } @@ -377,41 +360,54 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { this.indexedUserProperties = indexedUserProperties; } - protected void setReadOnly(boolean isReadOnly) { - this.isReadOnly = isReadOnly; - } - private static boolean readOnlyDefault(URI uri) { if (uri == null) return true; if (uri.getScheme().equals("file")) { File file = new File(uri); - return !file.canWrite(); + if (file.exists()) + return !file.canWrite(); + else + return !file.getParentFile().canWrite(); } return true; } public boolean isReadOnly() { - return isReadOnly; + return readOnly; } - UserAdmin getExternalRoles() { + protected UserAdmin getExternalRoles() { return externalRoles; } - public String getBaseDn() { - return baseDn; + public LdapName getBaseDn() { + // always clone so that the property is not modified by reference + return (LdapName) baseDn.clone(); } - protected String getUserObjectClass() { + /** dn can be null, in that case a default should be returned. */ + public String getUserObjectClass() { return userObjectClass; } - protected String getGroupObjectClass() { + public String getUserBase() { + return userBase; + } + + protected String newUserObjectClass(LdapName dn) { + return getUserObjectClass(); + } + + public String getGroupObjectClass() { return groupObjectClass; } - public Dictionary getProperties() { + public String getGroupBase() { + return groupBase; + } + + public Dictionary getProperties() { return properties; } @@ -423,156 +419,8 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { this.transactionManager = transactionManager; } - // - // XA RESOURCE - // - protected class WorkingCopy implements XAResource { - private Xid xid; - private int transactionTimeout = 0; - - private Map newUsers = new HashMap(); - private Map modifiedUsers = new HashMap(); - private Map deletedUsers = new HashMap(); - - @Override - public void start(Xid xid, int flags) throws XAException { - if (editingTransactionXid != null) - throw new UserDirectoryException("Transaction " - + editingTransactionXid + " already editing"); - this.xid = xid; - } - - @Override - public void end(Xid xid, int flags) throws XAException { - checkXid(xid); - - // clean collections - newUsers.clear(); - newUsers = null; - modifiedUsers.clear(); - modifiedUsers = null; - deletedUsers.clear(); - deletedUsers = null; - - // clean IDs - this.xid = null; - editingTransactionXid = null; - } - - @Override - public int prepare(Xid xid) throws XAException { - checkXid(xid); - if (noModifications()) - return XA_RDONLY; - try { - AbstractUserDirectory.this.prepare(this); - } catch (Exception e) { - log.error("Cannot prepare " + xid, e); - throw new XAException(XAException.XA_RBOTHER); - } - return XA_OK; - } - - @Override - public void commit(Xid xid, boolean onePhase) throws XAException { - checkXid(xid); - if (noModifications()) - return; - try { - if (onePhase) - AbstractUserDirectory.this.prepare(this); - AbstractUserDirectory.this.commit(this); - } catch (Exception e) { - log.error("Cannot commit " + xid, e); - throw new XAException(XAException.XA_RBOTHER); - } - } - - @Override - public void rollback(Xid xid) throws XAException { - checkXid(xid); - try { - AbstractUserDirectory.this.rollback(this); - } catch (Exception e) { - log.error("Cannot rollback " + xid, e); - throw new XAException(XAException.XA_HEURMIX); - } - } - - @Override - public void forget(Xid xid) throws XAException { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isSameRM(XAResource xares) throws XAException { - return xares == this; - } - - @Override - public Xid[] recover(int flag) throws XAException { - throw new UnsupportedOperationException(); - } - - @Override - public int getTransactionTimeout() throws XAException { - return transactionTimeout; - } - - @Override - public boolean setTransactionTimeout(int seconds) throws XAException { - transactionTimeout = seconds; - return true; - } - - private Xid getXid() { - return xid; - } - - private void checkXid(Xid xid) throws XAException { - if (this.xid == null) - throw new XAException(XAException.XAER_OUTSIDE); - if (!this.xid.equals(xid)) - throw new XAException(XAException.XAER_NOTA); - } - - @Override - protected void finalize() throws Throwable { - if (editingTransactionXid != null) - log.warn("Editing transaction still referenced but no working copy " - + editingTransactionXid); - editingTransactionXid = null; - } - - public boolean noModifications() { - return newUsers.size() == 0 && modifiedUsers.size() == 0 - && deletedUsers.size() == 0; - } - - public Attributes getAttributes(LdapName dn) { - if (modifiedUsers.containsKey(dn)) - return modifiedUsers.get(dn); - return null; - } - - public void startEditing(DirectoryUser user) { - LdapName dn = user.getDn(); - if (modifiedUsers.containsKey(dn)) - throw new UserDirectoryException("Already editing " + dn); - modifiedUsers.put(dn, (Attributes) user.getAttributes().clone()); - } - - public Map getNewUsers() { - return newUsers; - } - - public Map getDeletedUsers() { - return deletedUsers; - } - - public Map getModifiedUsers() { - return modifiedUsers; - } - + public WcXaResource getXaResource() { + return xaResource; } + }