IPA authentication working.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 28 Jul 2021 04:52:29 +0000 (06:52 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 28 Jul 2021 04:52:29 +0000 (06:52 +0200)
13 files changed:
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java
org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java
org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java

index 4b72903e22ae3dc66933ca0e91e267ff7b13b581..4057b26af800e042d6fc1076ec89ad2f5ee7be63 100644 (file)
@@ -308,6 +308,7 @@ public class UserAdminLoginModule implements LoginModule {
                        Set<User> collectedUsers = new HashSet<>();
                        // try dn
                        User user = null;
+                       user = null;
                        // try all indexes
                        for (String attr : indexedUserProperties) {
                                user = userAdmin.getUser(attr, providedUsername);
index 40e23541ba0ad46b3c444acfd586a094c3a4d9d0..1a9817450ed427deee762ed39f0866d1d6a3942c 100644 (file)
@@ -96,20 +96,25 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor
        @Override
        public void updated(String pid, Dictionary<String, ?> 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
index 62667c5180920001f1bb9c679487886a0b97d280..d9358c0830ee71f6130649e611a95f77eb3defae 100644 (file)
@@ -79,7 +79,7 @@ public class DnsBrowser implements Closeable {
        }
 
        /** Ordered, with preferred first. */
-       public List<String> getSrvRecordsAsHosts(String name) throws NamingException {
+       public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
                List<String> raw = getRecords(name, "SRV");
                if (raw.size() == 0)
                        return null;
@@ -96,7 +96,7 @@ public class DnsBrowser implements Closeable {
                }
                List<String> lst = new ArrayList<>();
                for (SrvRecord order : res) {
-                       lst.add(order.toHost());
+                       lst.add(order.toHost(withPort));
                }
                return Collections.unmodifiableList(lst);
        }
index d3515889a2988cc8d8dc5d6dba076e3360b3379f..8ecc9445790a1bcc32394e46f40484c18552b5f1 100644 (file)
@@ -20,10 +20,10 @@ class SrvRecord implements Comparable<SrvRecord> {
                        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<SrvRecord> {
                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 : "");
        }
 }
index 610f3f6400ca0bcc5d8753f9242192a09cd95ccb..f2d7c88fc232ca8d1090c065a5a2a5f9c1b5a975 100644 (file)
@@ -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<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() });
@@ -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<String, ?> props) {
+       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();
@@ -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;
+       }
+
 }
index 66d46d4e94c2acfe3cdb1807bea2ac0b00e018fd..bee513546cdd25a0f0adad343d07b2857ca8cab7 100644 (file)
@@ -74,9 +74,9 @@ public class AggregatingUserAdmin implements UserAdmin {
        public User getUser(String key, String value) {
                List<User> res = new ArrayList<User>();
                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<String> sysRoles = new HashSet<String>();
-               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<String> sysRoles = new HashSet<String>();
+                       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))
index 9d0056c55bddfc1c2d82cce29c690bc4200265fb..d56c06ac0964b8295fcfc655c4dc15ba0cf478a6 100644 (file)
@@ -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<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.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<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 UserDirectoryException("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 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<String, Object> 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 (file)
index 0000000..3e869f3
--- /dev/null
@@ -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<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 (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<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(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);
+               }
+       }
+}
index 22c178ef473916f6597ab471d8f69dd7dbb36b7a..2d9a8746973ebb74359b7fe5ff42dd26811b4058 100644 (file)
@@ -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<String, ?> properties) {
-               super(null, 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, 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<String, ?> 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<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
+                       NamingEnumeration<SearchResult> 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<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
+                       NamingEnumeration<SearchResult> 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);
                }
index 970ab7162949aa5ad82e12fcf3727679476785c9..b19e9bf4f311da72fc962bb5b411913d05ae57b0 100644 (file)
@@ -40,15 +40,19 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
 
        public LdifUserAdmin(String uri, String baseDn) {
-               this(fromUri(uri, baseDn));
+               this(fromUri(uri, baseDn), false);
        }
 
        public LdifUserAdmin(Dictionary<String, ?> properties) {
-               super(null, properties);
+               this(properties, false);
+       }
+
+       protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
+               super(null, properties, scoped);
        }
 
        public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
-               super(uri, properties);
+               super(uri, properties, false);
        }
 
        @Override
@@ -69,7 +73,7 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                }
                Dictionary<String, Object> 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);
                }
index fd7826303edb77ed951e2cfb34774193455c16e0..fe1ca7643f1a4ecf1596beeb870e9efdc21f7a2d 100644 (file)
@@ -21,7 +21,7 @@ public class OsUserDirectory extends AbstractUserDirectory {
        private final LdifUser osUser;
 
        public OsUserDirectory(URI uriArg, Dictionary<String, ?> 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<DirectoryUser> doGetRoles(Filter f) {
                List<DirectoryUser> res = new ArrayList<>();
-               if (f==null || f.match(osUser.getProperties()))
+               if (f == null || f.match(osUser.getProperties()))
                        res.add(osUser);
                return res;
        }
index 001d8e8bdea116e51a7a1a304cd4f71820ba17c4..ec41978dc3c441cb7f47ddc52cc392b902c23ca5 100644 (file)
@@ -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<String> 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 {
index 525176d962fcbb407b8c52db8eaf6e99e5c4b9c1..1630b6bd36befa841d3968b84d260750dda8e017 100644 (file)
@@ -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);