import java.util.function.Consumer;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.cms.swt.CmsSwtTheme;
import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.cms.ux.widgets.Column;
import org.argeo.cms.ux.widgets.TabularPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
private Consumer<Object> onSelected;
private Consumer<Object> onAction;
+ private CmsSwtTheme theme;
+
public SwtTabularPart(Composite parent, int style) {
+ theme = CmsSwtUtils.getCmsTheme(parent);
area = new Composite(parent, style);
area.setLayout(CmsSwtUtils.noSpaceGridLayout());
table = new Table(area, SWT.VIRTUAL | SWT.BORDER);
@Override
public void widgetSelected(SelectionEvent e) {
if (onSelected != null)
- onSelected.accept(e.item.getData());
+ onSelected.accept(getDataFromEvent(e));
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
if (onAction != null)
- onAction.accept(e.item.getData());
+ onAction.accept(getDataFromEvent(e));
}
});
}
+ protected Object getDataFromEvent(SelectionEvent e) {
+ Object data = e.item.getData();
+ if (data == null)
+ data = getData(getTable().indexOf((TableItem) e.item));
+ return data;
+ }
+
@Override
public void setInput(Object data) {
area.setData(data);
for (int i = 0; i < item.getParent().getColumnCount(); i++) {
Column<Object> column = (Column<Object>) item.getParent().getColumn(i).getData();
Object data = getData(row);
+ item.setData(data);
String text = data != null ? column.getText(data) : "";
item.setText(i, text);
+ CmsIcon icon = column.getIcon(data);
+ if (icon != null) {
+ Image image = theme.getSmallIcon(icon);
+ item.setImage(i, image);
+ }
}
}
package org.argeo.cms.ux.widgets;
+import org.argeo.api.cms.ux.CmsIcon;
+
public interface Column<T> {
String getText(T model);
default int getWidth() {
return 200;
}
-
+
+ default CmsIcon getIcon(T model) {
+ return null;
+ }
+
}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+
+abstract class AbstractDirectoryContent extends AbstractContent {
+ protected final DirectoryContentProvider provider;
+
+ public AbstractDirectoryContent(ProvidedSession session, DirectoryContentProvider provider) {
+ super(session);
+ this.provider = provider;
+ }
+
+ abstract Dictionary<String, Object> doGetProperties();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ String attrName = key.getLocalPart();
+ Object value = doGetProperties().get(attrName);
+ if (value == null)
+ return Optional.empty();
+ // TODO deal with type and multiple
+ return Optional.of((A) value);
+ }
+
+ @Override
+ protected Iterable<QName> keys() {
+ Dictionary<String, Object> properties = doGetProperties();
+ Set<QName> keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
+ keys: for (Enumeration<String> it = properties.keys(); it.hasMoreElements();) {
+ String key = it.nextElement();
+ if (key.equalsIgnoreCase(LdapAttrs.objectClass.name()))
+ continue keys;
+ if (key.equalsIgnoreCase(LdapAttrs.objectClasses.name()))
+ continue keys;
+ ContentName name = new ContentName(CrName.LDAP_NAMESPACE_URI, key, provider);
+ keys.add(name);
+ }
+ return keys;
+ }
+
+ @Override
+ public List<QName> getTypes() {
+ Dictionary<String, Object> properties = doGetProperties();
+ List<QName> contentClasses = new ArrayList<>();
+ String objectClass = properties.get(LdapAttrs.objectClass.name()).toString();
+ contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, objectClass, provider));
+
+ String[] objectClasses = properties.get(LdapAttrs.objectClasses.name()).toString().split("\\n");
+ objectClasses: for (String oc : objectClasses) {
+ if (LdapObjs.top.name().equalsIgnoreCase(oc))
+ continue objectClasses;
+ if (objectClass.equalsIgnoreCase(oc))
+ continue objectClasses;
+ contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, oc, provider));
+ }
+ return contentClasses;
+ }
+
+ @Override
+ public Object put(QName key, Object value) {
+ Object previous = get(key);
+ // TODO deal with typing
+ doGetProperties().put(key.getLocalPart(), value);
+ return previous;
+ }
+
+ @Override
+ protected void removeAttr(QName key) {
+ doGetProperties().remove(key.getLocalPart());
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return provider;
+ }
+
+
+}
package org.argeo.cms.acr.directory;
import java.util.ArrayList;
+import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.cms.acr.AbstractContent;
import org.argeo.util.directory.Directory;
import org.argeo.util.directory.HierarchyUnit;
-class DirectoryContent extends AbstractContent {
+class DirectoryContent extends AbstractDirectoryContent {
private Directory directory;
- private DirectoryContentProvider provider;
public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, Directory directory) {
- super(session);
- this.provider = provider;
+ super(session, provider);
this.directory = directory;
}
@Override
- public ContentProvider getProvider() {
- return provider;
+ Dictionary<String, Object> doGetProperties() {
+ return directory.getProperties();
}
@Override
return new UserManagerContent(session);
}
+ /*
+ * COMMON UTILITIES
+ */
class UserManagerContent extends AbstractContent {
public UserManagerContent(ProvidedSession session) {
package org.argeo.cms.acr.directory;
import java.util.ArrayList;
+import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
import org.argeo.api.acr.CrName;
-import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.cms.acr.AbstractContent;
import org.argeo.osgi.useradmin.UserDirectory;
import org.argeo.util.directory.HierarchyUnit;
import org.osgi.service.useradmin.Role;
-class HierarchyUnitContent extends AbstractContent {
+class HierarchyUnitContent extends AbstractDirectoryContent {
private HierarchyUnit hierarchyUnit;
- private DirectoryContentProvider provider;
-
public HierarchyUnitContent(ProvidedSession session, DirectoryContentProvider provider,
HierarchyUnit hierarchyUnit) {
- super(session);
+ super(session, provider);
Objects.requireNonNull(hierarchyUnit);
- this.provider = provider;
this.hierarchyUnit = hierarchyUnit;
}
@Override
- public ContentProvider getProvider() {
- return provider;
+ Dictionary<String, Object> doGetProperties() {
+ return hierarchyUnit.getProperties();
}
@Override
/*
* TYPING
*/
-
@Override
public List<QName> getTypes() {
- List<QName> res = new ArrayList<>();
- res.add(CrName.COLLECTION.get());
- return res;
+ List<QName> contentClasses = super.getTypes();
+ contentClasses.add(CrName.COLLECTION.get());
+ return contentClasses;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) {
+ if (clss.equals(HierarchyUnit.class))
+ return (A) hierarchyUnit;
+ return super.adapt(clss);
}
/*
package org.argeo.cms.acr.directory;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.Dictionary;
-import javax.swing.GroupLayout.Group;
import javax.xml.namespace.QName;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.CrName;
-import org.argeo.api.acr.NamespaceUtils;
-import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.cms.acr.AbstractContent;
import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
+import org.osgi.service.useradmin.Group;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
-class RoleContent extends AbstractContent {
+class RoleContent extends AbstractDirectoryContent {
- private DirectoryContentProvider provider;
private HierarchyUnitContent parent;
private Role role;
public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent,
Role role) {
- super(session);
- this.provider = provider;
+ super(session, provider);
this.parent = parent;
this.role = role;
}
@Override
- public ContentProvider getProvider() {
- return provider;
+ Dictionary<String, Object> doGetProperties() {
+ return role.getProperties();
}
@Override
return parent;
}
- @SuppressWarnings("unchecked")
- @Override
- public <A> Optional<A> get(QName key, Class<A> clss) {
- String attrName = key.getLocalPart();
- Object value = role.getProperties().get(attrName);
- if (value == null)
- return Optional.empty();
- // TODO deal with type and multiple
- return Optional.of((A) value);
- }
-
- @Override
- protected Iterable<QName> keys() {
- Set<QName> keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
- keys: for (Enumeration<String> it = role.getProperties().keys(); it.hasMoreElements();) {
- String key = it.nextElement();
- if (key.equalsIgnoreCase(LdapAttrs.objectClass.name()))
- continue keys;
- ContentName name = new ContentName(CrName.LDAP_NAMESPACE_URI, key, provider);
- keys.add(name);
- }
- return keys;
- }
-
- @Override
- public List<QName> getTypes() {
- List<QName> contentClasses = new ArrayList<>();
- String objectClass = role.getProperties().get(LdapAttrs.objectClass.name()).toString();
- contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, objectClass, provider));
-
- String[] objectClasses = role.getProperties().get(LdapAttrs.objectClasses.name()).toString().split("\\n");
- objectClasses: for (String oc : objectClasses) {
- if (LdapObjs.top.name().equalsIgnoreCase(oc))
- continue objectClasses;
- if (objectClass.equalsIgnoreCase(oc))
- continue objectClasses;
- contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, oc, provider));
- }
- return contentClasses;
- }
-
- @Override
- public Object put(QName key, Object value) {
- Object previous = get(key);
- // TODO deal with typing
- role.getProperties().put(key.getLocalPart(), value);
- return previous;
- }
-
- @Override
- protected void removeAttr(QName key) {
- role.getProperties().remove(key.getLocalPart());
- }
-
@SuppressWarnings("unchecked")
@Override
public <A> A adapt(Class<A> clss) {
*/
public static String getUserDisplayName(UserAdmin userAdmin, String dn) {
Role user = userAdmin.getRole(dn);
- String dName;
if (user == null)
- dName = getUserLocalId(dn);
- else {
- dName = getProperty(user, LdapAttrs.displayName.name());
- if (isEmpty(dName))
- dName = getProperty(user, LdapAttrs.cn.name());
- if (isEmpty(dName))
- dName = getUserLocalId(dn);
- }
+ return getUserLocalId(dn);
+ return getUserDisplayName(user);
+ }
+
+ public static String getUserDisplayName(Role user) {
+ String dName = getProperty(user, LdapAttrs.displayName.name());
+ if (isEmpty(dName))
+ dName = getProperty(user, LdapAttrs.cn.name());
+ if (isEmpty(dName))
+ dName = getUserLocalId(user.getName());
return dName;
}
import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
import org.argeo.util.directory.ldap.LdapNameUtils;
import org.argeo.util.directory.ldap.LdifDao;
-import org.argeo.util.naming.LdapObjs;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
String username = (String) credentials.get(SHARED_STATE_USERNAME);
if (username == null)
username = user.getName();
- Dictionary<String, Object> properties = cloneProperties();
+ Dictionary<String, Object> properties = cloneConfigProperties();
properties.put(Context.SECURITY_PRINCIPAL, username.toString());
Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
byte[] pwd = (byte[]) pwdCred;
} else {
throw new IllegalStateException("Password is required");
}
- Dictionary<String, Object> properties = cloneProperties();
+ Dictionary<String, Object> properties = cloneConfigProperties();
properties.put(DirectoryConf.readOnly.name(), "true");
DirectoryUserAdmin scopedUserAdmin = new DirectoryUserAdmin(null, properties, true);
// scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
checkEdit();
LdapEntryWorkingCopy wc = getWorkingCopy();
LdapName dn = toLdapName(name);
- if ((getDirectoryDao().daoHasEntry(dn) && !wc.getDeletedData().containsKey(dn))
+ if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn))
|| wc.getNewData().containsKey(dn))
throw new IllegalArgumentException("Already a role " + name);
BasicAttributes attrs = new BasicAttributes(true);
*/
protected LdapEntry newUser(LdapName name, Attributes attrs) {
// TODO support devices, applications, etc.
- return new LdifUser.LdifPerson(this, name, attrs);
+ return new LdifUser(this, name, attrs);
}
protected LdapEntry newGroup(LdapName name, Attributes attrs) {
- if (LdapNameUtils.getParentRdn(name).equals(getSystemRoleBaseRdn()))
- return new LdifGroup.LdifSystemPermissions(this, name, attrs);
-
- if (hasObjectClass(attrs, LdapObjs.organization))
- return new LdifGroup.LdifOrganization(this, name, attrs);
- else
- return new LdifGroup.LdifFunctionalGroup(this, name, attrs);
+ return new LdifGroup(this, name, attrs);
}
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
-import org.argeo.util.directory.FunctionalGroup;
-import org.argeo.util.directory.Organization;
-import org.argeo.util.directory.SystemPermissions;
import org.argeo.util.directory.ldap.AbstractLdapDirectory;
import org.osgi.service.useradmin.Role;
/** Directory group implementation */
-abstract class LdifGroup extends LdifUser implements DirectoryGroup {
+class LdifGroup extends LdifUser implements DirectoryGroup {
private final String memberAttributeId;
LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) {
return GROUP;
}
- /*
- * KIND
- */
- static class LdifFunctionalGroup extends LdifGroup implements FunctionalGroup {
-
- public LdifFunctionalGroup(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
- super(userAdmin, dn, attributes);
- }
-
- }
-
- static class LdifOrganization extends LdifGroup implements Organization {
-
- public LdifOrganization(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
- super(userAdmin, dn, attributes);
- }
-
- }
-
- static class LdifSystemPermissions extends LdifGroup implements SystemPermissions {
-
- public LdifSystemPermissions(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
- super(userAdmin, dn, attributes);
- }
-
+ protected DirectoryUserAdmin getUserAdmin() {
+ return (DirectoryUserAdmin) getDirectory();
}
}
package org.argeo.osgi.useradmin;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Collections;
import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-import java.util.StringJoiner;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
import javax.naming.ldap.LdapName;
-import org.argeo.util.directory.DirectoryDigestUtils;
-import org.argeo.util.directory.Person;
import org.argeo.util.directory.ldap.AbstractLdapDirectory;
-import org.argeo.util.directory.ldap.AbstractLdapEntry;
-import org.argeo.util.directory.ldap.AuthPassword;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.argeo.util.naming.SharedSecret;
+import org.argeo.util.directory.ldap.DefaultLdapEntry;
/** Directory user implementation */
-abstract class LdifUser extends AbstractLdapEntry implements DirectoryUser {
- private final AttributeDictionary properties;
- private final AttributeDictionary credentials;
-
+class LdifUser extends DefaultLdapEntry implements DirectoryUser {
LdifUser(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) {
super(userAdmin, dn, attributes);
- properties = new AttributeDictionary(false);
- credentials = new AttributeDictionary(true);
}
@Override
return USER;
}
- @Override
- public Dictionary<String, Object> getProperties() {
- return properties;
- }
-
@Override
public Dictionary<String, Object> getCredentials() {
return credentials;
}
-
- @Override
- public boolean hasCredential(String key, Object value) {
- if (key == null) {
- // TODO check other sources (like PKCS12)
- // String pwd = new String((char[]) value);
- // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
- char[] password = DirectoryDigestUtils.bytesToChars(value);
-
- if (getDirectory().getForcedPassword() != null
- && getDirectory().getForcedPassword().equals(new String(password)))
- return true;
-
- AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
- if (authPassword != null) {
- if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
- SharedSecret onceToken = new SharedSecret(authPassword);
- if (onceToken.isExpired()) {
- // AuthPassword.remove(getAttributes(), onceToken);
- return false;
- } else {
- // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
- return true;
- }
- // TODO delete expired tokens?
- } else {
- // TODO implement SHA
- throw new UnsupportedOperationException(
- "Unsupported authPassword scheme " + authPassword.getAuthScheme());
- }
- }
-
- // Regular password
-// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
- if (hasCredential(LdapAttrs.userPassword.name(), DirectoryDigestUtils.charsToBytes(password)))
- return true;
- return false;
- }
-
- Object storedValue = getCredentials().get(key);
- if (storedValue == null || value == null)
- return false;
- if (!(value instanceof String || value instanceof byte[]))
- return false;
- if (storedValue instanceof String && value instanceof String)
- return storedValue.equals(value);
- if (storedValue instanceof byte[] && value instanceof byte[]) {
- String storedBase64 = new String((byte[]) storedValue, US_ASCII);
- String passwordScheme = null;
- if (storedBase64.charAt(0) == '{') {
- int index = storedBase64.indexOf('}');
- if (index > 0) {
- passwordScheme = storedBase64.substring(1, index);
- String storedValueBase64 = storedBase64.substring(index + 1);
- byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
- char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value);
- byte[] valueBytes;
- if (DirectoryDigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
- valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null,
- null);
- } else if (DirectoryDigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
- // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
- byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
- BigInteger iterations = new BigInteger(iterationsArr);
- byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
- iterationsArr.length + 64);
- byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
- storedValueBytes.length);
- int keyLengthBits = keyArr.length * 8;
- valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
- iterations.intValue(), keyLengthBits);
- } else {
- throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
- }
- return Arrays.equals(storedValueBytes, valueBytes);
- }
- }
- }
-// if (storedValue instanceof byte[] && value instanceof byte[]) {
-// return Arrays.equals((byte[]) storedValue, (byte[]) value);
-// }
- return false;
- }
-
- /** Hash the password */
- byte[] sha1hash(char[] password) {
- byte[] hashedPassword = ("{SHA}" + Base64.getEncoder()
- .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password))))
- .getBytes(StandardCharsets.UTF_8);
- return hashedPassword;
- }
-
-// byte[] hash(char[] password, String passwordScheme) {
-// if (passwordScheme == null)
-// passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA;
-// byte[] hashedPassword = ("{" + passwordScheme + "}"
-// + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password)))
-// .getBytes(US_ASCII);
-// return hashedPassword;
-// }
-
- protected DirectoryUserAdmin getUserAdmin() {
- return (DirectoryUserAdmin) getDirectory();
- }
-
- private class AttributeDictionary extends Dictionary<String, Object> {
- private final List<String> effectiveKeys = new ArrayList<String>();
- private final List<String> attrFilter;
- private final Boolean includeFilter;
-
- public AttributeDictionary(Boolean credentials) {
- this.attrFilter = getDirectory().getCredentialAttributeIds();
- this.includeFilter = credentials;
- try {
- NamingEnumeration<String> ids = getAttributes().getIDs();
- while (ids.hasMore()) {
- String id = ids.next();
- if (credentials && attrFilter.contains(id))
- effectiveKeys.add(id);
- else if (!credentials && !attrFilter.contains(id))
- effectiveKeys.add(id);
- }
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot initialise attribute dictionary", e);
- }
- if (!credentials)
- effectiveKeys.add(LdapAttrs.objectClasses.name());
- }
-
- @Override
- public int size() {
- return effectiveKeys.size();
- }
-
- @Override
- public boolean isEmpty() {
- return effectiveKeys.size() == 0;
- }
-
- @Override
- public Enumeration<String> keys() {
- return Collections.enumeration(effectiveKeys);
- }
-
- @Override
- public Enumeration<Object> elements() {
- final Iterator<String> it = effectiveKeys.iterator();
- return new Enumeration<Object>() {
-
- @Override
- public boolean hasMoreElements() {
- return it.hasNext();
- }
-
- @Override
- public Object nextElement() {
- String key = it.next();
- return get(key);
- }
-
- };
- }
-
- @Override
- public Object get(Object key) {
- try {
- Attribute attr = !key.equals(LdapAttrs.objectClasses.name()) ? getAttributes().get(key.toString())
- : getAttributes().get(LdapAttrs.objectClass.name());
- if (attr == null)
- return null;
- Object value = attr.get();
- if (value instanceof byte[]) {
- if (key.equals(LdapAttrs.userPassword.name()))
- // TODO other cases (certificates, images)
- return value;
- value = new String((byte[]) value, StandardCharsets.UTF_8);
- }
- if (attr.size() == 1)
- return value;
- // special case for object class
- if (key.equals(LdapAttrs.objectClass.name())) {
- // TODO support multiple object classes
- NamingEnumeration<?> en = attr.getAll();
- String first = null;
- attrs: while (en.hasMore()) {
- String v = en.next().toString();
- if (v.equalsIgnoreCase(LdapObjs.top.name()))
- continue attrs;
- if (first == null)
- first = v;
- if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
- return getDirectory().getUserObjectClass();
- else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
- return getDirectory().getGroupObjectClass();
- }
- if (first != null)
- return first;
- throw new IllegalStateException("Cannot find objectClass in " + value);
- } else {
- NamingEnumeration<?> en = attr.getAll();
- StringJoiner values = new StringJoiner("\n");
- while (en.hasMore()) {
- String v = en.next().toString();
- values.add(v);
- }
- return values.toString();
- }
-// else
-// return value;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot get value for attribute " + key, e);
- }
- }
-
- @Override
- public Object put(String key, Object value) {
- if (key == null) {
- // TODO persist to other sources (like PKCS12)
- char[] password = DirectoryDigestUtils.bytesToChars(value);
- byte[] hashedPassword = sha1hash(password);
- return put(LdapAttrs.userPassword.name(), hashedPassword);
- }
- if (key.startsWith("X-")) {
- return put(LdapAttrs.authPassword.name(), value);
- }
-
- getDirectory().checkEdit();
- if (!isEditing())
- startEditing();
-
- if (!(value instanceof String || value instanceof byte[]))
- throw new IllegalArgumentException("Value must be String or byte[]");
-
- if (includeFilter && !attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " not included");
- else if (!includeFilter && attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " excluded");
-
- try {
- Attribute attribute = getModifiedAttributes().get(key.toString());
- // if (attribute == null) // block unit tests
- attribute = new BasicAttribute(key.toString());
- if (value instanceof String && !isAsciiPrintable(((String) value)))
- attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
- else
- attribute.add(value);
- Attribute previousAttribute = getModifiedAttributes().put(attribute);
- if (previousAttribute != null)
- return previousAttribute.get();
- else
- return null;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot get value for attribute " + key, e);
- }
- }
-
- @Override
- public Object remove(Object key) {
- getDirectory().checkEdit();
- if (!isEditing())
- startEditing();
-
- if (includeFilter && !attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " not included");
- else if (!includeFilter && attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " excluded");
-
- try {
- Attribute attr = getModifiedAttributes().remove(key.toString());
- if (attr != null)
- return attr.get();
- else
- return null;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot remove attribute " + key, e);
- }
- }
- }
-
- private static boolean isAsciiPrintable(String str) {
- if (str == null) {
- return false;
- }
- int sz = str.length();
- for (int i = 0; i < sz; i++) {
- if (isAsciiPrintable(str.charAt(i)) == false) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isAsciiPrintable(char ch) {
- return ch >= 32 && ch < 127;
- }
-
- static class LdifPerson extends LdifUser implements Person {
-
- public LdifPerson(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
- super(userAdmin, dn, attributes);
- }
-
- }
}
}
@Override
- public Boolean daoHasEntry(LdapName dn) {
+ public Boolean entryExists(LdapName dn) {
return osUserDn.equals(dn);
}
@Override
- public LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException {
+ public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
if (osUserDn.equals(key))
return osUser;
else
package org.argeo.util.directory;
+import java.util.Dictionary;
import java.util.Optional;
import org.argeo.util.transaction.WorkControl;
void setTransactionControl(WorkControl transactionControl);
+ /*
+ * METADATA
+ */
+ public Dictionary<String, Object> getProperties();
+
/*
* HIERARCHY
*/
+++ /dev/null
-package org.argeo.util.directory;
-
-public interface FunctionalGroup {
-
-}
package org.argeo.util.directory;
+import java.util.Dictionary;
+
/** A unit within the high-level organisational structure of a directory. */
public interface HierarchyUnit {
String getHierarchyUnitName();
Directory getDirectory();
-// Map<String,Object> getHierarchyProperties();
+ Dictionary<String, Object> getProperties();
}
+++ /dev/null
-package org.argeo.util.directory;
-
-public interface Organization {
-
-}
+++ /dev/null
-package org.argeo.util.directory;
-
-public interface Person {
-
-}
+++ /dev/null
-package org.argeo.util.directory;
-
-public interface SystemPermissions {
-
-}
protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
protected final LdapName baseDn;
- protected final Hashtable<String, Object> properties;
+ protected final Hashtable<String, Object> configProperties;
private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
private final String userObjectClass, groupObjectClass;
private String memberAttributeId = "member";
private LdapDirectoryDao directoryDao;
public AbstractLdapDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
- this.properties = new Hashtable<String, Object>();
+ this.configProperties = new Hashtable<String, Object>();
for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
String key = keys.nextElement();
- properties.put(key, props.get(key));
+ configProperties.put(key, props.get(key));
}
- baseDn = toLdapName(DirectoryConf.baseDn.getValue(properties));
+ baseDn = toLdapName(DirectoryConf.baseDn.getValue(configProperties));
this.scoped = scoped;
if (uriArg != null) {
uri = uriArg.toString();
// uri from properties is ignored
} else {
- String uriStr = DirectoryConf.uri.getValue(properties);
+ String uriStr = DirectoryConf.uri.getValue(configProperties);
if (uriStr == null)
uri = null;
else
uri = uriStr;
}
- forcedPassword = DirectoryConf.forcedPassword.getValue(properties);
+ forcedPassword = DirectoryConf.forcedPassword.getValue(configProperties);
- userObjectClass = DirectoryConf.userObjectClass.getValue(properties);
- groupObjectClass = DirectoryConf.groupObjectClass.getValue(properties);
+ userObjectClass = DirectoryConf.userObjectClass.getValue(configProperties);
+ groupObjectClass = DirectoryConf.groupObjectClass.getValue(configProperties);
- String userBase = DirectoryConf.userBase.getValue(properties);
- String groupBase = DirectoryConf.groupBase.getValue(properties);
- String systemRoleBase = DirectoryConf.systemRoleBase.getValue(properties);
+ String userBase = DirectoryConf.userBase.getValue(configProperties);
+ String groupBase = DirectoryConf.groupBase.getValue(configProperties);
+ String systemRoleBase = DirectoryConf.systemRoleBase.getValue(configProperties);
try {
// baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
userBaseRdn = new Rdn(userBase);
// groupBaseDn = new LdapName(groupBase + "," + baseDn);
systemRoleBaseRdn = new Rdn(systemRoleBase);
} catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formated base DN " + DirectoryConf.baseDn.getValue(properties),
- e);
+ throw new IllegalArgumentException(
+ "Badly formated base DN " + DirectoryConf.baseDn.getValue(configProperties), e);
}
// read only
- String readOnlyStr = DirectoryConf.readOnly.getValue(properties);
+ String readOnlyStr = DirectoryConf.readOnly.getValue(configProperties);
if (readOnlyStr == null) {
readOnly = readOnlyDefault(uri);
- properties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly));
+ configProperties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly));
} else
readOnly = Boolean.parseBoolean(readOnlyStr);
// disabled
- String disabledStr = DirectoryConf.disabled.getValue(properties);
+ String disabledStr = DirectoryConf.disabled.getValue(configProperties);
if (disabledStr != null)
disabled = Boolean.parseBoolean(disabledStr);
else
checkEdit();
LdapEntryWorkingCopy wc = getWorkingCopy();
boolean actuallyDeleted;
- if (getDirectoryDao().daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
+ if (getDirectoryDao().entryExists(dn) || wc.getNewData().containsKey(dn)) {
LdapEntry user = doGetRole(dn);
wc.getDeletedData().put(dn, user);
actuallyDeleted = true;
LdapEntryWorkingCopy wc = getWorkingCopy();
LdapEntry user;
try {
- user = getDirectoryDao().daoGetEntry(dn);
+ user = getDirectoryDao().doGetEntry(dn);
} catch (NameNotFoundException e) {
user = null;
}
return true;// read only by default
}
+ /*
+ * AS AN ENTRY
+ */
+ public LdapEntry asLdapEntry() {
+ try {
+ return directoryDao.doGetEntry(baseDn);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("Cannot get " + baseDn + " entry", e);
+ }
+ }
+
+ public Dictionary<String, Object> getProperties() {
+ return asLdapEntry().getProperties();
+ }
+
/*
* ACCESSORS
*/
@Override
public Optional<String> getRealm() {
- Object realm = getProperties().get(DirectoryConf.realm.name());
+ Object realm = configProperties.get(DirectoryConf.realm.name());
if (realm == null)
return Optional.empty();
return Optional.of(realm.toString());
return systemRoleBaseRdn;
}
- public Dictionary<String, Object> getProperties() {
- return properties;
- }
+// public Dictionary<String, Object> getConfigProperties() {
+// return configProperties;
+// }
- public Dictionary<String, Object> cloneProperties() {
- return new Hashtable<>(properties);
+ public Dictionary<String, Object> cloneConfigProperties() {
+ return new Hashtable<>(configProperties);
}
public String getForcedPassword() {
+++ /dev/null
-package org.argeo.util.directory.ldap;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-/** An entry in an LDAP (or LDIF) directory. */
-public abstract class AbstractLdapEntry implements LdapEntry {
- private final AbstractLdapDirectory directory;
-
- private final LdapName dn;
-
- private Attributes publishedAttributes;
-
- protected AbstractLdapEntry(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) {
- Objects.requireNonNull(directory);
- Objects.requireNonNull(dn);
- this.directory = directory;
- this.dn = dn;
- this.publishedAttributes = attributes;
- }
-
- @Override
- public LdapName getDn() {
- return dn;
- }
-
- public synchronized Attributes getAttributes() {
- return isEditing() ? getModifiedAttributes() : publishedAttributes;
- }
-
- @Override
- public List<LdapName> getReferences(String attributeId){
- Attribute memberAttribute = getAttributes().get(attributeId);
- if (memberAttribute == null)
- return new ArrayList<LdapName>();
- try {
- List<LdapName> roles = new ArrayList<LdapName>();
- NamingEnumeration<?> values = memberAttribute.getAll();
- while (values.hasMore()) {
- LdapName dn = new LdapName(values.next().toString());
- roles.add(dn);
- }
- return roles;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot get members", e);
- }
-
- }
-
- /** Should only be called from working copy thread. */
- protected synchronized Attributes getModifiedAttributes() {
- assert getWc() != null;
- return getWc().getModifiedData().get(getDn());
- }
-
- protected synchronized boolean isEditing() {
- return getWc() != null && getModifiedAttributes() != null;
- }
-
- private synchronized LdapEntryWorkingCopy getWc() {
- return directory.getWorkingCopy();
- }
-
- protected synchronized void startEditing() {
-// if (frozen)
-// throw new IllegalStateException("Cannot edit frozen view");
- if (directory.isReadOnly())
- throw new IllegalStateException("User directory is read-only");
- assert getModifiedAttributes() == null;
- getWc().startEditing(this);
- // modifiedAttributes = (Attributes) publishedAttributes.clone();
- }
-
- public synchronized void publishAttributes(Attributes modifiedAttributes) {
- publishedAttributes = modifiedAttributes;
- }
-
- public AbstractLdapDirectory getDirectory() {
- return directory;
- }
-
- public LdapDirectoryDao getDirectoryDao() {
- return directory.getDirectoryDao();
- }
-
- @Override
- public int hashCode() {
- return dn.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj instanceof LdapEntry) {
- LdapEntry that = (LdapEntry) obj;
- return this.dn.equals(that.getDn());
- }
- return false;
- }
-
- @Override
- public String toString() {
- return dn.toString();
- }
-
-}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.directory.DirectoryDigestUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.argeo.util.naming.SharedSecret;
+
+/** An entry in an LDAP (or LDIF) directory. */
+public class DefaultLdapEntry implements LdapEntry {
+ private final AbstractLdapDirectory directory;
+
+ private final LdapName dn;
+
+ private Attributes publishedAttributes;
+
+ // Temporarily expose the fields
+ protected final AttributeDictionary properties;
+ protected final AttributeDictionary credentials;
+
+ protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) {
+ Objects.requireNonNull(directory);
+ Objects.requireNonNull(dn);
+ this.directory = directory;
+ this.dn = dn;
+ this.publishedAttributes = attributes;
+ properties = new AttributeDictionary(false);
+ credentials = new AttributeDictionary(true);
+ }
+
+ @Override
+ public LdapName getDn() {
+ return dn;
+ }
+
+ public synchronized Attributes getAttributes() {
+ return isEditing() ? getModifiedAttributes() : publishedAttributes;
+ }
+
+ @Override
+ public List<LdapName> getReferences(String attributeId) {
+ Attribute memberAttribute = getAttributes().get(attributeId);
+ if (memberAttribute == null)
+ return new ArrayList<LdapName>();
+ try {
+ List<LdapName> roles = new ArrayList<LdapName>();
+ NamingEnumeration<?> values = memberAttribute.getAll();
+ while (values.hasMore()) {
+ LdapName dn = new LdapName(values.next().toString());
+ roles.add(dn);
+ }
+ return roles;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get members", e);
+ }
+
+ }
+
+ /** Should only be called from working copy thread. */
+ protected synchronized Attributes getModifiedAttributes() {
+ assert getWc() != null;
+ return getWc().getModifiedData().get(getDn());
+ }
+
+ protected synchronized boolean isEditing() {
+ return getWc() != null && getModifiedAttributes() != null;
+ }
+
+ private synchronized LdapEntryWorkingCopy getWc() {
+ return directory.getWorkingCopy();
+ }
+
+ protected synchronized void startEditing() {
+// if (frozen)
+// throw new IllegalStateException("Cannot edit frozen view");
+ if (directory.isReadOnly())
+ throw new IllegalStateException("User directory is read-only");
+ assert getModifiedAttributes() == null;
+ getWc().startEditing(this);
+ // modifiedAttributes = (Attributes) publishedAttributes.clone();
+ }
+
+ public synchronized void publishAttributes(Attributes modifiedAttributes) {
+ publishedAttributes = modifiedAttributes;
+ }
+
+ /*
+ * PROPERTIES
+ */
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ return properties;
+ }
+
+ /*
+ * CREDENTIALS
+ */
+ @Override
+ public boolean hasCredential(String key, Object value) {
+ if (key == null) {
+ // TODO check other sources (like PKCS12)
+ // String pwd = new String((char[]) value);
+ // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
+ char[] password = DirectoryDigestUtils.bytesToChars(value);
+
+ if (getDirectory().getForcedPassword() != null
+ && getDirectory().getForcedPassword().equals(new String(password)))
+ return true;
+
+ AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
+ if (authPassword != null) {
+ if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
+ SharedSecret onceToken = new SharedSecret(authPassword);
+ if (onceToken.isExpired()) {
+ // AuthPassword.remove(getAttributes(), onceToken);
+ return false;
+ } else {
+ // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
+ return true;
+ }
+ // TODO delete expired tokens?
+ } else {
+ // TODO implement SHA
+ throw new UnsupportedOperationException(
+ "Unsupported authPassword scheme " + authPassword.getAuthScheme());
+ }
+ }
+
+ // Regular password
+// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
+ if (hasCredential(LdapAttrs.userPassword.name(), DirectoryDigestUtils.charsToBytes(password)))
+ return true;
+ return false;
+ }
+
+ Object storedValue = credentials.get(key);
+ if (storedValue == null || value == null)
+ return false;
+ if (!(value instanceof String || value instanceof byte[]))
+ return false;
+ if (storedValue instanceof String && value instanceof String)
+ return storedValue.equals(value);
+ if (storedValue instanceof byte[] && value instanceof byte[]) {
+ String storedBase64 = new String((byte[]) storedValue, US_ASCII);
+ String passwordScheme = null;
+ if (storedBase64.charAt(0) == '{') {
+ int index = storedBase64.indexOf('}');
+ if (index > 0) {
+ passwordScheme = storedBase64.substring(1, index);
+ String storedValueBase64 = storedBase64.substring(index + 1);
+ byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
+ char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value);
+ byte[] valueBytes;
+ if (DirectoryDigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+ valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null,
+ null);
+ } else if (DirectoryDigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+ // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
+ byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
+ BigInteger iterations = new BigInteger(iterationsArr);
+ byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
+ iterationsArr.length + 64);
+ byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
+ storedValueBytes.length);
+ int keyLengthBits = keyArr.length * 8;
+ valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
+ iterations.intValue(), keyLengthBits);
+ } else {
+ throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
+ }
+ return Arrays.equals(storedValueBytes, valueBytes);
+ }
+ }
+ }
+// if (storedValue instanceof byte[] && value instanceof byte[]) {
+// return Arrays.equals((byte[]) storedValue, (byte[]) value);
+// }
+ return false;
+ }
+
+ /** Hash the password */
+ private static byte[] sha1hash(char[] password) {
+ byte[] hashedPassword = ("{SHA}" + Base64.getEncoder()
+ .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password))))
+ .getBytes(StandardCharsets.UTF_8);
+ return hashedPassword;
+ }
+
+ public AbstractLdapDirectory getDirectory() {
+ return directory;
+ }
+
+ public LdapDirectoryDao getDirectoryDao() {
+ return directory.getDirectoryDao();
+ }
+
+ @Override
+ public int hashCode() {
+ return dn.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj instanceof LdapEntry) {
+ LdapEntry that = (LdapEntry) obj;
+ return this.dn.equals(that.getDn());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return dn.toString();
+ }
+
+ private static boolean isAsciiPrintable(String str) {
+ if (str == null) {
+ return false;
+ }
+ int sz = str.length();
+ for (int i = 0; i < sz; i++) {
+ if (isAsciiPrintable(str.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isAsciiPrintable(char ch) {
+ return ch >= 32 && ch < 127;
+ }
+
+ protected class AttributeDictionary extends Dictionary<String, Object> {
+ private final List<String> effectiveKeys = new ArrayList<String>();
+ private final List<String> attrFilter;
+ private final Boolean includeFilter;
+
+ public AttributeDictionary(Boolean credentials) {
+ this.attrFilter = getDirectory().getCredentialAttributeIds();
+ this.includeFilter = credentials;
+ try {
+ NamingEnumeration<String> ids = getAttributes().getIDs();
+ while (ids.hasMore()) {
+ String id = ids.next();
+ if (credentials && attrFilter.contains(id))
+ effectiveKeys.add(id);
+ else if (!credentials && !attrFilter.contains(id))
+ effectiveKeys.add(id);
+ }
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot initialise attribute dictionary", e);
+ }
+ if (!credentials)
+ effectiveKeys.add(LdapAttrs.objectClasses.name());
+ }
+
+ @Override
+ public int size() {
+ return effectiveKeys.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return effectiveKeys.size() == 0;
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ return Collections.enumeration(effectiveKeys);
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ final Iterator<String> it = effectiveKeys.iterator();
+ return new Enumeration<Object>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return it.hasNext();
+ }
+
+ @Override
+ public Object nextElement() {
+ String key = it.next();
+ return get(key);
+ }
+
+ };
+ }
+
+ @Override
+ public Object get(Object key) {
+ try {
+ Attribute attr = !key.equals(LdapAttrs.objectClasses.name()) ? getAttributes().get(key.toString())
+ : getAttributes().get(LdapAttrs.objectClass.name());
+ if (attr == null)
+ return null;
+ Object value = attr.get();
+ if (value instanceof byte[]) {
+ if (key.equals(LdapAttrs.userPassword.name()))
+ // TODO other cases (certificates, images)
+ return value;
+ value = new String((byte[]) value, StandardCharsets.UTF_8);
+ }
+ if (attr.size() == 1)
+ return value;
+ // special case for object class
+ if (key.equals(LdapAttrs.objectClass.name())) {
+ // TODO support multiple object classes
+ NamingEnumeration<?> en = attr.getAll();
+ String first = null;
+ attrs: while (en.hasMore()) {
+ String v = en.next().toString();
+ if (v.equalsIgnoreCase(LdapObjs.top.name()))
+ continue attrs;
+ if (first == null)
+ first = v;
+ if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
+ return getDirectory().getUserObjectClass();
+ else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
+ return getDirectory().getGroupObjectClass();
+ }
+ if (first != null)
+ return first;
+ throw new IllegalStateException("Cannot find objectClass in " + value);
+ } else {
+ NamingEnumeration<?> en = attr.getAll();
+ StringJoiner values = new StringJoiner("\n");
+ while (en.hasMore()) {
+ String v = en.next().toString();
+ values.add(v);
+ }
+ return values.toString();
+ }
+// else
+// return value;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get value for attribute " + key, e);
+ }
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ if (key == null) {
+ // TODO persist to other sources (like PKCS12)
+ char[] password = DirectoryDigestUtils.bytesToChars(value);
+ byte[] hashedPassword = sha1hash(password);
+ return put(LdapAttrs.userPassword.name(), hashedPassword);
+ }
+ if (key.startsWith("X-")) {
+ return put(LdapAttrs.authPassword.name(), value);
+ }
+
+ getDirectory().checkEdit();
+ if (!isEditing())
+ startEditing();
+
+ if (!(value instanceof String || value instanceof byte[]))
+ throw new IllegalArgumentException("Value must be String or byte[]");
+
+ if (includeFilter && !attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " not included");
+ else if (!includeFilter && attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " excluded");
+
+ try {
+ Attribute attribute = getModifiedAttributes().get(key.toString());
+ // if (attribute == null) // block unit tests
+ attribute = new BasicAttribute(key.toString());
+ if (value instanceof String && !isAsciiPrintable(((String) value)))
+ attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
+ else
+ attribute.add(value);
+ Attribute previousAttribute = getModifiedAttributes().put(attribute);
+ if (previousAttribute != null)
+ return previousAttribute.get();
+ else
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get value for attribute " + key, e);
+ }
+ }
+
+ @Override
+ public Object remove(Object key) {
+ getDirectory().checkEdit();
+ if (!isEditing())
+ startEditing();
+
+ if (includeFilter && !attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " not included");
+ else if (!includeFilter && attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " excluded");
+
+ try {
+ Attribute attr = getModifiedAttributes().remove(key.toString());
+ if (attr != null)
+ return attr.get();
+ else
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot remove attribute " + key, e);
+ }
+ }
+
+ }
+
+}
@Override
public void init() {
- ldapConnection = new LdapConnection(getDirectory().getUri().toString(), getDirectory().getProperties());
+ ldapConnection = new LdapConnection(getDirectory().getUri().toString(), getDirectory().cloneConfigProperties());
}
public void destroy() {
// }
@Override
- public Boolean daoHasEntry(LdapName dn) {
+ public Boolean entryExists(LdapName dn) {
try {
- return daoGetEntry(dn) != null;
+ return doGetEntry(dn) != null;
} catch (NameNotFoundException e) {
return false;
}
}
@Override
- public LdapEntry daoGetEntry(LdapName name) throws NameNotFoundException {
+ public LdapEntry doGetEntry(LdapName name) throws NameNotFoundException {
try {
Attributes attrs = ldapConnection.getAttributes(name);
if (attrs.size() == 0)
return null;
// int roleType = roleType(name);
LdapEntry res;
- if (isGroup(name))
+ Rdn technicalRdn = LdapNameUtils.getParentRdn(name);
+ if (getDirectory().getGroupBaseRdn().equals(technicalRdn)
+ || getDirectory().getSystemRoleBaseRdn().equals(technicalRdn))
res = newGroup(name, attrs);
- else
+ else if (getDirectory().getUserBaseRdn().equals(technicalRdn))
res = newUser(name, attrs);
+ else
+ res = new DefaultLdapEntry(getDirectory(), name, attrs);
+// if (isGroup(name))
+// res = newGroup(name, attrs);
+// else
+// res = newUser(name, attrs);
// else
// throw new IllegalArgumentException("Unsupported LDAP type for " + name);
return res;
}
}
- protected boolean isGroup(LdapName dn) {
- Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
- if (getDirectory().getGroupBaseRdn().equals(technicalRdn)
- || getDirectory().getSystemRoleBaseRdn().equals(technicalRdn))
- return true;
- else if (getDirectory().getUserBaseRdn().equals(technicalRdn))
- return false;
- else
- throw new IllegalArgumentException(
- "Cannot dind role type, " + technicalRdn + " is not a technical RDN for " + dn);
- }
+// protected boolean isGroup(LdapName dn) {
+// Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
+// if (getDirectory().getGroupBaseRdn().equals(technicalRdn)
+// || getDirectory().getSystemRoleBaseRdn().equals(technicalRdn))
+// return true;
+// else if (getDirectory().getUserBaseRdn().equals(technicalRdn))
+// return false;
+// else
+// throw new IllegalArgumentException(
+// "Cannot find role type, " + technicalRdn + " is not a technical RDN for " + dn);
+// }
@Override
public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
@Override
public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
try {
+ if (getDirectory().getBaseDn().equals(dn))
+ return null;
Attributes attrs = ldapConnection.getAttributes(dn);
return new LdapHierarchyUnit(getDirectory(), dn, attrs);
} catch (NamingException e) {
import org.argeo.util.transaction.WorkingCopyProcessor;
public interface LdapDirectoryDao extends WorkingCopyProcessor<LdapEntryWorkingCopy> {
- Boolean daoHasEntry(LdapName dn);
+ Boolean entryExists(LdapName dn);
- LdapEntry daoGetEntry(LdapName name) throws NameNotFoundException;
+ LdapEntry doGetEntry(LdapName name) throws NameNotFoundException;
List<LdapEntry> doGetEntries(LdapName searchBase, String filter, boolean deep);
LdapEntry newUser(LdapName name, Attributes attrs);
LdapEntry newGroup(LdapName name, Attributes attrs);
-
+
void init();
-
+
void destroy();
}
package org.argeo.util.directory.ldap;
+import java.util.Dictionary;
import java.util.List;
import javax.naming.directory.Attributes;
void publishAttributes(Attributes modifiedAttributes);
public List<LdapName> getReferences(String attributeId);
+
+ public Dictionary<String, Object> getProperties();
+
+ public boolean hasCredential(String key, Object value) ;
+
}
import org.argeo.util.directory.HierarchyUnit;
/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
-public class LdapHierarchyUnit extends AbstractLdapEntry implements HierarchyUnit {
+public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit {
private final boolean functional;
public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) {
*/
@Override
- public LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException {
+ public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
// if (groups.containsKey(key))
// return groups.get(key);
// if (users.containsKey(key))
}
@Override
- public Boolean daoHasEntry(LdapName dn) {
+ public Boolean entryExists(LdapName dn) {
return entries.containsKey(dn);// || groups.containsKey(dn);
}
entries: for (LdapName name : entries.keySet()) {
LdapEntry group;
try {
- LdapEntry entry = daoGetEntry(name);
+ LdapEntry entry = doGetEntry(name);
if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
group = entry;
} else {
Attributes modifiedAttrs = wc.getModifiedData().get(dn);
LdapEntry user;
try {
- user = daoGetEntry(dn);
+ user = doGetEntry(dn);
} catch (NameNotFoundException e) {
throw new IllegalStateException("User to modify no found " + dn, e);
}