]> git.argeo.org Git - lgpl/argeo-commons.git/blob - CmsUserAdmin.java
890e283914d9d2027c5e4964a07239c101b46ff9
[lgpl/argeo-commons.git] / 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.Optional;
15 import java.util.Set;
16
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.AggregatingUserAdmin;
39 import org.argeo.osgi.useradmin.LdapUserAdmin;
40 import org.argeo.osgi.useradmin.LdifUserAdmin;
41 import org.argeo.osgi.useradmin.OsUserDirectory;
42 import org.argeo.osgi.useradmin.UserAdminConf;
43 import org.argeo.osgi.useradmin.UserDirectory;
44 import org.argeo.util.naming.DnsBrowser;
45 import org.ietf.jgss.GSSCredential;
46 import org.ietf.jgss.GSSException;
47 import org.ietf.jgss.GSSManager;
48 import org.ietf.jgss.GSSName;
49 import org.ietf.jgss.Oid;
50 import org.osgi.service.useradmin.Authorization;
51 import org.osgi.service.useradmin.Group;
52 import org.osgi.service.useradmin.Role;
53
54 /**
55 * Aggregates multiple {@link UserDirectory} and integrates them with system
56 * roles.
57 */
58 public class CmsUserAdmin extends AggregatingUserAdmin {
59 private final static CmsLog log = CmsLog.getLog(CmsUserAdmin.class);
60
61 // GSS API
62 private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
63 private GSSCredential acceptorCredentials;
64
65 private boolean singleUser = false;
66
67 private WorkControl transactionManager;
68 private WorkTransaction userTransaction;
69
70 public CmsUserAdmin() {
71 super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
72 }
73
74 public void start() {
75 }
76
77 public void stop() {
78 }
79
80 public UserDirectory enableUserDirectory(Dictionary<String, ?> properties) {
81 String uri = (String) properties.get(UserAdminConf.uri.name());
82 Object realm = properties.get(UserAdminConf.realm.name());
83 URI u;
84 try {
85 if (uri == null) {
86 String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
87 u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
88 } else if (realm != null) {
89 u = null;
90 } else {
91 u = new URI(uri);
92 }
93 } catch (URISyntaxException e) {
94 throw new IllegalArgumentException("Badly formatted URI " + uri, e);
95 }
96
97 // Create
98 UserDirectory userDirectory;
99 if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
100 || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
101 userDirectory = new LdapUserAdmin(properties);
102 } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
103 userDirectory = new LdifUserAdmin(u, properties);
104 } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
105 userDirectory = new OsUserDirectory(u, properties);
106 singleUser = true;
107 } else {
108 throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
109 }
110 String basePath = userDirectory.getContext();
111
112 addUserDirectory(userDirectory);
113 if (isSystemRolesBaseDn(basePath)) {
114 addStandardSystemRoles();
115 }
116 if (log.isDebugEnabled()) {
117 log.debug("User directory " + userDirectory.getContext() + (u != null ? " [" + u.getScheme() + "]" : "")
118 + " enabled." + (realm != null ? " " + realm + " realm." : ""));
119 }
120 return userDirectory;
121 }
122
123 protected void addStandardSystemRoles() {
124 // we assume UserTransaction is already available (TODO make it more robust)
125 try {
126 userTransaction.begin();
127 Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
128 if (adminRole == null) {
129 adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
130 }
131 if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
132 Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
133 userAdminRole.addMember(adminRole);
134 }
135 userTransaction.commit();
136 } catch (Exception e) {
137 try {
138 userTransaction.rollback();
139 } catch (Exception e1) {
140 // silent
141 }
142 throw new IllegalStateException("Cannot add standard system roles", e);
143 }
144 }
145
146 @Override
147 protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
148 if (rawAuthorization.getName() == null) {
149 sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
150 } else {
151 sysRoles.add(CmsConstants.ROLE_USER);
152 }
153 }
154
155 @Override
156 protected void postAdd(UserDirectory userDirectory) {
157 userDirectory.setTransactionControl(transactionManager);
158
159 Optional<String> realm = userDirectory.getRealm();
160 if (realm.isPresent()) {
161 if (Files.exists(nodeKeyTab)) {
162 String servicePrincipal = getKerberosServicePrincipal(realm.get());
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 @Override
197 protected void preDestroy(UserDirectory userDirectory) {
198 Optional<String> realm = userDirectory.getRealm();
199 if (realm.isPresent()) {
200 if (acceptorCredentials != null) {
201 try {
202 acceptorCredentials.dispose();
203 } catch (GSSException e) {
204 // silent
205 }
206 acceptorCredentials = null;
207 }
208 }
209 }
210
211 private String getKerberosServicePrincipal(String realm) {
212 String hostname;
213 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
214 InetAddress localhost = InetAddress.getLocalHost();
215 hostname = localhost.getHostName();
216 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
217 String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
218 boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
219 String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
220 if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
221 return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
222 } else
223 return null;
224 } catch (Exception e) {
225 log.warn("Exception when determining kerberos principal", e);
226 return null;
227 }
228 }
229
230 private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
231 // not static because class is not supported by Android
232 final Oid KERBEROS_OID;
233 try {
234 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
235 } catch (GSSException e) {
236 throw new IllegalStateException("Cannot create Kerberos OID", e);
237 }
238 // GSS
239 Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
240 if (!krb5It.hasNext())
241 return null;
242 KerberosPrincipal krb5Principal = null;
243 while (krb5It.hasNext()) {
244 KerberosPrincipal principal = krb5It.next();
245 if (principal.getName().equals(servicePrincipal))
246 krb5Principal = principal;
247 }
248
249 if (krb5Principal == null)
250 return null;
251
252 GSSManager manager = GSSManager.getInstance();
253 try {
254 GSSName gssName = manager.createName(krb5Principal.getName(), null);
255 GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
256
257 @Override
258 public GSSCredential run() throws GSSException {
259 return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
260 GSSCredential.ACCEPT_ONLY);
261
262 }
263
264 });
265 if (log.isDebugEnabled())
266 log.debug("GSS acceptor configured for " + krb5Principal);
267 return serverCredentials;
268 } catch (Exception gsse) {
269 throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
270 }
271 }
272
273 public GSSCredential getAcceptorCredentials() {
274 return acceptorCredentials;
275 }
276
277 public boolean hasAcceptorCredentials() {
278 return acceptorCredentials != null;
279 }
280
281 public boolean isSingleUser() {
282 return singleUser;
283 }
284
285 public void setTransactionManager(WorkControl transactionManager) {
286 this.transactionManager = transactionManager;
287 }
288
289 public void setUserTransaction(WorkTransaction userTransaction) {
290 this.userTransaction = userTransaction;
291 }
292
293 /*
294 * STATIC
295 */
296
297 }