From: Mathieu Baudier Date: Tue, 15 Sep 2015 07:48:59 +0000 (+0000) Subject: - Improve transaction support X-Git-Tag: argeo-commons-2.1.30~159 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=9a9418f4c0df975756de3093df71d757c72a386d;p=lgpl%2Fargeo-commons.git - Improve transaction support - Can write in LDAP git-svn-id: https://svn.argeo.org/commons/trunk@8393 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java index a07645f15..f3a27fe53 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java @@ -24,8 +24,11 @@ class SimpleTransaction implements Transaction, Status { private int status = Status.STATUS_ACTIVE; private final List xaResources = new ArrayList(); - public SimpleTransaction() { - xid = new UuidXid(); + private final SimpleTransactionManager transactionManager; + + public SimpleTransaction(SimpleTransactionManager transactionManager) { + this.xid = new UuidXid(); + this.transactionManager = transactionManager; } @Override @@ -64,19 +67,73 @@ class SimpleTransaction implements Transaction, Status { rollback(); throw new RollbackException(); } + + // complete status = STATUS_COMMITTED; + if (log.isDebugEnabled()) + log.debug("COMMITTED " + xid); + clearResources(XAResource.TMSUCCESS); + transactionManager.unregister(xid); } @Override - public synchronized boolean delistResource(XAResource xaRes, int flag) - throws IllegalStateException, SystemException { - return xaResources.remove(xaRes); + public synchronized void rollback() throws IllegalStateException, + SystemException { + status = STATUS_ROLLING_BACK; + for (XAResource xaRes : xaResources) { + try { + xaRes.rollback(xid); + } catch (XAException e) { + log.error("Cannot rollback " + xaRes + " for " + xid, e); + } + } + + // complete + status = STATUS_ROLLEDBACK; + if (log.isDebugEnabled()) + log.debug("ROLLEDBACK " + xid); + clearResources(XAResource.TMFAIL); + transactionManager.unregister(xid); } @Override public synchronized boolean enlistResource(XAResource xaRes) throws RollbackException, IllegalStateException, SystemException { - return xaResources.add(xaRes); + if (xaResources.add(xaRes)) { + try { + xaRes.start(getXid(), XAResource.TMNOFLAGS); + return true; + } catch (XAException e) { + log.error("Cannot enlist " + xaRes, e); + return false; + } + } else + return false; + } + + @Override + public synchronized boolean delistResource(XAResource xaRes, int flag) + throws IllegalStateException, SystemException { + if (xaResources.remove(xaRes)) { + try { + xaRes.end(getXid(), flag); + } catch (XAException e) { + log.error("Cannot delist " + xaRes, e); + return false; + } + return true; + } else + return false; + } + + protected void clearResources(int flag) { + for (XAResource xaRes : xaResources) + try { + xaRes.end(getXid(), flag); + } catch (XAException e) { + log.error("Cannot end " + xaRes, e); + } + xaResources.clear(); } @Override @@ -90,20 +147,6 @@ class SimpleTransaction implements Transaction, Status { throw new UnsupportedOperationException(); } - @Override - public synchronized void rollback() throws IllegalStateException, - SystemException { - status = STATUS_ROLLING_BACK; - for (XAResource xaRes : xaResources) { - try { - xaRes.rollback(xid); - } catch (XAException e) { - log.error("Cannot rollback " + xaRes + " for " + xid, e); - } - } - status = STATUS_ROLLEDBACK; - } - @Override public void setRollbackOnly() throws IllegalStateException, SystemException { status = STATUS_MARKED_ROLLBACK; @@ -114,7 +157,7 @@ class SimpleTransaction implements Transaction, Status { return xid.hashCode(); } - public Xid getXid() { + Xid getXid() { return xid; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java index 6261a0265..edb572681 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java @@ -38,11 +38,11 @@ public class SimpleTransactionManager implements TransactionManager, if (getCurrent() != null) throw new NotSupportedException( "Nested transactions are not supported"); - SimpleTransaction transaction = new SimpleTransaction(); + SimpleTransaction transaction = new SimpleTransaction(this); knownTransactions.put(transaction.getXid(), transaction); current.set(transaction); if (log.isDebugEnabled()) - log.debug("Started transaction " + transaction.getXid()); + log.debug("STARTED " + transaction.getXid()); } @Override @@ -75,17 +75,14 @@ public class SimpleTransactionManager implements TransactionManager, if (Status.STATUS_COMMITTED == status || Status.STATUS_ROLLEDBACK == status) { current.remove(); - knownTransactions.remove(transaction.getXid()); - if (log.isDebugEnabled()) - log.debug("Completed transaction " - + transaction.getXid() - + " [" - + (status == Status.STATUS_ROLLEDBACK ? "FAILED" : "OK") - + "]"); return null; } return transaction; } + + void unregister(Xid xid){ + knownTransactions.remove(xid); + } @Override public void resume(Transaction tobj) throws InvalidTransactionException, 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..77f68463f 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 @@ -3,9 +3,16 @@ package org.argeo.osgi.useradmin; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; -import javax.transaction.RollbackException; +import javax.naming.InvalidNameException; +import javax.naming.directory.Attributes; +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; @@ -14,12 +21,20 @@ 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; +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; public abstract class AbstractUserDirectory implements UserAdmin { + private final static Log log = LogFactory + .getLog(AbstractUserDirectory.class); private boolean isReadOnly; private URI uri; @@ -31,10 +46,12 @@ public abstract class AbstractUserDirectory implements UserAdmin { private List credentialAttributeIds = Arrays .asList(new String[] { "userpassword" }); - private TransactionSynchronizationRegistry syncRegistry; - private Object editingTransactionKey = null; + // private TransactionSynchronizationRegistry syncRegistry; + // private Object editingTransactionKey = null; + private TransactionManager transactionManager; - private Transaction editingTransaction; + private ThreadLocal workingCopy = new ThreadLocal(); + private Xid editingTransactionXid = null; public AbstractUserDirectory() { } @@ -45,7 +62,16 @@ public abstract class AbstractUserDirectory implements UserAdmin { } /** Returns the {@link Group}s this user is a direct member of. */ - protected abstract List getDirectGroups(User user); + protected abstract List getDirectGroups(User user); + + protected abstract Boolean daoHasRole(LdapName dn); + + protected abstract DirectoryUser daoGetRole(LdapName key); + + protected abstract List doGetRoles(Filter f); + + protected abstract void doGetUser(String key, String value, + List collectedUsers); public void init() { @@ -56,32 +82,53 @@ public abstract class AbstractUserDirectory implements UserAdmin { } boolean isEditing() { - if (editingTransactionKey == null) - return false; - Object currentTrKey = syncRegistry.getTransactionKey(); - if (currentTrKey == null) + if (editingTransactionXid == null) return false; - return editingTransactionKey.equals(currentTrKey); + return workingCopy.get() != null; + // Object currentTrKey = syncRegistry.getTransactionKey(); + // if (currentTrKey == null) + // return false; + // return editingTransactionKey.equals(currentTrKey); + } + + protected WorkingCopy getWorkingCopy() { + WorkingCopy wc = workingCopy.get(); + if (wc == null) + return null; + if (wc.xid == null) { + workingCopy.set(null); + return null; + } + return wc; } void checkEdit() { - Object currentTrKey = syncRegistry.getTransactionKey(); - if (currentTrKey == null) + 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 (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); - } + if (editingTransactionXid == null) { + WorkingCopy wc = new WorkingCopy(); + try { + transaction.enlistResource(wc); + editingTransactionXid = wc.getXid(); + workingCopy.set(wc); + } catch (Exception e) { + throw new UserDirectoryException("Cannot enlist " + wc, e); + } } else { - if (!editingTransactionKey.equals(currentTrKey)) + if (workingCopy.get() == null) throw new UserDirectoryException("Transaction " - + editingTransactionKey + " already editing"); + + editingTransactionXid + " already editing"); + else if (!editingTransactionXid.equals(workingCopy.get().getXid())) + throw new UserDirectoryException("Working copy Xid " + + workingCopy.get().getXid() + " inconsistent with" + + editingTransactionXid); } } @@ -107,10 +154,153 @@ public abstract class AbstractUserDirectory implements UserAdmin { // TODO gather anonymous roles } - public XAResource getXAResource() { + // USER ADMIN + @Override + public Role getRole(String name) { + LdapName key = toDn(name); + WorkingCopy wc = getWorkingCopy(); + DirectoryUser user = daoGetRole(key); + if (wc != null) { + if (user == null && wc.getNewUsers().containsKey(key)) + user = wc.getNewUsers().get(key); + else if (wc.getDeletedUsers().containsKey(key)) + user = null; + } + return user; + } + + @Override + public Role[] getRoles(String filter) throws InvalidSyntaxException { + WorkingCopy 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); return null; } + @Override + public Authorization getAuthorization(User user) { + return new LdifAuthorization((DirectoryUser) user, + getAllRoles((DirectoryUser) user)); + } + + @Override + public Role createRole(String name, int type) { + checkEdit(); + WorkingCopy 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(); + attrs.put("dn", 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; + if (type == Role.USER) { + newRole = new LdifUser(this, dn, attrs); + // users.put(dn, newRole); + } else if (type == Role.GROUP) { + newRole = new LdifGroup(this, dn, attrs); + // groups.put(dn, (LdifGroup) newRole); + } else + throw new UserDirectoryException("Unsupported type " + type); + return newRole; + } + + @Override + public boolean removeRole(String name) { + checkEdit(); + WorkingCopy 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()); + } + return true; + } + + // TRANSACTION + protected void prepare(WorkingCopy wc) { + + } + + protected void commit(WorkingCopy wc) { + + } + + protected void rollback(WorkingCopy wc) { + + } + + // UTILITIES + protected LdapName toDn(String name) { + try { + return new LdapName(name); + } catch (InvalidNameException e) { + throw new UserDirectoryException("Badly formatted name", e); + } + } + + // GETTERS + String getMemberAttributeId() { return memberAttributeId; } @@ -152,11 +342,163 @@ public abstract class AbstractUserDirectory implements UserAdmin { } public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) { - this.syncRegistry = syncRegistry; + // this.syncRegistry = syncRegistry; } public void setTransactionManager(TransactionManager transactionManager) { 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; + } + + } } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java index 05107ab56..34988cf89 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java @@ -9,4 +9,6 @@ interface DirectoryUser extends User { LdapName getDn(); Attributes getAttributes(); + + void publishAttributes(Attributes modifiedAttributes); } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java deleted file mode 100644 index d99fc3390..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.osgi.useradmin; - -import org.osgi.service.useradmin.Role; - -public interface EditorRole extends Role { - public static final int EDITOR = -1; - - -} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java index 9bb8fbc7d..0cb435f07 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java @@ -11,15 +11,20 @@ import javax.naming.InvalidNameException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; +import javax.transaction.xa.XAException; +import javax.transaction.xa.Xid; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; +import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.Group; @@ -73,26 +78,21 @@ public class LdapUserAdmin extends AbstractUserDirectory { } @Override - public Role createRole(String name, int type) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean removeRole(String name) { - // TODO Auto-generated method stub - return false; + protected Boolean daoHasRole(LdapName dn) { + return daoGetRole(dn) != null; } @Override - public Role getRole(String name) { + protected DirectoryUser daoGetRole(LdapName name) { try { Attributes attrs = initialLdapContext.getAttributes(name); + if (attrs.size() == 0) + return null; LdifUser res; if (attrs.get("objectClass").contains("groupOfNames")) - res = new LdifGroup(this, new LdapName(name), attrs); + res = new LdifGroup(this, name, attrs); else if (attrs.get("objectClass").contains("inetOrgPerson")) - res = new LdifUser(this, new LdapName(name), attrs); + res = new LdifUser(this, name, attrs); else throw new UserDirectoryException("Unsupported LDAP type for " + name); @@ -103,11 +103,11 @@ public class LdapUserAdmin extends AbstractUserDirectory { } @Override - public Role[] getRoles(String filter) throws InvalidSyntaxException { + protected List doGetRoles(Filter f) { + // TODO Auto-generated method stub try { - String searchFilter = filter; - if (searchFilter == null) - searchFilter = "(|(objectClass=inetOrgPerson)(objectClass=groupOfNames))"; + String searchFilter = f != null ? f.toString() + : "(|(objectClass=inetOrgPerson)(objectClass=groupOfNames))"; SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); @@ -115,7 +115,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { NamingEnumeration results = initialLdapContext .search(searchBase, searchFilter, searchControls); - ArrayList res = new ArrayList(); + ArrayList res = new ArrayList(); while (results.hasMoreElements()) { SearchResult searchResult = results.next(); Attributes attrs = searchResult.getAttributes(); @@ -132,13 +132,43 @@ public class LdapUserAdmin extends AbstractUserDirectory { + searchResult.getName()); res.add(role); } - return res.toArray(new Role[res.size()]); + return res; } catch (Exception e) { - throw new UserDirectoryException("Cannot get roles for filter " - + filter, e); + throw new UserDirectoryException( + "Cannot get roles for filter " + f, e); } } + @Override + protected void doGetUser(String key, String value, + List collectedUsers) { + try { + String searchFilter = "(&(objectClass=inetOrgPerson)(" + key + "=" + + value + "))"; + + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + String searchBase = baseDn; + NamingEnumeration results = initialLdapContext + .search(searchBase, searchFilter, searchControls); + + SearchResult searchResult = null; + if (results.hasMoreElements()) { + searchResult = (SearchResult) results.nextElement(); + if (results.hasMoreElements()) + searchResult = null; + } + if (searchResult != null) + collectedUsers.add(new LdifUser(this, toDn(searchBase, + searchResult), searchResult.getAttributes())); + } catch (Exception e) { + throw new UserDirectoryException("Cannot get user with " + key + + "=" + value, e); + } + + } + @Override public User getUser(String key, String value) { if (key == null) { @@ -181,13 +211,6 @@ public class LdapUserAdmin extends AbstractUserDirectory { } } - @Override - public Authorization getAuthorization(User user) { - LdifUser u = (LdifUser) user; - // populateDirectMemberOf(u); - return new LdifAuthorization(u, getAllRoles(u)); - } - private LdapName toDn(String baseDn, Binding binding) throws InvalidNameException { return new LdapName(binding.isRelative() ? binding.getName() + "," @@ -224,8 +247,8 @@ public class LdapUserAdmin extends AbstractUserDirectory { // } @Override - protected List getDirectGroups(User user) { - List directGroups = new ArrayList(); + protected List getDirectGroups(User user) { + List directGroups = new ArrayList(); try { String searchFilter = "(&(objectClass=groupOfNames)(member=" + user.getName() + "))"; @@ -255,4 +278,66 @@ public class LdapUserAdmin extends AbstractUserDirectory { // TODO configure group search base return baseDn; } + + @Override + protected void prepare(WorkingCopy wc) { + try { + initialLdapContext.reconnect(initialLdapContext + .getConnectControls()); + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + if (!entryExists(dn)) + throw new UserDirectoryException("User to delete no found " + + dn); + } + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + if (!entryExists(dn)) + throw new UserDirectoryException("User to create found " + + dn); + } + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + if (!entryExists(dn)) + throw new UserDirectoryException("User to modify no found " + + dn); + } + } catch (NamingException e) { + throw new UserDirectoryException("Cannot prepare LDAP", e); + } + } + + private boolean entryExists(LdapName dn) throws NamingException { + return initialLdapContext.getAttributes(dn).size() != 0; + } + + @Override + protected void commit(WorkingCopy wc) { + try { + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + initialLdapContext.destroySubcontext(dn); + } + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + DirectoryUser user = wc.getNewUsers().get(dn); + initialLdapContext.createSubcontext(dn, user.getAttributes()); + } + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); + initialLdapContext.modifyAttributes(dn, + DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); + } + } catch (NamingException e) { + throw new UserDirectoryException("Cannot commit LDAP", e); + } + } + + @Override + protected void rollback(WorkingCopy wc) { + // TODO Auto-generated method stub + super.rollback(wc); + } + } diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java index cd5401a55..304dda9b1 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java @@ -15,6 +15,8 @@ import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.ldap.LdapName; +import org.argeo.osgi.useradmin.AbstractUserDirectory.WorkingCopy; + class LdifUser implements DirectoryUser { private final AbstractUserDirectory userAdmin; @@ -22,7 +24,6 @@ class LdifUser implements DirectoryUser { private final boolean frozen; private Attributes publishedAttributes; - private Attributes modifiedAttributes = null; private final AttributeDictionary properties; private final AttributeDictionary credentials; @@ -82,11 +83,21 @@ class LdifUser implements DirectoryUser { @Override public synchronized Attributes getAttributes() { - return isEditing() ? modifiedAttributes : publishedAttributes; + return isEditing() ? getModifiedAttributes() : publishedAttributes; + } + + /** Should only be called from working copy thread. */ + private synchronized Attributes getModifiedAttributes() { + assert getWc() != null; + return getWc().getAttributes(getDn()); } - protected synchronized boolean isEditing() { - return userAdmin.isEditing() && modifiedAttributes != null; + private synchronized boolean isEditing() { + return getWc() != null && getModifiedAttributes() != null; + } + + private synchronized WorkingCopy getWc() { + return userAdmin.getWorkingCopy(); } protected synchronized void startEditing() { @@ -94,17 +105,22 @@ class LdifUser implements DirectoryUser { throw new UserDirectoryException("Cannot edit frozen view"); if (getUserAdmin().isReadOnly()) throw new UserDirectoryException("User directory is read-only"); - assert modifiedAttributes == null; - modifiedAttributes = (Attributes) publishedAttributes.clone(); + assert getModifiedAttributes() == null; + getWc().startEditing(this); + // modifiedAttributes = (Attributes) publishedAttributes.clone(); } - protected synchronized void stopEditing(boolean apply) { - assert modifiedAttributes != null; - if (apply) - publishedAttributes = modifiedAttributes; - modifiedAttributes = null; + public synchronized void publishAttributes(Attributes modifiedAttributes) { + publishedAttributes = modifiedAttributes; } + // protected synchronized void stopEditing(boolean apply) { + // assert getModifiedAttributes() != null; + // if (apply) + // publishedAttributes = getModifiedAttributes(); + // // modifiedAttributes = null; + // } + public DirectoryUser getPublished() { return new LdifUser(userAdmin, dn, publishedAttributes, true); } @@ -226,10 +242,12 @@ class LdifUser implements DirectoryUser { throw new IllegalArgumentException("Key " + key + " excluded"); try { - Attribute attribute = modifiedAttributes.get(key.toString()); + Attribute attribute = getModifiedAttributes().get( + key.toString()); attribute = new BasicAttribute(key.toString()); attribute.add(value); - Attribute previousAttribute = modifiedAttributes.put(attribute); + Attribute previousAttribute = getModifiedAttributes().put( + attribute); if (previousAttribute != null) return previousAttribute.get(); else @@ -253,7 +271,7 @@ class LdifUser implements DirectoryUser { throw new IllegalArgumentException("Key " + key + " excluded"); try { - Attribute attr = modifiedAttributes.remove(key.toString()); + Attribute attr = getModifiedAttributes().remove(key.toString()); if (attr != null) return attr.get(); else diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java index 098243638..608a1f751 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java @@ -18,31 +18,22 @@ import java.util.TreeMap; import javax.naming.InvalidNameException; import javax.naming.NamingEnumeration; import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttributes; import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; import org.apache.commons.io.IOUtils; 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; /** User admin implementation using LDIF file(s) as backend. */ public class LdifUserAdmin extends AbstractUserDirectory { - SortedMap users = new TreeMap(); - SortedMap groups = new TreeMap(); + SortedMap users = new TreeMap(); + SortedMap groups = new TreeMap(); - private Map> userIndexes = new LinkedHashMap>(); + private Map> userIndexes = new LinkedHashMap>(); // private Map> directMemberOf = new // TreeMap>(); - private XaRes xaRes = new XaRes(); public LdifUserAdmin(String uri) { this(uri, readOnlyDefault(uri)); @@ -124,6 +115,9 @@ public class LdifUserAdmin extends AbstractUserDirectory { protected void load(InputStream in) { try { + users.clear(); + groups.clear(); + LdifParser ldifParser = new LdifParser(); SortedMap allEntries = ldifParser.read(in); for (LdapName key : allEntries.keySet()) { @@ -148,14 +142,14 @@ public class LdifUserAdmin extends AbstractUserDirectory { // indexes for (String attr : getIndexedUserProperties()) - userIndexes.put(attr, new TreeMap()); + userIndexes.put(attr, new TreeMap()); - for (LdifUser user : users.values()) { + for (DirectoryUser user : users.values()) { Dictionary properties = user.getProperties(); for (String attr : getIndexedUserProperties()) { Object value = properties.get(attr); if (value != null) { - LdifUser otherUser = userIndexes.get(attr).put( + DirectoryUser otherUser = userIndexes.get(attr).put( value.toString(), user); if (otherUser != null) throw new UserDirectoryException("User " + user @@ -178,17 +172,7 @@ public class LdifUserAdmin extends AbstractUserDirectory { groups = null; } - @Override - public Role getRole(String name) { - LdapName key; - try { - key = new LdapName(name); - } catch (InvalidNameException e) { - // TODO implements default base DN - throw new IllegalArgumentException("Badly formatted role name: " - + name, e); - } - + protected DirectoryUser daoGetRole(LdapName key) { if (groups.containsKey(key)) return groups.get(key); if (users.containsKey(key)) @@ -196,148 +180,56 @@ public class LdifUserAdmin extends AbstractUserDirectory { return null; } - @Override - public Authorization getAuthorization(User user) { - return new LdifAuthorization((LdifUser) user, - getAllRoles((LdifUser) user)); + protected Boolean daoHasRole(LdapName dn) { + return users.containsKey(dn) || groups.containsKey(dn); } - @Override - public Role createRole(String name, int type) { - try { - LdapName dn = new LdapName(name); - if (users.containsKey(dn) || groups.containsKey(dn)) - throw new UserDirectoryException("Already a role " + name); - - BasicAttributes attrs = new BasicAttributes(); - attrs.put("dn", dn.toString()); - Rdn nameRdn = dn.getRdn(dn.size() - 1); - // TODO deal with multiple attr RDN - attrs.put(nameRdn.getType(), nameRdn.getValue()); - LdifUser newRole; - if (type == Role.USER) { - newRole = new LdifUser(this, dn, attrs); - users.put(dn, newRole); - } else if (type == Role.GROUP) { - newRole = new LdifGroup(this, dn, attrs); - groups.put(dn, (LdifGroup) newRole); - } else - throw new UserDirectoryException("Unsupported type " + type); - return newRole; - } catch (InvalidNameException e) { - throw new UserDirectoryException("Cannot create role " + name, e); - } - } - - @Override - public boolean removeRole(String name) { - try { - LdapName dn = new LdapName(name); - LdifUser role = null; - if (users.containsKey(dn)) - role = users.remove(dn); - else if (groups.containsKey(dn)) - role = groups.remove(dn); - else - throw new UserDirectoryException("There is no role " + name); - if (role == null) - return false; - for (LdifGroup group : getDirectGroups(role)) { - // group.directMembers.remove(role); - group.getAttributes().get(getMemberAttributeId()) - .remove(dn.toString()); - } - if (role instanceof LdifGroup) { - LdifGroup group = (LdifGroup) role; - // for (Role user : group.directMembers) { - // if (user instanceof LdifUser) - // directMemberOf.get(((LdifUser) user).getDn()).remove( - // group); - // } - } - return true; - } catch (InvalidNameException e) { - throw new UserDirectoryException("Cannot create role " + name, e); - } - } + // @Override + // public boolean removeRole(String name) { + // LdapName dn = toDn(name); + // LdifUser role = null; + // if (users.containsKey(dn)) + // role = users.remove(dn); + // else if (groups.containsKey(dn)) + // role = groups.remove(dn); + // else + // throw new UserDirectoryException("There is no role " + name); + // if (role == null) + // return false; + // for (LdifGroup group : getDirectGroups(role)) { + // group.getAttributes().get(getMemberAttributeId()) + // .remove(dn.toString()); + // } + // return true; + // } - @Override - public Role[] getRoles(String filter) throws InvalidSyntaxException { - ArrayList res = new ArrayList(); - if (filter == null) { + protected List doGetRoles(Filter f) { + ArrayList res = new ArrayList(); + if (f == null) { res.addAll(users.values()); res.addAll(groups.values()); } else { - Filter f = FrameworkUtil.createFilter(filter); - for (LdifUser user : users.values()) + // Filter f = FrameworkUtil.createFilter(filter); + for (DirectoryUser user : users.values()) if (f.match(user.getProperties())) res.add(user); - for (LdifUser group : groups.values()) + for (DirectoryUser group : groups.values()) if (f.match(group.getProperties())) res.add(group); } - return res.toArray(new Role[res.size()]); + return res; } - @Override - public User getUser(String key, String value) { - // TODO check value null or empty - if (key != null) { - if (!userIndexes.containsKey(key)) - return null; - return userIndexes.get(key).get(value); - } - - // Try all indexes - List collectedUsers = new ArrayList( - getIndexedUserProperties().size()); - // try dn - LdifUser user = null; - try { - user = (LdifUser) getRole(value); - if (user != null) - collectedUsers.add(user); - } catch (Exception e) { - // silent - } - for (String attr : userIndexes.keySet()) { - user = userIndexes.get(attr).get(value); - if (user != null) - collectedUsers.add(user); - } - - if (collectedUsers.size() == 1) - return collectedUsers.get(0); - return null; - // throw new UnsupportedOperationException(); + protected void doGetUser(String key, String value, + List collectedUsers) { + assert key != null; + DirectoryUser user = userIndexes.get(key).get(value); + if (user != null) + collectedUsers.add(user); } - // protected void loadMembers(LdifGroup group) { - // group.directMembers = new ArrayList(); - // for (LdapName ldapName : group.getMemberNames()) { - // LdifUser role = null; - // if (groups.containsKey(ldapName)) - // role = groups.get(ldapName); - // else if (users.containsKey(ldapName)) - // role = users.get(ldapName); - // else { - // if (getExternalRoles() != null) - // role = (LdifUser) getExternalRoles().getRole( - // ldapName.toString()); - // if (role == null) - // throw new ArgeoUserAdminException("No role found for " - // + ldapName); - // } - // // role.directMemberOf.add(group); - // // if (!directMemberOf.containsKey(role.getDn())) - // // directMemberOf.put(role.getDn(), new ArrayList()); - // // directMemberOf.get(role.getDn()).add(group); - // group.directMembers.add(role); - // } - // } - @Override - protected List getDirectGroups(User user) { + protected List getDirectGroups(User user) { LdapName dn; if (user instanceof LdifUser) dn = ((LdifUser) user).getDn(); @@ -349,85 +241,61 @@ public class LdifUserAdmin extends AbstractUserDirectory { + user.getName(), e); } - List directGroups = new ArrayList(); + List directGroups = new ArrayList(); for (LdapName name : groups.keySet()) { - LdifGroup group = groups.get(name); + DirectoryGroup group = groups.get(name); if (group.getMemberNames().contains(dn)) directGroups.add(group); } return directGroups; - // if (directMemberOf.containsKey(dn)) - // return Collections.unmodifiableList(directMemberOf.get(dn)); - // else - // return Collections.EMPTY_LIST; } @Override - public XAResource getXAResource() { - return xaRes; - } - - private class XaRes implements XAResource { - - @Override - public void commit(Xid xid, boolean onePhase) throws XAException { - save(); - } - - @Override - public void end(Xid xid, int flags) throws XAException { - // TODO Auto-generated method stub - - } - - @Override - public void forget(Xid xid) throws XAException { - // TODO Auto-generated method stub - - } - - @Override - public int getTransactionTimeout() throws XAException { - // TODO Auto-generated method stub - return 0; - } - - @Override - public boolean isSameRM(XAResource xares) throws XAException { - // TODO Auto-generated method stub - return false; - } - - @Override - public int prepare(Xid xid) throws XAException { - // TODO Auto-generated method stub - return 0; - } - - @Override - public Xid[] recover(int flag) throws XAException { - // TODO Auto-generated method stub - return null; + protected void prepare(WorkingCopy wc) { + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + if (users.containsKey(dn)) + users.remove(dn); + else if (groups.containsKey(dn)) + groups.remove(dn); + else + throw new UserDirectoryException("User to delete no found " + + dn); } - - @Override - public void rollback(Xid xid) throws XAException { - // TODO Auto-generated method stub - + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + DirectoryUser user = wc.getNewUsers().get(dn); + if (Role.USER == user.getType()) + users.put(dn, user); + else if (Role.GROUP == user.getType()) + groups.put(dn, (DirectoryGroup) user); + else + throw new UserDirectoryException("Unsupported role type " + + user.getType() + " for new user " + dn); } - - @Override - public boolean setTransactionTimeout(int seconds) throws XAException { - // TODO Auto-generated method stub - return false; + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); + DirectoryUser user; + if (users.containsKey(dn)) + user = users.get(dn); + else if (groups.containsKey(dn)) + user = groups.get(dn); + else + throw new UserDirectoryException("User to modify no found " + + dn); + user.publishAttributes(modifiedAttrs); } + } - @Override - public void start(Xid xid, int flags) throws XAException { - // TODO Auto-generated method stub - - } + @Override + protected void commit(WorkingCopy wc) { + save(); + } + @Override + protected void rollback(WorkingCopy wc) { + init(); } }