package org.argeo.osgi.useradmin; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.ldap.LdapName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.naming.DnsBrowser; import org.argeo.naming.NamingUtils; import org.osgi.framework.Constants; /** 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"), /** Read-only source */ readOnly(null), /** Disabled source */ disabled(null), /** Authentication realm */ realm(null); public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config"; private final static Log log = LogFactory.getLog(UserAdminConf.class); public final static String SCHEME_LDAP = "ldap"; 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 properties) { Object res = getRawValue(properties); if (res == null) return null; return res.toString(); } @SuppressWarnings("unchecked") public T getRawValue(Dictionary 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 properties) { StringBuilder query = new StringBuilder(); boolean first = true; for (Enumeration 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())) { if (first) first = false; else query.append('&'); query.append(valueOf(key).name()); query.append('=').append(properties.get(key).toString()); } } String bDn = (String) properties.get(baseDn.name()); try { return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null, null); } catch (URISyntaxException e) { throw new UserDirectoryException("Cannot create URI from properties", e); } } public static Dictionary uriAsProperties(String uriStr) { try { Hashtable res = new Hashtable(); URI u = new URI(uriStr); String scheme = u.getScheme(); if (scheme != null && scheme.equals(SCHEME_IPA)) { u = convertIpaConfig(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("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 UserDirectoryException("Unsupported scheme " + scheme); Map> query = NamingUtils.queryToMap(u); for (String key : query.keySet()) { UserAdminConf ldapProp = UserAdminConf.valueOf(key); List values = query.get(key); if (values.size() == 1) { res.put(ldapProp.name(), values.get(0)); } else { throw new UserDirectoryException("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 (Exception e) { throw new UserDirectoryException("Cannot convert " + uri + " to properties", e); } } 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)); if (log.isDebugEnabled()) log.debug("Converted " + uri + " to " + convertedUri); 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 { hostname = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { log.warn("Using localhost as hostname", 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 properties) { String bDn = (String) properties.get(baseDn.name()); if (bDn == null) throw new UserDirectoryException("No baseDn in " + properties); return DigestUtils.sha1str(bDn); } }