]> 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
125 protected void addStandardSystemRoles() {
126 // we assume UserTransaction is already available (TODO make it more robust)
127 try {
128 userTransaction.begin();
129 Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
130 if (adminRole == null) {
131 adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
132 }
133 if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
134 Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
135 userAdminRole.addMember(adminRole);
136 }
137 userTransaction.commit();
138 } catch (Exception e) {
139 try {
140 userTransaction.rollback();
141 } catch (Exception e1) {
142 // silent
143 }
144 throw new IllegalStateException("Cannot add standard system roles", e);
145 }
146 }
147
148
149 @Override
150 protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
151 if (rawAuthorization.getName() == null) {
152 sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
153 } else {
154 sysRoles.add(CmsConstants.ROLE_USER);
155 }
156 }
157
158 protected void postAdd(AbstractUserDirectory userDirectory) {
159 userDirectory.setTransactionControl(transactionManager);
160
161 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
162 if (realm != null) {
163 if (Files.exists(nodeKeyTab)) {
164 String servicePrincipal = getKerberosServicePrincipal(realm.toString());
165 if (servicePrincipal != null) {
166 CallbackHandler callbackHandler = new CallbackHandler() {
167 @Override
168 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
169 for (Callback callback : callbacks)
170 if (callback instanceof NameCallback)
171 ((NameCallback) callback).setName(servicePrincipal);
172
173 }
174 };
175 try {
176 LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
177 nodeLc.login();
178 acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
179 } catch (LoginException e) {
180 throw new IllegalStateException("Cannot log in kernel", e);
181 }
182 }
183 }
184
185 // Register client-side SPNEGO auth scheme
186 AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
187 HttpParams params = DefaultHttpParams.getDefaultParams();
188 ArrayList<String> schemes = new ArrayList<>();
189 schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
190 // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
191 params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
192 params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
193 params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
194 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
195 }
196 }
197
198 protected void preDestroy(AbstractUserDirectory userDirectory) {
199 Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
200 if (realm != null) {
201 if (acceptorCredentials != null) {
202 try {
203 acceptorCredentials.dispose();
204 } catch (GSSException e) {
205 // silent
206 }
207 acceptorCredentials = null;
208 }
209 }
210 }
211
212 private String getKerberosServicePrincipal(String realm) {
213 String hostname;
214 try (DnsBrowser dnsBrowser = new DnsBrowser()) {
215 InetAddress localhost = InetAddress.getLocalHost();
216 hostname = localhost.getHostName();
217 String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
218 String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
219 boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
220 String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
221 if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
222 return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
223 } else
224 return null;
225 } catch (Exception e) {
226 log.warn("Exception when determining kerberos principal", e);
227 return null;
228 }
229 }
230
231 private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
232 // GSS
233 Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
234 if (!krb5It.hasNext())
235 return null;
236 KerberosPrincipal krb5Principal = null;
237 while (krb5It.hasNext()) {
238 KerberosPrincipal principal = krb5It.next();
239 if (principal.getName().equals(servicePrincipal))
240 krb5Principal = principal;
241 }
242
243 if (krb5Principal == null)
244 return null;
245
246 GSSManager manager = GSSManager.getInstance();
247 try {
248 GSSName gssName = manager.createName(krb5Principal.getName(), null);
249 GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
250
251 @Override
252 public GSSCredential run() throws GSSException {
253 return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
254 GSSCredential.ACCEPT_ONLY);
255
256 }
257
258 });
259 if (log.isDebugEnabled())
260 log.debug("GSS acceptor configured for " + krb5Principal);
261 return serverCredentials;
262 } catch (Exception gsse) {
263 throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
264 }
265 }
266
267 public GSSCredential getAcceptorCredentials() {
268 return acceptorCredentials;
269 }
270
271 public boolean hasAcceptorCredentials() {
272 return acceptorCredentials != null;
273 }
274
275 public boolean isSingleUser() {
276 return singleUser;
277 }
278
279 public void setTransactionManager(WorkControl transactionManager) {
280 this.transactionManager = transactionManager;
281 }
282
283 public void setUserTransaction(WorkTransaction userTransaction) {
284 this.userTransaction = userTransaction;
285 }
286
287 /*
288 * STATIC
289 */
290
291 public final static Oid KERBEROS_OID;
292 static {
293 try {
294 KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
295 } catch (GSSException e) {
296 throw new IllegalStateException("Cannot create Kerberos OID", e);
297 }
298 }
299 }