X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FNodeUserAdmin.java;h=17daa3e14ae15b33f8af32f1dfe6560a9c6c04cb;hb=b71546ddc74d6ca49d252806aafd491c75dfe1fb;hp=9dd516137ebe72e268689df709c625132e290e22;hpb=25071ab6bcb2df1fa4057c2c04137f2d606772e7;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 9dd516137..17daa3e14 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -1,187 +1,348 @@ package org.argeo.cms.internal.kernel; +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.Arrays; +import java.util.Dictionary; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +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.transaction.Transaction; -import javax.transaction.TransactionManager; -import javax.transaction.TransactionSynchronizationRegistry; +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.argeo.cms.KernelHeader; +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.CmsUserManager; +import org.argeo.cms.internal.auth.CmsUserManagerImpl; +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.UserAdminAggregator; -import org.argeo.osgi.useradmin.UserDirectoryException; -import org.osgi.framework.InvalidSyntaxException; +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.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedServiceFactory; import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; +import org.osgi.util.tracker.ServiceTracker; -public class NodeUserAdmin implements UserAdmin, UserAdminAggregator { - final static LdapName ROLES_BASE; - static { - try { - ROLES_BASE = new LdapName(KernelHeader.ROLES_BASEDN); - } catch (InvalidNameException e) { - throw new UserDirectoryException("Cannot initialize " - + NodeUserAdmin.class, e); - } - } +/** + * Aggregates multiple {@link UserDirectory} and integrates them with system + * roles. + */ +class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants { + private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class); +// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - private UserAdmin nodeRoles = null; - private Map userAdmins = new HashMap(); + // OSGi + private Map pidToBaseDn = new HashMap<>(); +// private Map> pidToServiceRegs = new HashMap<>(); +// private ServiceRegistration userAdminReg; - private TransactionSynchronizationRegistry syncRegistry; - private TransactionManager transactionManager; + // JTA + private final ServiceTracker tmTracker; + // private final String cacheName = UserDirectory.class.getName(); - @Override - public Role createRole(String name, int type) { - return findUserAdmin(name).createRole(name, type); + // 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; + + public NodeUserAdmin(String systemRolesBaseDn, String tokensBaseDn) { + super(systemRolesBaseDn, tokensBaseDn); + 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; + } } @Override - public boolean removeRole(String name) { - return findUserAdmin(name).removeRole(name); + 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()); + } + addUserDirectory(userDirectory); + + // OSGi + LdapName baseDn = userDirectory.getBaseDn(); + 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); + Activator.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)) { + // publishes only when system roles are available + Dictionary userAdminregProps = new Hashtable<>(); + userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT); + userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); + Activator.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); +// } } @Override - public Role getRole(String name) { - return findUserAdmin(name).getRole(name); + public void deleted(String pid) { + // assert pidToServiceRegs.get(pid) != null; + assert pidToBaseDn.get(pid) != null; + // pidToServiceRegs.remove(pid).unregister(); + LdapName baseDn = pidToBaseDn.remove(pid); + removeUserDirectory(baseDn); } @Override - public Role[] getRoles(String filter) throws InvalidSyntaxException { - List res = new ArrayList(); - for (UserAdmin userAdmin : userAdmins.values()) { - res.addAll(Arrays.asList(userAdmin.getRoles(filter))); - } - res.addAll(Arrays.asList(nodeRoles.getRoles(filter))); - return res.toArray(new Role[res.size()]); + public String getName() { + return "Node User Admin"; } @Override - public User getUser(String key, String value) { - List res = new ArrayList(); - for (UserAdmin userAdmin : userAdmins.values()) { - User u = userAdmin.getUser(key, value); - if (u != null) - res.add(u); + protected void addAbstractSystemRoles(Authorization rawAuthorization, Set sysRoles) { + if (rawAuthorization.getName() == null) { + sysRoles.add(CmsConstants.ROLE_ANONYMOUS); + } else { + sysRoles.add(CmsConstants.ROLE_USER); } - // Note: node roles cannot contain users, so it is not searched - return res.size() == 1 ? res.get(0) : null; } - @Override - public Authorization getAuthorization(User user) { - UserAdmin userAdmin = findUserAdmin(user.getName()); - Authorization rawAuthorization = userAdmin.getAuthorization(user); - // gather system roles - Set systemRoles = new HashSet(); - for (String role : rawAuthorization.getRoles()) { - Authorization auth = nodeRoles.getAuthorization((User) userAdmin - .getRole(role)); - systemRoles.addAll(Arrays.asList(auth.getRoles())); + 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(tm); +// 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); } - return new NodeAuthorization(rawAuthorization.getName(), - rawAuthorization.toString(), systemRoles, - rawAuthorization.getRoles()); } - // - // USER ADMIN AGGREGATOR - // - @Override - public synchronized void addUserAdmin(String baseDn, UserAdmin userAdmin) { - if (userAdmin instanceof AbstractUserDirectory) - ((AbstractUserDirectory) userAdmin).setSyncRegistry(syncRegistry); + protected void preDestroy(AbstractUserDirectory userDirectory) { +// if (tmTracker.getService() instanceof BitronixTransactionManager) +// EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource()); - if (baseDn.equals(KernelHeader.ROLES_BASEDN)) { - nodeRoles = userAdmin; - return; + Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); + if (realm != null) { + if (acceptorCredentials != null) { + try { + acceptorCredentials.dispose(); + } catch (GSSException e) { + // silent + } + acceptorCredentials = null; + } } + } - if (userAdmins.containsKey(baseDn)) - throw new UserDirectoryException( - "There is already a user admin for " + baseDn); - try { - userAdmins.put(new LdapName(baseDn), userAdmin); - } catch (InvalidNameException e) { - throw new UserDirectoryException("Badly formatted base DN " - + baseDn, e); + 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; } } - @Override - public synchronized void removeUserAdmin(String baseDn) { - if (baseDn.equals(KernelHeader.ROLES_BASEDN)) - throw new UserDirectoryException("Node roles cannot be removed."); - LdapName base; - try { - base = new LdapName(baseDn); - } catch (InvalidNameException e) { - throw new UserDirectoryException("Badly formatted base DN " - + baseDn, e); + 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 (!userAdmins.containsKey(base)) - throw new UserDirectoryException("There is no user admin for " - + base); - UserAdmin userAdmin = userAdmins.remove(base); - if (userAdmin instanceof AbstractUserDirectory) - ((AbstractUserDirectory) userAdmin).setSyncRegistry(null); - } - private UserAdmin findUserAdmin(String name) { + if (krb5Principal == null) + return null; + + GSSManager manager = GSSManager.getInstance(); try { - return findUserAdmin(new LdapName(name)); - } catch (InvalidNameException e) { - throw new UserDirectoryException("Badly formatted name " + name, e); + 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); } } - private UserAdmin findUserAdmin(LdapName name) { - if (name.startsWith(ROLES_BASE)) - return nodeRoles; - List res = new ArrayList(1); - for (LdapName baseDn : userAdmins.keySet()) { - if (name.startsWith(baseDn)) - res.add(userAdmins.get(baseDn)); - } - if (res.size() == 0) - throw new UserDirectoryException("Cannot find user admin for " - + name); - if (res.size() > 1) - throw new UserDirectoryException("Multiple user admin found for " - + name); - return res.get(0); + public GSSCredential getAcceptorCredentials() { + return acceptorCredentials; } - public void setTransactionManager(TransactionManager transactionManager) { - this.transactionManager = transactionManager; - if (nodeRoles instanceof AbstractUserDirectory) - ((AbstractUserDirectory) nodeRoles) - .setTransactionManager(transactionManager); - for (UserAdmin userAdmin : userAdmins.values()) { - if (userAdmin instanceof AbstractUserDirectory) - ((AbstractUserDirectory) userAdmin) - .setTransactionManager(transactionManager); - } + public boolean isSingleUser() { + return singleUser; } - public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) { - this.syncRegistry = syncRegistry; - if (nodeRoles instanceof AbstractUserDirectory) - ((AbstractUserDirectory) nodeRoles).setSyncRegistry(syncRegistry); - for (UserAdmin userAdmin : userAdmins.values()) { - if (userAdmin instanceof AbstractUserDirectory) - ((AbstractUserDirectory) userAdmin) - .setSyncRegistry(syncRegistry); + 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); } }