]> git.argeo.org Git - lgpl/argeo-commons.git/blob - runtime/CmsUserAdmin.java
Prepare next development cycle
[lgpl/argeo-commons.git] / runtime / CmsUserAdmin.java
1 package org.argeo.cms.internal.runtime;
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.Iterator;
14 import java.util.Set;
15
16 import javax.naming.ldap.LdapName;
17 import javax.security.auth.Subject;
18 import javax.security.auth.callback.Callback;
19 import javax.security.auth.callback.CallbackHandler;
20 import javax.security.auth.callback.NameCallback;
21 import javax.security.auth.callback.UnsupportedCallbackException;
22 import javax.security.auth.kerberos.KerberosPrincipal;
23 import javax.security.auth.login.LoginContext;
24 import javax.security.auth.login.LoginException;
25
26 import org.apache.commons.httpclient.auth.AuthPolicy;
27 import org.apache.commons.httpclient.auth.CredentialsProvider;
28 import org.apache.commons.httpclient.params.DefaultHttpParams;
29 import org.apache.commons.httpclient.params.HttpMethodParams;
30 import org.apache.commons.httpclient.params.HttpParams;
31 import org.argeo.api.cms.CmsAuth;
32 import org.argeo.api.cms.CmsConstants;
33 import org.argeo.api.cms.CmsLog;
34 import org.argeo.cms.internal.http.client.HttpCredentialProvider;
35 import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
36 import org.argeo.osgi.transaction.WorkControl;
37 import org.argeo.osgi.transaction.WorkTransaction;
38 import org.argeo.osgi.useradmin.AbstractUserDirectory;
39 import org.argeo.osgi.useradmin.AggregatingUserAdmin;
40 import org.argeo.osgi.useradmin.LdapUserAdmin;
41 import org.argeo.osgi.useradmin.LdifUserAdmin;
42 import org.argeo.osgi.useradmin.OsUserDirectory;
43 import org.argeo.osgi.useradmin.UserAdminConf;
44 import org.argeo.osgi.useradmin.UserDirectory;
45 import org.argeo.util.naming.DnsBrowser;
46 import org.ietf.jgss.GSSCredential;
47 import org.ietf.jgss.GSSException;
48 import org.ietf.jgss.GSSManager;
49 import org.ietf.jgss.GSSName;
50 import org.ietf.jgss.Oid;
51 import org.osgi.service.useradmin.Authorization;
52 import org.osgi.service.useradmin.Group;
53 import org.osgi.service.useradmin.Role;
54
55 /**
56 * Aggregates multiple {@link UserDirectory} and integrates them with system
57 * roles.
58 */
59 public class CmsUserAdmin extends AggregatingUserAdmin {
60 private final static CmsLog log = CmsLog.getLog(CmsUserAdmin.class);
61
62 // GSS API
63 private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
64 private GSSCredential acceptorCredentials;
65
66 private boolean singleUser = false;
67
68 private WorkControl transactionManager;
69 private WorkTransaction userTransaction;
70
71 public CmsUserAdmin() {
72 super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
73 }
74
75 public void start() {
76 }
77
78 public void stop() {
79 }
80
81 public UserDirectory enableUserDirectory(Dictionary<String, ?> properties) {
82 String uri = (String) properties.get(UserAdminConf.uri.name());
83 Object realm = properties.get(UserAdminConf.realm.name());
84 URI u;
85 try {
86 if (uri == null) {
87 String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
88 u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
89 } else if (realm != null) {
90 u = null;
91 } else {
92 u = new URI(uri);
93 }
94 } catch (URISyntaxException e) {
95 throw new IllegalArgumentException("Badly formatted URI " + uri, e);
96 }
97
98 // Create
99 AbstractUserDirectory userDirectory;
100 if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
101 || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
102 userDirectory = new LdapUserAdmin(properties);
103 } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
104 userDirectory = new LdifUserAdmin(u, properties);
105 } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
106 userDirectory = new OsUserDirectory(u, properties);
107 singleUser = true;
108 } else {
109 throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
110 }
111 LdapName baseDn = userDirectory.getBaseDn();
112
113 addUserDirectory(userDirectory);
114 if (isSystemRolesBaseDn(baseDn)) {
115 addStandardSystemRoles();
116 }
117 if (log.isDebugEnabled()) {
118 log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
119 + " enabled." + (realm != null ? " " + realm + " realm." : ""));
120 }
121 return userDirectory;
122 }
123
124 protected void addStandardSystemRoles() {
125 // we assume UserTransaction is already available (TODO make it more robust)
126 try {
127 userTransaction.begin();
128 Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
129 if (adminRole == null) {
130 adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
131 }
132 if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
133 Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
134 userAdminRole.addMember(adminRole);
135 }
136 userTransaction.commit();
137 } catch (Exception e) {
138 try {
139 userTransaction.rollback();
140 } catch (Exception e1) {
141 // silent
142 }
143 throw new IllegalStateException("Cannot add standard system roles", e);
144 }
145 }
146
147 @Override
148 protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
149 if (rawAuthorization.getName() == null) {
150 sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
151 } else {
152 sysRoles.add(CmsConstants.ROLE_USER);
153 }
154 }
155
156 protected void postAdd(AbstractUserDirectory userDirectory) {
157 userDirectory.setTransactionControl(transactionManager);
158
159 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
160 if (realm != null) {
161 if (Files.exists(nodeKeyTab)) {
162 String servicePrincipal = getKerberosServicePrincipal(realm.toString());
163 if (servicePrincipal != null) {
164 CallbackHandler callbackHandler = new CallbackHandler() {
165 @Override
166 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
167 for (Callback callback : callbacks)
168 if (callback instanceof NameCallback)
169 ((NameCallback) callback).setName(servicePrincipal);
170
171 }
172 };
173 try {
174 LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
175 nodeLc.login();
176 acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
177 } catch (LoginException e) {
178 throw new IllegalStateException("Cannot log in kernel", e);
179 }
180 }
181 }
182
183 // Register client-side SPNEGO auth scheme
184 AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
185 HttpParams params = DefaultHttpParams.getDefaultParams();
186 ArrayList<String> schemes = new ArrayList<>();
187 schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
188 // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
189 params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
190 params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
191 params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
192 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
193 }
194 }
195
196 protected void preDestroy(AbstractUserDirectory userDirectory) {
197 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
198 if (realm != null) {
199 if (acceptorCredentials != null) {
200 try {
201 acceptorCredentials.dispose();
202 } catch (GSSException e) {
203 // silent
204 }
205 acceptorCredentials = null;
206 }
207 }
208 }
209
210 private String getKerberosServicePrincipal(String realm) {
211 String hostname;
212 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
213 InetAddress localhost = InetAddress.getLocalHost();
214 hostname = localhost.getHostName();
215 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
216 String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
217 boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
218 String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
219 if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
220 return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
221 } else
222 return null;
223 } catch (Exception e) {
224 log.warn("Exception when determining kerberos principal", e);
225 return null;
226 }
227 }
228
229 private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
230 // not static because class is not supported by Android
231 final Oid KERBEROS_OID;
232 try {
233 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
234 } catch (GSSException e) {
235 throw new IllegalStateException("Cannot create Kerberos OID", e);
236 }
237 // GSS
238 Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
239 if (!krb5It.hasNext())
240 return null;
241 KerberosPrincipal krb5Principal = null;
242 while (krb5It.hasNext()) {
243 KerberosPrincipal principal = krb5It.next();
244 if (principal.getName().equals(servicePrincipal))
245 krb5Principal = principal;
246 }
247
248 if (krb5Principal == null)
249 return null;
250
251 GSSManager manager = GSSManager.getInstance();
252 try {
253 GSSName gssName = manager.createName(krb5Principal.getName(), null);
254 GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
255
256 @Override
257 public GSSCredential run() throws GSSException {
258 return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
259 GSSCredential.ACCEPT_ONLY);
260
261 }
262
263 });
264 if (log.isDebugEnabled())
265 log.debug("GSS acceptor configured for " + krb5Principal);
266 return serverCredentials;
267 } catch (Exception gsse) {
268 throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
269 }
270 }
271
272 public GSSCredential getAcceptorCredentials() {
273 return acceptorCredentials;
274 }
275
276 public boolean hasAcceptorCredentials() {
277 return acceptorCredentials != null;
278 }
279
280 public boolean isSingleUser() {
281 return singleUser;
282 }
283
284 public void setTransactionManager(WorkControl transactionManager) {
285 this.transactionManager = transactionManager;
286 }
287
288 public void setUserTransaction(WorkTransaction userTransaction) {
289 this.userTransaction = userTransaction;
290 }
291
292 /*
293 * STATIC
294 */
295
296 }