From: Mathieu Baudier Date: Fri, 20 May 2022 09:51:32 +0000 (+0200) Subject: Introduce static CMS. X-Git-Tag: v2.3.10~224 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=b843d903237a2a4192c40d8c933e71137284050b;p=lgpl%2Fargeo-commons.git Introduce static CMS. --- diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java index deb330475..c575b1da3 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java @@ -7,7 +7,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Dictionary; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -35,6 +37,16 @@ import org.osgi.service.cm.ConfigurationListener; /** Manages the LDIF-based deployment configuration. */ public class DeployConfig implements ConfigurationListener { + private final static LdapName USER_ADMIN_BASE_DN; + static { + try { + USER_ADMIN_BASE_DN = new LdapName( + CmsConstants.OU + "=" + CmsConstants.NODE_USER_ADMIN_PID + "," + CmsConstants.DEPLOY_BASEDN); + } catch (InvalidNameException e) { + throw new IllegalArgumentException(e); + } + } + private final CmsLog log = CmsLog.getLog(getClass()); // private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); @@ -48,24 +60,6 @@ public class DeployConfig implements ConfigurationListener { private ConfigurationAdmin configurationAdmin; - public DeployConfig() { -// this.dataModels = dataModels; - // ConfigurationAdmin configurationAdmin = -// // bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); -// try { -// if (!isInitialized()) { // first init -// isFirstInit = true; -// firstInit(); -// } -// this.configurationAdmin = configurationAdmin; -//// init(configurationAdmin, isClean, isFirstInit); -// } catch (IOException e) { -// throw new RuntimeException("Could not init deploy configs", e); -// } - // FIXME check race conditions during initialization - // bc.registerService(ConfigurationListener.class, this, null); - } - private void firstInit() throws IOException { log.info("## FIRST INIT ##"); Files.createDirectories(deployConfigPath.getParent()); @@ -140,31 +134,37 @@ public class DeployConfig implements ConfigurationListener { // .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT)); } - public void start() throws IOException { - if (!isInitialized()) { // first init - isFirstInit = true; - firstInit(); - } - - boolean isClean; + public void start() { try { - Configuration[] confs = configurationAdmin - .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")"); - isClean = confs == null || confs.length == 0; - } catch (Exception e) { - throw new IllegalStateException("Cannot analyse clean state", e); - } + if (!isInitialized()) { // first init + isFirstInit = true; + firstInit(); + } - try (InputStream in = Files.newInputStream(deployConfigPath)) { - deployConfigs = new LdifParser().read(in); - } - if (isClean) { - if (log.isDebugEnabled()) - log.debug("Clean state, loading from framework properties..."); - setFromFrameworkProperties(isFirstInit); - loadConfigs(); + boolean isClean = true; + if (configurationAdmin != null) + try { + Configuration[] confs = configurationAdmin + .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")"); + isClean = confs == null || confs.length == 0; + } catch (Exception e) { + throw new IllegalStateException("Cannot analyse clean state", e); + } + + try (InputStream in = Files.newInputStream(deployConfigPath)) { + deployConfigs = new LdifParser().read(in); + } + if (isClean) { + if (log.isDebugEnabled()) + log.debug("Clean state, loading from framework properties..."); + setFromFrameworkProperties(isFirstInit); + if (configurationAdmin != null) + loadConfigs(); + } + // TODO check consistency if not clean + } catch (IOException e) { + throw new RuntimeException("Cannot load deploy configuration", e); } - // TODO check consistency if not clean } public void stop() { @@ -220,6 +220,17 @@ public class DeployConfig implements ConfigurationListener { } + public Set> getUserDirectoryConfigs() { + Set> res = new HashSet<>(); + for (LdapName dn : deployConfigs.keySet()) { + if (dn.endsWith(USER_ADMIN_BASE_DN)) { + Attributes attributes = deployConfigs.get(dn); + res.add(new AttributesDictionary(attributes)); + } + } + return res; + } + @Override public void configurationEvent(ConfigurationEvent event) { try { @@ -312,6 +323,10 @@ public class DeployConfig implements ConfigurationListener { } public boolean hasDomain() { + // FIXME lookup deploy configs directly + if (configurationAdmin == null) + return false; + Configuration[] configs; try { configs = configurationAdmin diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java index 626a057c0..4eda98c35 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java @@ -1,154 +1,43 @@ package org.argeo.cms.internal.osgi; -import java.io.IOException; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; -import java.util.Iterator; import java.util.Map; -import java.util.Set; +import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -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.cms.internal.http.client.HttpCredentialProvider; -import org.argeo.cms.internal.http.client.SpnegoAuthScheme; +import org.argeo.cms.internal.runtime.CmsUserAdmin; import org.argeo.cms.internal.runtime.KernelConstants; -import org.argeo.cms.internal.runtime.KernelUtils; -import org.argeo.osgi.transaction.WorkControl; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.osgi.useradmin.AbstractUserDirectory; -import org.argeo.osgi.useradmin.AggregatingUserAdmin; -import org.argeo.osgi.useradmin.LdapUserAdmin; -import org.argeo.osgi.useradmin.LdifUserAdmin; -import org.argeo.osgi.useradmin.OsUserDirectory; import org.argeo.osgi.useradmin.UserAdminConf; import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.naming.DnsBrowser; -import org.ietf.jgss.GSSCredential; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; import org.osgi.framework.Constants; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.UserAdmin; /** * Aggregates multiple {@link UserDirectory} and integrates them with system * roles. */ -public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants { +public class NodeUserAdmin extends CmsUserAdmin implements ManagedServiceFactory, KernelConstants { private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class); // OSGi private Map pidToBaseDn = new HashMap<>(); -// private Map> pidToServiceRegs = new HashMap<>(); -// private ServiceRegistration userAdminReg; - - // JTA -// private final ServiceTracker tmTracker; - // private final String cacheName = UserDirectory.class.getName(); - - // GSS API - private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH); - private GSSCredential acceptorCredentials; - - private boolean singleUser = false; -// private boolean systemRolesAvailable = false; - -// CmsUserManagerImpl userManager; - private WorkControl transactionManager; - private WorkTransaction userTransaction; - - public NodeUserAdmin() { - super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN); -// BundleContext bc = Activator.getBundleContext(); -// if (bc != null) { -// tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) { -// -// @Override -// public WorkControl addingService(ServiceReference reference) { -// WorkControl workControl = super.addingService(reference); -// userManager = new CmsUserManagerImpl(); -// userManager.setUserAdmin(NodeUserAdmin.this); -// // FIXME make it more robust -// userManager.setUserTransaction((WorkTransaction) workControl); -// bc.registerService(CmsUserManager.class, userManager, null); -// return workControl; -// } -// }; -// tmTracker.open(); -// } else { -// tmTracker = null; -// } - } - - public void start() { - } - - public void stop() { - } @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 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 (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); - } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) { - userDirectory = new OsUserDirectory(u, properties); - singleUser = true; - } else { - throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); + LdapName baseDn; + try { + baseDn = new LdapName((String) properties.get(UserAdminConf.baseDn.name())); + } catch (InvalidNameException e) { + throw new IllegalArgumentException(e); } - LdapName baseDn = userDirectory.getBaseDn(); // FIXME make updates more robust if (pidToBaseDn.containsValue(baseDn)) { @@ -157,74 +46,24 @@ public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServic return; } - addUserDirectory(userDirectory); - + UserDirectory userDirectory = enableUserDirectory(properties); // OSGi Hashtable regProps = new Hashtable<>(); regProps.put(Constants.SERVICE_PID, pid); if (isSystemRolesBaseDn(baseDn)) regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); regProps.put(UserAdminConf.baseDn.name(), baseDn); - // ServiceRegistration reg = - // bc.registerService(UserDirectory.class, userDirectory, regProps); + CmsActivator.getBundleContext().registerService(UserDirectory.class, userDirectory, regProps); -// userManager.addUserDirectory(userDirectory, regProps); pidToBaseDn.put(pid, baseDn); - // pidToServiceRegs.put(pid, reg); - - if (log.isDebugEnabled()) { - log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "") - + " enabled." + (realm != null ? " " + realm + " realm." : "")); - } if (isSystemRolesBaseDn(baseDn)) { - addStandardSystemRoles(); - // publishes itself as user admin only when system roles are available Dictionary userAdminregProps = new Hashtable<>(); userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT); userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); CmsActivator.getBundleContext().registerService(UserAdmin.class, this, userAdminregProps); } - -// if (isSystemRolesBaseDn(baseDn)) -// systemRolesAvailable = true; -// -// // start publishing only when system roles are available -// if (systemRolesAvailable) { -// // The list of baseDns is published as properties -// // TODO clients should rather reference USerDirectory services -// if (userAdminReg != null) -// userAdminReg.unregister(); -// // register self as main user admin -// Dictionary userAdminregProps = currentState(); -// userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT); -// userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); -// userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps); -// } - } - - private void addStandardSystemRoles() { - // we assume UserTransaction is already available (TODO make it more robust) - try { - userTransaction.begin(); - Role adminRole = getRole(CmsConstants.ROLE_ADMIN); - if (adminRole == null) { - adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP); - } - if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) { - Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP); - userAdminRole.addMember(adminRole); - } - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - // silent - } - throw new IllegalStateException("Cannot add standard system roles", e); - } } @Override @@ -241,163 +80,4 @@ public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServic return "Node User Admin"; } - @Override - protected void addAbstractSystemRoles(Authorization rawAuthorization, Set sysRoles) { - if (rawAuthorization.getName() == null) { - sysRoles.add(CmsConstants.ROLE_ANONYMOUS); - } else { - sysRoles.add(CmsConstants.ROLE_USER); - } - } - - protected void postAdd(AbstractUserDirectory userDirectory) { - // JTA -// WorkControl tm = tmTracker != null ? tmTracker.getService() : null; -// if (tm == null) -// throw new IllegalStateException("A JTA transaction manager must be available."); - userDirectory.setTransactionControl(transactionManager); -// if (tmTracker.getService() instanceof BitronixTransactionManager) -// EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource()); - - Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); - if (realm != null) { - if (Files.exists(nodeKeyTab)) { - String servicePrincipal = getKerberosServicePrincipal(realm.toString()); - if (servicePrincipal != null) { - CallbackHandler callbackHandler = new CallbackHandler() { - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) - if (callback instanceof NameCallback) - ((NameCallback) callback).setName(servicePrincipal); - - } - }; - try { - LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler); - nodeLc.login(); - acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal); - } catch (LoginException e) { - throw new IllegalStateException("Cannot log in kernel", e); - } - } - } - - // Register client-side SPNEGO auth scheme - AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class); - HttpParams params = DefaultHttpParams.getDefaultParams(); - ArrayList 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); - } - } - - protected void preDestroy(AbstractUserDirectory userDirectory) { -// if (tmTracker.getService() instanceof BitronixTransactionManager) -// EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource()); - - Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); - if (realm != null) { - if (acceptorCredentials != null) { - try { - acceptorCredentials.dispose(); - } catch (GSSException e) { - // silent - } - acceptorCredentials = null; - } - } - } - - private String getKerberosServicePrincipal(String realm) { - String hostname; - try (DnsBrowser dnsBrowser = new DnsBrowser()) { - InetAddress localhost = InetAddress.getLocalHost(); - hostname = localhost.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 kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT"); - if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) { - return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain; - } else - return null; - } catch (Exception e) { - log.warn("Exception when determining kerberos principal", e); - return null; - } - } - - private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) { - // GSS - Iterator krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator(); - if (!krb5It.hasNext()) - return null; - KerberosPrincipal krb5Principal = null; - while (krb5It.hasNext()) { - KerberosPrincipal principal = krb5It.next(); - if (principal.getName().equals(servicePrincipal)) - krb5Principal = principal; - } - - if (krb5Principal == null) - return null; - - GSSManager manager = GSSManager.getInstance(); - try { - GSSName gssName = manager.createName(krb5Principal.getName(), null); - GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction() { - - @Override - public GSSCredential run() throws GSSException { - return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID, - GSSCredential.ACCEPT_ONLY); - - } - - }); - if (log.isDebugEnabled()) - log.debug("GSS acceptor configured for " + krb5Principal); - return serverCredentials; - } catch (Exception gsse) { - throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse); - } - } - - public GSSCredential getAcceptorCredentials() { - return acceptorCredentials; - } - - public boolean hasAcceptorCredentials() { - return acceptorCredentials != null; - } - - public boolean isSingleUser() { - return singleUser; - } - - public void setTransactionManager(WorkControl transactionManager) { - this.transactionManager = transactionManager; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - - /* - * STATIC - */ - - public final static Oid KERBEROS_OID; - static { - try { - KERBEROS_OID = new Oid("1.3.6.1.5.5.2"); - } catch (GSSException e) { - throw new IllegalStateException("Cannot create Kerberos OID", e); - } - } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java new file mode 100644 index 000000000..7c4d80774 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java @@ -0,0 +1,299 @@ +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.nio.file.Files; +import java.nio.file.Path; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Iterator; +import java.util.Set; + +import javax.naming.ldap.LdapName; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +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.cms.internal.http.client.HttpCredentialProvider; +import org.argeo.cms.internal.http.client.SpnegoAuthScheme; +import org.argeo.osgi.transaction.WorkControl; +import org.argeo.osgi.transaction.WorkTransaction; +import org.argeo.osgi.useradmin.AbstractUserDirectory; +import org.argeo.osgi.useradmin.AggregatingUserAdmin; +import org.argeo.osgi.useradmin.LdapUserAdmin; +import org.argeo.osgi.useradmin.LdifUserAdmin; +import org.argeo.osgi.useradmin.OsUserDirectory; +import org.argeo.osgi.useradmin.UserAdminConf; +import org.argeo.osgi.useradmin.UserDirectory; +import org.argeo.util.naming.DnsBrowser; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; + +/** + * Aggregates multiple {@link UserDirectory} and integrates them with system + * roles. + */ +public class CmsUserAdmin extends AggregatingUserAdmin { + private final static CmsLog log = CmsLog.getLog(CmsUserAdmin.class); + + // GSS API + private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH); + private GSSCredential acceptorCredentials; + + private boolean singleUser = false; + + private WorkControl transactionManager; + private WorkTransaction userTransaction; + + public CmsUserAdmin() { + super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN); + } + + public void start() { + } + + public void stop() { + } + + public UserDirectory enableUserDirectory(Dictionary properties) { + 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 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 (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); + } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) { + userDirectory = new OsUserDirectory(u, properties); + singleUser = true; + } else { + throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); + } + LdapName baseDn = userDirectory.getBaseDn(); + + addUserDirectory(userDirectory); + if (isSystemRolesBaseDn(baseDn)) { + addStandardSystemRoles(); + } + if (log.isDebugEnabled()) { + log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "") + + " enabled." + (realm != null ? " " + realm + " realm." : "")); + } + return userDirectory; + } + + + protected void addStandardSystemRoles() { + // we assume UserTransaction is already available (TODO make it more robust) + try { + userTransaction.begin(); + Role adminRole = getRole(CmsConstants.ROLE_ADMIN); + if (adminRole == null) { + adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP); + } + if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) { + Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP); + userAdminRole.addMember(adminRole); + } + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + // silent + } + throw new IllegalStateException("Cannot add standard system roles", e); + } + } + + + @Override + protected void addAbstractSystemRoles(Authorization rawAuthorization, Set sysRoles) { + if (rawAuthorization.getName() == null) { + sysRoles.add(CmsConstants.ROLE_ANONYMOUS); + } else { + sysRoles.add(CmsConstants.ROLE_USER); + } + } + + protected void postAdd(AbstractUserDirectory userDirectory) { + userDirectory.setTransactionControl(transactionManager); + + Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); + if (realm != null) { + if (Files.exists(nodeKeyTab)) { + String servicePrincipal = getKerberosServicePrincipal(realm.toString()); + if (servicePrincipal != null) { + CallbackHandler callbackHandler = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) + if (callback instanceof NameCallback) + ((NameCallback) callback).setName(servicePrincipal); + + } + }; + try { + LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler); + nodeLc.login(); + acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal); + } catch (LoginException e) { + throw new IllegalStateException("Cannot log in kernel", e); + } + } + } + + // Register client-side SPNEGO auth scheme + AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class); + HttpParams params = DefaultHttpParams.getDefaultParams(); + ArrayList 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); + } + } + + protected void preDestroy(AbstractUserDirectory userDirectory) { + Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); + if (realm != null) { + if (acceptorCredentials != null) { + try { + acceptorCredentials.dispose(); + } catch (GSSException e) { + // silent + } + acceptorCredentials = null; + } + } + } + + private String getKerberosServicePrincipal(String realm) { + String hostname; + try (DnsBrowser dnsBrowser = new DnsBrowser()) { + InetAddress localhost = InetAddress.getLocalHost(); + hostname = localhost.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 kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT"); + if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) { + return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain; + } else + return null; + } catch (Exception e) { + log.warn("Exception when determining kerberos principal", e); + return null; + } + } + + private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) { + // GSS + Iterator krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator(); + if (!krb5It.hasNext()) + return null; + KerberosPrincipal krb5Principal = null; + while (krb5It.hasNext()) { + KerberosPrincipal principal = krb5It.next(); + if (principal.getName().equals(servicePrincipal)) + krb5Principal = principal; + } + + if (krb5Principal == null) + return null; + + GSSManager manager = GSSManager.getInstance(); + try { + GSSName gssName = manager.createName(krb5Principal.getName(), null); + GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction() { + + @Override + public GSSCredential run() throws GSSException { + return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID, + GSSCredential.ACCEPT_ONLY); + + } + + }); + if (log.isDebugEnabled()) + log.debug("GSS acceptor configured for " + krb5Principal); + return serverCredentials; + } catch (Exception gsse) { + throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse); + } + } + + public GSSCredential getAcceptorCredentials() { + return acceptorCredentials; + } + + public boolean hasAcceptorCredentials() { + return acceptorCredentials != null; + } + + public boolean isSingleUser() { + return singleUser; + } + + public void setTransactionManager(WorkControl transactionManager) { + this.transactionManager = transactionManager; + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + /* + * STATIC + */ + + public final static Oid KERBEROS_OID; + static { + try { + KERBEROS_OID = new Oid("1.3.6.1.5.5.2"); + } catch (GSSException e) { + throw new IllegalStateException("Cannot create Kerberos OID", e); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java index 70ea9ec48..1ca5e4a55 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java @@ -238,15 +238,17 @@ public class InitUtils { // TODO also uncompress archives if (initDir.exists()) try { - FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() { + // TODO use NIO utilities + FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstancePath("").toFile(), + new FileFilter() { - @Override - public boolean accept(File pathname) { - if (pathname.getName().equals(".svn") || pathname.getName().equals(".git")) - return false; - return true; - } - }); + @Override + public boolean accept(File pathname) { + if (pathname.getName().equals(".svn") || pathname.getName().equals(".git")) + return false; + return true; + } + }); log.info("CMS initialized from " + initDir.getCanonicalPath()); } catch (IOException e) { throw new RuntimeException("Cannot initialize from " + initDir, e); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java index afcb9ff26..60c796af7 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java @@ -63,10 +63,10 @@ public class KernelUtils implements KernelConstants { } } - static File getOsgiInstanceDir() { - return new File(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) - .getAbsoluteFile(); - } +// static File getOsgiInstanceDir() { +// return new File(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) +// .getAbsoluteFile(); +// } public static Path getOsgiInstancePath(String relativePath) { return Paths.get(getOsgiInstanceUri(relativePath)); @@ -77,12 +77,13 @@ public class KernelUtils implements KernelConstants { if (osgiInstanceBaseUri != null) return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); else - return Paths.get(System.getProperty("user.dir")).toUri(); + return Paths.get(System.getProperty("user.dir"), (relativePath != null ? relativePath : "")).toUri(); } static File getOsgiConfigurationFile(String relativePath) { try { - return new File(new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) + return new File( + new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) .getCanonicalFile(); } catch (Exception e) { throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e); diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java new file mode 100644 index 000000000..5ffff1d0e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java @@ -0,0 +1,103 @@ +package org.argeo.cms.runtime; + +import java.util.Dictionary; + +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.internal.osgi.DeployConfig; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.internal.runtime.CmsDeploymentImpl; +import org.argeo.cms.internal.runtime.CmsStateImpl; +import org.argeo.cms.internal.runtime.CmsUserAdmin; +import org.argeo.osgi.transaction.SimpleTransactionManager; +import org.argeo.osgi.transaction.WorkControl; +import org.argeo.osgi.transaction.WorkTransaction; +import org.argeo.util.register.Component; +import org.argeo.util.register.ComponentRegister; +import org.argeo.util.register.StaticRegister; +import org.osgi.service.useradmin.UserAdmin; + +/** + * A CMS assembly which is programatically defined, as an alternative to OSGi + * deployment. Useful for testing or AOT compilation. + */ +public class StaticCms { + + public void start() { + ComponentRegister register = StaticRegister.getInstance(); + + // CMS State + CmsStateImpl cmsState = new CmsStateImpl(); + Component cmsStateC = new Component.Builder<>(cmsState) // + .addType(CmsState.class) // + .addActivation(cmsState::start) // + .addDeactivation(cmsState::stop) // + .build(register); + + // Deployment Configuration + DeployConfig deployConfig = new DeployConfig(); + Component deployConfigC = new Component.Builder<>(deployConfig) // + .addType(DeployConfig.class) // + .addActivation(deployConfig::start) // + .addDeactivation(deployConfig::stop) // + .build(register); + + // CMS Deployment + CmsDeploymentImpl cmsDeployment = new CmsDeploymentImpl(); + Component cmsDeploymentC = new Component.Builder<>(cmsDeployment) // + .addType(CmsDeployment.class) // + .addActivation(cmsDeployment::start) // + .addDeactivation(cmsDeployment::stop) // + .addDependency(cmsStateC.getType(CmsState.class), cmsDeployment::setCmsState, null) // + .addDependency(deployConfigC.getType(DeployConfig.class), cmsDeployment::setDeployConfig, null) // + .build(register); + + // Transaction manager + SimpleTransactionManager transactionManager = new SimpleTransactionManager(); + Component transactionManagerC = new Component.Builder<>(transactionManager) // + .addType(WorkControl.class) // + .addType(WorkTransaction.class) // + .build(register); + + // User Admin + CmsUserAdmin userAdmin = new CmsUserAdmin(); + + Component userAdminC = new Component.Builder<>(userAdmin) // + .addType(UserAdmin.class) // + .addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) // + .addDependency(transactionManagerC.getType(WorkTransaction.class), userAdmin::setUserTransaction, null) // + .addDependency(deployConfigC.getType(DeployConfig.class), (d) -> { + for (Dictionary userDirectoryConfig : d.getUserDirectoryConfigs()) + userAdmin.enableUserDirectory(userDirectoryConfig); + }, null) // + .build(register); + + // CMS Context + CmsContextImpl cmsContext = new CmsContextImpl(); + Component cmsContextC = new Component.Builder<>(cmsContext) // + .addType(CmsContext.class) // + .addActivation(cmsContext::start) // + .addDeactivation(cmsContext::stop) // + .addDependency(cmsStateC.getType(CmsState.class), cmsContext::setCmsState, null) // + .addDependency(cmsDeploymentC.getType(CmsDeployment.class), cmsContext::setCmsDeployment, null) // + .addDependency(userAdminC.getType(UserAdmin.class), cmsContext::setUserAdmin, null) // + .build(register); + assert cmsContextC.getInstance() == cmsContext; + + register.activate(); + } + + public void stop() { + if (StaticRegister.getInstance().isActive()) + StaticRegister.getInstance().deactivate(); + } + + public static void main(String[] args) { + StaticCms staticCms = new StaticCms(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown")); + staticCms.start(); + + } + +}