Introduce CMS-specific user APIs, based at this stage on OSGi UserAdmin
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / runtime / CmsUserAdmin.java
index e73d96f65ec7ab80c5abe7f14d13faa05c8028d5..e6f903d393179003f2862331a9ab131f0133de11 100644 (file)
@@ -1,13 +1,13 @@
 package org.argeo.cms.internal.runtime;
 
 import java.io.IOException;
-import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.Dictionary;
@@ -25,24 +25,18 @@ import javax.security.auth.kerberos.KerberosPrincipal;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.params.HttpParams;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
-import org.argeo.cms.internal.http.client.HttpCredentialProvider;
-import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
-import org.argeo.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.osgi.useradmin.DirectoryUserAdmin;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.directory.DirectoryConf;
-import org.argeo.util.naming.dns.DnsBrowser;
-import org.argeo.util.transaction.WorkControl;
-import org.argeo.util.transaction.WorkTransaction;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.dns.DnsBrowser;
+import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.cms.osgi.useradmin.DirectoryUserAdmin;
+import org.argeo.cms.runtime.DirectoryConf;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
@@ -71,16 +65,16 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
        private CmsState cmsState;
 
        public CmsUserAdmin() {
-               super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+               super(CmsConstants.SYSTEM_ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
        }
 
        public void start() {
                super.start();
-               List<Dictionary<String, Object>> configs = InitUtils.getUserDirectoryConfigs();
+               List<Dictionary<String, Object>> configs = getUserDirectoryConfigs();
                for (Dictionary<String, Object> config : configs) {
-                       UserDirectory userDirectory = enableUserDirectory(config);
-                       if (userDirectory.getRealm().isPresent())
-                               loadIpaJaasConfiguration();
+                       enableUserDirectory(config);
+//                     if (userDirectory.getRealm().isPresent())
+//                             loadIpaJaasConfiguration();
                }
                log.debug(() -> "CMS user admin available");
        }
@@ -92,6 +86,109 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                super.stop();
        }
 
