From: Mathieu Baudier Date: Wed, 28 Jul 2021 04:52:29 +0000 (+0200) Subject: IPA authentication working. X-Git-Tag: argeo-commons-2.1.103~3 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=73a89e099608a51d9aef814a3f85a62947275f59 IPA authentication working. --- diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 4b72903e2..4057b26af 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -308,6 +308,7 @@ public class UserAdminLoginModule implements LoginModule { Set collectedUsers = new HashSet<>(); // try dn User user = null; + user = null; // try all indexes for (String attr : indexedUserProperties) { user = userAdmin.getUser(attr, providedUsername); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 40e23541b..1a9817450 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -96,20 +96,25 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor @Override public void updated(String pid, Dictionary properties) throws ConfigurationException { String uri = (String) properties.get(UserAdminConf.uri.name()); + Object realm = properties.get(UserAdminConf.realm.name()); URI u; try { if (uri == null) { String baseDn = (String) properties.get(UserAdminConf.baseDn.name()); u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif"); - } else + } else if (realm != null) { + u = null; + } else { u = new URI(uri); + } } catch (URISyntaxException e) { throw new IllegalArgumentException("Badly formatted URI " + uri, e); } // Create AbstractUserDirectory userDirectory; - if (UserAdminConf.SCHEME_LDAP.equals(u.getScheme())) { + if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme()) + || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) { userDirectory = new LdapUserAdmin(properties); } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) { userDirectory = new LdifUserAdmin(u, properties); @@ -119,7 +124,6 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor } else { throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); } - Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); addUserDirectory(userDirectory); // OSGi @@ -135,9 +139,10 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor pidToBaseDn.put(pid, baseDn); // pidToServiceRegs.put(pid, reg); - if (log.isDebugEnabled()) - log.debug("User directory " + userDirectory.getBaseDn() + " [" + u.getScheme() + "] enabled." - + (realm != null ? " " + realm + " realm." : "")); + if (log.isDebugEnabled()) { + log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "") + + " enabled." + (realm != null ? " " + realm + " realm." : "")); + } if (isSystemRolesBaseDn(baseDn)) { // publishes only when system roles are available diff --git a/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java b/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java index 62667c518..d9358c083 100644 --- a/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java +++ b/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java @@ -79,7 +79,7 @@ public class DnsBrowser implements Closeable { } /** Ordered, with preferred first. */ - public List getSrvRecordsAsHosts(String name) throws NamingException { + public List getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException { List raw = getRecords(name, "SRV"); if (raw.size() == 0) return null; @@ -96,7 +96,7 @@ public class DnsBrowser implements Closeable { } List lst = new ArrayList<>(); for (SrvRecord order : res) { - lst.add(order.toHost()); + lst.add(order.toHost(withPort)); } return Collections.unmodifiableList(lst); } diff --git a/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java b/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java index d3515889a..8ecc94457 100644 --- a/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java +++ b/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java @@ -20,10 +20,10 @@ class SrvRecord implements Comparable { return priority - other.priority; if (weight != other.weight) return other.weight - other.weight; - String host = toHost(); - String otherHost = other.toHost(); + String host = toHost(false); + String otherHost = other.toHost(false); if (host.length() == otherHost.length()) - return toHost().compareTo(other.toHost()); + return host.compareTo(otherHost); else return host.length() - otherHost.length(); } @@ -43,10 +43,10 @@ class SrvRecord implements Comparable { return priority + " " + weight; } - public String toHost() { + public String toHost(boolean withPort) { String hostStr = hostname; if (hostname.charAt(hostname.length() - 1) == '.') hostStr = hostname.substring(0, hostname.length() - 1); - return hostStr + ":" + port; + return hostStr + (withPort ? ":" + port : ""); } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java index 610f3f640..f2d7c88fc 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -51,13 +51,15 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory private final boolean readOnly; private final boolean disabled; - private final URI uri; + private final String uri; private UserAdmin externalRoles; // private List indexedUserProperties = Arrays // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(), // LdapAttrs.cn.name() }); + private final boolean scoped; + private String memberAttributeId = "member"; private List credentialAttributeIds = Arrays .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() }); @@ -66,7 +68,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory private TransactionManager transactionManager; private WcXaResource xaResource = new WcXaResource(this); - public AbstractUserDirectory(URI uriArg, Dictionary props) { + AbstractUserDirectory(URI uriArg, Dictionary props, boolean scoped) { + this.scoped = scoped; properties = new Hashtable(); for (Enumeration keys = props.keys(); keys.hasMoreElements();) { String key = keys.nextElement(); @@ -74,18 +77,14 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory } if (uriArg != null) { - uri = uriArg; + uri = uriArg.toString(); // uri from properties is ignored } else { String uriStr = UserAdminConf.uri.getValue(properties); if (uriStr == null) uri = null; else - try { - uri = new URI(uriStr); - } catch (URISyntaxException e) { - throw new UserDirectoryException("Badly formatted URI " + uriStr, e); - } + uri = uriStr; } userObjectClass = UserAdminConf.userObjectClass.getValue(properties); @@ -175,14 +174,16 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory Attributes attrs = user.getAttributes(); // TODO centralize attribute name Attribute memberOf = attrs.get(LdapAttrs.memberOf.name()); - if (memberOf != null) { + // if user belongs to this directory, we only check meberOf + if (memberOf != null && user.getDn().startsWith(getBaseDn())) { try { NamingEnumeration values = memberOf.getAll(); while (values.hasMore()) { Object value = values.next(); LdapName groupDn = new LdapName(value.toString()); DirectoryUser group = doGetRole(groupDn); - allRoles.add(group); + if (group != null) + allRoles.add(group); } } catch (Exception e) { throw new UserDirectoryException("Cannot get memberOf groups for " + user, e); @@ -191,8 +192,10 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory for (LdapName groupDn : getDirectGroups(user.getDn())) { // TODO check for loops DirectoryUser group = doGetRole(groupDn); - allRoles.add(group); - collectRoles(group, allRoles); + if (group != null) { + allRoles.add(group); + collectRoles(group, allRoles); + } } } } @@ -398,13 +401,20 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory return credentialAttributeIds; } - protected URI getUri() { + protected String getUri() { return uri; } - private static boolean readOnlyDefault(URI uri) { - if (uri == null) + 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)) { @@ -488,4 +498,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory return xaResource; } + public boolean isScoped() { + return scoped; + } + } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index 66d46d4e9..bee513546 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -74,9 +74,9 @@ public class AggregatingUserAdmin implements UserAdmin { public User getUser(String key, String value) { List res = new ArrayList(); for (UserAdmin userAdmin : businessRoles.values()) { - User u = userAdmin.getUser(key, value); - if (u != null) - res.add(u); + User u = userAdmin.getUser(key, value); + if (u != null) + res.add(u); } // Note: node roles cannot contain users, so it is not searched return res.size() == 1 ? res.get(0) : null; @@ -87,11 +87,12 @@ public class AggregatingUserAdmin implements UserAdmin { if (user == null) {// anonymous return systemRoles.getAuthorization(null); } - UserAdmin userAdmin = findUserAdmin(user.getName()); - Authorization rawAuthorization = userAdmin.getAuthorization(user); + AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName()); + Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user); String usernameToUse; String displayNameToUse; if (user instanceof Group) { + // TODO check whether this is still working String ownerDn = TokenUtils.userDn((Group) user); if (ownerDn != null) {// tokens UserAdmin ownerUserAdmin = findUserAdmin(ownerDn); @@ -106,21 +107,38 @@ public class AggregatingUserAdmin implements UserAdmin { usernameToUse = rawAuthorization.getName(); displayNameToUse = rawAuthorization.toString(); } - // gather system roles - Set sysRoles = new HashSet(); - for (String role : rawAuthorization.getRoles()) { - Authorization auth = systemRoles.getAuthorization((User) userAdmin.getRole(role)); - systemRoles: for (String systemRole : auth.getRoles()) { - if (role.equals(systemRole)) - continue systemRoles; - sysRoles.add(systemRole); - } + + // gather roles from other referentials + final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating + if (user instanceof DirectoryUser) { + userAdminToUse = userReferentialOfThisUser; + } else if (user instanceof AuthenticatingUser) { + userAdminToUse = userReferentialOfThisUser.scope(user); + } else { + throw new IllegalArgumentException("Unsupported user type " + user.getClass()); + } + + try { + Set sysRoles = new HashSet(); + for (String role : rawAuthorization.getRoles()) { + User userOrGroup = (User) userAdminToUse.getRole(role); + Authorization auth = systemRoles.getAuthorization(userOrGroup); + systemRoles: for (String systemRole : auth.getRoles()) { + if (role.equals(systemRole)) + continue systemRoles; + sysRoles.add(systemRole); + } // sysRoles.addAll(Arrays.asList(auth.getRoles())); + } + addAbstractSystemRoles(rawAuthorization, sysRoles); + Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles, + rawAuthorization.getRoles()); + return authorization; + } finally { + if (userAdminToUse != null && userAdminToUse.isScoped()) { + userAdminToUse.destroy(); + } } - addAbstractSystemRoles(rawAuthorization, sysRoles); - Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles, - rawAuthorization.getRoles()); - return authorization; } /** @@ -155,16 +173,26 @@ public class AggregatingUserAdmin implements UserAdmin { protected void postAdd(AbstractUserDirectory userDirectory) { } - private UserAdmin findUserAdmin(String name) { +// private UserAdmin findUserAdmin(User user) { +// if (user == null) +// throw new IllegalArgumentException("User should not be null"); +// AbstractUserDirectory userAdmin = findUserAdmin(user.getName()); +// if (user instanceof DirectoryUser) { +// return userAdmin; +// } else { +// return userAdmin.scope(user); +// } +// } + + private AbstractUserDirectory findUserAdmin(String name) { try { - UserAdmin userAdmin = findUserAdmin(new LdapName(name)); - return userAdmin; + return findUserAdmin(new LdapName(name)); } catch (InvalidNameException e) { throw new UserDirectoryException("Badly formatted name " + name, e); } } - private UserAdmin findUserAdmin(LdapName name) { + private AbstractUserDirectory findUserAdmin(LdapName name) { if (name.startsWith(systemRolesBaseDn)) return systemRoles; if (tokensBaseDn != null && name.startsWith(tokensBaseDn)) diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java index 9d0056c55..d56c06ac0 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java @@ -1,8 +1,19 @@ 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.naming.DnsBrowser; import org.argeo.naming.LdapAttrs; /** Free IPA specific conventions. */ @@ -16,10 +27,19 @@ public class IpaUtils { 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 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.readOnly.name(), Boolean.TRUE.toString()); + } + public static String domainToBaseDn(String domain) { String[] dcs = domain.split("\\."); StringBuilder sb = new StringBuilder(); @@ -51,4 +71,67 @@ public class IpaUtils { 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 (Exception e) { + throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e); + } + + } + + public static Dictionary 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 UserDirectoryException("No Kerberos domain available for " + uri); + // TODO intergrate CA certificate in truststore + // String schemeToUse = SCHEME_LDAPS; + String schemeToUse = UserAdminConf.SCHEME_LDAP; + List 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 UserDirectoryException("Cannot configure LDAP for IPA " + uri); + } else { + ldapHostsStr = ldapHosts.get(0); + } + } catch (NamingException | IOException e) { + throw new UserDirectoryException("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 UserDirectoryException("cannot convert IPA uri " + uri, e); + } + + Hashtable res = new Hashtable<>(); + res.put(UserAdminConf.uri.name(), uriStr.toString()); + addIpaConfig(kerberosRealm, res); + return res; + } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java new file mode 100644 index 000000000..3e869f3b0 --- /dev/null +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java @@ -0,0 +1,147 @@ +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.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 properties) { + try { + Hashtable connEnv = new Hashtable(); + 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 (Exception e) { + throw new UserDirectoryException("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 search(LdapName searchBase, String searchFilter, + SearchControls searchControls) throws NamingException { + NamingEnumeration 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(UserDirectoryWorkingCopy wc) throws NamingException { + // make sure connection will work + reconnect(); + + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + if (!entryExists(dn)) + throw new UserDirectoryException("User to delete no found " + dn); + } + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + if (entryExists(dn)) + throw new UserDirectoryException("User to create found " + dn); + } + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn)) + throw new UserDirectoryException("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(UserDirectoryWorkingCopy wc) throws NamingException { + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + getLdapContext().destroySubcontext(dn); + } + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + DirectoryUser user = wc.getNewUsers().get(dn); + getLdapContext().createSubcontext(dn, user.getAttributes()); + } + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); + getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); + } + } +} diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java index 22c178ef4..2d9a87469 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java @@ -4,9 +4,9 @@ import static org.argeo.naming.LdapAttrs.objectClass; import java.util.ArrayList; import java.util.Dictionary; -import java.util.Hashtable; import java.util.List; +import javax.naming.AuthenticationNotSupportedException; import javax.naming.Binding; import javax.naming.Context; import javax.naming.InvalidNameException; @@ -15,14 +15,11 @@ import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; 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 javax.transaction.TransactionManager; -import org.argeo.naming.LdapAttrs; import org.osgi.framework.Filter; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; @@ -32,53 +29,19 @@ import org.osgi.service.useradmin.User; * and an open transaction for write access. */ public class LdapUserAdmin extends AbstractUserDirectory { - private InitialLdapContext initialLdapContext = null; - -// private LdapName adminUserDn = null; -// private LdifUser adminUser = null; + private LdapConnection ldapConnection; public LdapUserAdmin(Dictionary properties) { - super(null, properties); - try { - Hashtable connEnv = new Hashtable(); - connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - connEnv.put(Context.PROVIDER_URL, getUri().toString()); - connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name()); + this(properties, false); + } - 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()); -// adminUserDn = new LdapName(principal.toString()); -// BasicAttributes adminUserAttrs = new BasicAttributes(); -// adminUser = new LdifUser(this, adminUserDn, adminUserAttrs); - Object creds = properties.get(Context.SECURITY_CREDENTIALS); - if (creds != null) { - initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString()); -// adminUserAttrs.put(LdapAttrs.userPassword.name(), adminUser.hash(creds.toString().toCharArray())); - } -// adminUserAttrs.put(LdapAttrs.memberOf.name(), "cn=admin,ou=roles,ou=node"); - } - } catch (Exception e) { - throw new UserDirectoryException("Cannot connect to LDAP", e); - } + public LdapUserAdmin(Dictionary properties, boolean scoped) { + super(null, properties, scoped); + ldapConnection = new LdapConnection(getUri().toString(), properties); } public void destroy() { - try { - // tls.close(); - initialLdapContext.close(); - } catch (NamingException e) { - e.printStackTrace(); - } + ldapConnection.destroy(); } @Override @@ -97,12 +60,12 @@ public class LdapUserAdmin extends AbstractUserDirectory { } else { properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); } - return new LdapUserAdmin(properties); + return new LdapUserAdmin(properties, true); } - protected InitialLdapContext getLdapContext() { - return initialLdapContext; - } +// protected InitialLdapContext getLdapContext() { +// return initialLdapContext; +// } @Override protected Boolean daoHasRole(LdapName dn) { @@ -116,7 +79,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { @Override protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException { try { - Attributes attrs = getLdapContext().getAttributes(name); + Attributes attrs = ldapConnection.getAttributes(name); if (attrs.size() == 0) return null; int roleType = roleType(name); @@ -129,9 +92,6 @@ public class LdapUserAdmin extends AbstractUserDirectory { throw new UserDirectoryException("Unsupported LDAP type for " + name); return res; } catch (NameNotFoundException e) { -// if (adminUserDn != null && adminUserDn.equals(name)) { -// return adminUser; -// } throw e; } catch (NamingException e) { return null; @@ -149,7 +109,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); LdapName searchBase = getBaseDn(); - NamingEnumeration results = getLdapContext().search(searchBase, searchFilter, searchControls); + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); results: while (results.hasMoreElements()) { SearchResult searchResult = results.next(); @@ -170,9 +130,12 @@ public class LdapUserAdmin extends AbstractUserDirectory { res.add(role); } return res; -// } catch (NameNotFoundException e) { -// return res; + } catch (AuthenticationNotSupportedException e) { + // ignore (typically an unsupported anonymous bind) + // TODO better logging + return res; } catch (Exception e) { + e.printStackTrace(); throw new UserDirectoryException("Cannot get roles for filter " + f, e); } } @@ -192,7 +155,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); LdapName searchBase = getBaseDn(); - NamingEnumeration results = getLdapContext().search(searchBase, searchFilter, searchControls); + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); while (results.hasMoreElements()) { SearchResult searchResult = (SearchResult) results.nextElement(); @@ -207,52 +170,16 @@ public class LdapUserAdmin extends AbstractUserDirectory { @Override protected void prepare(UserDirectoryWorkingCopy wc) { try { - getLdapContext().reconnect(getLdapContext().getConnectControls()); - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - if (!entryExists(dn)) - throw new UserDirectoryException("User to delete no found " + dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - if (entryExists(dn)) - throw new UserDirectoryException("User to create found " + dn); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn)) - throw new UserDirectoryException("User to modify not found " + dn); - } + ldapConnection.prepareChanges(wc); } catch (NamingException e) { throw new UserDirectoryException("Cannot prepare LDAP", e); } } - private boolean entryExists(LdapName dn) throws NamingException { - try { - return getLdapContext().getAttributes(dn).size() != 0; - } catch (NameNotFoundException e) { - return false; - } - } - @Override protected void commit(UserDirectoryWorkingCopy wc) { try { - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - getLdapContext().destroySubcontext(dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - DirectoryUser user = wc.getNewUsers().get(dn); - getLdapContext().createSubcontext(dn, user.getAttributes()); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); - getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); - } + ldapConnection.commitChanges(wc); } catch (NamingException e) { throw new UserDirectoryException("Cannot commit LDAP", e); } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java index 970ab7162..b19e9bf4f 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java @@ -40,15 +40,19 @@ public class LdifUserAdmin extends AbstractUserDirectory { private SortedMap groups = new TreeMap(); public LdifUserAdmin(String uri, String baseDn) { - this(fromUri(uri, baseDn)); + this(fromUri(uri, baseDn), false); } public LdifUserAdmin(Dictionary properties) { - super(null, properties); + this(properties, false); + } + + protected LdifUserAdmin(Dictionary properties, boolean scoped) { + super(null, properties, scoped); } public LdifUserAdmin(URI uri, Dictionary properties) { - super(uri, properties); + super(uri, properties, false); } @Override @@ -69,7 +73,7 @@ public class LdifUserAdmin extends AbstractUserDirectory { } Dictionary properties = cloneProperties(); properties.put(UserAdminConf.readOnly.name(), "true"); - LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties); + LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true); scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups); scopedUserAdmin.users = Collections.unmodifiableSortedMap(users); return scopedUserAdmin; @@ -83,13 +87,15 @@ public class LdifUserAdmin extends AbstractUserDirectory { } public void init() { + try { - if (getUri().getScheme().equals("file")) { - File file = new File(getUri()); + URI u = new URI(getUri()); + if (u.getScheme().equals("file")) { + File file = new File(u); if (!file.exists()) return; } - load(getUri().toURL().openStream()); + load(u.toURL().openStream()); } catch (Exception e) { throw new UserDirectoryException("Cannot open URL " + getUri(), e); } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java index fd7826303..fe1ca7643 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java @@ -21,7 +21,7 @@ public class OsUserDirectory extends AbstractUserDirectory { private final LdifUser osUser; public OsUserDirectory(URI uriArg, Dictionary props) { - super(uriArg, props); + super(uriArg, props, false); try { osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn()); Attributes attributes = new BasicAttributes(); @@ -53,7 +53,7 @@ public class OsUserDirectory extends AbstractUserDirectory { @Override protected List doGetRoles(Filter f) { List res = new ArrayList<>(); - if (f==null || f.match(osUser.getProperties())) + if (f == null || f.match(osUser.getProperties())) res.add(osUser); return res; } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java index 001d8e8bd..ec41978dc 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java @@ -1,6 +1,5 @@ package org.argeo.osgi.useradmin; -import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; @@ -11,10 +10,8 @@ import java.util.List; import java.util.Map; import javax.naming.Context; -import javax.naming.NamingException; import javax.naming.ldap.LdapName; -import org.argeo.naming.DnsBrowser; import org.argeo.naming.NamingUtils; /** Properties used to configure user admins. */ @@ -49,6 +46,7 @@ public enum UserAdminConf { 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"; @@ -147,8 +145,8 @@ public enum UserAdminConf { URI u = new URI(uriStr); String scheme = u.getScheme(); if (scheme != null && scheme.equals(SCHEME_IPA)) { - u = convertIpaConfig(u); - scheme = u.getScheme(); + return IpaUtils.convertIpaUri(u); +// scheme = u.getScheme(); } String path = u.getPath(); // base DN @@ -166,7 +164,7 @@ public enum UserAdminConf { String principal = null; String credentials = null; if (scheme != null) - if (scheme.equals(SCHEME_LDAP) || scheme.equals("ldaps")) { + if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) { // TODO additional checks if (u.getUserInfo() != null) { String[] userInfo = u.getUserInfo().split(":"); @@ -210,49 +208,6 @@ public enum UserAdminConf { } } - private static URI convertIpaConfig(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 UserDirectoryException("No Kerberos domain available for " + uri); - try (DnsBrowser dnsBrowser = new DnsBrowser()) { - String ldapHostsStr = uri.getHost(); - if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) { - List ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase()); - if (ldapHosts == null || ldapHosts.size() == 0) { - throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri); - } else { - ldapHostsStr = ldapHosts.get(0); - } - } - URI convertedUri = new URI( - SCHEME_LDAP + "://" + ldapHostsStr + "/" + IpaUtils.domainToUserDirectoryConfigPath(kerberosRealm)); - return convertedUri; - } catch (NamingException | IOException | URISyntaxException e) { - throw new UserDirectoryException("cannot convert IPA uri " + uri, e); - } - } - - private 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 (Exception e) { - throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e); - } - - } - private static String getBaseDnFromHostname() { String hostname; try { diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java index 525176d96..1630b6bd3 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java @@ -62,6 +62,7 @@ class WcXaResource implements XAResource { try { userDirectory.prepare(wc); } catch (Exception e) { + e.printStackTrace(); throw new XAException(XAException.XAER_RMERR); } return XA_OK; @@ -78,6 +79,7 @@ class WcXaResource implements XAResource { userDirectory.prepare(wc); userDirectory.commit(wc); } catch (Exception e) { + e.printStackTrace(); throw new XAException(XAException.XAER_RMERR); } finally { cleanUp(xid); @@ -90,6 +92,7 @@ class WcXaResource implements XAResource { checkXid(xid); userDirectory.rollback(wc(xid)); } catch (Exception e) { + e.printStackTrace(); throw new XAException(XAException.XAER_RMERR); } finally { cleanUp(xid);