]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java
Make remote default workspace configurable via System properties.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / NodeUserAdmin.java
1 package org.argeo.cms.internal.kernel;
2
3 import java.io.IOException;
4 import java.net.Inet6Address;
5 import java.net.InetAddress;
6 import java.net.URI;
7 import java.net.URISyntaxException;
8 import java.nio.file.Files;
9 import java.nio.file.Path;
10 import java.security.PrivilegedExceptionAction;
11 import java.util.ArrayList;
12 import java.util.Dictionary;
13 import java.util.HashMap;
14 import java.util.Hashtable;
15 import java.util.Iterator;
16 import java.util.Map;
17 import java.util.Set;
18
19 import javax.naming.ldap.LdapName;
20 import javax.security.auth.Subject;
21 import javax.security.auth.callback.Callback;
22 import javax.security.auth.callback.CallbackHandler;
23 import javax.security.auth.callback.NameCallback;
24 import javax.security.auth.callback.UnsupportedCallbackException;
25 import javax.security.auth.kerberos.KerberosPrincipal;
26 import javax.security.auth.login.LoginContext;
27 import javax.security.auth.login.LoginException;
28 import javax.transaction.TransactionManager;
29
30 import org.apache.commons.httpclient.auth.AuthPolicy;
31 import org.apache.commons.httpclient.auth.CredentialsProvider;
32 import org.apache.commons.httpclient.params.DefaultHttpParams;
33 import org.apache.commons.httpclient.params.HttpMethodParams;
34 import org.apache.commons.httpclient.params.HttpParams;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.argeo.api.NodeConstants;
38 import org.argeo.cms.internal.http.client.HttpCredentialProvider;
39 import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
40 import org.argeo.naming.DnsBrowser;
41 import org.argeo.osgi.useradmin.AbstractUserDirectory;
42 import org.argeo.osgi.useradmin.AggregatingUserAdmin;
43 import org.argeo.osgi.useradmin.LdapUserAdmin;
44 import org.argeo.osgi.useradmin.LdifUserAdmin;
45 import org.argeo.osgi.useradmin.OsUserDirectory;
46 import org.argeo.osgi.useradmin.UserAdminConf;
47 import org.argeo.osgi.useradmin.UserDirectory;
48 import org.ietf.jgss.GSSCredential;
49 import org.ietf.jgss.GSSException;
50 import org.ietf.jgss.GSSManager;
51 import org.ietf.jgss.GSSName;
52 import org.ietf.jgss.Oid;
53 import org.osgi.framework.BundleContext;
54 import org.osgi.framework.Constants;
55 import org.osgi.service.cm.ConfigurationException;
56 import org.osgi.service.cm.ManagedServiceFactory;
57 import org.osgi.service.useradmin.Authorization;
58 import org.osgi.service.useradmin.UserAdmin;
59 import org.osgi.util.tracker.ServiceTracker;
60
61 /**
62 * Aggregates multiple {@link UserDirectory} and integrates them with system
63 * roles.
64 */
65 class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
66 private final static Log log = LogFactory.getLog(NodeUserAdmin.class);
67 // private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
68
69 // OSGi
70 private Map<String, LdapName> pidToBaseDn = new HashMap<>();
71 // private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
72 // private ServiceRegistration<UserAdmin> userAdminReg;
73
74 // JTA
75 private final ServiceTracker<TransactionManager, TransactionManager> tmTracker;
76 // private final String cacheName = UserDirectory.class.getName();
77
78 // GSS API
79 private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
80 private GSSCredential acceptorCredentials;
81
82 private boolean singleUser = false;
83 // private boolean systemRolesAvailable = false;
84
85 public NodeUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
86 super(systemRolesBaseDn, tokensBaseDn);
87 BundleContext bc = Activator.getBundleContext();
88 if (bc != null) {
89 tmTracker = new ServiceTracker<>(bc, TransactionManager.class, null);
90 tmTracker.open();
91 } else {
92 tmTracker = null;
93 }
94 }
95
96 @Override
97 public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
98 String uri = (String) properties.get(UserAdminConf.uri.name());
99 URI u;
100 try {
101 if (uri == null) {
102 String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
103 u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
104 } else
105 u = new URI(uri);
106 } catch (URISyntaxException e) {
107 throw new IllegalArgumentException("Badly formatted URI " + uri, e);
108 }
109
110 // Create
111 AbstractUserDirectory userDirectory;
112 if (UserAdminConf.SCHEME_LDAP.equals(u.getScheme())) {
113 userDirectory = new LdapUserAdmin(properties);
114 } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
115 userDirectory = new LdifUserAdmin(u, properties);
116 } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
117 userDirectory = new OsUserDirectory(u, properties);
118 singleUser = true;
119 } else {
120 throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
121 }
122 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
123 addUserDirectory(userDirectory);
124
125 // OSGi
126 LdapName baseDn = userDirectory.getBaseDn();
127 Dictionary<String, Object> regProps = new Hashtable<>();
128 regProps.put(Constants.SERVICE_PID, pid);
129 if (isSystemRolesBaseDn(baseDn))
130 regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
131 regProps.put(UserAdminConf.baseDn.name(), baseDn);
132 // ServiceRegistration<UserDirectory> reg =
133 // bc.registerService(UserDirectory.class, userDirectory, regProps);
134 Activator.registerService(UserDirectory.class, userDirectory, regProps);
135 pidToBaseDn.put(pid, baseDn);
136 // pidToServiceRegs.put(pid, reg);
137
138 if (log.isDebugEnabled())
139 log.debug("User directory " + userDirectory.getBaseDn() + " [" + u.getScheme() + "] enabled."
140 + (realm != null ? " " + realm + " realm." : ""));
141
142 if (isSystemRolesBaseDn(baseDn)) {
143 // publishes only when system roles are available
144 Dictionary<String, Object> userAdminregProps = new Hashtable<>();
145 userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
146 userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
147 Activator.registerService(UserAdmin.class, this, userAdminregProps);
148 }
149
150 // if (isSystemRolesBaseDn(baseDn))
151 // systemRolesAvailable = true;
152 //
153 // // start publishing only when system roles are available
154 // if (systemRolesAvailable) {
155 // // The list of baseDns is published as properties
156 // // TODO clients should rather reference USerDirectory services
157 // if (userAdminReg != null)
158 // userAdminReg.unregister();
159 // // register self as main user admin
160 // Dictionary<String, Object> userAdminregProps = currentState();
161 // userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
162 // userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
163 // userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
164 // }
165 }
166
167 @Override
168 public void deleted(String pid) {
169 // assert pidToServiceRegs.get(pid) != null;
170 assert pidToBaseDn.get(pid) != null;
171 // pidToServiceRegs.remove(pid).unregister();
172 LdapName baseDn = pidToBaseDn.remove(pid);
173 removeUserDirectory(baseDn);
174 }
175
176 @Override
177 public String getName() {
178 return "Node User Admin";
179 }
180
181 @Override
182 protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
183 if (rawAuthorization.getName() == null) {
184 sysRoles.add(NodeConstants.ROLE_ANONYMOUS);
185 } else {
186 sysRoles.add(NodeConstants.ROLE_USER);
187 }
188 }
189
190 protected void postAdd(AbstractUserDirectory userDirectory) {
191 // JTA
192 TransactionManager tm = tmTracker != null ? tmTracker.getService() : null;
193 if (tm == null)
194 throw new IllegalStateException("A JTA transaction manager must be available.");
195 userDirectory.setTransactionManager(tm);
196 // if (tmTracker.getService() instanceof BitronixTransactionManager)
197 // EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
198
199 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
200 if (realm != null) {
201 if (Files.exists(nodeKeyTab)) {
202 String servicePrincipal = getKerberosServicePrincipal(realm.toString());
203 if (servicePrincipal != null) {
204 CallbackHandler callbackHandler = new CallbackHandler() {
205 @Override
206 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
207 for (Callback callback : callbacks)
208 if (callback instanceof NameCallback)
209 ((NameCallback) callback).setName(servicePrincipal);
210
211 }
212 };
213 try {
214 LoginContext nodeLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, callbackHandler);
215 nodeLc.login();
216 acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
217 } catch (LoginException e) {
218 throw new IllegalStateException("Cannot log in kernel", e);
219 }
220 }
221 }
222
223 // Register client-side SPNEGO auth scheme
224 AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
225 HttpParams params = DefaultHttpParams.getDefaultParams();
226 ArrayList<String> schemes = new ArrayList<>();
227 schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
228 // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
229 params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
230 params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
231 params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
232 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
233 }
234 }
235
236 protected void preDestroy(AbstractUserDirectory userDirectory) {
237 // if (tmTracker.getService() instanceof BitronixTransactionManager)
238 // EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
239
240 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
241 if (realm != null) {
242 if (acceptorCredentials != null) {
243 try {
244 acceptorCredentials.dispose();
245 } catch (GSSException e) {
246 // silent
247 }
248 acceptorCredentials = null;
249 }
250 }
251 }
252
253 private String getKerberosServicePrincipal(String realm) {
254 String hostname;
255 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
256 InetAddress localhost = InetAddress.getLocalHost();
257 hostname = localhost.getHostName();
258 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
259 String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
260 boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
261 String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
262 if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
263 return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
264 } else
265 return null;
266 } catch (Exception e) {
267 log.warn("Exception when determining kerberos principal", e);
268 return null;
269 }
270 }
271
272 private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
273 // GSS
274 Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
275 if (!krb5It.hasNext())
276 return null;
277 KerberosPrincipal krb5Principal = null;
278 while (krb5It.hasNext()) {
279 KerberosPrincipal principal = krb5It.next();
280 if (principal.getName().equals(servicePrincipal))
281 krb5Principal = principal;
282 }
283
284 if (krb5Principal == null)
285 return null;
286
287 GSSManager manager = GSSManager.getInstance();
288 try {
289 GSSName gssName = manager.createName(krb5Principal.getName(), null);
290 GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
291
292 @Override
293 public GSSCredential run() throws GSSException {
294 return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
295 GSSCredential.ACCEPT_ONLY);
296
297 }
298
299 });
300 if (log.isDebugEnabled())
301 log.debug("GSS acceptor configured for " + krb5Principal);
302 return serverCredentials;
303 } catch (Exception gsse) {
304 throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
305 }
306 }
307
308 public GSSCredential getAcceptorCredentials() {
309 return acceptorCredentials;
310 }
311
312 public boolean isSingleUser() {
313 return singleUser;
314 }
315
316 public final static Oid KERBEROS_OID;
317 static {
318 try {
319 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
320 } catch (GSSException e) {
321 throw new IllegalStateException("Cannot create Kerberos OID", e);
322 }
323 }
324
325 }