import org.argeo.api.cms.CmsConstants;
import org.argeo.cms.CmsException;
-import org.argeo.osgi.useradmin.UserAdminConf;
import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.DirectoryConf;
import org.argeo.util.transaction.WorkTransaction;
import org.osgi.service.useradmin.UserAdmin;
import org.osgi.service.useradmin.UserAdminEvent;
continue;
if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
continue;
- dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
+ dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
}
// for (String uri : uris) {
import org.argeo.cms.e4.users.UserAdminWrapper;
import org.argeo.eclipse.ui.EclipseUiUtils;
import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.util.directory.DirectoryConf;
import org.argeo.util.naming.LdapAttrs;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.jface.wizard.Wizard;
Map<String, String> dns = getDns();
String bdn = baseDnCmb.getText();
if (EclipseUiUtils.notEmpty(bdn)) {
- Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(bdn));
- String dn = LdapAttrs.cn.name() + "=" + cn + "," + UserAdminConf.groupBase.getValue(props) + "," + bdn;
+ Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(bdn));
+ String dn = LdapAttrs.cn.name() + "=" + cn + "," + DirectoryConf.groupBase.getValue(props) + "," + bdn;
return dn;
}
return null;
import org.argeo.cms.e4.users.UserAdminWrapper;
import org.argeo.eclipse.ui.EclipseUiUtils;
import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.util.directory.DirectoryConf;
import org.argeo.util.naming.LdapAttrs;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.jface.wizard.Wizard;
Map<String, String> dns = getDns();
String bdn = baseDnCmb.getText();
if (EclipseUiUtils.notEmpty(bdn)) {
- Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(bdn));
- String dn = LdapAttrs.uid.name() + "=" + uid + "," + UserAdminConf.userBase.getValue(props) + "," + bdn;
+ Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(bdn));
+ String dn = LdapAttrs.uid.name() + "=" + uid + "," + DirectoryConf.userBase.getValue(props) + "," + bdn;
return dn;
}
return null;
<provide interface="org.argeo.cms.CmsUserManager"/>
</service>
<reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
- <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
+ <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.util.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
<reference bind="addUserDirectory" cardinality="0..n" interface="org.argeo.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
</scr:component>
<service>
<provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
</service>
- <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkControl" name="WorkControl" policy="static"/>
- <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+ <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.util.transaction.WorkControl" name="WorkControl" policy="static"/>
+ <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.util.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
<reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
</scr:component>
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Simple Transaction Manager">
- <implementation class="org.argeo.osgi.transaction.SimpleTransactionManager"/>
+ <implementation class="org.argeo.util.transaction.SimpleTransactionManager"/>
<service>
- <provide interface="org.argeo.osgi.transaction.WorkControl"/>
- <provide interface="org.argeo.osgi.transaction.WorkTransaction"/>
+ <provide interface="org.argeo.util.transaction.WorkControl"/>
+ <provide interface="org.argeo.util.transaction.WorkTransaction"/>
</service>
</scr:component>
Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
Import-Package: \
-org.argeo.osgi.transaction, \
org.apache.commons.httpclient.cookie;resolution:=optional,\
!com.sun.security.jgss,\
org.osgi.*;version=0.0.0,\
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.HierarchyUnit;
-import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.Directory;
+import org.argeo.util.directory.HierarchyUnit;
class DirectoryContent extends AbstractContent {
- private UserDirectory directory;
+ private Directory directory;
private DirectoryContentProvider provider;
- public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, UserDirectory directory) {
+ public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, Directory directory) {
super(session);
this.provider = provider;
this.directory = directory;
import org.argeo.cms.CmsUserManager;
import org.argeo.cms.acr.AbstractContent;
import org.argeo.cms.acr.ContentUtils;
-import org.argeo.osgi.useradmin.HierarchyUnit;
import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.HierarchyUnit;
import org.osgi.service.useradmin.User;
public class DirectoryContentProvider implements ContentProvider {
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.HierarchyUnit;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.HierarchyUnit;
import org.osgi.service.useradmin.Role;
class HierarchyUnitContent extends AbstractContent {
for (HierarchyUnit hu : hierarchyUnit.getDirectHierachyUnits(false))
lst.add(new HierarchyUnitContent(getSession(), provider, hu));
- for (Role role : hierarchyUnit.getHierarchyUnitRoles(null, false))
+ for (Role role : ((UserDirectory) hierarchyUnit.getDirectory()).getHierarchyUnitRoles(hierarchyUnit, null,
+ false))
lst.add(new RoleContent(getSession(), provider, this, role));
return lst.iterator();
}
return hierarchyUnit;
}
-
}
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.Role;
@Override
public QName getName() {
- String name = parent.getHierarchyUnit().getDirectory().getRoleSimpleName(role);
+ String name = ((UserDirectory) parent.getHierarchyUnit().getDirectory()).getRoleSimpleName(role);
return new ContentName(name);
}
import javax.security.auth.x500.X500Principal;
import org.argeo.api.cms.CmsLog;
-import org.argeo.osgi.useradmin.IpaUtils;
import org.argeo.osgi.useradmin.OsUserUtils;
+import org.argeo.util.directory.ldap.IpaUtils;
import org.argeo.util.naming.LdapAttrs;
import org.osgi.service.useradmin.Authorization;
import org.argeo.cms.internal.runtime.CmsContextImpl;
import org.argeo.cms.security.CryptoKeyring;
import org.argeo.osgi.useradmin.AuthenticatingUser;
-import org.argeo.osgi.useradmin.IpaUtils;
import org.argeo.osgi.useradmin.TokenUtils;
+import org.argeo.util.directory.ldap.IpaUtils;
import org.argeo.util.naming.LdapAttrs;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.argeo.cms.auth.CurrentUser;
import org.argeo.cms.auth.UserAdminUtils;
import org.argeo.osgi.useradmin.TokenUtils;
-import org.argeo.osgi.useradmin.UserAdminConf;
import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.DirectoryConf;
import org.argeo.util.naming.LdapAttrs;
import org.argeo.util.naming.NamingUtils;
import org.argeo.util.naming.SharedSecret;
continue;
if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
continue;
- dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
+ dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
}
return dns;
public String buildDistinguishedName(String localId, String baseDn, int type) {
Map<String, String> dns = getKnownBaseDns(true);
- Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
+ Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(baseDn));
String dn = null;
if (Role.GROUP == type)
- dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
+ dn = LdapAttrs.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn;
else if (Role.USER == type)
- dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn;
+ dn = LdapAttrs.uid.name() + "=" + localId + "," + DirectoryConf.userBase.getValue(props) + "," + baseDn;
else
throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
return dn;
import org.argeo.cms.CmsException;
import org.argeo.cms.auth.CurrentUser;
import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.util.directory.DirectoryConf;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
// user directories
- Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
+ Object baseDn = sr.getProperty(DirectoryConf.baseDn.name());
if (baseDn != null)
- sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
+ sb.append(" " + DirectoryConf.baseDn.name() + ": " + baseDn);
}
return sb.toString();
import org.argeo.cms.internal.runtime.InitUtils;
import org.argeo.cms.internal.runtime.KernelConstants;
import org.argeo.cms.internal.runtime.KernelUtils;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.ldap.AttributesDictionary;
-import org.argeo.util.naming.ldap.LdifParser;
-import org.argeo.util.naming.ldap.LdifWriter;
+import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.directory.ldap.AttributesDictionary;
+import org.argeo.util.directory.ldap.LdifParser;
+import org.argeo.util.directory.ldap.LdifWriter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
List<String> activeCns = new ArrayList<>();
for (int i = 0; i < userDirectoryConfigs.size(); i++) {
Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
- String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
+ String baseDn = (String) userDirectoryConfig.get(DirectoryConf.baseDn.name());
String cn;
if (CmsConstants.ROLES_BASEDN.equals(baseDn))
cn = ROLES;
else
- cn = UserAdminConf.baseDnHash(userDirectoryConfig);
+ cn = DirectoryConf.baseDnHash(userDirectoryConfig);
activeCns.add(cn);
userDirectoryConfig.put(CmsConstants.CN, cn);
putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
Attributes attrs = deployConfigs.get(name);
String cn = name.getRdn(name.size() - 1).getValue().toString();
if (!activeCns.contains(cn)) {
- attrs.put(UserAdminConf.disabled.name(), "true");
+ attrs.put(DirectoryConf.disabled.name(), "true");
}
// } catch (Exception e) {
// throw new CmsException("Cannot disable user directory " + name, e);
// service factory definition
}
} else {
- Attribute disabled = deployConfig.get(UserAdminConf.disabled.name());
+ Attribute disabled = deployConfig.get(DirectoryConf.disabled.name());
if (disabled != null)
continue deployConfigs;
// service factory service
boolean hasDomain = false;
for (Configuration config : configs) {
- Object realm = config.getProperties().get(UserAdminConf.realm.name());
+ Object realm = config.getProperties().get(DirectoryConf.realm.name());
if (realm != null) {
log.debug("Found realm: " + realm);
hasDomain = true;
import org.argeo.api.cms.CmsLog;
import org.argeo.cms.internal.runtime.CmsUserAdmin;
import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.DirectoryConf;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
@Override
public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
- String basePath = (String) properties.get(UserAdminConf.baseDn.name());
+ String basePath = (String) properties.get(DirectoryConf.baseDn.name());
// FIXME make updates more robust
if (pidToBaseDn.containsValue(basePath)) {
regProps.put(Constants.SERVICE_PID, pid);
if (isSystemRolesBaseDn(basePath))
regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
- regProps.put(UserAdminConf.baseDn.name(), basePath);
+ regProps.put(DirectoryConf.baseDn.name(), basePath);
CmsActivator.getBundleContext().registerService(UserDirectory.class, userDirectory, regProps);
pidToBaseDn.put(pid, basePath);
import org.argeo.osgi.useradmin.LdapUserAdmin;
import org.argeo.osgi.useradmin.LdifUserAdmin;
import org.argeo.osgi.useradmin.OsUserDirectory;
-import org.argeo.osgi.useradmin.UserAdminConf;
import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.DirectoryConf;
import org.argeo.util.naming.dns.DnsBrowser;
import org.argeo.util.transaction.WorkControl;
import org.argeo.util.transaction.WorkTransaction;
}
public UserDirectory enableUserDirectory(Dictionary<String, ?> properties) {
- String uri = (String) properties.get(UserAdminConf.uri.name());
- Object realm = properties.get(UserAdminConf.realm.name());
+ String uri = (String) properties.get(DirectoryConf.uri.name());
+ Object realm = properties.get(DirectoryConf.realm.name());
URI u;
try {
if (uri == null) {
- String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
+ String baseDn = (String) properties.get(DirectoryConf.baseDn.name());
u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
} else if (realm != null) {
u = null;
// Create
UserDirectory userDirectory;
- if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
- || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
+ if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+ || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
userDirectory = new LdapUserAdmin(properties);
- } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
+ } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
userDirectory = new LdifUserAdmin(u, properties);
- } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
+ } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
userDirectory = new OsUserDirectory(u, properties);
singleUser = true;
} else {
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
import org.argeo.cms.internal.http.InternalHttpConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.util.directory.DirectoryConf;
/**
* Interprets framework properties in order to generate the initial deploy
u = new URI(uri);
} else
throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
- } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
+ } else if (u.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
u = new File(u).getCanonicalFile().toURI();
}
} catch (Exception e) {
throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
}
- Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
+ Dictionary<String, Object> properties = DirectoryConf.uriAsProperties(u.toString());
res.add(properties);
}
import static org.argeo.util.naming.LdapObjs.person;
import static org.argeo.util.naming.LdapObjs.top;
-import java.io.File;
import java.net.URI;
-import java.net.URISyntaxException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
-import java.util.Optional;
-import java.util.StringJoiner;
import javax.naming.InvalidNameException;
import javax.naming.NameNotFoundException;
import javax.naming.directory.BasicAttributes;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
-import javax.transaction.xa.XAResource;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.AbstractLdapDirectory;
+import org.argeo.util.directory.ldap.LdapEntry;
+import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
+import org.argeo.util.directory.ldap.LdapNameUtils;
import org.argeo.util.naming.LdapAttrs;
import org.argeo.util.naming.LdapObjs;
-import org.argeo.util.transaction.WorkControl;
-import org.argeo.util.transaction.WorkingCopyProcessor;
-import org.argeo.util.transaction.WorkingCopyXaResource;
-import org.argeo.util.transaction.XAResourceProvider;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.useradmin.UserAdmin;
/** Base class for a {@link UserDirectory}. */
-abstract class AbstractUserDirectory
- implements UserAdmin, UserDirectory, WorkingCopyProcessor<DirectoryUserWorkingCopy>, XAResourceProvider {
- static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
- static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
-
- private final Hashtable<String, Object> properties;
- private final LdapName baseDn;
- // private final LdapName userBaseDn, groupBaseDn;
- private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
- private final String userObjectClass, groupObjectClass;
-
- private final boolean readOnly;
- private final boolean disabled;
- private final String uri;
+abstract class AbstractUserDirectory extends AbstractLdapDirectory implements UserAdmin, UserDirectory {
private UserAdmin externalRoles;
// private List<String> indexedUserProperties = Arrays
// .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
// LdapAttrs.cn.name() });
- private final boolean scoped;
-
- private String memberAttributeId = "member";
- private List<String> credentialAttributeIds = Arrays
- .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
-
// Transaction
// private TransactionManager transactionManager;
- private WorkControl transactionControl;
- private WorkingCopyXaResource<DirectoryUserWorkingCopy> xaResource = new WorkingCopyXaResource<>(this);
-
- private String forcedPassword;
-
AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
- this.scoped = scoped;
- properties = new Hashtable<String, Object>();
- for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
- String key = keys.nextElement();
- properties.put(key, props.get(key));
- }
-
- if (uriArg != null) {
- uri = uriArg.toString();
- // uri from properties is ignored
- } else {
- String uriStr = UserAdminConf.uri.getValue(properties);
- if (uriStr == null)
- uri = null;
- else
- uri = uriStr;
- }
-
- forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
-
- userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
- String userBase = UserAdminConf.userBase.getValue(properties);
- groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
- String groupBase = UserAdminConf.groupBase.getValue(properties);
- String systemRoleBase = UserAdminConf.systemRoleBase.getValue(properties);
- try {
- baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
- userBaseRdn = new Rdn(userBase);
-// userBaseDn = new LdapName(userBase + "," + baseDn);
- groupBaseRdn = new Rdn(groupBase);
-// groupBaseDn = new LdapName(groupBase + "," + baseDn);
- systemRoleBaseRdn = new Rdn(systemRoleBase);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties),
- e);
- }
-
- // read only
- String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
- if (readOnlyStr == null) {
- readOnly = readOnlyDefault(uri);
- properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
- } else
- readOnly = Boolean.parseBoolean(readOnlyStr);
-
- // disabled
- String disabledStr = UserAdminConf.disabled.getValue(properties);
- if (disabledStr != null)
- disabled = Boolean.parseBoolean(disabledStr);
- else
- disabled = false;
+ super(uriArg, props, scoped);
}
/*
* ABSTRACT METHODS
*/
+ protected abstract AbstractLdapDirectory scope(User user);
+
/** Returns the groups this user is a direct member of. */
protected abstract List<LdapName> getDirectGroups(LdapName dn);
- protected abstract Boolean daoHasRole(LdapName dn);
-
- protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
-
- protected abstract List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep);
-
- protected abstract AbstractUserDirectory scope(User user);
-
- protected abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
-
- protected abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
-
/*
* INITIALIZATION
*/
}
- /*
- * PATHS
- */
-
- @Override
- public String getContext() {
- return getBaseDn().toString();
- }
-
- @Override
- public String getName() {
- return nameToSimple(getBaseDn(), ".");
- }
-
@Override
public String getRolePath(Role role) {
return nameToRelativePath(((DirectoryUser) role).getDn());
return name;
}
- protected String nameToRelativePath(LdapName dn) {
- LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn);
- return nameToSimple(name, "/");
- }
-
- protected String nameToSimple(LdapName name, String separator) {
- StringJoiner path = new StringJoiner(separator);
- for (int i = 0; i < name.size(); i++) {
- path.add(name.getRdn(i).getValue().toString());
- }
- return path.toString();
-
- }
-
- protected LdapName pathToName(String path) {
- try {
- LdapName name = (LdapName) getBaseDn().clone();
- String[] segments = path.split("/");
- Rdn parentRdn = null;
- for (String segment : segments) {
- // TODO make attr names configurable ?
- String attr = LdapAttrs.ou.name();
- if (parentRdn != null) {
- if (getUserBaseRdn().equals(parentRdn))
- attr = LdapAttrs.uid.name();
- else if (getGroupBaseRdn().equals(parentRdn))
- attr = LdapAttrs.cn.name();
- else if (getSystemRoleBaseRdn().equals(parentRdn))
- attr = LdapAttrs.cn.name();
- }
- Rdn rdn = new Rdn(attr, segment);
- name.add(rdn);
- parentRdn = rdn;
- }
- return name;
- } catch (InvalidNameException e) {
- throw new IllegalStateException("Cannot get role " + path, e);
- }
-
- }
-
@Override
public Role getRoleByPath(String path) {
return doGetRole(pathToName(path));
}
- @Override
- public Optional<String> getRealm() {
- Object realm = getProperties().get(UserAdminConf.realm.name());
- if (realm == null)
- return Optional.empty();
- return Optional.of(realm.toString());
- }
-
- /*
- * EDITION
- */
-
- protected boolean isEditing() {
- return xaResource.wc() != null;
- }
-
- protected DirectoryUserWorkingCopy getWorkingCopy() {
- DirectoryUserWorkingCopy wc = xaResource.wc();
- if (wc == null)
- return null;
- return wc;
- }
-
- protected void checkEdit() {
- if (xaResource.wc() == null) {
- try {
- transactionControl.getWorkContext().registerXAResource(xaResource, null);
- } catch (Exception e) {
- throw new IllegalStateException("Cannot enlist " + xaResource, e);
- }
- } else {
- }
- }
-
protected List<Role> getAllRoles(DirectoryUser user) {
List<Role> allRoles = new ArrayList<Role>();
if (user != null) {
}
protected DirectoryUser doGetRole(LdapName dn) {
- DirectoryUserWorkingCopy wc = getWorkingCopy();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
DirectoryUser user;
try {
- user = daoGetRole(dn);
+ user = (DirectoryUser) daoGetEntry(dn);
} catch (NameNotFoundException e) {
user = null;
}
if (wc != null) {
if (user == null && wc.getNewData().containsKey(dn))
- user = wc.getNewData().get(dn);
+ user = (DirectoryUser) wc.getNewData().get(dn);
else if (wc.getDeletedData().containsKey(dn))
user = null;
}
}
List<DirectoryUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
- DirectoryUserWorkingCopy wc = getWorkingCopy();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
- List<DirectoryUser> res = doGetRoles(searchBase, f, deep);
+ List<LdapEntry> searchRes = doGetEntries(searchBase, f, deep);
+ List<DirectoryUser> res = new ArrayList<>();
+ for (LdapEntry entry : searchRes)
+ res.add((DirectoryUser) entry);
if (wc != null) {
for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
- DirectoryUser user = it.next();
+ DirectoryUser user = (DirectoryUser) it.next();
LdapName dn = user.getDn();
if (wc.getDeletedData().containsKey(dn))
it.remove();
}
- for (DirectoryUser user : wc.getNewData().values()) {
+ for (LdapEntry ldapEntry : wc.getNewData().values()) {
+ DirectoryUser user = (DirectoryUser) ldapEntry;
if (f == null || f.match(user.getProperties()))
res.add(user);
}
protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
try {
Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
- List<DirectoryUser> users = doGetRoles(getBaseDn(), f, true);
- collectedUsers.addAll(users);
+ List<LdapEntry> users = doGetEntries(getBaseDn(), f, true);
+ for (LdapEntry entry : users)
+ collectedUsers.add((DirectoryUser) entry);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Cannot get user with " + key + "=" + value, e);
}
return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
} else {
// bind
- AbstractUserDirectory scopedUserAdmin = scope(user);
+ AbstractUserDirectory scopedUserAdmin = (AbstractUserDirectory) scope(user);
try {
DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
if (directoryUser == null)
@Override
public Role createRole(String name, int type) {
checkEdit();
- DirectoryUserWorkingCopy wc = getWorkingCopy();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
LdapName dn = toLdapName(name);
- if ((daoHasRole(dn) && !wc.getDeletedData().containsKey(dn)) || wc.getNewData().containsKey(dn))
+ if ((daoHasEntry(dn) && !wc.getDeletedData().containsKey(dn)) || wc.getNewData().containsKey(dn))
throw new IllegalArgumentException("Already a role " + name);
BasicAttributes attrs = new BasicAttributes(true);
// attrs.put(LdifName.dn.name(), dn.toString());
@Override
public boolean removeRole(String name) {
checkEdit();
- DirectoryUserWorkingCopy wc = getWorkingCopy();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
LdapName dn = toLdapName(name);
boolean actuallyDeleted;
- if (daoHasRole(dn) || wc.getNewData().containsKey(dn)) {
+ if (daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
DirectoryUser user = (DirectoryUser) getRole(name);
wc.getDeletedData().put(dn, user);
actuallyDeleted = true;
return actuallyDeleted;
}
- /*
- * TRANSACTION
- */
- @Override
- public DirectoryUserWorkingCopy newWorkingCopy() {
- return new DirectoryUserWorkingCopy();
- }
-
/*
* HIERARCHY
*/
- @Override
- public HierarchyUnit getHierarchyUnit(String path) {
- LdapName dn = pathToName(path);
- return doGetHierarchyUnit(dn);
- }
-
@Override
public HierarchyUnit getHierarchyUnit(Role role) {
LdapName dn = LdapNameUtils.toLdapName(role.getName());
}
@Override
- public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
- return doGetDirectHierarchyUnits(baseDn, functionalOnly);
+ public Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
+ LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getContext());
+ try {
+ return getRoles(dn, filter, deep);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
+ }
}
/*
}
- private boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) {
- try {
- Attribute attr = attrs.get(LdapAttrs.objectClass.name());
- NamingEnumeration<?> en = attr.getAll();
- while (en.hasMore()) {
- String v = en.next().toString();
- if (v.equalsIgnoreCase(objectClass.name()))
- return true;
-
- }
- return false;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot search for objectClass " + objectClass.name(), e);
- }
- }
-
// GETTERS
- protected String getMemberAttributeId() {
- return memberAttributeId;
- }
-
- protected List<String> getCredentialAttributeIds() {
- return credentialAttributeIds;
- }
-
- protected String getUri() {
- return uri;
- }
-
- private static boolean readOnlyDefault(String uriStr) {
- if (uriStr == null)
- return true;
- /// TODO make it more generic
- URI uri;
- try {
- uri = new URI(uriStr.split(" ")[0]);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- if (uri.getScheme() == null)
- return false;// assume relative file to be writable
- if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
- File file = new File(uri);
- if (file.exists())
- return !file.canWrite();
- else
- return !file.getParentFile().canWrite();
- } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
- if (uri.getAuthority() != null)// assume writable if authenticated
- return false;
- } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
- return true;
- }
- return true;// read only by default
- }
-
- public boolean isReadOnly() {
- return readOnly;
- }
-
- public boolean isDisabled() {
- return disabled;
- }
-
protected UserAdmin getExternalRoles() {
return externalRoles;
}
Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
if (getGroupBaseRdn().equals(technicalRdn) || getSystemRoleBaseRdn().equals(technicalRdn))
return Role.GROUP;
- else if (userBaseRdn.equals(technicalRdn))
+ else if (getUserBaseRdn().equals(technicalRdn))
return Role.USER;
else
throw new IllegalArgumentException(
"Cannot dind role type, " + technicalRdn + " is not a technical RDN for " + dn);
}
- /** dn can be null, in that case a default should be returned. */
- public String getUserObjectClass() {
- return userObjectClass;
- }
-
- Rdn getUserBaseRdn() {
- return userBaseRdn;
- }
-
- protected String newUserObjectClass(LdapName dn) {
- return getUserObjectClass();
- }
-
- public String getGroupObjectClass() {
- return groupObjectClass;
- }
-
- Rdn getGroupBaseRdn() {
- return groupBaseRdn;
- }
-
- Rdn getSystemRoleBaseRdn() {
- return systemRoleBaseRdn;
- }
-
- LdapName getBaseDn() {
- return (LdapName) baseDn.clone();
- }
-
- public Dictionary<String, Object> getProperties() {
- return properties;
- }
-
- public Dictionary<String, Object> cloneProperties() {
- return new Hashtable<>(properties);
- }
-
public void setExternalRoles(UserAdmin externalRoles) {
this.externalRoles = externalRoles;
}
// this.transactionManager = transactionManager;
// }
- public String getForcedPassword() {
- return forcedPassword;
- }
-
- public void setTransactionControl(WorkControl transactionControl) {
- this.transactionControl = transactionControl;
- }
-
- public XAResource getXaResource() {
- return xaResource;
- }
-
- public boolean isScoped() {
- return scoped;
- }
-
- @Override
- public int hashCode() {
- return baseDn.hashCode();
- }
-
- @Override
- public String toString() {
- return "User Directory " + baseDn.toString();
- }
-
/*
* STATIC UTILITIES
*/
if (user instanceof DirectoryUser) {
userAdminToUse = userReferentialOfThisUser;
} else if (user instanceof AuthenticatingUser) {
- userAdminToUse = userReferentialOfThisUser.scope(user);
+ userAdminToUse = (AbstractUserDirectory) userReferentialOfThisUser.scope(user);
} else {
throw new IllegalArgumentException("Unsupported user type " + user.getClass());
}
import javax.naming.ldap.LdapName;
+import org.argeo.util.directory.DirectoryDigestUtils;
import org.osgi.service.useradmin.User;
/**
this.name = name;
credentials = new Hashtable<>();
credentials.put(SHARED_STATE_NAME, name);
- byte[] pwd = DigestUtils.charsToBytes(password);
+ byte[] pwd = DirectoryDigestUtils.charsToBytes(password);
credentials.put(SHARED_STATE_PWD, pwd);
}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.util.Arrays;
-
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-/** Utilities around digests, mostly those related to passwords. */
-class DigestUtils {
- final static String PASSWORD_SCHEME_SHA = "SHA";
- final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
-
- static byte[] sha1(byte[] bytes) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA1");
- digest.update(bytes);
- byte[] checksum = digest.digest();
- return checksum;
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Cannot SHA1 digest", e);
- }
- }
-
- static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
- Integer keyLength) {
- try {
- if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
- MessageDigest digest = MessageDigest.getInstance("SHA1");
- byte[] bytes = charsToBytes(password);
- digest.update(bytes);
- return digest.digest();
- } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
- KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
-
- SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
- final int ITERATION_LENGTH = 4;
- byte[] key = f.generateSecret(spec).getEncoded();
- byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
- byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
- if (iterationsArr.length < ITERATION_LENGTH) {
- Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
- System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
- iterationsArr.length);
- } else {
- System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
- }
- System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
- System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
- return result;
- } else {
- throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
- }
- } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new IllegalStateException("Cannot digest", e);
- }
- }
-
- static char[] bytesToChars(Object obj) {
- if (obj instanceof char[])
- return (char[]) obj;
- if (!(obj instanceof byte[]))
- throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
- ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
- CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
- char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
- // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
- // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
- // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
- return res;
- }
-
- static byte[] charsToBytes(char[] chars) {
- CharBuffer charBuffer = CharBuffer.wrap(chars);
- ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
- byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
- // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
- // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
- return bytes;
- }
-
- static String sha1str(String str) {
- byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
- return encodeHexString(hash);
- }
-
- final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
- /**
- * From
- * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
- * -a-hex-string-in-java
- */
- public static String encodeHexString(byte[] bytes) {
- char[] hexChars = new char[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = hexArray[v >>> 4];
- hexChars[j * 2 + 1] = hexArray[v & 0x0F];
- }
- return new String(hexChars);
- }
-
- private DigestUtils() {
- }
-}
package org.argeo.osgi.useradmin;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
+import org.argeo.util.directory.ldap.LdapEntry;
import org.osgi.service.useradmin.User;
/** A user in a user directory. */
-interface DirectoryUser extends User {
- LdapName getDn();
-
- Attributes getAttributes();
-
- void publishAttributes(Attributes modifiedAttributes);
+interface DirectoryUser extends User, LdapEntry {
}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.transaction.AbstractWorkingCopy;
-
-/** Working copy for a user directory being edited. */
-class DirectoryUserWorkingCopy extends AbstractWorkingCopy<DirectoryUser, Attributes, LdapName> {
- @Override
- protected LdapName getId(DirectoryUser user) {
- return user.getDn();
- }
-
- @Override
- protected Attributes cloneAttributes(DirectoryUser user) {
- return (Attributes) user.getAttributes().clone();
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.List;
-
-import org.osgi.service.useradmin.Role;
-
-/** A unit within the high-level organisational structure of a directory. */
-public interface HierarchyUnit {
- String getHierarchyUnitName();
-
- HierarchyUnit getParent();
-
- Iterable<HierarchyUnit> getDirectHierachyUnits(boolean functionalOnly);
-
- boolean isFunctional();
-
- String getContext();
-
- List<? extends Role> getHierarchyUnitRoles(String filter, boolean deep);
-
- UserDirectory getDirectory();
-
-// Map<String,Object> getHierarchyProperties();
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.dns.DnsBrowser;
-
-/** Free IPA specific conventions. */
-public class IpaUtils {
- public final static String IPA_USER_BASE = "cn=users,cn=accounts";
- public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts";
- public final static String IPA_ROLE_BASE = "cn=roles,cn=accounts";
- public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts";
-
- private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
-
- public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
- + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
-
- @Deprecated
- static String domainToUserDirectoryConfigPath(String realm) {
- return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm;
- }
-
- public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
- properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm));
- properties.put(UserAdminConf.realm.name(), realm);
- properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE);
- properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE);
- properties.put(UserAdminConf.systemRoleBase.name(), IPA_ROLE_BASE);
- properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString());
- }
-
- public static String domainToBaseDn(String domain) {
- String[] dcs = domain.split("\\.");
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < dcs.length; i++) {
- if (i != 0)
- sb.append(',');
- String dc = dcs[i];
- sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
- }
- return sb.toString();
- }
-
- public static LdapName kerberosToDn(String kerberosName) {
- String[] kname = kerberosName.split("@");
- String username = kname[0];
- String baseDn = domainToBaseDn(kname[1]);
- String dn;
- if (!username.contains("/"))
- dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
- else
- dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
- try {
- return new LdapName(dn);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
- }
- }
-
- private IpaUtils() {
-
- }
-
- public static String kerberosDomainFromDns() {
- String kerberosDomain;
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- InetAddress localhost = InetAddress.getLocalHost();
- String hostname = localhost.getHostName();
- String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
- kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
- return kerberosDomain;
- } catch (NamingException | IOException e) {
- throw new IllegalStateException("Cannot determine Kerberos domain from DNS", e);
- }
-
- }
-
- public static Dictionary<String, Object> convertIpaUri(URI uri) {
- String path = uri.getPath();
- String kerberosRealm;
- if (path == null || path.length() <= 1) {
- kerberosRealm = kerberosDomainFromDns();
- } else {
- kerberosRealm = path.substring(1);
- }
-
- if (kerberosRealm == null)
- throw new IllegalStateException("No Kerberos domain available for " + uri);
- // TODO intergrate CA certificate in truststore
- // String schemeToUse = SCHEME_LDAPS;
- String schemeToUse = UserAdminConf.SCHEME_LDAP;
- List<String> ldapHosts;
- String ldapHostsStr = uri.getHost();
- if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
- schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false);
- if (ldapHosts == null || ldapHosts.size() == 0) {
- throw new IllegalStateException("Cannot configure LDAP for IPA " + uri);
- } else {
- ldapHostsStr = ldapHosts.get(0);
- }
- } catch (NamingException | IOException e) {
- throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
- }
- } else {
- ldapHosts = new ArrayList<>();
- ldapHosts.add(ldapHostsStr);
- }
-
- StringBuilder uriStr = new StringBuilder();
- try {
- for (String host : ldapHosts) {
- URI convertedUri = new URI(schemeToUse + "://" + host + "/");
- uriStr.append(convertedUri).append(' ');
- }
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
- }
-
- Hashtable<String, Object> res = new Hashtable<>();
- res.put(UserAdminConf.uri.name(), uriStr.toString());
- addIpaConfig(kerberosRealm, res);
- return res;
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.CommunicationException;
-import javax.naming.Context;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.InitialLdapContext;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdapAttrs;
-
-/** A synchronized wrapper for a single {@link InitialLdapContext}. */
-// TODO implement multiple contexts and connection pooling.
-class LdapConnection {
- private InitialLdapContext initialLdapContext = null;
-
- LdapConnection(String url, Dictionary<String, ?> properties) {
- try {
- Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
- connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
- connEnv.put(Context.PROVIDER_URL, url);
- connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
- // use pooling in order to avoid connection timeout
-// connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
-// connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
-
- initialLdapContext = new InitialLdapContext(connEnv, null);
- // StartTlsResponse tls = (StartTlsResponse) ctx
- // .extendedOperation(new StartTlsRequest());
- // tls.negotiate();
- Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
- if (securityAuthentication != null)
- initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
- else
- initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
- Object principal = properties.get(Context.SECURITY_PRINCIPAL);
- if (principal != null) {
- initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
- Object creds = properties.get(Context.SECURITY_CREDENTIALS);
- if (creds != null) {
- initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
- }
- }
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot connect to LDAP", e);
- }
-
- }
-
- public void init() {
-
- }
-
- public void destroy() {
- try {
- // tls.close();
- initialLdapContext.close();
- initialLdapContext = null;
- } catch (NamingException e) {
- e.printStackTrace();
- }
- }
-
- protected InitialLdapContext getLdapContext() {
- return initialLdapContext;
- }
-
- protected void reconnect() throws NamingException {
- initialLdapContext.reconnect(initialLdapContext.getConnectControls());
- }
-
- public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
- SearchControls searchControls) throws NamingException {
- NamingEnumeration<SearchResult> results;
- try {
- results = getLdapContext().search(searchBase, searchFilter, searchControls);
- } catch (CommunicationException e) {
- reconnect();
- results = getLdapContext().search(searchBase, searchFilter, searchControls);
- }
- return results;
- }
-
- public synchronized Attributes getAttributes(LdapName name) throws NamingException {
- try {
- return getLdapContext().getAttributes(name);
- } catch (CommunicationException e) {
- reconnect();
- return getLdapContext().getAttributes(name);
- }
- }
-
- synchronized void prepareChanges(DirectoryUserWorkingCopy wc) throws NamingException {
- // make sure connection will work
- reconnect();
-
- // delete
- for (LdapName dn : wc.getDeletedData().keySet()) {
- if (!entryExists(dn))
- throw new IllegalStateException("User to delete no found " + dn);
- }
- // add
- for (LdapName dn : wc.getNewData().keySet()) {
- if (entryExists(dn))
- throw new IllegalStateException("User to create found " + dn);
- }
- // modify
- for (LdapName dn : wc.getModifiedData().keySet()) {
- if (!wc.getNewData().containsKey(dn) && !entryExists(dn))
- throw new IllegalStateException("User to modify not found " + dn);
- }
-
- }
-
- protected boolean entryExists(LdapName dn) throws NamingException {
- try {
- return getAttributes(dn).size() != 0;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
-
- synchronized void commitChanges(DirectoryUserWorkingCopy wc) throws NamingException {
- // delete
- for (LdapName dn : wc.getDeletedData().keySet()) {
- getLdapContext().destroySubcontext(dn);
- }
- // add
- for (LdapName dn : wc.getNewData().keySet()) {
- DirectoryUser user = wc.getNewData().get(dn);
- getLdapContext().createSubcontext(dn, user.getAttributes());
- }
- // modify
- for (LdapName dn : wc.getModifiedData().keySet()) {
- Attributes modifiedAttrs = wc.getModifiedData().get(dn);
- getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
- }
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-/** Utilities to simplify using {@link LdapName}. */
-class LdapNameUtils {
-
- static LdapName relativeName(LdapName prefix, LdapName dn) {
- try {
- if (!dn.startsWith(prefix))
- throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
- LdapName res = (LdapName) dn.clone();
- for (int i = 0; i < prefix.size(); i++) {
- res.remove(0);
- }
- return res;
- } catch (InvalidNameException e) {
- throw new IllegalStateException("Cannot find realtive name", e);
- }
- }
-
- static LdapName getParent(LdapName dn) {
- try {
- LdapName parent = (LdapName) dn.clone();
- parent.remove(parent.size() - 1);
- return parent;
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot get parent of " + dn, e);
- }
- }
-
- static Rdn getParentRdn(LdapName dn) {
- if (dn.size() < 2)
- throw new IllegalArgumentException(dn + " has no parent");
- Rdn parentRdn = dn.getRdn(dn.size() - 2);
- return parentRdn;
- }
-
- static LdapName toLdapName(String distinguishedName) {
- try {
- return new LdapName(distinguishedName);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
- }
- }
-
- static Rdn getLastRdn(LdapName dn) {
- return dn.getRdn(dn.size() - 1);
- }
-
- static String getLastRdnAsString(LdapName dn) {
- return getLastRdn(dn).toString();
- }
-
- static String getLastRdnValue(LdapName dn) {
- return getLastRdn(dn).getValue().toString();
- }
-
- /** singleton */
- private LdapNameUtils() {
-
- }
-}
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
+import org.argeo.util.directory.DirectoryDigestUtils;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.LdapConnection;
+import org.argeo.util.directory.ldap.LdapEntry;
+import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
+import org.argeo.util.directory.ldap.LdapHierarchyUnit;
import org.argeo.util.naming.LdapObjs;
import org.osgi.framework.Filter;
import org.osgi.service.useradmin.Role;
Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
byte[] pwd = (byte[]) pwdCred;
if (pwd != null) {
- char[] password = DigestUtils.bytesToChars(pwd);
+ char[] password = DirectoryDigestUtils.bytesToChars(pwd);
properties.put(Context.SECURITY_CREDENTIALS, new String(password));
} else {
properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
// }
@Override
- protected Boolean daoHasRole(LdapName dn) {
+ protected Boolean daoHasEntry(LdapName dn) {
try {
- return daoGetRole(dn) != null;
+ return daoGetEntry(dn) != null;
} catch (NameNotFoundException e) {
return false;
}
}
@Override
- protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
+ protected DirectoryUser daoGetEntry(LdapName name) throws NameNotFoundException {
try {
Attributes attrs = ldapConnection.getAttributes(name);
if (attrs.size() == 0)
}
@Override
- protected List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep) {
- ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
+ protected List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep) {
+ ArrayList<LdapEntry> res = new ArrayList<>();
try {
String searchFilter = f != null ? f.toString()
: "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
}
@Override
- public void prepare(DirectoryUserWorkingCopy wc) {
+ public void prepare(LdapEntryWorkingCopy wc) {
try {
ldapConnection.prepareChanges(wc);
} catch (NamingException e) {
}
@Override
- public void commit(DirectoryUserWorkingCopy wc) {
+ public void commit(LdapEntryWorkingCopy wc) {
try {
ldapConnection.commitChanges(wc);
} catch (NamingException e) {
}
@Override
- public void rollback(DirectoryUserWorkingCopy wc) {
+ public void rollback(LdapEntryWorkingCopy wc) {
// prepare not impacting
}
*/
@Override
- protected Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+ public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
List<HierarchyUnit> res = new ArrayList<>();
try {
String searchFilter = "(|(" + objectClass + "=" + LdapObjs.organizationalUnit.name() + ")(" + objectClass
SearchResult searchResult = (SearchResult) results.nextElement();
LdapName dn = toDn(searchBase, searchResult);
Attributes attrs = searchResult.getAttributes();
- LdifHierarchyUnit hierarchyUnit = new LdifHierarchyUnit(this, dn, attrs);
+ LdapHierarchyUnit hierarchyUnit = new LdapHierarchyUnit(this, dn, attrs);
if (functionalOnly) {
if (hierarchyUnit.isFunctional())
res.add(hierarchyUnit);
}
@Override
- protected HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+ public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
try {
Attributes attrs = ldapConnection.getAttributes(dn);
- return new LdifHierarchyUnit(this, dn, attrs);
+ return new LdapHierarchyUnit(this, dn, attrs);
} catch (NamingException e) {
throw new IllegalStateException("Cannot get hierarchy unit " + dn, e);
}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.List;
-import java.util.Objects;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-
-/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
-class LdifHierarchyUnit implements HierarchyUnit {
- private final AbstractUserDirectory directory;
-
- private final LdapName dn;
- private final boolean functional;
- private final Attributes attributes;
-
-// HierarchyUnit parent;
-// List<HierarchyUnit> children = new ArrayList<>();
-
- LdifHierarchyUnit(AbstractUserDirectory directory, LdapName dn, Attributes attributes) {
- Objects.requireNonNull(directory);
- Objects.requireNonNull(dn);
-
- this.directory = directory;
- this.dn = dn;
- this.attributes = attributes;
-
- Rdn rdn = LdapNameUtils.getLastRdn(dn);
- functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn)
- || directory.getSystemRoleBaseRdn().equals(rdn));
- }
-
- @Override
- public HierarchyUnit getParent() {
- return directory.doGetHierarchyUnit(LdapNameUtils.getParent(dn));
- }
-
- @Override
- public Iterable<HierarchyUnit> getDirectHierachyUnits(boolean functionalOnly) {
-// List<HierarchyUnit> res = new ArrayList<>();
-// if (functionalOnly)
-// for (HierarchyUnit hu : children) {
-// if (hu.isFunctional())
-// res.add(hu);
-// }
-// else
-// res.addAll(children);
-// return Collections.unmodifiableList(res);
- return directory.doGetDirectHierarchyUnits(dn, functionalOnly);
- }
-
- @Override
- public boolean isFunctional() {
- return functional;
- }
-
- @Override
- public String getHierarchyUnitName() {
- String name = LdapNameUtils.getLastRdnValue(dn);
- // TODO check ou, o, etc.
- return name;
- }
-
- public Attributes getAttributes() {
- return attributes;
- }
-
- @Override
- public String getContext() {
- return dn.toString();
- }
-
- @Override
- public List<? extends Role> getHierarchyUnitRoles(String filter, boolean deep) {
- try {
- return directory.getRoles(dn, filter, deep);
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
- }
- }
-
- @Override
- public UserDirectory getDirectory() {
- return directory;
- }
-
- @Override
- public int hashCode() {
- return dn.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LdifHierarchyUnit))
- return false;
- return ((LdifHierarchyUnit) obj).dn.equals(dn);
- }
-
- @Override
- public String toString() {
- return "Hierarchy Unit " + dn.toString();
- }
-
-}
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.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.naming.ldap.AuthPassword;
/** Directory user implementation */
-abstract class LdifUser implements DirectoryUser {
- private final AbstractUserDirectory userAdmin;
-
- private final LdapName dn;
-
- private final boolean frozen;
- private Attributes publishedAttributes;
-
+abstract class LdifUser extends AbstractLdapEntry implements DirectoryUser {
private final AttributeDictionary properties;
private final AttributeDictionary credentials;
LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
- this(userAdmin, dn, attributes, false);
- }
-
- private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) {
- this.userAdmin = userAdmin;
- this.dn = dn;
- this.publishedAttributes = attributes;
+ super(userAdmin, dn, attributes);
properties = new AttributeDictionary(false);
credentials = new AttributeDictionary(true);
- this.frozen = frozen;
}
@Override
public String getName() {
- return dn.toString();
+ return getDn().toString();
}
@Override
// TODO check other sources (like PKCS12)
// String pwd = new String((char[]) value);
// authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
- char[] password = DigestUtils.bytesToChars(value);
+ char[] password = DirectoryDigestUtils.bytesToChars(value);
- if (userAdmin.getForcedPassword() != null && userAdmin.getForcedPassword().equals(new String(password)))
+ if (getDirectory().getForcedPassword() != null
+ && getDirectory().getForcedPassword().equals(new String(password)))
return true;
AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
// Regular password
// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
- if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password)))
+ if (hasCredential(LdapAttrs.userPassword.name(), DirectoryDigestUtils.charsToBytes(password)))
return true;
return false;
}
passwordScheme = storedBase64.substring(1, index);
String storedValueBase64 = storedBase64.substring(index + 1);
byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
- char[] passwordValue = DigestUtils.bytesToChars((byte[]) value);
+ char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value);
byte[] valueBytes;
- if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
- valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null);
- } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+ 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[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
storedValueBytes.length);
int keyLengthBits = keyArr.length * 8;
- valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
+ valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
iterations.intValue(), keyLengthBits);
} else {
throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
/** Hash the password */
byte[] sha1hash(char[] password) {
- byte[] hashedPassword = ("{SHA}"
- + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
+ byte[] hashedPassword = ("{SHA}" + Base64.getEncoder()
+ .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password))))
.getBytes(StandardCharsets.UTF_8);
return hashedPassword;
}
// return hashedPassword;
// }
- @Override
- public LdapName getDn() {
- return dn;
- }
-
- @Override
- public synchronized Attributes getAttributes() {
- return isEditing() ? getModifiedAttributes() : publishedAttributes;
- }
-
- /** Should only be called from working copy thread. */
- private synchronized Attributes getModifiedAttributes() {
- assert getWc() != null;
- return getWc().getModifiedData().get(getDn());
- }
-
- protected synchronized boolean isEditing() {
- return getWc() != null && getModifiedAttributes() != null;
- }
-
- private synchronized DirectoryUserWorkingCopy getWc() {
- return userAdmin.getWorkingCopy();
- }
-
- protected synchronized void startEditing() {
- if (frozen)
- throw new IllegalStateException("Cannot edit frozen view");
- if (getUserAdmin().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 DirectoryUser getPublished() {
-// return new LdifUser(userAdmin, dn, publishedAttributes, true);
-// }
-
- @Override
- public int hashCode() {
- return dn.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj instanceof LdifUser) {
- LdifUser that = (LdifUser) obj;
- return this.dn.equals(that.dn);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return dn.toString();
- }
-
protected AbstractUserDirectory getUserAdmin() {
- return userAdmin;
+ return (AbstractUserDirectory) getDirectory();
}
private class AttributeDictionary extends Dictionary<String, Object> {
private final Boolean includeFilter;
public AttributeDictionary(Boolean credentials) {
- this.attrFilter = userAdmin.getCredentialAttributeIds();
+ this.attrFilter = getDirectory().getCredentialAttributeIds();
this.includeFilter = credentials;
try {
NamingEnumeration<String> ids = getAttributes().getIDs();
continue attrs;
if (first == null)
first = v;
- if (v.equalsIgnoreCase(userAdmin.getUserObjectClass()))
- return userAdmin.getUserObjectClass();
- else if (v.equalsIgnoreCase(userAdmin.getGroupObjectClass()))
- return userAdmin.getGroupObjectClass();
+ if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
+ return getDirectory().getUserObjectClass();
+ else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
+ return getDirectory().getGroupObjectClass();
}
if (first != null)
return first;
public Object put(String key, Object value) {
if (key == null) {
// TODO persist to other sources (like PKCS12)
- char[] password = DigestUtils.bytesToChars(value);
+ char[] password = DirectoryDigestUtils.bytesToChars(value);
byte[] hashedPassword = sha1hash(password);
return put(LdapAttrs.userPassword.name(), hashedPassword);
}
return put(LdapAttrs.authPassword.name(), value);
}
- userAdmin.checkEdit();
+ getDirectory().checkEdit();
if (!isEditing())
startEditing();
@Override
public Object remove(Object key) {
- userAdmin.checkEdit();
+ getDirectory().checkEdit();
if (!isEditing())
startEditing();
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
+import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.directory.DirectoryDigestUtils;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.LdapEntry;
+import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
+import org.argeo.util.directory.ldap.LdapHierarchyUnit;
+import org.argeo.util.directory.ldap.LdifParser;
+import org.argeo.util.directory.ldap.LdifWriter;
import org.argeo.util.naming.LdapObjs;
-import org.argeo.util.naming.ldap.LdifParser;
-import org.argeo.util.naming.ldap.LdifWriter;
import org.osgi.framework.Filter;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
/** A user admin based on a LDIF files. */
public class LdifUserAdmin extends AbstractUserDirectory {
- private NavigableMap<LdapName, DirectoryUser> users = new TreeMap<>();
- private NavigableMap<LdapName, DirectoryGroup> groups = new TreeMap<>();
+ private NavigableMap<LdapName, LdapEntry> users = new TreeMap<>();
+ private NavigableMap<LdapName, LdapEntry> groups = new TreeMap<>();
- private NavigableMap<LdapName, LdifHierarchyUnit> hierarchy = new TreeMap<>();
+ private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
// private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
public LdifUserAdmin(String uri, String baseDn) {
Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
byte[] pwd = (byte[]) pwdCred;
if (pwd != null) {
- char[] password = DigestUtils.bytesToChars(pwd);
+ char[] password = DirectoryDigestUtils.bytesToChars(pwd);
User directoryUser = (User) getRole(username);
if (!directoryUser.hasCredential(null, password))
throw new IllegalStateException("Invalid credentials");
throw new IllegalStateException("Password is required");
}
Dictionary<String, Object> properties = cloneProperties();
- properties.put(UserAdminConf.readOnly.name(), "true");
+ properties.put(DirectoryConf.readOnly.name(), "true");
LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
Hashtable<String, Object> res = new Hashtable<String, Object>();
- res.put(UserAdminConf.uri.name(), uri);
- res.put(UserAdminConf.baseDn.name(), baseDn);
+ res.put(DirectoryConf.uri.name(), uri);
+ res.put(DirectoryConf.baseDn.name(), baseDn);
return res;
}
// if (getUserBase().equalsIgnoreCase(name) || getGroupBase().equalsIgnoreCase(name))
// break objectClasses; // skip
// TODO skip if it does not contain groups or users
- hierarchy.put(key, new LdifHierarchyUnit(this, key, attributes));
+ hierarchy.put(key, new LdapHierarchyUnit(this, key, attributes));
break objectClasses;
}
}
*/
@Override
- protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
+ protected DirectoryUser daoGetEntry(LdapName key) throws NameNotFoundException {
if (groups.containsKey(key))
- return groups.get(key);
+ return (DirectoryUser) groups.get(key);
if (users.containsKey(key))
- return users.get(key);
+ return (DirectoryUser) users.get(key);
throw new NameNotFoundException(key + " not persisted");
}
@Override
- protected Boolean daoHasRole(LdapName dn) {
+ protected Boolean daoHasEntry(LdapName dn) {
return users.containsKey(dn) || groups.containsKey(dn);
}
@Override
- protected List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep) {
+ protected List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep) {
Objects.requireNonNull(searchBase);
- ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
+ ArrayList<LdapEntry> res = new ArrayList<>();
if (f == null && deep && getBaseDn().equals(searchBase)) {
res.addAll(users.values());
res.addAll(groups.values());
return res;
}
- private void filterRoles(SortedMap<LdapName, ? extends DirectoryUser> map, LdapName searchBase, Filter f,
- boolean deep, List<DirectoryUser> res) {
+ private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, Filter f, boolean deep,
+ List<LdapEntry> res) {
// TODO reduce map with search base ?
- roles: for (DirectoryUser user : map.values()) {
+ roles: for (LdapEntry user : map.values()) {
LdapName dn = user.getDn();
if (dn.startsWith(searchBase)) {
if (!deep && dn.size() != (searchBase.size() + 1))
continue roles;
if (f == null)
res.add(user);
- else if (f.match(user.getProperties()))
- res.add(user);
+ else {
+ if (f.match(((DirectoryUser) user).getProperties()))
+ res.add(user);
+ }
}
}
protected List<LdapName> getDirectGroups(LdapName dn) {
List<LdapName> directGroups = new ArrayList<LdapName>();
for (LdapName name : groups.keySet()) {
- DirectoryGroup group = groups.get(name);
+ DirectoryGroup group;
+ try {
+ group = (DirectoryGroup) daoGetEntry(name);
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Group " + dn + " not found", e);
+ }
if (group.getMemberNames().contains(dn))
directGroups.add(group.getDn());
}
}
@Override
- public void prepare(DirectoryUserWorkingCopy wc) {
+ public void prepare(LdapEntryWorkingCopy wc) {
// delete
for (LdapName dn : wc.getDeletedData().keySet()) {
if (users.containsKey(dn))
}
// add
for (LdapName dn : wc.getNewData().keySet()) {
- DirectoryUser user = wc.getNewData().get(dn);
+ DirectoryUser user = (DirectoryUser) wc.getNewData().get(dn);
if (users.containsKey(dn) || groups.containsKey(dn))
throw new IllegalStateException("User to create found " + dn);
else if (Role.USER == user.getType())
for (LdapName dn : wc.getModifiedData().keySet()) {
Attributes modifiedAttrs = wc.getModifiedData().get(dn);
DirectoryUser user;
- if (users.containsKey(dn))
- user = users.get(dn);
- else if (groups.containsKey(dn))
- user = groups.get(dn);
- else
+ try {
+ user = daoGetEntry(dn);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("User to modify no found " + dn, e);
+ }
+ if (user == null)
throw new IllegalStateException("User to modify no found " + dn);
user.publishAttributes(modifiedAttrs);
}
}
@Override
- public void commit(DirectoryUserWorkingCopy wc) {
+ public void commit(LdapEntryWorkingCopy wc) {
save();
}
@Override
- public void rollback(DirectoryUserWorkingCopy wc) {
+ public void rollback(LdapEntryWorkingCopy wc) {
init();
}
// return rootHierarchyUnits.get(i);
// }
@Override
- protected HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+ public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
return hierarchy.get(dn);
}
@Override
- protected Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+ public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
List<HierarchyUnit> res = new ArrayList<>();
for (LdapName n : hierarchy.keySet()) {
if (n.size() == searchBase.size() + 1) {
import javax.naming.directory.BasicAttributes;
import javax.naming.ldap.LdapName;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.LdapEntry;
+import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
import org.argeo.util.naming.LdapAttrs;
import org.osgi.framework.Filter;
import org.osgi.service.useradmin.User;
}
@Override
- protected Boolean daoHasRole(LdapName dn) {
+ protected Boolean daoHasEntry(LdapName dn) {
return osUserDn.equals(dn);
}
@Override
- protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
+ protected DirectoryUser daoGetEntry(LdapName key) throws NameNotFoundException {
if (osUserDn.equals(key))
return osUser;
else
}
@Override
- protected List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep) {
- List<DirectoryUser> res = new ArrayList<>();
+ protected List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep) {
+ List<LdapEntry> res = new ArrayList<>();
if (f == null || f.match(osUser.getProperties()))
res.add(osUser);
return res;
}
@Override
- protected HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+ public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
return null;
}
@Override
- protected Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+ public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
return new ArrayList<>();
}
- public void prepare(DirectoryUserWorkingCopy wc) {
+ public void prepare(LdapEntryWorkingCopy wc) {
}
- public void commit(DirectoryUserWorkingCopy wc) {
+ public void commit(LdapEntryWorkingCopy wc) {
}
- public void rollback(DirectoryUserWorkingCopy wc) {
+ public void rollback(LdapEntryWorkingCopy wc) {
}
-
}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.Context;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.NamingUtils;
-
-/** Properties used to configure user admins. */
-public enum UserAdminConf {
- /** Base DN (cannot be configured externally) */
- baseDn("dc=example,dc=com"),
-
- /** URI of the underlying resource (cannot be configured externally) */
- uri("ldap://localhost:10389"),
-
- /** User objectClass */
- userObjectClass("inetOrgPerson"),
-
- /** Relative base DN for users */
- userBase("ou=People"),
-
- /** Groups objectClass */
- groupObjectClass("groupOfNames"),
-
- /** Relative base DN for users */
- groupBase("ou=Groups"),
-
- /** Relative base DN for users */
- systemRoleBase("ou=Roles"),
-
- /** Read-only source */
- readOnly(null),
-
- /** Disabled source */
- disabled(null),
-
- /** Authentication realm */
- realm(null),
-
- /** Override all passwords with this value (typically for testing purposes) */
- forcedPassword(null);
-
- public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
-
- public final static String SCHEME_LDAP = "ldap";
- public final static String SCHEME_LDAPS = "ldaps";
- public final static String SCHEME_FILE = "file";
- public final static String SCHEME_OS = "os";
- public final static String SCHEME_IPA = "ipa";
-
- /** The default value. */
- private Object def;
-
- UserAdminConf(Object def) {
- this.def = def;
- }
-
- public Object getDefault() {
- return def;
- }
-
- /**
- * For use as Java property.
- *
- * @deprecated use {@link #name()} instead
- */
- @Deprecated
- public String property() {
- return name();
- }
-
- public String getValue(Dictionary<String, ?> properties) {
- Object res = getRawValue(properties);
- if (res == null)
- return null;
- return res.toString();
- }
-
- @SuppressWarnings("unchecked")
- public <T> T getRawValue(Dictionary<String, ?> properties) {
- Object res = properties.get(name());
- if (res == null)
- res = getDefault();
- return (T) res;
- }
-
- /** @deprecated use {@link #valueOf(String)} instead */
- @Deprecated
- public static UserAdminConf local(String property) {
- return UserAdminConf.valueOf(property);
- }
-
- /** Hides host and credentials. */
- public static URI propertiesAsUri(Dictionary<String, ?> properties) {
- StringBuilder query = new StringBuilder();
-
- boolean first = true;
-// for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
-// String key = keys.nextElement();
-// // TODO clarify which keys are relevant (list only the enum?)
-// if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
-// && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
-// && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
-// && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
-// if (first)
-// first = false;
-// else
-// query.append('&');
-// query.append(valueOf(key).name());
-// query.append('=').append(properties.get(key).toString());
-// }
-// }
-
- keys: for (UserAdminConf key : UserAdminConf.values()) {
- if (key.equals(baseDn) || key.equals(uri))
- continue keys;
- Object value = properties.get(key.name());
- if (value == null)
- continue keys;
- if (first)
- first = false;
- else
- query.append('&');
- query.append(key.name());
- query.append('=').append(value.toString());
-
- }
-
- Object bDnObj = properties.get(baseDn.name());
- String bDn = bDnObj != null ? bDnObj.toString() : null;
- try {
- return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
- null);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot create URI from properties", e);
- }
- }
-
- public static Dictionary<String, Object> uriAsProperties(String uriStr) {
- try {
- Hashtable<String, Object> res = new Hashtable<String, Object>();
- URI u = new URI(uriStr);
- String scheme = u.getScheme();
- if (scheme != null && scheme.equals(SCHEME_IPA)) {
- return IpaUtils.convertIpaUri(u);
-// scheme = u.getScheme();
- }
- String path = u.getPath();
- // base DN
- String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
- if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
- bDn = getBaseDnFromHostname();
- }
-
- if (bDn.endsWith(".ldif"))
- bDn = bDn.substring(0, bDn.length() - ".ldif".length());
-
- // Normalize base DN as LDAP name
- bDn = new LdapName(bDn).toString();
-
- String principal = null;
- String credentials = null;
- if (scheme != null)
- if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
- // TODO additional checks
- if (u.getUserInfo() != null) {
- String[] userInfo = u.getUserInfo().split(":");
- principal = userInfo.length > 0 ? userInfo[0] : null;
- credentials = userInfo.length > 1 ? userInfo[1] : null;
- }
- } else if (scheme.equals(SCHEME_FILE)) {
- } else if (scheme.equals(SCHEME_IPA)) {
- } else if (scheme.equals(SCHEME_OS)) {
- } else
- throw new IllegalArgumentException("Unsupported scheme " + scheme);
- Map<String, List<String>> query = NamingUtils.queryToMap(u);
- for (String key : query.keySet()) {
- UserAdminConf ldapProp = UserAdminConf.valueOf(key);
- List<String> values = query.get(key);
- if (values.size() == 1) {
- res.put(ldapProp.name(), values.get(0));
- } else {
- throw new IllegalArgumentException("Only single values are supported");
- }
- }
- res.put(baseDn.name(), bDn);
- if (SCHEME_OS.equals(scheme))
- res.put(readOnly.name(), "true");
- if (principal != null)
- res.put(Context.SECURITY_PRINCIPAL, principal);
- if (credentials != null)
- res.put(Context.SECURITY_CREDENTIALS, credentials);
- if (scheme != null) {// relative URIs are dealt with externally
- if (SCHEME_OS.equals(scheme)) {
- res.put(uri.name(), SCHEME_OS + ":///");
- } else {
- URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
- scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
- res.put(uri.name(), bareUri.toString());
- }
- }
- return res;
- } catch (URISyntaxException | InvalidNameException e) {
- throw new IllegalArgumentException("Cannot convert " + uri + " to properties", e);
- }
- }
-
- private static String getBaseDnFromHostname() {
- String hostname;
- try {
- hostname = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- hostname = "localhost.localdomain";
- }
- int dotIdx = hostname.indexOf('.');
- if (dotIdx >= 0) {
- String domain = hostname.substring(dotIdx + 1, hostname.length());
- String bDn = ("." + domain).replaceAll("\\.", ",dc=");
- bDn = bDn.substring(1, bDn.length());
- return bDn;
- } else {
- return "dc=" + hostname;
- }
- }
-
- /**
- * Hash the base DN in order to have a deterministic string to be used as a cn
- * for the underlying user directory.
- */
- public static String baseDnHash(Dictionary<String, Object> properties) {
- String bDn = (String) properties.get(baseDn.name());
- if (bDn == null)
- throw new IllegalStateException("No baseDn in " + properties);
- return DigestUtils.sha1str(bDn);
- }
-}
package org.argeo.osgi.useradmin;
-import java.util.Optional;
-
-import org.argeo.util.transaction.WorkControl;
+import org.argeo.util.directory.Directory;
+import org.argeo.util.directory.HierarchyUnit;
import org.osgi.service.useradmin.Role;
/** Information about a user directory. */
-public interface UserDirectory {
- /**
- * The base of the hierarchy defined by this directory. This could typically be
- * an LDAP base DN.
- */
- String getContext();
-
- String getName();
-
-// /** The base DN of all entries in this user directory */
-// LdapName getBaseDn();
-
-// /** The related {@link XAResource} */
-// XAResource getXaResource();
-
- boolean isReadOnly();
-
- boolean isDisabled();
-
- String getUserObjectClass();
-
-// String getUserBase();
-
- String getGroupObjectClass();
-
-// String getGroupBase();
-
- Optional<String> getRealm();
-
- Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly);
-
- HierarchyUnit getHierarchyUnit(String path);
+public interface UserDirectory extends Directory {
HierarchyUnit getHierarchyUnit(Role role);
+ Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep);
+
String getRolePath(Role role);
String getRoleSimpleName(Role role);
Role getRoleByPath(String path);
-
- @Deprecated
- void setTransactionControl(WorkControl transactionControl);
}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** {@link XAResource} for a user directory being edited. */
-class WcXaResource implements XAResource {
- private final AbstractUserDirectory userDirectory;
-
- private Map<Xid, DirectoryUserWorkingCopy> workingCopies = new HashMap<Xid, DirectoryUserWorkingCopy>();
- private Xid editingXid = null;
- private int transactionTimeout = 0;
-
- public WcXaResource(AbstractUserDirectory userDirectory) {
- this.userDirectory = userDirectory;
- }
-
- @Override
- public synchronized void start(Xid xid, int flags) throws XAException {
- if (editingXid != null)
- throw new IllegalStateException("Already editing " + editingXid);
- DirectoryUserWorkingCopy wc = workingCopies.put(xid, new DirectoryUserWorkingCopy());
- if (wc != null)
- throw new IllegalStateException("There is already a working copy for " + xid);
- this.editingXid = xid;
- }
-
- @Override
- public void end(Xid xid, int flags) throws XAException {
- checkXid(xid);
- }
-
- private DirectoryUserWorkingCopy wc(Xid xid) {
- return workingCopies.get(xid);
- }
-
- synchronized DirectoryUserWorkingCopy wc() {
- if (editingXid == null)
- return null;
- DirectoryUserWorkingCopy wc = workingCopies.get(editingXid);
- if (wc == null)
- throw new IllegalStateException("No working copy found for " + editingXid);
- return wc;
- }
-
- private synchronized void cleanUp(Xid xid) {
- wc(xid).cleanUp();
- workingCopies.remove(xid);
- editingXid = null;
- }
-
- @Override
- public int prepare(Xid xid) throws XAException {
- checkXid(xid);
- DirectoryUserWorkingCopy wc = wc(xid);
- if (wc.noModifications())
- return XA_RDONLY;
- try {
- userDirectory.prepare(wc);
- } catch (Exception e) {
- e.printStackTrace();
- throw new XAException(XAException.XAER_RMERR);
- }
- return XA_OK;
- }
-
- @Override
- public void commit(Xid xid, boolean onePhase) throws XAException {
- try {
- checkXid(xid);
- DirectoryUserWorkingCopy wc = wc(xid);
- if (wc.noModifications())
- return;
- if (onePhase)
- userDirectory.prepare(wc);
- userDirectory.commit(wc);
- } catch (Exception e) {
- e.printStackTrace();
- throw new XAException(XAException.XAER_RMERR);
- } finally {
- cleanUp(xid);
- }
- }
-
- @Override
- public void rollback(Xid xid) throws XAException {
- try {
- checkXid(xid);
- userDirectory.rollback(wc(xid));
- } catch (Exception e) {
- e.printStackTrace();
- throw new XAException(XAException.XAER_RMERR);
- } finally {
- cleanUp(xid);
- }
- }
-
- @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 {
- return new Xid[0];
- }
-
- @Override
- public int getTransactionTimeout() throws XAException {
- return transactionTimeout;
- }
-
- @Override
- public boolean setTransactionTimeout(int seconds) throws XAException {
- transactionTimeout = seconds;
- return true;
- }
-
- private void checkXid(Xid xid) throws XAException {
- if (xid == null)
- throw new XAException(XAException.XAER_OUTSIDE);
- if (!xid.equals(xid))
- throw new XAException(XAException.XAER_NOTA);
- }
-
-}
--- /dev/null
+package org.argeo.util.directory;
+
+import java.util.Optional;
+
+import org.argeo.util.transaction.WorkControl;
+
+public interface Directory {
+ /**
+ * The base of the hierarchy defined by this directory. This could typically be
+ * an LDAP base DN.
+ */
+ String getContext();
+
+ String getName();
+
+ boolean isReadOnly();
+
+ boolean isDisabled();
+
+ String getUserObjectClass();
+
+ String getGroupObjectClass();
+
+ Optional<String> getRealm();
+
+ void setTransactionControl(WorkControl transactionControl);
+
+ /*
+ * HIERARCHY
+ */
+
+ Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly);
+
+ HierarchyUnit getHierarchyUnit(String path);
+
+}
--- /dev/null
+package org.argeo.util.directory;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.util.directory.ldap.IpaUtils;
+import org.argeo.util.naming.NamingUtils;
+
+/** Properties used to configure user admins. */
+public enum DirectoryConf {
+ /** Base DN (cannot be configured externally) */
+ baseDn("dc=example,dc=com"),
+
+ /** URI of the underlying resource (cannot be configured externally) */
+ uri("ldap://localhost:10389"),
+
+ /** User objectClass */
+ userObjectClass("inetOrgPerson"),
+
+ /** Relative base DN for users */
+ userBase("ou=People"),
+
+ /** Groups objectClass */
+ groupObjectClass("groupOfNames"),
+
+ /** Relative base DN for users */
+ groupBase("ou=Groups"),
+
+ /** Relative base DN for users */
+ systemRoleBase("ou=Roles"),
+
+ /** Read-only source */
+ readOnly(null),
+
+ /** Disabled source */
+ disabled(null),
+
+ /** Authentication realm */
+ realm(null),
+
+ /** Override all passwords with this value (typically for testing purposes) */
+ forcedPassword(null);
+
+ public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
+
+ public final static String SCHEME_LDAP = "ldap";
+ public final static String SCHEME_LDAPS = "ldaps";
+ public final static String SCHEME_FILE = "file";
+ public final static String SCHEME_OS = "os";
+ public final static String SCHEME_IPA = "ipa";
+
+ private final static String SECURITY_PRINCIPAL = "java.naming.security.principal";
+ private final static String SECURITY_CREDENTIALS = "java.naming.security.credentials";
+
+ /** The default value. */
+ private Object def;
+
+ DirectoryConf(Object def) {
+ this.def = def;
+ }
+
+ public Object getDefault() {
+ return def;
+ }
+
+ /**
+ * For use as Java property.
+ *
+ * @deprecated use {@link #name()} instead
+ */
+ @Deprecated
+ public String property() {
+ return name();
+ }
+
+ public String getValue(Dictionary<String, ?> properties) {
+ Object res = getRawValue(properties);
+ if (res == null)
+ return null;
+ return res.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getRawValue(Dictionary<String, ?> properties) {
+ Object res = properties.get(name());
+ if (res == null)
+ res = getDefault();
+ return (T) res;
+ }
+
+ /** @deprecated use {@link #valueOf(String)} instead */
+ @Deprecated
+ public static DirectoryConf local(String property) {
+ return DirectoryConf.valueOf(property);
+ }
+
+ /** Hides host and credentials. */
+ public static URI propertiesAsUri(Dictionary<String, ?> properties) {
+ StringBuilder query = new StringBuilder();
+
+ boolean first = true;
+// for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
+// String key = keys.nextElement();
+// // TODO clarify which keys are relevant (list only the enum?)
+// if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
+// && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
+// && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
+// && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
+// if (first)
+// first = false;
+// else
+// query.append('&');
+// query.append(valueOf(key).name());
+// query.append('=').append(properties.get(key).toString());
+// }
+// }
+
+ keys: for (DirectoryConf key : DirectoryConf.values()) {
+ if (key.equals(baseDn) || key.equals(uri))
+ continue keys;
+ Object value = properties.get(key.name());
+ if (value == null)
+ continue keys;
+ if (first)
+ first = false;
+ else
+ query.append('&');
+ query.append(key.name());
+ query.append('=').append(value.toString());
+
+ }
+
+ Object bDnObj = properties.get(baseDn.name());
+ String bDn = bDnObj != null ? bDnObj.toString() : null;
+ try {
+ return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
+ null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot create URI from properties", e);
+ }
+ }
+
+ public static Dictionary<String, Object> uriAsProperties(String uriStr) {
+ try {
+ Hashtable<String, Object> res = new Hashtable<String, Object>();
+ URI u = new URI(uriStr);
+ String scheme = u.getScheme();
+ if (scheme != null && scheme.equals(SCHEME_IPA)) {
+ return IpaUtils.convertIpaUri(u);
+// scheme = u.getScheme();
+ }
+ String path = u.getPath();
+ // base DN
+ String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
+ if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
+ bDn = getBaseDnFromHostname();
+ }
+
+ if (bDn.endsWith(".ldif"))
+ bDn = bDn.substring(0, bDn.length() - ".ldif".length());
+
+ // Normalize base DN as LDAP name
+// bDn = new LdapName(bDn).toString();
+
+ String principal = null;
+ String credentials = null;
+ if (scheme != null)
+ if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
+ // TODO additional checks
+ if (u.getUserInfo() != null) {
+ String[] userInfo = u.getUserInfo().split(":");
+ principal = userInfo.length > 0 ? userInfo[0] : null;
+ credentials = userInfo.length > 1 ? userInfo[1] : null;
+ }
+ } else if (scheme.equals(SCHEME_FILE)) {
+ } else if (scheme.equals(SCHEME_IPA)) {
+ } else if (scheme.equals(SCHEME_OS)) {
+ } else
+ throw new IllegalArgumentException("Unsupported scheme " + scheme);
+ Map<String, List<String>> query = NamingUtils.queryToMap(u);
+ for (String key : query.keySet()) {
+ DirectoryConf ldapProp = DirectoryConf.valueOf(key);
+ List<String> values = query.get(key);
+ if (values.size() == 1) {
+ res.put(ldapProp.name(), values.get(0));
+ } else {
+ throw new IllegalArgumentException("Only single values are supported");
+ }
+ }
+ res.put(baseDn.name(), bDn);
+ if (SCHEME_OS.equals(scheme))
+ res.put(readOnly.name(), "true");
+ if (principal != null)
+ res.put(SECURITY_PRINCIPAL, principal);
+ if (credentials != null)
+ res.put(SECURITY_CREDENTIALS, credentials);
+ if (scheme != null) {// relative URIs are dealt with externally
+ if (SCHEME_OS.equals(scheme)) {
+ res.put(uri.name(), SCHEME_OS + ":///");
+ } else {
+ URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
+ scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
+ res.put(uri.name(), bareUri.toString());
+ }
+ }
+ return res;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot convert " + uri + " to properties", e);
+ }
+ }
+
+ private static String getBaseDnFromHostname() {
+ String hostname;
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ hostname = "localhost.localdomain";
+ }
+ int dotIdx = hostname.indexOf('.');
+ if (dotIdx >= 0) {
+ String domain = hostname.substring(dotIdx + 1, hostname.length());
+ String bDn = ("." + domain).replaceAll("\\.", ",dc=");
+ bDn = bDn.substring(1, bDn.length());
+ return bDn;
+ } else {
+ return "dc=" + hostname;
+ }
+ }
+
+ /**
+ * Hash the base DN in order to have a deterministic string to be used as a cn
+ * for the underlying user directory.
+ */
+ public static String baseDnHash(Dictionary<String, Object> properties) {
+ String bDn = (String) properties.get(baseDn.name());
+ if (bDn == null)
+ throw new IllegalStateException("No baseDn in " + properties);
+ return DirectoryDigestUtils.sha1str(bDn);
+ }
+}
--- /dev/null
+package org.argeo.util.directory;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/** Utilities around digests, mostly those related to passwords. */
+public class DirectoryDigestUtils {
+ public final static String PASSWORD_SCHEME_SHA = "SHA";
+ public final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
+
+ public static byte[] sha1(byte[] bytes) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+ digest.update(bytes);
+ byte[] checksum = digest.digest();
+ return checksum;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Cannot SHA1 digest", e);
+ }
+ }
+
+ public static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
+ Integer keyLength) {
+ try {
+ if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+ byte[] bytes = charsToBytes(password);
+ digest.update(bytes);
+ return digest.digest();
+ } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+ KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
+
+ SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+ final int ITERATION_LENGTH = 4;
+ byte[] key = f.generateSecret(spec).getEncoded();
+ byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
+ byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
+ if (iterationsArr.length < ITERATION_LENGTH) {
+ Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
+ System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
+ iterationsArr.length);
+ } else {
+ System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
+ }
+ System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
+ System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
+ return result;
+ } else {
+ throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
+ }
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new IllegalStateException("Cannot digest", e);
+ }
+ }
+
+ public static char[] bytesToChars(Object obj) {
+ if (obj instanceof char[])
+ return (char[]) obj;
+ if (!(obj instanceof byte[]))
+ throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+ ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+ CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
+ char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
+ // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+ // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+ // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+ return res;
+ }
+
+ public static byte[] charsToBytes(char[] chars) {
+ CharBuffer charBuffer = CharBuffer.wrap(chars);
+ ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
+ byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+ // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+ // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+ return bytes;
+ }
+
+ public static String sha1str(String str) {
+ byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
+ return encodeHexString(hash);
+ }
+
+ final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+ /**
+ * From
+ * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
+ * -a-hex-string-in-java
+ */
+ public static String encodeHexString(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ /** singleton */
+ private DirectoryDigestUtils() {
+ }
+}
--- /dev/null
+package org.argeo.util.directory;
+
+/** A unit within the high-level organisational structure of a directory. */
+public interface HierarchyUnit {
+ String getHierarchyUnitName();
+
+ HierarchyUnit getParent();
+
+ Iterable<HierarchyUnit> getDirectHierachyUnits(boolean functionalOnly);
+
+ boolean isFunctional();
+
+ String getContext();
+
+ Directory getDirectory();
+
+// Map<String,Object> getHierarchyProperties();
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import static org.argeo.util.directory.ldap.LdapNameUtils.toLdapName;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.transaction.xa.XAResource;
+
+import org.argeo.util.directory.Directory;
+import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.argeo.util.transaction.WorkControl;
+import org.argeo.util.transaction.WorkingCopyProcessor;
+import org.argeo.util.transaction.WorkingCopyXaResource;
+import org.argeo.util.transaction.XAResourceProvider;
+import org.osgi.framework.Filter;
+
+public abstract class AbstractLdapDirectory
+ implements Directory, WorkingCopyProcessor<LdapEntryWorkingCopy>, XAResourceProvider {
+ protected static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
+ protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
+
+ protected final LdapName baseDn;
+ protected final Hashtable<String, Object> properties;
+ private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
+ private final String userObjectClass, groupObjectClass;
+
+ private final boolean readOnly;
+ private final boolean disabled;
+ private final String uri;
+
+ private String forcedPassword;
+
+ private final boolean scoped;
+
+ private String memberAttributeId = "member";
+ private List<String> credentialAttributeIds = Arrays
+ .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
+
+ private WorkControl transactionControl;
+ private WorkingCopyXaResource<LdapEntryWorkingCopy> xaResource = new WorkingCopyXaResource<>(this);
+
+ public AbstractLdapDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+ this.properties = new Hashtable<String, Object>();
+ for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ properties.put(key, props.get(key));
+ }
+ baseDn = toLdapName(DirectoryConf.baseDn.getValue(properties));
+ this.scoped = scoped;
+
+ if (uriArg != null) {
+ uri = uriArg.toString();
+ // uri from properties is ignored
+ } else {
+ String uriStr = DirectoryConf.uri.getValue(properties);
+ if (uriStr == null)
+ uri = null;
+ else
+ uri = uriStr;
+ }
+
+ forcedPassword = DirectoryConf.forcedPassword.getValue(properties);
+
+ userObjectClass = DirectoryConf.userObjectClass.getValue(properties);
+ String userBase = DirectoryConf.userBase.getValue(properties);
+ groupObjectClass = DirectoryConf.groupObjectClass.getValue(properties);
+ String groupBase = DirectoryConf.groupBase.getValue(properties);
+ String systemRoleBase = DirectoryConf.systemRoleBase.getValue(properties);
+ try {
+// baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
+ userBaseRdn = new Rdn(userBase);
+// userBaseDn = new LdapName(userBase + "," + baseDn);
+ groupBaseRdn = new Rdn(groupBase);
+// groupBaseDn = new LdapName(groupBase + "," + baseDn);
+ systemRoleBaseRdn = new Rdn(systemRoleBase);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formated base DN " + DirectoryConf.baseDn.getValue(properties),
+ e);
+ }
+
+ // read only
+ String readOnlyStr = DirectoryConf.readOnly.getValue(properties);
+ if (readOnlyStr == null) {
+ readOnly = readOnlyDefault(uri);
+ properties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly));
+ } else
+ readOnly = Boolean.parseBoolean(readOnlyStr);
+
+ // disabled
+ String disabledStr = DirectoryConf.disabled.getValue(properties);
+ if (disabledStr != null)
+ disabled = Boolean.parseBoolean(disabledStr);
+ else
+ disabled = false;
+ }
+
+ /*
+ * ABSTRACT METHODS
+ */
+
+ public abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
+
+ public abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+
+ protected abstract Boolean daoHasEntry(LdapName dn);
+
+ protected abstract LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException;
+
+ protected abstract List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep);
+
+ /*
+ * EDITION
+ */
+
+ public boolean isEditing() {
+ return xaResource.wc() != null;
+ }
+
+ public LdapEntryWorkingCopy getWorkingCopy() {
+ LdapEntryWorkingCopy wc = xaResource.wc();
+ if (wc == null)
+ return null;
+ return wc;
+ }
+
+ public void checkEdit() {
+ if (xaResource.wc() == null) {
+ try {
+ transactionControl.getWorkContext().registerXAResource(xaResource, null);
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot enlist " + xaResource, e);
+ }
+ } else {
+ }
+ }
+
+ public void setTransactionControl(WorkControl transactionControl) {
+ this.transactionControl = transactionControl;
+ }
+
+ public XAResource getXaResource() {
+ return xaResource;
+ }
+
+ @Override
+ public LdapEntryWorkingCopy newWorkingCopy() {
+ return new LdapEntryWorkingCopy();
+ }
+
+ /*
+ * HIERARCHY
+ */
+ @Override
+ public HierarchyUnit getHierarchyUnit(String path) {
+ LdapName dn = pathToName(path);
+ return doGetHierarchyUnit(dn);
+ }
+
+ @Override
+ public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+ return doGetDirectHierarchyUnits(baseDn, functionalOnly);
+ }
+
+ /*
+ * PATHS
+ */
+
+ @Override
+ public String getContext() {
+ return getBaseDn().toString();
+ }
+
+ @Override
+ public String getName() {
+ return nameToSimple(getBaseDn(), ".");
+ }
+
+ protected String nameToRelativePath(LdapName dn) {
+ LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn);
+ return nameToSimple(name, "/");
+ }
+
+ protected String nameToSimple(LdapName name, String separator) {
+ StringJoiner path = new StringJoiner(separator);
+ for (int i = 0; i < name.size(); i++) {
+ path.add(name.getRdn(i).getValue().toString());
+ }
+ return path.toString();
+
+ }
+
+ protected LdapName pathToName(String path) {
+ try {
+ LdapName name = (LdapName) getBaseDn().clone();
+ String[] segments = path.split("/");
+ Rdn parentRdn = null;
+ for (String segment : segments) {
+ // TODO make attr names configurable ?
+ String attr = LdapAttrs.ou.name();
+ if (parentRdn != null) {
+ if (getUserBaseRdn().equals(parentRdn))
+ attr = LdapAttrs.uid.name();
+ else if (getGroupBaseRdn().equals(parentRdn))
+ attr = LdapAttrs.cn.name();
+ else if (getSystemRoleBaseRdn().equals(parentRdn))
+ attr = LdapAttrs.cn.name();
+ }
+ Rdn rdn = new Rdn(attr, segment);
+ name.add(rdn);
+ parentRdn = rdn;
+ }
+ return name;
+ } catch (InvalidNameException e) {
+ throw new IllegalStateException("Cannot get role " + path, e);
+ }
+
+ }
+
+ /*
+ * UTILITIES
+ */
+
+ protected static boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) {
+ try {
+ Attribute attr = attrs.get(LdapAttrs.objectClass.name());
+ NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ String v = en.next().toString();
+ if (v.equalsIgnoreCase(objectClass.name()))
+ return true;
+
+ }
+ return false;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot search for objectClass " + objectClass.name(), e);
+ }
+ }
+
+ private static boolean readOnlyDefault(String uriStr) {
+ if (uriStr == null)
+ return true;
+ /// TODO make it more generic
+ URI uri;
+ try {
+ uri = new URI(uriStr.split(" ")[0]);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ if (uri.getScheme() == null)
+ return false;// assume relative file to be writable
+ if (uri.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
+ File file = new File(uri);
+ if (file.exists())
+ return !file.canWrite();
+ else
+ return !file.getParentFile().canWrite();
+ } else if (uri.getScheme().equals(DirectoryConf.SCHEME_LDAP)) {
+ if (uri.getAuthority() != null)// assume writable if authenticated
+ return false;
+ } else if (uri.getScheme().equals(DirectoryConf.SCHEME_OS)) {
+ return true;
+ }
+ return true;// read only by default
+ }
+
+ /*
+ * ACCESSORS
+ */
+ @Override
+ public Optional<String> getRealm() {
+ Object realm = getProperties().get(DirectoryConf.realm.name());
+ if (realm == null)
+ return Optional.empty();
+ return Optional.of(realm.toString());
+ }
+
+ protected LdapName getBaseDn() {
+ return (LdapName) baseDn.clone();
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ /** dn can be null, in that case a default should be returned. */
+ public String getUserObjectClass() {
+ return userObjectClass;
+ }
+
+ public Rdn getUserBaseRdn() {
+ return userBaseRdn;
+ }
+
+ protected String newUserObjectClass(LdapName dn) {
+ return getUserObjectClass();
+ }
+
+ public String getGroupObjectClass() {
+ return groupObjectClass;
+ }
+
+ public Rdn getGroupBaseRdn() {
+ return groupBaseRdn;
+ }
+
+ public Rdn getSystemRoleBaseRdn() {
+ return systemRoleBaseRdn;
+ }
+
+ public Dictionary<String, Object> getProperties() {
+ return properties;
+ }
+
+ public Dictionary<String, Object> cloneProperties() {
+ return new Hashtable<>(properties);
+ }
+
+ public String getForcedPassword() {
+ return forcedPassword;
+ }
+
+ public boolean isScoped() {
+ return scoped;
+ }
+
+ public String getMemberAttributeId() {
+ return memberAttributeId;
+ }
+
+ public List<String> getCredentialAttributeIds() {
+ return credentialAttributeIds;
+ }
+
+ protected String getUri() {
+ return uri;
+ }
+
+ /*
+ * OBJECT METHODS
+ */
+
+ @Override
+ public int hashCode() {
+ return baseDn.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Directory " + baseDn.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+public abstract class AbstractLdapEntry implements LdapEntry {
+ private final AbstractLdapDirectory directory;
+
+ private final LdapName dn;
+
+ private Attributes publishedAttributes;
+
+ protected AbstractLdapEntry(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) {
+ this.directory = userAdmin;
+ this.dn = dn;
+ this.publishedAttributes = attributes;
+ }
+
+ @Override
+ public LdapName getDn() {
+ return dn;
+ }
+
+ public synchronized Attributes getAttributes() {
+ return isEditing() ? getModifiedAttributes() : publishedAttributes;
+ }
+
+ /** 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;
+ }
+
+ protected AbstractLdapDirectory getDirectory() {
+ return directory;
+ }
+
+ @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 java.util.Dictionary;
+import java.util.Enumeration;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+
+public class AttributesDictionary extends Dictionary<String, Object> {
+ private final Attributes attributes;
+
+ /** The provided attributes is wrapped, not copied. */
+ public AttributesDictionary(Attributes attributes) {
+ if (attributes == null)
+ throw new IllegalArgumentException("Attributes cannot be null");
+ this.attributes = attributes;
+ }
+
+ @Override
+ public int size() {
+ return attributes.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return attributes.size() == 0;
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+ return new Enumeration<String>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return namingEnumeration.hasMoreElements();
+ }
+
+ @Override
+ public String nextElement() {
+ return namingEnumeration.nextElement();
+ }
+
+ };
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+ return new Enumeration<Object>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return namingEnumeration.hasMoreElements();
+ }
+
+ @Override
+ public Object nextElement() {
+ String key = namingEnumeration.nextElement();
+ return get(key);
+ }
+
+ };
+ }
+
+ @Override
+ /** @returns a <code>String</code> or <code>String[]</code> */
+ public Object get(Object key) {
+ try {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ Attribute attr = attributes.get(key.toString());
+ if (attr == null)
+ return null;
+ if (attr.size() == 0)
+ throw new IllegalStateException("There must be at least one value");
+ else if (attr.size() == 1) {
+ return attr.get().toString();
+ } else {// multiple
+ String[] res = new String[attr.size()];
+ for (int i = 0; i < attr.size(); i++) {
+ Object value = attr.get();
+ if (value == null)
+ throw new RuntimeException("Values cannot be null");
+ res[i] = attr.get(i).toString();
+ }
+ return res;
+ }
+ } catch (NamingException e) {
+ throw new RuntimeException("Cannot get value for " + key, e);
+ }
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ if (value == null)
+ throw new IllegalArgumentException("Value cannot be null");
+
+ Object oldValue = get(key);
+ Attribute attr = attributes.get(key);
+ if (attr == null) {
+ attr = new BasicAttribute(key);
+ attributes.put(attr);
+ }
+
+ if (value instanceof String[]) {
+ String[] values = (String[]) value;
+ // clean additional values
+ for (int i = values.length; i < attr.size(); i++)
+ attr.remove(i);
+ // set values
+ for (int i = 0; i < values.length; i++) {
+ attr.set(i, values[i]);
+ }
+ } else {
+ if (attr.size() > 1)
+ throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
+ if (attr.size() == 1) {
+ try {
+ if (!attr.get(0).equals(value))
+ attr.set(0, value.toString());
+ } catch (NamingException e) {
+ throw new RuntimeException("Cannot check existing value", e);
+ }
+ } else {
+ attr.add(value.toString());
+ }
+ }
+ return oldValue;
+ }
+
+ @Override
+ public Object remove(Object key) {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ Object oldValue = get(key);
+ if (oldValue == null)
+ return null;
+ return attributes.remove(key.toString());
+ }
+
+ /**
+ * Copy the <b>content</b> of an {@link Attributes} to the provided
+ * {@link Dictionary}.
+ */
+ public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
+ AttributesDictionary ad = new AttributesDictionary(attributes);
+ Enumeration<String> keys = ad.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ dictionary.put(key, ad.get(key));
+ }
+ }
+
+ /**
+ * Copy a {@link Dictionary} into an {@link Attributes}.
+ */
+ public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
+ AttributesDictionary ad = new AttributesDictionary(attributes);
+ Enumeration<String> keys = dictionary.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ ad.put(key, dictionary.get(key));
+ }
+ }
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.util.naming.LdapAttrs;
+
+/** LDAP authPassword field according to RFC 3112 */
+public class AuthPassword implements CallbackHandler {
+ private final String authScheme;
+ private final String authInfo;
+ private final String authValue;
+
+ public AuthPassword(String value) {
+ StringTokenizer st = new StringTokenizer(value, "$");
+ // TODO make it more robust, deal with bad formatting
+ this.authScheme = st.nextToken().trim();
+ this.authInfo = st.nextToken().trim();
+ this.authValue = st.nextToken().trim();
+
+ String expectedAuthScheme = getExpectedAuthScheme();
+ if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
+ throw new IllegalArgumentException(
+ "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
+ }
+
+ protected AuthPassword(String authInfo, String authValue) {
+ this.authScheme = getExpectedAuthScheme();
+ if (authScheme == null)
+ throw new IllegalArgumentException("Expected auth scheme cannot be null");
+ this.authInfo = authInfo;
+ this.authValue = authValue;
+ }
+
+ protected AuthPassword(AuthPassword authPassword) {
+ this.authScheme = authPassword.getAuthScheme();
+ this.authInfo = authPassword.getAuthInfo();
+ this.authValue = authPassword.getAuthValue();
+ }
+
+ protected String getExpectedAuthScheme() {
+ return null;
+ }
+
+ protected boolean matchAuthValue(Object object) {
+ return authValue.equals(object.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AuthPassword))
+ return false;
+ AuthPassword authPassword = (AuthPassword) obj;
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
+ && authValue.equals(authValue);
+ }
+
+ public boolean keyEquals(AuthPassword authPassword) {
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return authValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return toAuthPassword();
+ }
+
+ public final String toAuthPassword() {
+ return getAuthScheme() + '$' + authInfo + '$' + authValue;
+ }
+
+ public String getAuthScheme() {
+ return authScheme;
+ }
+
+ public String getAuthInfo() {
+ return authInfo;
+ }
+
+ public String getAuthValue() {
+ return authValue;
+ }
+
+ public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
+ try {
+ Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+ if (authPassword != null) {
+ NamingEnumeration<?> values = authPassword.getAll();
+ while (values.hasMore()) {
+ Object val = values.next();
+ AuthPassword token = new AuthPassword(val.toString());
+ String auth;
+ if (Arrays.binarySearch(value, '$') >= 0) {
+ auth = token.authInfo + '$' + token.authValue;
+ } else {
+ auth = token.authValue;
+ }
+ if (Arrays.equals(auth.toCharArray(), value))
+ return token;
+ // if (token.matchAuthValue(value))
+ // return token;
+ }
+ }
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot check attribute", e);
+ }
+ }
+
+ public static boolean remove(Attributes attributes, AuthPassword value) {
+ Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+ return authPassword.remove(value.toAuthPassword());
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(toAuthPassword());
+ else if (callback instanceof PasswordCallback)
+ ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.dns.DnsBrowser;
+
+/** Free IPA specific conventions. */
+public class IpaUtils {
+ public final static String IPA_USER_BASE = "cn=users,cn=accounts";
+ public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts";
+ public final static String IPA_ROLE_BASE = "cn=roles,cn=accounts";
+ public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts";
+
+ private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
+
+ public final static String IPA_USER_DIRECTORY_CONFIG = DirectoryConf.userBase + "=" + IPA_USER_BASE + "&"
+ + DirectoryConf.groupBase + "=" + IPA_GROUP_BASE + "&" + DirectoryConf.readOnly + "=true";
+
+ @Deprecated
+ static String domainToUserDirectoryConfigPath(String realm) {
+ return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + DirectoryConf.realm.name() + "=" + realm;
+ }
+
+ public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
+ properties.put(DirectoryConf.baseDn.name(), domainToBaseDn(realm));
+ properties.put(DirectoryConf.realm.name(), realm);
+ properties.put(DirectoryConf.userBase.name(), IPA_USER_BASE);
+ properties.put(DirectoryConf.groupBase.name(), IPA_GROUP_BASE);
+ properties.put(DirectoryConf.systemRoleBase.name(), IPA_ROLE_BASE);
+ properties.put(DirectoryConf.readOnly.name(), Boolean.TRUE.toString());
+ }
+
+ public static String domainToBaseDn(String domain) {
+ String[] dcs = domain.split("\\.");
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < dcs.length; i++) {
+ if (i != 0)
+ sb.append(',');
+ String dc = dcs[i];
+ sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
+ }
+ return sb.toString();
+ }
+
+ public static LdapName kerberosToDn(String kerberosName) {
+ String[] kname = kerberosName.split("@");
+ String username = kname[0];
+ String baseDn = domainToBaseDn(kname[1]);
+ String dn;
+ if (!username.contains("/"))
+ dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
+ else
+ dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
+ try {
+ return new LdapName(dn);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
+ }
+ }
+
+ private IpaUtils() {
+
+ }
+
+ public static String kerberosDomainFromDns() {
+ String kerberosDomain;
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ InetAddress localhost = InetAddress.getLocalHost();
+ String hostname = localhost.getHostName();
+ String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+ kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+ return kerberosDomain;
+ } catch (NamingException | IOException e) {
+ throw new IllegalStateException("Cannot determine Kerberos domain from DNS", e);
+ }
+
+ }
+
+ public static Dictionary<String, Object> convertIpaUri(URI uri) {
+ String path = uri.getPath();
+ String kerberosRealm;
+ if (path == null || path.length() <= 1) {
+ kerberosRealm = kerberosDomainFromDns();
+ } else {
+ kerberosRealm = path.substring(1);
+ }
+
+ if (kerberosRealm == null)
+ throw new IllegalStateException("No Kerberos domain available for " + uri);
+ // TODO intergrate CA certificate in truststore
+ // String schemeToUse = SCHEME_LDAPS;
+ String schemeToUse = DirectoryConf.SCHEME_LDAP;
+ List<String> ldapHosts;
+ String ldapHostsStr = uri.getHost();
+ if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
+ schemeToUse.equals(DirectoryConf.SCHEME_LDAP) ? true : false);
+ if (ldapHosts == null || ldapHosts.size() == 0) {
+ throw new IllegalStateException("Cannot configure LDAP for IPA " + uri);
+ } else {
+ ldapHostsStr = ldapHosts.get(0);
+ }
+ } catch (NamingException | IOException e) {
+ throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
+ }
+ } else {
+ ldapHosts = new ArrayList<>();
+ ldapHosts.add(ldapHostsStr);
+ }
+
+ StringBuilder uriStr = new StringBuilder();
+ try {
+ for (String host : ldapHosts) {
+ URI convertedUri = new URI(schemeToUse + "://" + host + "/");
+ uriStr.append(convertedUri).append(' ');
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
+ }
+
+ Hashtable<String, Object> res = new Hashtable<>();
+ res.put(DirectoryConf.uri.name(), uriStr.toString());
+ addIpaConfig(kerberosRealm, res);
+ return res;
+ }
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.CommunicationException;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.transaction.WorkingCopy;
+
+/** A synchronized wrapper for a single {@link InitialLdapContext}. */
+// TODO implement multiple contexts and connection pooling.
+public class LdapConnection {
+ private InitialLdapContext initialLdapContext = null;
+
+ public LdapConnection(String url, Dictionary<String, ?> properties) {
+ try {
+ Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
+ connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ connEnv.put(Context.PROVIDER_URL, url);
+ connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
+ // use pooling in order to avoid connection timeout
+// connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
+// connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
+
+ initialLdapContext = new InitialLdapContext(connEnv, null);
+ // StartTlsResponse tls = (StartTlsResponse) ctx
+ // .extendedOperation(new StartTlsRequest());
+ // tls.negotiate();
+ Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
+ if (securityAuthentication != null)
+ initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
+ else
+ initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
+ Object principal = properties.get(Context.SECURITY_PRINCIPAL);
+ if (principal != null) {
+ initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
+ Object creds = properties.get(Context.SECURITY_CREDENTIALS);
+ if (creds != null) {
+ initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
+ }
+ }
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot connect to LDAP", e);
+ }
+
+ }
+
+ public void init() {
+
+ }
+
+ public void destroy() {
+ try {
+ // tls.close();
+ initialLdapContext.close();
+ initialLdapContext = null;
+ } catch (NamingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected InitialLdapContext getLdapContext() {
+ return initialLdapContext;
+ }
+
+ protected void reconnect() throws NamingException {
+ initialLdapContext.reconnect(initialLdapContext.getConnectControls());
+ }
+
+ public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
+ SearchControls searchControls) throws NamingException {
+ NamingEnumeration<SearchResult> results;
+ try {
+ results = getLdapContext().search(searchBase, searchFilter, searchControls);
+ } catch (CommunicationException e) {
+ reconnect();
+ results = getLdapContext().search(searchBase, searchFilter, searchControls);
+ }
+ return results;
+ }
+
+ public synchronized Attributes getAttributes(LdapName name) throws NamingException {
+ try {
+ return getLdapContext().getAttributes(name);
+ } catch (CommunicationException e) {
+ reconnect();
+ return getLdapContext().getAttributes(name);
+ }
+ }
+
+ public synchronized void prepareChanges(WorkingCopy<?, ?, LdapName> wc) throws NamingException {
+ // make sure connection will work
+ reconnect();
+
+ // delete
+ for (LdapName dn : wc.getDeletedData().keySet()) {
+ if (!entryExists(dn))
+ throw new IllegalStateException("User to delete no found " + dn);
+ }
+ // add
+ for (LdapName dn : wc.getNewData().keySet()) {
+ if (entryExists(dn))
+ throw new IllegalStateException("User to create found " + dn);
+ }
+ // modify
+ for (LdapName dn : wc.getModifiedData().keySet()) {
+ if (!wc.getNewData().containsKey(dn) && !entryExists(dn))
+ throw new IllegalStateException("User to modify not found " + dn);
+ }
+
+ }
+
+ protected boolean entryExists(LdapName dn) throws NamingException {
+ try {
+ return getAttributes(dn).size() != 0;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public synchronized void commitChanges(LdapEntryWorkingCopy wc) throws NamingException {
+ // delete
+ for (LdapName dn : wc.getDeletedData().keySet()) {
+ getLdapContext().destroySubcontext(dn);
+ }
+ // add
+ for (LdapName dn : wc.getNewData().keySet()) {
+ LdapEntry user = wc.getNewData().get(dn);
+ getLdapContext().createSubcontext(dn, user.getAttributes());
+ }
+ // modify
+ for (LdapName dn : wc.getModifiedData().keySet()) {
+ Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+ getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
+ }
+ }
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+public interface LdapEntry {
+ LdapName getDn();
+
+ Attributes getAttributes();
+
+ void publishAttributes(Attributes modifiedAttributes);
+
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.transaction.AbstractWorkingCopy;
+
+/** Working copy for a user directory being edited. */
+public class LdapEntryWorkingCopy extends AbstractWorkingCopy<LdapEntry, Attributes, LdapName> {
+ @Override
+ protected LdapName getId(LdapEntry entry) {
+ return entry.getDn();
+ }
+
+ @Override
+ protected Attributes cloneAttributes(LdapEntry entry) {
+ return (Attributes) entry.getAttributes().clone();
+ }
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import java.util.Objects;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.util.directory.Directory;
+import org.argeo.util.directory.HierarchyUnit;
+
+/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
+public class LdapHierarchyUnit implements HierarchyUnit {
+ private final AbstractLdapDirectory directory;
+
+ private final LdapName dn;
+ private final boolean functional;
+ private final Attributes attributes;
+
+// HierarchyUnit parent;
+// List<HierarchyUnit> children = new ArrayList<>();
+
+ public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) {
+ Objects.requireNonNull(directory);
+ Objects.requireNonNull(dn);
+
+ this.directory = directory;
+ this.dn = dn;
+ this.attributes = attributes;
+
+ Rdn rdn = LdapNameUtils.getLastRdn(dn);
+ functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn)
+ || directory.getSystemRoleBaseRdn().equals(rdn));
+ }
+
+ @Override
+ public HierarchyUnit getParent() {
+ return directory.doGetHierarchyUnit(LdapNameUtils.getParent(dn));
+ }
+
+ @Override
+ public Iterable<HierarchyUnit> getDirectHierachyUnits(boolean functionalOnly) {
+// List<HierarchyUnit> res = new ArrayList<>();
+// if (functionalOnly)
+// for (HierarchyUnit hu : children) {
+// if (hu.isFunctional())
+// res.add(hu);
+// }
+// else
+// res.addAll(children);
+// return Collections.unmodifiableList(res);
+ return directory.doGetDirectHierarchyUnits(dn, functionalOnly);
+ }
+
+ @Override
+ public boolean isFunctional() {
+ return functional;
+ }
+
+ @Override
+ public String getHierarchyUnitName() {
+ String name = LdapNameUtils.getLastRdnValue(dn);
+ // TODO check ou, o, etc.
+ return name;
+ }
+
+ public Attributes getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public String getContext() {
+ return dn.toString();
+ }
+
+ @Override
+ public Directory getDirectory() {
+ return directory;
+ }
+
+ @Override
+ public int hashCode() {
+ return dn.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LdapHierarchyUnit))
+ return false;
+ return ((LdapHierarchyUnit) obj).dn.equals(dn);
+ }
+
+ @Override
+ public String toString() {
+ return "Hierarchy Unit " + dn.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+ public static LdapName relativeName(LdapName prefix, LdapName dn) {
+ try {
+ if (!dn.startsWith(prefix))
+ throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+ LdapName res = (LdapName) dn.clone();
+ for (int i = 0; i < prefix.size(); i++) {
+ res.remove(0);
+ }
+ return res;
+ } catch (InvalidNameException e) {
+ throw new IllegalStateException("Cannot find realtive name", e);
+ }
+ }
+
+ public static LdapName getParent(LdapName dn) {
+ try {
+ LdapName parent = (LdapName) dn.clone();
+ parent.remove(parent.size() - 1);
+ return parent;
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+ }
+ }
+
+ public static Rdn getParentRdn(LdapName dn) {
+ if (dn.size() < 2)
+ throw new IllegalArgumentException(dn + " has no parent");
+ Rdn parentRdn = dn.getRdn(dn.size() - 2);
+ return parentRdn;
+ }
+
+ public static LdapName toLdapName(String distinguishedName) {
+ try {
+ return new LdapName(distinguishedName);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+ }
+ }
+
+ public static Rdn getLastRdn(LdapName dn) {
+ return dn.getRdn(dn.size() - 1);
+ }
+
+ public static String getLastRdnAsString(LdapName dn) {
+ return getLastRdn(dn).toString();
+ }
+
+ public static String getLastRdnValue(LdapName dn) {
+ return getLastRdn(dn).getValue().toString();
+ }
+
+ /** singleton */
+ private LdapNameUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.util.naming.LdapAttrs;
+
+/** Basic LDIF parser. */
+public class LdifParser {
+ private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+ protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
+ Attributes currentAttributes) {
+ try {
+ Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
+ Attribute nameAttr = currentAttributes.get(nameRdn.getType());
+ if (nameAttr == null)
+ currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
+ else if (!nameAttr.get().equals(nameRdn.getValue()))
+ throw new IllegalStateException(
+ "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
+ + " (shortly before line " + lineNumber + " in LDIF file)");
+ Attributes previous = res.put(currentDn, currentAttributes);
+ return previous;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot add " + currentDn, e);
+ }
+ }
+
+ /** With UTF-8 charset */
+ public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
+ try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
+ return read(reader);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ }
+
+ /** Will close the reader. */
+ public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
+ SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
+ try {
+ List<String> lines = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(reader)) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+ }
+ if (lines.size() == 0)
+ return res;
+ // add an empty new line since the last line is not checked
+ if (!lines.get(lines.size() - 1).equals(""))
+ lines.add("");
+
+ LdapName currentDn = null;
+ Attributes currentAttributes = null;
+ StringBuilder currentEntry = new StringBuilder();
+
+ readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
+ String line = lines.get(lineNumber);
+ boolean isLastLine = false;
+ if (lineNumber == lines.size() - 1)
+ isLastLine = true;
+ if (line.startsWith(" ")) {
+ currentEntry.append(line.substring(1));
+ if (!isLastLine)
+ continue readLines;
+ }
+
+ if (currentEntry.length() != 0 || isLastLine) {
+ // read previous attribute
+ StringBuilder attrId = new StringBuilder(8);
+ boolean isBase64 = false;
+ readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
+ char c = currentEntry.charAt(i);
+ if (c == ':') {
+ if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
+ isBase64 = true;
+ currentEntry.delete(0, i + (isBase64 ? 2 : 1));
+ break readAttrId;
+ } else {
+ attrId.append(c);
+ }
+ }
+
+ String attributeId = attrId.toString();
+ // TODO should we really trim the end of the string as well?
+ String cleanValueStr = currentEntry.toString().trim();
+ Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
+
+ // manage DN attributes
+ if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
+ if (currentDn != null) {
+ //
+ // ADD
+ //
+ Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
+ if (previous != null) {
+// log.warn("There was already an entry with DN " + currentDn
+// + ", which has been discarded by a subsequent one.");
+ }
+ }
+
+ if (attributeId.equals(LdapAttrs.DN))
+ try {
+ currentDn = new LdapName(attributeValue.toString());
+ currentAttributes = new BasicAttributes(true);
+ } catch (InvalidNameException e) {
+// log.error(attributeValue + " not a valid DN, skipping the entry.");
+ currentDn = null;
+ currentAttributes = null;
+ }
+ }
+
+ // store attribute
+ if (currentAttributes != null) {
+ Attribute attribute = currentAttributes.get(attributeId);
+ if (attribute == null) {
+ attribute = new BasicAttribute(attributeId);
+ currentAttributes.put(attribute);
+ }
+ attribute.add(attributeValue);
+ }
+ currentEntry = new StringBuilder();
+ }
+ currentEntry.append(line);
+ }
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ return res;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.util.directory.ldap;
+
+import static org.argeo.util.naming.LdapAttrs.DN;
+import static org.argeo.util.naming.LdapAttrs.member;
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapAttrs.uniqueMember;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Basic LDIF writer */
+public class LdifWriter {
+ private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+ private final Writer writer;
+
+ /** Writer must be closed by caller */
+ public LdifWriter(Writer writer) {
+ this.writer = writer;
+ }
+
+ /** Stream must be closed by caller */
+ public LdifWriter(OutputStream out) {
+ this(new OutputStreamWriter(out, DEFAULT_CHARSET));
+ }
+
+ public void writeEntry(LdapName name, Attributes attributes) throws IOException {
+ try {
+ // check consistency
+ Rdn nameRdn = name.getRdn(name.size() - 1);
+ Attribute nameAttr = attributes.get(nameRdn.getType());
+ if (!nameAttr.get().equals(nameRdn.getValue()))
+ throw new IllegalArgumentException(
+ "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
+
+ writer.append(DN + ": ").append(name.toString()).append('\n');
+ Attribute objectClassAttr = attributes.get(objectClass.name());
+ if (objectClassAttr != null)
+ writeAttribute(objectClassAttr);
+ attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+ Attribute attribute = attrs.next();
+ if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
+ continue attributes;// skip DN attribute
+ if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+ continue attributes;// skip member and uniqueMember attributes, so that they are always written last
+ writeAttribute(attribute);
+ }
+ // write member and uniqueMember attributes last
+ for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+ Attribute attribute = attrs.next();
+ if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+ writeMemberAttribute(attribute);
+ }
+ writer.append('\n');
+ writer.flush();
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot write LDIF", e);
+ }
+ }
+
+ public void write(Map<LdapName, Attributes> entries) throws IOException {
+ for (LdapName dn : entries.keySet())
+ writeEntry(dn, entries.get(dn));
+ }
+
+ protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
+ for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+ Object value = attrValues.next();
+ if (value instanceof byte[]) {
+ String encoded = Base64.getEncoder().encodeToString((byte[]) value);
+ writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
+ } else {
+ writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
+ }
+ }
+ }
+
+ protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
+ // Note: duplicate entries will be swallowed
+ SortedSet<String> values = new TreeSet<>();
+ for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+ String value = attrValues.next().toString();
+ values.add(value);
+ }
+
+ for (String value : values) {
+ writer.append(attribute.getID()).append(": ").append(value).append('\n');
+ }
+ }
+}
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
-import org.argeo.util.naming.ldap.AuthPassword;
+import org.argeo.util.directory.ldap.AuthPassword;
public class SharedSecret extends AuthPassword {
public final static String X_SHARED_SECRET = "X-SharedSecret";
+++ /dev/null
-package org.argeo.util.naming.ldap;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-
-public class AttributesDictionary extends Dictionary<String, Object> {
- private final Attributes attributes;
-
- /** The provided attributes is wrapped, not copied. */
- public AttributesDictionary(Attributes attributes) {
- if (attributes == null)
- throw new IllegalArgumentException("Attributes cannot be null");
- this.attributes = attributes;
- }
-
- @Override
- public int size() {
- return attributes.size();
- }
-
- @Override
- public boolean isEmpty() {
- return attributes.size() == 0;
- }
-
- @Override
- public Enumeration<String> keys() {
- NamingEnumeration<String> namingEnumeration = attributes.getIDs();
- return new Enumeration<String>() {
-
- @Override
- public boolean hasMoreElements() {
- return namingEnumeration.hasMoreElements();
- }
-
- @Override
- public String nextElement() {
- return namingEnumeration.nextElement();
- }
-
- };
- }
-
- @Override
- public Enumeration<Object> elements() {
- NamingEnumeration<String> namingEnumeration = attributes.getIDs();
- return new Enumeration<Object>() {
-
- @Override
- public boolean hasMoreElements() {
- return namingEnumeration.hasMoreElements();
- }
-
- @Override
- public Object nextElement() {
- String key = namingEnumeration.nextElement();
- return get(key);
- }
-
- };
- }
-
- @Override
- /** @returns a <code>String</code> or <code>String[]</code> */
- public Object get(Object key) {
- try {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- Attribute attr = attributes.get(key.toString());
- if (attr == null)
- return null;
- if (attr.size() == 0)
- throw new IllegalStateException("There must be at least one value");
- else if (attr.size() == 1) {
- return attr.get().toString();
- } else {// multiple
- String[] res = new String[attr.size()];
- for (int i = 0; i < attr.size(); i++) {
- Object value = attr.get();
- if (value == null)
- throw new RuntimeException("Values cannot be null");
- res[i] = attr.get(i).toString();
- }
- return res;
- }
- } catch (NamingException e) {
- throw new RuntimeException("Cannot get value for " + key, e);
- }
- }
-
- @Override
- public Object put(String key, Object value) {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- if (value == null)
- throw new IllegalArgumentException("Value cannot be null");
-
- Object oldValue = get(key);
- Attribute attr = attributes.get(key);
- if (attr == null) {
- attr = new BasicAttribute(key);
- attributes.put(attr);
- }
-
- if (value instanceof String[]) {
- String[] values = (String[]) value;
- // clean additional values
- for (int i = values.length; i < attr.size(); i++)
- attr.remove(i);
- // set values
- for (int i = 0; i < values.length; i++) {
- attr.set(i, values[i]);
- }
- } else {
- if (attr.size() > 1)
- throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
- if (attr.size() == 1) {
- try {
- if (!attr.get(0).equals(value))
- attr.set(0, value.toString());
- } catch (NamingException e) {
- throw new RuntimeException("Cannot check existing value", e);
- }
- } else {
- attr.add(value.toString());
- }
- }
- return oldValue;
- }
-
- @Override
- public Object remove(Object key) {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- Object oldValue = get(key);
- if (oldValue == null)
- return null;
- return attributes.remove(key.toString());
- }
-
- /**
- * Copy the <b>content</b> of an {@link Attributes} to the provided
- * {@link Dictionary}.
- */
- public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
- AttributesDictionary ad = new AttributesDictionary(attributes);
- Enumeration<String> keys = ad.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- dictionary.put(key, ad.get(key));
- }
- }
-
- /**
- * Copy a {@link Dictionary} into an {@link Attributes}.
- */
- public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
- AttributesDictionary ad = new AttributesDictionary(attributes);
- Enumeration<String> keys = dictionary.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- ad.put(key, dictionary.get(key));
- }
- }
-}
+++ /dev/null
-package org.argeo.util.naming.ldap;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.StringTokenizer;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.util.naming.LdapAttrs;
-
-/** LDAP authPassword field according to RFC 3112 */
-public class AuthPassword implements CallbackHandler {
- private final String authScheme;
- private final String authInfo;
- private final String authValue;
-
- public AuthPassword(String value) {
- StringTokenizer st = new StringTokenizer(value, "$");
- // TODO make it more robust, deal with bad formatting
- this.authScheme = st.nextToken().trim();
- this.authInfo = st.nextToken().trim();
- this.authValue = st.nextToken().trim();
-
- String expectedAuthScheme = getExpectedAuthScheme();
- if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
- throw new IllegalArgumentException(
- "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
- }
-
- protected AuthPassword(String authInfo, String authValue) {
- this.authScheme = getExpectedAuthScheme();
- if (authScheme == null)
- throw new IllegalArgumentException("Expected auth scheme cannot be null");
- this.authInfo = authInfo;
- this.authValue = authValue;
- }
-
- protected AuthPassword(AuthPassword authPassword) {
- this.authScheme = authPassword.getAuthScheme();
- this.authInfo = authPassword.getAuthInfo();
- this.authValue = authPassword.getAuthValue();
- }
-
- protected String getExpectedAuthScheme() {
- return null;
- }
-
- protected boolean matchAuthValue(Object object) {
- return authValue.equals(object.toString());
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof AuthPassword))
- return false;
- AuthPassword authPassword = (AuthPassword) obj;
- return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
- && authValue.equals(authValue);
- }
-
- public boolean keyEquals(AuthPassword authPassword) {
- return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
- }
-
- @Override
- public int hashCode() {
- return authValue.hashCode();
- }
-
- @Override
- public String toString() {
- return toAuthPassword();
- }
-
- public final String toAuthPassword() {
- return getAuthScheme() + '$' + authInfo + '$' + authValue;
- }
-
- public String getAuthScheme() {
- return authScheme;
- }
-
- public String getAuthInfo() {
- return authInfo;
- }
-
- public String getAuthValue() {
- return authValue;
- }
-
- public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
- try {
- Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
- if (authPassword != null) {
- NamingEnumeration<?> values = authPassword.getAll();
- while (values.hasMore()) {
- Object val = values.next();
- AuthPassword token = new AuthPassword(val.toString());
- String auth;
- if (Arrays.binarySearch(value, '$') >= 0) {
- auth = token.authInfo + '$' + token.authValue;
- } else {
- auth = token.authValue;
- }
- if (Arrays.equals(auth.toCharArray(), value))
- return token;
- // if (token.matchAuthValue(value))
- // return token;
- }
- }
- return null;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot check attribute", e);
- }
- }
-
- public static boolean remove(Attributes attributes, AuthPassword value) {
- Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
- return authPassword.remove(value.toAuthPassword());
- }
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (Callback callback : callbacks) {
- if (callback instanceof NameCallback)
- ((NameCallback) callback).setName(toAuthPassword());
- else if (callback instanceof PasswordCallback)
- ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
- }
- }
-
-}
+++ /dev/null
-package org.argeo.util.naming.ldap;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.util.naming.LdapAttrs;
-
-/** Basic LDIF parser. */
-public class LdifParser {
- private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
- protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
- Attributes currentAttributes) {
- try {
- Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
- Attribute nameAttr = currentAttributes.get(nameRdn.getType());
- if (nameAttr == null)
- currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
- else if (!nameAttr.get().equals(nameRdn.getValue()))
- throw new IllegalStateException(
- "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
- + " (shortly before line " + lineNumber + " in LDIF file)");
- Attributes previous = res.put(currentDn, currentAttributes);
- return previous;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot add " + currentDn, e);
- }
- }
-
- /** With UTF-8 charset */
- public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
- try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
- return read(reader);
- } finally {
- try {
- in.close();
- } catch (IOException e) {
- // silent
- }
- }
- }
-
- /** Will close the reader. */
- public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
- SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
- try {
- List<String> lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(reader)) {
- String line;
- while ((line = br.readLine()) != null) {
- lines.add(line);
- }
- }
- if (lines.size() == 0)
- return res;
- // add an empty new line since the last line is not checked
- if (!lines.get(lines.size() - 1).equals(""))
- lines.add("");
-
- LdapName currentDn = null;
- Attributes currentAttributes = null;
- StringBuilder currentEntry = new StringBuilder();
-
- readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
- String line = lines.get(lineNumber);
- boolean isLastLine = false;
- if (lineNumber == lines.size() - 1)
- isLastLine = true;
- if (line.startsWith(" ")) {
- currentEntry.append(line.substring(1));
- if (!isLastLine)
- continue readLines;
- }
-
- if (currentEntry.length() != 0 || isLastLine) {
- // read previous attribute
- StringBuilder attrId = new StringBuilder(8);
- boolean isBase64 = false;
- readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
- char c = currentEntry.charAt(i);
- if (c == ':') {
- if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
- isBase64 = true;
- currentEntry.delete(0, i + (isBase64 ? 2 : 1));
- break readAttrId;
- } else {
- attrId.append(c);
- }
- }
-
- String attributeId = attrId.toString();
- // TODO should we really trim the end of the string as well?
- String cleanValueStr = currentEntry.toString().trim();
- Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
-
- // manage DN attributes
- if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
- if (currentDn != null) {
- //
- // ADD
- //
- Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
- if (previous != null) {
-// log.warn("There was already an entry with DN " + currentDn
-// + ", which has been discarded by a subsequent one.");
- }
- }
-
- if (attributeId.equals(LdapAttrs.DN))
- try {
- currentDn = new LdapName(attributeValue.toString());
- currentAttributes = new BasicAttributes(true);
- } catch (InvalidNameException e) {
-// log.error(attributeValue + " not a valid DN, skipping the entry.");
- currentDn = null;
- currentAttributes = null;
- }
- }
-
- // store attribute
- if (currentAttributes != null) {
- Attribute attribute = currentAttributes.get(attributeId);
- if (attribute == null) {
- attribute = new BasicAttribute(attributeId);
- currentAttributes.put(attribute);
- }
- attribute.add(attributeValue);
- }
- currentEntry = new StringBuilder();
- }
- currentEntry.append(line);
- }
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
- // silent
- }
- }
- return res;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.util.naming.ldap;
-
-import static org.argeo.util.naming.LdapAttrs.DN;
-import static org.argeo.util.naming.LdapAttrs.member;
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapAttrs.uniqueMember;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-/** Basic LDIF writer */
-public class LdifWriter {
- private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
- private final Writer writer;
-
- /** Writer must be closed by caller */
- public LdifWriter(Writer writer) {
- this.writer = writer;
- }
-
- /** Stream must be closed by caller */
- public LdifWriter(OutputStream out) {
- this(new OutputStreamWriter(out, DEFAULT_CHARSET));
- }
-
- public void writeEntry(LdapName name, Attributes attributes) throws IOException {
- try {
- // check consistency
- Rdn nameRdn = name.getRdn(name.size() - 1);
- Attribute nameAttr = attributes.get(nameRdn.getType());
- if (!nameAttr.get().equals(nameRdn.getValue()))
- throw new IllegalArgumentException(
- "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
-
- writer.append(DN + ": ").append(name.toString()).append('\n');
- Attribute objectClassAttr = attributes.get(objectClass.name());
- if (objectClassAttr != null)
- writeAttribute(objectClassAttr);
- attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
- Attribute attribute = attrs.next();
- if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
- continue attributes;// skip DN attribute
- if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
- continue attributes;// skip member and uniqueMember attributes, so that they are always written last
- writeAttribute(attribute);
- }
- // write member and uniqueMember attributes last
- for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
- Attribute attribute = attrs.next();
- if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
- writeMemberAttribute(attribute);
- }
- writer.append('\n');
- writer.flush();
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot write LDIF", e);
- }
- }
-
- public void write(Map<LdapName, Attributes> entries) throws IOException {
- for (LdapName dn : entries.keySet())
- writeEntry(dn, entries.get(dn));
- }
-
- protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
- for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
- Object value = attrValues.next();
- if (value instanceof byte[]) {
- String encoded = Base64.getEncoder().encodeToString((byte[]) value);
- writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
- } else {
- writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
- }
- }
- }
-
- protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
- // Note: duplicate entries will be swallowed
- SortedSet<String> values = new TreeSet<>();
- for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
- String value = attrValues.next().toString();
- values.add(value);
- }
-
- for (String value : values) {
- writer.append(attribute.getID()).append(": ").append(value).append('\n');
- }
- }
-}
import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.eclipse.ui.specific.UiContext;
import org.argeo.jcr.JcrUtils;
+import org.argeo.util.directory.ldap.AuthPassword;
import org.argeo.util.naming.SharedSecret;
-import org.argeo.util.naming.ldap.AuthPassword;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.application.AbstractEntryPoint;
import org.eclipse.rap.rwt.client.WebClient;