+       protected List<Dictionary<String, Object>> getUserDirectoryConfigs() {
+               List<Dictionary<String, Object>> res = new ArrayList<>();
+               Path nodeBase = cmsState.getDataPath(KernelConstants.DIR_PRIVATE);
+               List<String> uris = new ArrayList<>();
+
+               // node roles
+               String nodeRolesUri = null;// getFrameworkProp(CmsConstants.ROLES_URI);
+               String baseNodeRoleDn = CmsConstants.SYSTEM_ROLES_BASEDN;
+               if (nodeRolesUri == null && nodeBase != null) {
+                       nodeRolesUri = baseNodeRoleDn + ".ldif";
+                       Path nodeRolesFile = nodeBase.resolve(nodeRolesUri);
+                       if (!Files.exists(nodeRolesFile))
+                               try {
+                                       Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeRoleDn + ".ldif"), nodeRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               if (nodeRolesUri != null)
+                       uris.add(nodeRolesUri);
+
+               // node tokens
+               String nodeTokensUri = null;// getFrameworkProp(CmsConstants.TOKENS_URI);
+               String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
+               if (nodeTokensUri == null && nodeBase != null) {
+                       nodeTokensUri = baseNodeTokensDn + ".ldif";
+                       Path nodeTokensFile = nodeBase.resolve(nodeTokensUri);
+                       if (!Files.exists(nodeTokensFile))
+                               try {
+                                       Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeTokensDn + ".ldif"), nodeTokensFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               if (nodeTokensUri != null)
+                       uris.add(nodeTokensUri);
+
+               // Business roles
+//             String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
+               List<String> userAdminUris = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DIRECTORY);// getFrameworkProp(CmsConstants.USERADMIN_URIS);
+               for (String userAdminUri : userAdminUris) {
+                       if (userAdminUri == null)
+                               continue;
+//                     if (!userAdminUri.trim().equals(""))
+                       uris.add(userAdminUri);
+               }
+
+               if (uris.size() == 0 && nodeBase != null) {
+                       // TODO put this somewhere else
+                       String demoBaseDn = "dc=example,dc=com";
+                       String userAdminUri = demoBaseDn + ".ldif";
+                       Path businessRolesFile = nodeBase.resolve(userAdminUri);
+                       Path systemRolesFile = nodeBase.resolve("ou=roles,ou=node.ldif");
+                       if (!Files.exists(businessRolesFile))
+                               try {
+                                       Files.copy(CmsUserAdmin.class.getResourceAsStream(demoBaseDn + ".ldif"), businessRolesFile);
+                                       if (!Files.exists(systemRolesFile))
+                                               Files.copy(CmsUserAdmin.class.getResourceAsStream("example-ou=roles,ou=node.ldif"),
+                                                               systemRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resources", e);
+                               }
+                       // userAdminUris = businessRolesFile.toURI().toString();
+                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
+                       // TODO downgrade security level
+               }
+
+               // Interprets URIs
+               for (String uri : uris) {
+                       URI u;
+                       try {
+                               u = new URI(uri);
+                               if (u.getPath() == null)
+                                       throw new IllegalArgumentException(
+                                                       "URI " + uri + " must have a path in order to determine base DN");
+                               if (u.getScheme() == null) {
+                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
+                                               u = Paths.get(uri).toRealPath().toUri();
+                                       else if (!uri.contains("/")) {
+                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
+                                               u = new URI(uri);
+                                       } else
+                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
+                               } else if (u.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
+                                       u = Paths.get(u).toRealPath().toUri();
+                               }
+                       } catch (Exception e) {
+                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
+                       }
+
+                       try {
+                               Dictionary<String, Object> properties = DirectoryConf.uriAsProperties(u.toString());
+                               res.add(properties);
+                       } catch (Exception e) {
+                               log.error("Cannot load user directory " + u, e);
+                       }
+               }
+
+               return res;
+       }
+
        public UserDirectory enableUserDirectory(Dictionary<String, ?> properties) {
                String uri = (String) properties.get(DirectoryConf.uri.name());
                Object realm = properties.get(DirectoryConf.realm.name());
@@ -99,7 +196,7 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                try {
                        if (uri == null) {
                                String baseDn = (String) properties.get(DirectoryConf.baseDn.name());
-                               u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
+                               u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_PRIVATE + '/' + baseDn + ".ldif");
                        } else if (realm != null) {
                                u = null;
                        } else {
@@ -122,14 +219,14 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
 //             } else {
 //                     throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
 //             }
-               String basePath = userDirectory.getContext();
+               String basePath = userDirectory.getBase();
 
                addUserDirectory(userDirectory);
                if (isSystemRolesBaseDn(basePath)) {
                        addStandardSystemRoles();
                }
                if (log.isDebugEnabled()) {
-                       log.debug("User directory " + userDirectory.getContext() + (u != null ? " [" + u.getScheme() + "]" : "")
+                       log.debug("User directory " + userDirectory.getBase() + (u != null ? " [" + u.getScheme() + "]" : "")
                                        + " enabled." + (realm != null ? " " + realm + " realm." : ""));
                }
                return userDirectory;
@@ -173,6 +270,7 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
 
                Optional<String> realm = userDirectory.getRealm();
                if (realm.isPresent()) {
+                       loadIpaJaasConfiguration();
                        if (Files.exists(nodeKeyTab)) {
                                String servicePrincipal = getKerberosServicePrincipal(realm.get());
                                if (servicePrincipal != null) {
@@ -186,7 +284,7 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                                                }
                                        };
                                        try {
-                                               LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
+                                               LoginContext nodeLc = CmsAuth.NODE.newLoginContext(callbackHandler);
                                                nodeLc.login();
                                                acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
                                        } catch (LoginException e) {
@@ -195,16 +293,6 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                                }
                        }
 
-                       // Register client-side SPNEGO auth scheme
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
-                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
-                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
                }
        }
 
@@ -224,7 +312,7 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
        }
 
        private void loadIpaJaasConfiguration() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+               if (CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) {
                        String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
                        URL url = getClass().getClassLoader().getResource(jaasConfig);
                        KernelUtils.setJaasConfiguration(url);
@@ -232,16 +320,21 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                }
        }
 
-       private String getKerberosServicePrincipal(String realm) {
-               String hostname;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       hostname = localhost.getHostName();
+       protected String getKerberosServicePrincipal(String realm) {
+               if (!Files.exists(nodeKeyTab))
+                       return null;
+               List<String> dns = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DNS);
+               String hostname = CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.HOST);
+               try (DnsBrowser dnsBrowser = new DnsBrowser(dns)) {
+                       hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName();
                        String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
-                       boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
+                       String ipv4fromDns = dnsBrowser.getRecord(hostname, "A");
+                       String ipv6fromDns = dnsBrowser.getRecord(hostname, "AAAA");
+                       if (ipv4fromDns == null && ipv6fromDns == null)
+                               throw new IllegalStateException("hostname " + hostname + " is not registered in DNS");
+                       // boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
                        String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
+                       if (kerberosDomain != null && kerberosDomain.equals(realm)) {
                                return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
                        } else
                                return null;