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