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=4f20dc379895790af0ae2af4435daded268d53a1;hpb=25071ab6bcb2df1fa4057c2c04137f2d606772e7;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 4f20dc379..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 @@ -1,51 +1,107 @@ package org.argeo.osgi.useradmin; +import static org.argeo.osgi.useradmin.LdifName.inetOrgPerson; +import static org.argeo.osgi.useradmin.LdifName.objectClass; +import static org.argeo.osgi.useradmin.LdifName.organizationalPerson; +import static org.argeo.osgi.useradmin.LdifName.person; +import static org.argeo.osgi.useradmin.LdifName.top; + +import java.io.File; import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; import java.util.List; -import javax.transaction.RollbackException; +import javax.naming.InvalidNameException; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; -import org.osgi.service.useradmin.Group; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.Role; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; -public abstract class AbstractUserDirectory implements UserAdmin { - private boolean isReadOnly; - private URI uri; +/** Base class for a {@link UserDirectory}. */ +public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { + private final static Log log = LogFactory.getLog(AbstractUserDirectory.class); + + private final Hashtable properties; + private final LdapName baseDn; + private final String userObjectClass, userBase, groupObjectClass, groupBase; + + private final boolean readOnly; + private final URI uri; private UserAdmin externalRoles; - private List indexedUserProperties = Arrays.asList(new String[] { - "uid", "mail", "cn" }); + 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[] { "userpassword" }); + private List credentialAttributeIds = Arrays.asList(new String[] { LdifName.userPassword.name() }); - private TransactionSynchronizationRegistry syncRegistry; - private Object editingTransactionKey = null; + // JTA private TransactionManager transactionManager; - private Transaction editingTransaction; + private WcXaResource xaResource = new WcXaResource(this); - public AbstractUserDirectory() { - } + public AbstractUserDirectory(Dictionary props) { + properties = new Hashtable(); + for (Enumeration keys = props.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + properties.put(key, props.get(key)); + } - public AbstractUserDirectory(URI uri, boolean isReadOnly) { - this.uri = uri; - this.isReadOnly = isReadOnly; + String uriStr = UserAdminConf.uri.getValue(properties); + if (uriStr == null) + uri = null; + else + try { + uri = new URI(uriStr); + } catch (URISyntaxException e) { + throw new UserDirectoryException("Badly formatted URI " + uriStr, e); + } + + 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); + + userObjectClass = UserAdminConf.userObjectClass.getValue(properties); + userBase = UserAdminConf.userBase.getValue(properties); + groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties); + groupBase = UserAdminConf.groupBase.getValue(properties); } - /** 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); + + protected abstract DirectoryUser daoGetRole(LdapName key); + + protected abstract List doGetRoles(Filter f); public void init() { @@ -55,37 +111,37 @@ public abstract class AbstractUserDirectory implements UserAdmin { } - boolean isEditing() { - if (editingTransactionKey == null) - return false; - Object currentTrKey = syncRegistry.getTransactionKey(); - if (currentTrKey == null) - return false; - return editingTransactionKey.equals(currentTrKey); - } - - void checkEdit() { - Object currentTrKey = syncRegistry.getTransactionKey(); - if (currentTrKey == null) - throw new UserDirectoryException( - "A transaction needs to be active in order to edit"); - if (editingTransactionKey == null) { - editingTransactionKey = currentTrKey; - XAResource xaRes = getXAResource(); - if (xaRes != null) - try { - transactionManager.getTransaction().enlistResource(xaRes); - } catch (Exception e) { - throw new UserDirectoryException("Cannot enlist " + this, e); - } + protected boolean isEditing() { + return xaResource.wc() != null; + } + + protected UserDirectoryWorkingCopy getWorkingCopy() { + UserDirectoryWorkingCopy wc = xaResource.wc(); + if (wc == null) + return null; + return wc; + } + + protected void checkEdit() { + Transaction transaction; + try { + transaction = transactionManager.getTransaction(); + } catch (SystemException e) { + throw new UserDirectoryException("Cannot get transaction", e); + } + if (transaction == null) + throw new UserDirectoryException("A transaction needs to be active in order to edit"); + if (xaResource.wc() == null) { + try { + transaction.enlistResource(xaResource); + } catch (Exception e) { + throw new UserDirectoryException("Cannot enlist " + xaResource, e); + } } else { - if (!editingTransactionKey.equals(currentTrKey)) - throw new UserDirectoryException("Transaction " - + editingTransactionKey + " already editing"); } } - List getAllRoles(User user) { + protected List getAllRoles(DirectoryUser user) { List allRoles = new ArrayList(); if (user != null) { collectRoles(user, allRoles); @@ -95,9 +151,10 @@ public abstract class AbstractUserDirectory implements UserAdmin { 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); } @@ -107,15 +164,187 @@ public abstract class AbstractUserDirectory implements UserAdmin { // TODO gather anonymous roles } - public XAResource getXAResource() { + // USER ADMIN + @Override + public Role getRole(String name) { + 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(dn)) + user = wc.getNewUsers().get(dn); + else if (wc.getDeletedUsers().containsKey(dn)) + user = null; + } + return user; + } + + @SuppressWarnings("unchecked") + @Override + public Role[] getRoles(String filter) throws InvalidSyntaxException { + UserDirectoryWorkingCopy wc = getWorkingCopy(); + Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null; + List res = doGetRoles(f); + if (wc != null) { + for (Iterator it = res.iterator(); it.hasNext();) { + DirectoryUser user = it.next(); + LdapName dn = user.getDn(); + if (wc.getDeletedUsers().containsKey(dn)) + it.remove(); + } + for (DirectoryUser user : wc.getNewUsers().values()) { + if (f == null || f.match(user.getProperties())) + res.add(user); + } + // no need to check modified users, + // since doGetRoles was already based on the modified attributes + } + return res.toArray(new Role[res.size()]); + } + + @Override + public User getUser(String key, String value) { + // TODO check value null or empty + List collectedUsers = new ArrayList(getIndexedUserProperties().size()); + if (key != null) { + doGetUser(key, value, collectedUsers); + } else { + // try dn + DirectoryUser user = null; + try { + user = (DirectoryUser) getRole(value); + if (user != null) + collectedUsers.add(user); + } catch (Exception e) { + // silent + } + // try all indexes + for (String attr : getIndexedUserProperties()) + doGetUser(attr, value, collectedUsers); + } + 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; } - String getMemberAttributeId() { + 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)); + } + + @Override + public Role createRole(String name, int type) { + checkEdit(); + UserDirectoryWorkingCopy wc = getWorkingCopy(); + LdapName dn = toDn(name); + if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn)) + throw new UserDirectoryException("Already a role " + name); + 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()); + if (wc.getDeletedUsers().containsKey(dn)) { + wc.getDeletedUsers().remove(dn); + wc.getModifiedUsers().put(dn, attrs); + } else { + wc.getModifiedUsers().put(dn, attrs); + DirectoryUser newRole = newRole(dn, type, attrs); + wc.getNewUsers().put(dn, newRole); + } + return getRole(name); + } + + protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) { + LdifUser newRole; + BasicAttribute objClass = new BasicAttribute(objectClass.name()); + if (type == Role.USER) { + String userObjClass = newUserObjectClass(dn); + objClass.add(userObjClass); + if (inetOrgPerson.name().equals(userObjClass)) { + objClass.add(organizationalPerson.name()); + objClass.add(person.name()); + } else if (organizationalPerson.name().equals(userObjClass)) { + objClass.add(person.name()); + } + objClass.add(top.name()); + attrs.put(objClass); + newRole = new LdifUser(this, dn, attrs); + } else if (type == Role.GROUP) { + 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 + throw new UserDirectoryException("Unsupported type " + type); + return newRole; + } + + @Override + public boolean removeRole(String name) { + checkEdit(); + UserDirectoryWorkingCopy wc = getWorkingCopy(); + LdapName dn = toDn(name); + 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; + } + for (LdapName groupDn : getDirectGroups(dn)) { + DirectoryUser group = doGetRole(groupDn); + group.getAttributes().get(getMemberAttributeId()).remove(dn.toString()); + } + return actuallyDeleted; + } + + // TRANSACTION + protected void prepare(UserDirectoryWorkingCopy wc) { + + } + + protected void commit(UserDirectoryWorkingCopy wc) { + + } + + protected void rollback(UserDirectoryWorkingCopy wc) { + + } + + // UTILITIES + protected LdapName toDn(String name) { + try { + return new LdapName(name); + } catch (InvalidNameException e) { + throw new UserDirectoryException("Badly formatted name", e); + } + } + + // GETTERS + protected String getMemberAttributeId() { return memberAttributeId; } - List getCredentialAttributeIds() { + protected List getCredentialAttributeIds() { return credentialAttributeIds; } @@ -123,10 +352,6 @@ public abstract class AbstractUserDirectory implements UserAdmin { return uri; } - protected void setUri(URI uri) { - this.uri = uri; - } - protected List getIndexedUserProperties() { return indexedUserProperties; } @@ -135,28 +360,67 @@ public abstract class AbstractUserDirectory implements UserAdmin { 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); + 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 void setExternalRoles(UserAdmin externalRoles) { - this.externalRoles = externalRoles; + public LdapName getBaseDn() { + // always clone so that the property is not modified by reference + return (LdapName) baseDn.clone(); + } + + /** dn can be null, in that case a default should be returned. */ + public String getUserObjectClass() { + return userObjectClass; + } + + public String getUserBase() { + return userBase; + } + + protected String newUserObjectClass(LdapName dn) { + return getUserObjectClass(); + } + + public String getGroupObjectClass() { + return groupObjectClass; + } + + public String getGroupBase() { + return groupBase; } - public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) { - this.syncRegistry = syncRegistry; + public Dictionary getProperties() { + return properties; + } + + public void setExternalRoles(UserAdmin externalRoles) { + this.externalRoles = externalRoles; } public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } + public WcXaResource getXaResource() { + return xaResource; + } + }