1 package org
.argeo
.cms
.internal
.osgi
;
3 import java
.io
.IOException
;
4 import java
.net
.Inet6Address
;
5 import java
.net
.InetAddress
;
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
;
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
;
29 import org
.apache
.commons
.httpclient
.auth
.AuthPolicy
;
30 import org
.apache
.commons
.httpclient
.auth
.CredentialsProvider
;
31 import org
.apache
.commons
.httpclient
.params
.DefaultHttpParams
;
32 import org
.apache
.commons
.httpclient
.params
.HttpMethodParams
;
33 import org
.apache
.commons
.httpclient
.params
.HttpParams
;
34 import org
.argeo
.api
.cms
.CmsAuth
;
35 import org
.argeo
.api
.cms
.CmsConstants
;
36 import org
.argeo
.api
.cms
.CmsLog
;
37 import org
.argeo
.cms
.internal
.http
.client
.HttpCredentialProvider
;
38 import org
.argeo
.cms
.internal
.http
.client
.SpnegoAuthScheme
;
39 import org
.argeo
.cms
.internal
.runtime
.KernelConstants
;
40 import org
.argeo
.cms
.internal
.runtime
.KernelUtils
;
41 import org
.argeo
.osgi
.transaction
.WorkControl
;
42 import org
.argeo
.osgi
.transaction
.WorkTransaction
;
43 import org
.argeo
.osgi
.useradmin
.AbstractUserDirectory
;
44 import org
.argeo
.osgi
.useradmin
.AggregatingUserAdmin
;
45 import org
.argeo
.osgi
.useradmin
.LdapUserAdmin
;
46 import org
.argeo
.osgi
.useradmin
.LdifUserAdmin
;
47 import org
.argeo
.osgi
.useradmin
.OsUserDirectory
;
48 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
49 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
50 import org
.argeo
.util
.naming
.DnsBrowser
;
51 import org
.ietf
.jgss
.GSSCredential
;
52 import org
.ietf
.jgss
.GSSException
;
53 import org
.ietf
.jgss
.GSSManager
;
54 import org
.ietf
.jgss
.GSSName
;
55 import org
.ietf
.jgss
.Oid
;
56 import org
.osgi
.framework
.Constants
;
57 import org
.osgi
.service
.cm
.ConfigurationException
;
58 import org
.osgi
.service
.cm
.ManagedServiceFactory
;
59 import org
.osgi
.service
.useradmin
.Authorization
;
60 import org
.osgi
.service
.useradmin
.Group
;
61 import org
.osgi
.service
.useradmin
.Role
;
62 import org
.osgi
.service
.useradmin
.UserAdmin
;
65 * Aggregates multiple {@link UserDirectory} and integrates them with system
68 public class NodeUserAdmin
extends AggregatingUserAdmin
implements ManagedServiceFactory
, KernelConstants
{
69 private final static CmsLog log
= CmsLog
.getLog(NodeUserAdmin
.class);
72 private Map
<String
, LdapName
> pidToBaseDn
= new HashMap
<>();
73 // private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
74 // private ServiceRegistration<UserAdmin> userAdminReg;
77 // private final ServiceTracker<WorkControl, WorkControl> tmTracker;
78 // private final String cacheName = UserDirectory.class.getName();
81 private Path nodeKeyTab
= KernelUtils
.getOsgiInstancePath(KernelConstants
.NODE_KEY_TAB_PATH
);
82 private GSSCredential acceptorCredentials
;
84 private boolean singleUser
= false;
85 // private boolean systemRolesAvailable = false;
87 // CmsUserManagerImpl userManager;
88 private WorkControl transactionManager
;
89 private WorkTransaction userTransaction
;
91 public NodeUserAdmin() {
92 super(CmsConstants
.ROLES_BASEDN
, CmsConstants
.TOKENS_BASEDN
);
93 // BundleContext bc = Activator.getBundleContext();
95 // tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) {
98 // public WorkControl addingService(ServiceReference<WorkControl> reference) {
99 // WorkControl workControl = super.addingService(reference);
100 // userManager = new CmsUserManagerImpl();
101 // userManager.setUserAdmin(NodeUserAdmin.this);
102 // // FIXME make it more robust
103 // userManager.setUserTransaction((WorkTransaction) workControl);
104 // bc.registerService(CmsUserManager.class, userManager, null);
105 // return workControl;
114 public void start() {
121 public void updated(String pid
, Dictionary
<String
, ?
> properties
) throws ConfigurationException
{
122 String uri
= (String
) properties
.get(UserAdminConf
.uri
.name());
123 Object realm
= properties
.get(UserAdminConf
.realm
.name());
127 String baseDn
= (String
) properties
.get(UserAdminConf
.baseDn
.name());
128 u
= KernelUtils
.getOsgiInstanceUri(KernelConstants
.DIR_NODE
+ '/' + baseDn
+ ".ldif");
129 } else if (realm
!= null) {
134 } catch (URISyntaxException e
) {
135 throw new IllegalArgumentException("Badly formatted URI " + uri
, e
);
139 AbstractUserDirectory userDirectory
;
140 if (realm
!= null || UserAdminConf
.SCHEME_LDAP
.equals(u
.getScheme())
141 || UserAdminConf
.SCHEME_LDAPS
.equals(u
.getScheme())) {
142 userDirectory
= new LdapUserAdmin(properties
);
143 } else if (UserAdminConf
.SCHEME_FILE
.equals(u
.getScheme())) {
144 userDirectory
= new LdifUserAdmin(u
, properties
);
145 } else if (UserAdminConf
.SCHEME_OS
.equals(u
.getScheme())) {
146 userDirectory
= new OsUserDirectory(u
, properties
);
149 throw new IllegalArgumentException("Unsupported scheme " + u
.getScheme());
151 LdapName baseDn
= userDirectory
.getBaseDn();
153 // FIXME make updates more robust
154 if (pidToBaseDn
.containsValue(baseDn
)) {
155 if (log
.isDebugEnabled())
156 log
.debug("Ignoring user directory update of " + baseDn
);
160 addUserDirectory(userDirectory
);
163 Hashtable
<String
, Object
> regProps
= new Hashtable
<>();
164 regProps
.put(Constants
.SERVICE_PID
, pid
);
165 if (isSystemRolesBaseDn(baseDn
))
166 regProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
167 regProps
.put(UserAdminConf
.baseDn
.name(), baseDn
);
168 // ServiceRegistration<UserDirectory> reg =
169 // bc.registerService(UserDirectory.class, userDirectory, regProps);
170 CmsActivator
.getBundleContext().registerService(UserDirectory
.class, userDirectory
, regProps
);
171 // userManager.addUserDirectory(userDirectory, regProps);
172 pidToBaseDn
.put(pid
, baseDn
);
173 // pidToServiceRegs.put(pid, reg);
175 if (log
.isDebugEnabled()) {
176 log
.debug("User directory " + userDirectory
.getBaseDn() + (u
!= null ?
" [" + u
.getScheme() + "]" : "")
177 + " enabled." + (realm
!= null ?
" " + realm
+ " realm." : ""));
180 if (isSystemRolesBaseDn(baseDn
)) {
181 addStandardSystemRoles();
183 // publishes itself as user admin only when system roles are available
184 Dictionary
<String
, Object
> userAdminregProps
= new Hashtable
<>();
185 userAdminregProps
.put(CmsConstants
.CN
, CmsConstants
.DEFAULT
);
186 userAdminregProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
187 CmsActivator
.getBundleContext().registerService(UserAdmin
.class, this, userAdminregProps
);
190 // if (isSystemRolesBaseDn(baseDn))
191 // systemRolesAvailable = true;
193 // // start publishing only when system roles are available
194 // if (systemRolesAvailable) {
195 // // The list of baseDns is published as properties
196 // // TODO clients should rather reference USerDirectory services
197 // if (userAdminReg != null)
198 // userAdminReg.unregister();
199 // // register self as main user admin
200 // Dictionary<String, Object> userAdminregProps = currentState();
201 // userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
202 // userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
203 // userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
207 private void addStandardSystemRoles() {
208 // we assume UserTransaction is already available (TODO make it more robust)
210 userTransaction
.begin();
211 Role adminRole
= getRole(CmsConstants
.ROLE_ADMIN
);
212 if (adminRole
== null) {
213 adminRole
= createRole(CmsConstants
.ROLE_ADMIN
, Role
.GROUP
);
215 if (getRole(CmsConstants
.ROLE_USER_ADMIN
) == null) {
216 Group userAdminRole
= (Group
) createRole(CmsConstants
.ROLE_USER_ADMIN
, Role
.GROUP
);
217 userAdminRole
.addMember(adminRole
);
219 userTransaction
.commit();
220 } catch (Exception e
) {
222 userTransaction
.rollback();
223 } catch (Exception e1
) {
226 throw new IllegalStateException("Cannot add standard system roles", e
);
231 public void deleted(String pid
) {
232 // assert pidToServiceRegs.get(pid) != null;
233 assert pidToBaseDn
.get(pid
) != null;
234 // pidToServiceRegs.remove(pid).unregister();
235 LdapName baseDn
= pidToBaseDn
.remove(pid
);
236 removeUserDirectory(baseDn
);
240 public String
getName() {
241 return "Node User Admin";
245 protected void addAbstractSystemRoles(Authorization rawAuthorization
, Set
<String
> sysRoles
) {
246 if (rawAuthorization
.getName() == null) {
247 sysRoles
.add(CmsConstants
.ROLE_ANONYMOUS
);
249 sysRoles
.add(CmsConstants
.ROLE_USER
);
253 protected void postAdd(AbstractUserDirectory userDirectory
) {
255 // WorkControl tm = tmTracker != null ? tmTracker.getService() : null;
257 // throw new IllegalStateException("A JTA transaction manager must be available.");
258 userDirectory
.setTransactionControl(transactionManager
);
259 // if (tmTracker.getService() instanceof BitronixTransactionManager)
260 // EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
262 Object realm
= userDirectory
.getProperties().get(UserAdminConf
.realm
.name());
264 if (Files
.exists(nodeKeyTab
)) {
265 String servicePrincipal
= getKerberosServicePrincipal(realm
.toString());
266 if (servicePrincipal
!= null) {
267 CallbackHandler callbackHandler
= new CallbackHandler() {
269 public void handle(Callback
[] callbacks
) throws IOException
, UnsupportedCallbackException
{
270 for (Callback callback
: callbacks
)
271 if (callback
instanceof NameCallback
)
272 ((NameCallback
) callback
).setName(servicePrincipal
);
277 LoginContext nodeLc
= new LoginContext(CmsAuth
.LOGIN_CONTEXT_NODE
, callbackHandler
);
279 acceptorCredentials
= logInAsAcceptor(nodeLc
.getSubject(), servicePrincipal
);
280 } catch (LoginException e
) {
281 throw new IllegalStateException("Cannot log in kernel", e
);
286 // Register client-side SPNEGO auth scheme
287 AuthPolicy
.registerAuthScheme(SpnegoAuthScheme
.NAME
, SpnegoAuthScheme
.class);
288 HttpParams params
= DefaultHttpParams
.getDefaultParams();
289 ArrayList
<String
> schemes
= new ArrayList
<>();
290 schemes
.add(SpnegoAuthScheme
.NAME
);// SPNEGO preferred
291 // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
292 params
.setParameter(AuthPolicy
.AUTH_SCHEME_PRIORITY
, schemes
);
293 params
.setParameter(CredentialsProvider
.PROVIDER
, new HttpCredentialProvider());
294 params
.setParameter(HttpMethodParams
.COOKIE_POLICY
, KernelConstants
.COOKIE_POLICY_BROWSER_COMPATIBILITY
);
295 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
299 protected void preDestroy(AbstractUserDirectory userDirectory
) {
300 // if (tmTracker.getService() instanceof BitronixTransactionManager)
301 // EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
303 Object realm
= userDirectory
.getProperties().get(UserAdminConf
.realm
.name());
305 if (acceptorCredentials
!= null) {
307 acceptorCredentials
.dispose();
308 } catch (GSSException e
) {
311 acceptorCredentials
= null;
316 private String
getKerberosServicePrincipal(String realm
) {
318 try (DnsBrowser dnsBrowser
= new DnsBrowser()) {
319 InetAddress localhost
= InetAddress
.getLocalHost();
320 hostname
= localhost
.getHostName();
321 String dnsZone
= hostname
.substring(hostname
.indexOf('.') + 1);
322 String ipfromDns
= dnsBrowser
.getRecord(hostname
, localhost
instanceof Inet6Address ?
"AAAA" : "A");
323 boolean consistentIp
= localhost
.getHostAddress().equals(ipfromDns
);
324 String kerberosDomain
= dnsBrowser
.getRecord("_kerberos." + dnsZone
, "TXT");
325 if (consistentIp
&& kerberosDomain
!= null && kerberosDomain
.equals(realm
) && Files
.exists(nodeKeyTab
)) {
326 return KernelConstants
.DEFAULT_KERBEROS_SERVICE
+ "/" + hostname
+ "@" + kerberosDomain
;
329 } catch (Exception e
) {
330 log
.warn("Exception when determining kerberos principal", e
);
335 private GSSCredential
logInAsAcceptor(Subject subject
, String servicePrincipal
) {
337 Iterator
<KerberosPrincipal
> krb5It
= subject
.getPrincipals(KerberosPrincipal
.class).iterator();
338 if (!krb5It
.hasNext())
340 KerberosPrincipal krb5Principal
= null;
341 while (krb5It
.hasNext()) {
342 KerberosPrincipal principal
= krb5It
.next();
343 if (principal
.getName().equals(servicePrincipal
))
344 krb5Principal
= principal
;
347 if (krb5Principal
== null)
350 GSSManager manager
= GSSManager
.getInstance();
352 GSSName gssName
= manager
.createName(krb5Principal
.getName(), null);
353 GSSCredential serverCredentials
= Subject
.doAs(subject
, new PrivilegedExceptionAction
<GSSCredential
>() {
356 public GSSCredential
run() throws GSSException
{
357 return manager
.createCredential(gssName
, GSSCredential
.INDEFINITE_LIFETIME
, KERBEROS_OID
,
358 GSSCredential
.ACCEPT_ONLY
);
363 if (log
.isDebugEnabled())
364 log
.debug("GSS acceptor configured for " + krb5Principal
);
365 return serverCredentials
;
366 } catch (Exception gsse
) {
367 throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal
, gsse
);
371 public GSSCredential
getAcceptorCredentials() {
372 return acceptorCredentials
;
375 public boolean hasAcceptorCredentials() {
376 return acceptorCredentials
!= null;
379 public boolean isSingleUser() {
383 public void setTransactionManager(WorkControl transactionManager
) {
384 this.transactionManager
= transactionManager
;
387 public void setUserTransaction(WorkTransaction userTransaction
) {
388 this.userTransaction
= userTransaction
;
395 public final static Oid KERBEROS_OID
;
398 KERBEROS_OID
= new Oid("1.3.6.1.5.5.2");
399 } catch (GSSException e
) {
400 throw new IllegalStateException("Cannot create Kerberos OID", e
);