1 package org
.argeo
.cms
.internal
.kernel
;
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
;
28 import javax
.transaction
.TransactionManager
;
30 import org
.apache
.commons
.httpclient
.auth
.AuthPolicy
;
31 import org
.apache
.commons
.httpclient
.auth
.CredentialsProvider
;
32 import org
.apache
.commons
.httpclient
.params
.DefaultHttpParams
;
33 import org
.apache
.commons
.httpclient
.params
.HttpMethodParams
;
34 import org
.apache
.commons
.httpclient
.params
.HttpParams
;
35 import org
.apache
.commons
.logging
.Log
;
36 import org
.apache
.commons
.logging
.LogFactory
;
37 import org
.argeo
.cms
.CmsException
;
38 import org
.argeo
.cms
.internal
.http
.client
.HttpCredentialProvider
;
39 import org
.argeo
.cms
.internal
.http
.client
.SpnegoAuthScheme
;
40 import org
.argeo
.naming
.DnsBrowser
;
41 import org
.argeo
.node
.NodeConstants
;
42 import org
.argeo
.osgi
.useradmin
.AbstractUserDirectory
;
43 import org
.argeo
.osgi
.useradmin
.AggregatingUserAdmin
;
44 import org
.argeo
.osgi
.useradmin
.LdapUserAdmin
;
45 import org
.argeo
.osgi
.useradmin
.LdifUserAdmin
;
46 import org
.argeo
.osgi
.useradmin
.OsUserDirectory
;
47 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
48 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
49 import org
.ietf
.jgss
.GSSCredential
;
50 import org
.ietf
.jgss
.GSSException
;
51 import org
.ietf
.jgss
.GSSManager
;
52 import org
.ietf
.jgss
.GSSName
;
53 import org
.ietf
.jgss
.Oid
;
54 import org
.osgi
.framework
.BundleContext
;
55 import org
.osgi
.framework
.Constants
;
56 import org
.osgi
.framework
.FrameworkUtil
;
57 import org
.osgi
.framework
.ServiceRegistration
;
58 import org
.osgi
.service
.cm
.ConfigurationException
;
59 import org
.osgi
.service
.cm
.ManagedServiceFactory
;
60 import org
.osgi
.service
.useradmin
.Authorization
;
61 import org
.osgi
.service
.useradmin
.UserAdmin
;
62 import org
.osgi
.util
.tracker
.ServiceTracker
;
64 import bitronix
.tm
.BitronixTransactionManager
;
65 import bitronix
.tm
.resource
.ehcache
.EhCacheXAResourceProducer
;
68 * Aggregates multiple {@link UserDirectory} and integrates them with system
71 class NodeUserAdmin
extends AggregatingUserAdmin
implements ManagedServiceFactory
, KernelConstants
{
72 private final static Log log
= LogFactory
.getLog(NodeUserAdmin
.class);
73 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
76 private Map
<String
, LdapName
> pidToBaseDn
= new HashMap
<>();
77 private Map
<String
, ServiceRegistration
<UserDirectory
>> pidToServiceRegs
= new HashMap
<>();
78 private ServiceRegistration
<UserAdmin
> userAdminReg
;
81 private final ServiceTracker
<TransactionManager
, TransactionManager
> tmTracker
;
82 private final String cacheName
= UserDirectory
.class.getName();
85 private Path nodeKeyTab
= KernelUtils
.getOsgiInstancePath(KernelConstants
.NODE_KEY_TAB_PATH
);
86 private GSSCredential acceptorCredentials
;
88 private boolean singleUser
= false;
89 private boolean systemRolesAvailable
= false;
91 public NodeUserAdmin(String systemRolesBaseDn
) {
92 super(systemRolesBaseDn
);
93 tmTracker
= new ServiceTracker
<>(bc
, TransactionManager
.class, null);
98 public void updated(String pid
, Dictionary
<String
, ?
> properties
) throws ConfigurationException
{
99 String uri
= (String
) properties
.get(UserAdminConf
.uri
.name());
103 String baseDn
= (String
) properties
.get(UserAdminConf
.baseDn
.name());
104 u
= KernelUtils
.getOsgiInstanceUri(KernelConstants
.DIR_NODE
+ '/' + baseDn
+ ".ldif");
107 } catch (URISyntaxException e
) {
108 throw new CmsException("Badly formatted URI " + uri
, e
);
112 AbstractUserDirectory userDirectory
;
113 if (UserAdminConf
.SCHEME_LDAP
.equals(u
.getScheme())) {
114 userDirectory
= new LdapUserAdmin(properties
);
115 } else if (UserAdminConf
.SCHEME_FILE
.equals(u
.getScheme())) {
116 userDirectory
= new LdifUserAdmin(u
, properties
);
117 } else if (UserAdminConf
.SCHEME_OS
.equals(u
.getScheme())) {
118 userDirectory
= new OsUserDirectory(u
, properties
);
121 throw new CmsException("Unsupported scheme " + u
.getScheme());
123 Object realm
= userDirectory
.getProperties().get(UserAdminConf
.realm
.name());
124 addUserDirectory(userDirectory
);
127 LdapName baseDn
= userDirectory
.getBaseDn();
128 Dictionary
<String
, Object
> regProps
= new Hashtable
<>();
129 regProps
.put(Constants
.SERVICE_PID
, pid
);
130 if (isSystemRolesBaseDn(baseDn
))
131 regProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
132 regProps
.put(UserAdminConf
.baseDn
.name(), baseDn
);
133 ServiceRegistration
<UserDirectory
> reg
= bc
.registerService(UserDirectory
.class, userDirectory
, regProps
);
134 pidToBaseDn
.put(pid
, baseDn
);
135 pidToServiceRegs
.put(pid
, reg
);
137 if (log
.isDebugEnabled())
138 log
.debug("User directory " + userDirectory
.getBaseDn() + " [" + u
.getScheme() + "] enabled."
139 + (realm
!= null ?
" " + realm
+ " realm." : ""));
141 if (isSystemRolesBaseDn(baseDn
))
142 systemRolesAvailable
= true;
144 // start publishing only when system roles are available
145 if (systemRolesAvailable
) {
146 // The list of baseDns is published as properties
147 // TODO clients should rather reference USerDirectory services
148 if (userAdminReg
!= null)
149 userAdminReg
.unregister();
150 // register self as main user admin
151 Dictionary
<String
, Object
> userAdminregProps
= currentState();
152 userAdminregProps
.put(NodeConstants
.CN
, NodeConstants
.DEFAULT
);
153 userAdminregProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
154 userAdminReg
= bc
.registerService(UserAdmin
.class, this, userAdminregProps
);
159 public void deleted(String pid
) {
160 assert pidToServiceRegs
.get(pid
) != null;
161 assert pidToBaseDn
.get(pid
) != null;
162 pidToServiceRegs
.remove(pid
).unregister();
163 LdapName baseDn
= pidToBaseDn
.remove(pid
);
164 removeUserDirectory(baseDn
);
168 public String
getName() {
169 return "Node User Admin";
173 protected void addAbstractSystemRoles(Authorization rawAuthorization
, Set
<String
> sysRoles
) {
174 if (rawAuthorization
.getName() == null) {
175 sysRoles
.add(NodeConstants
.ROLE_ANONYMOUS
);
177 sysRoles
.add(NodeConstants
.ROLE_USER
);
181 protected void postAdd(AbstractUserDirectory userDirectory
) {
183 TransactionManager tm
= tmTracker
.getService();
185 throw new CmsException("A JTA transaction manager must be available.");
186 userDirectory
.setTransactionManager(tm
);
187 if (tmTracker
.getService() instanceof BitronixTransactionManager
)
188 EhCacheXAResourceProducer
.registerXAResource(cacheName
, userDirectory
.getXaResource());
190 Object realm
= userDirectory
.getProperties().get(UserAdminConf
.realm
.name());
192 if (Files
.exists(nodeKeyTab
)) {
193 String servicePrincipal
= getKerberosServicePrincipal(realm
.toString());
194 if (servicePrincipal
!= null) {
195 CallbackHandler callbackHandler
= new CallbackHandler() {
197 public void handle(Callback
[] callbacks
) throws IOException
, UnsupportedCallbackException
{
198 for (Callback callback
: callbacks
)
199 if (callback
instanceof NameCallback
)
200 ((NameCallback
) callback
).setName(servicePrincipal
);
205 LoginContext nodeLc
= new LoginContext(NodeConstants
.LOGIN_CONTEXT_NODE
, callbackHandler
);
207 acceptorCredentials
= logInAsAcceptor(nodeLc
.getSubject(), servicePrincipal
);
208 } catch (LoginException e
) {
209 throw new CmsException("Cannot log in kernel", e
);
214 // Register client-side SPNEGO auth scheme
215 AuthPolicy
.registerAuthScheme(SpnegoAuthScheme
.NAME
, SpnegoAuthScheme
.class);
216 HttpParams params
= DefaultHttpParams
.getDefaultParams();
217 ArrayList
<String
> schemes
= new ArrayList
<>();
218 schemes
.add(SpnegoAuthScheme
.NAME
);// SPNEGO preferred
219 // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
220 params
.setParameter(AuthPolicy
.AUTH_SCHEME_PRIORITY
, schemes
);
221 params
.setParameter(CredentialsProvider
.PROVIDER
, new HttpCredentialProvider());
222 params
.setParameter(HttpMethodParams
.COOKIE_POLICY
, KernelConstants
.COOKIE_POLICY_BROWSER_COMPATIBILITY
);
223 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
227 protected void preDestroy(AbstractUserDirectory userDirectory
) {
228 if (tmTracker
.getService() instanceof BitronixTransactionManager
)
229 EhCacheXAResourceProducer
.unregisterXAResource(cacheName
, userDirectory
.getXaResource());
231 Object realm
= userDirectory
.getProperties().get(UserAdminConf
.realm
.name());
233 if (acceptorCredentials
!= null) {
235 acceptorCredentials
.dispose();
236 } catch (GSSException e
) {
239 acceptorCredentials
= null;
244 private String
getKerberosServicePrincipal(String realm
) {
246 try (DnsBrowser dnsBrowser
= new DnsBrowser()) {
247 InetAddress localhost
= InetAddress
.getLocalHost();
248 hostname
= localhost
.getHostName();
249 String dnsZone
= hostname
.substring(hostname
.indexOf('.') + 1);
250 String ipfromDns
= dnsBrowser
.getRecord(hostname
, localhost
instanceof Inet6Address ?
"AAAA" : "A");
251 boolean consistentIp
= localhost
.getHostAddress().equals(ipfromDns
);
252 String kerberosDomain
= dnsBrowser
.getRecord("_kerberos." + dnsZone
, "TXT");
253 if (consistentIp
&& kerberosDomain
!= null && kerberosDomain
.equals(realm
) && Files
.exists(nodeKeyTab
)) {
254 return NodeHttp
.DEFAULT_SERVICE
+ "/" + hostname
+ "@" + kerberosDomain
;
257 } catch (Exception e
) {
258 log
.warn("Exception when determining kerberos principal", e
);
263 private GSSCredential
logInAsAcceptor(Subject subject
, String servicePrincipal
) {
265 Iterator
<KerberosPrincipal
> krb5It
= subject
.getPrincipals(KerberosPrincipal
.class).iterator();
266 if (!krb5It
.hasNext())
268 KerberosPrincipal krb5Principal
= null;
269 while (krb5It
.hasNext()) {
270 KerberosPrincipal principal
= krb5It
.next();
271 if (principal
.getName().equals(servicePrincipal
))
272 krb5Principal
= principal
;
275 if (krb5Principal
== null)
278 GSSManager manager
= GSSManager
.getInstance();
280 GSSName gssName
= manager
.createName(krb5Principal
.getName(), null);
281 GSSCredential serverCredentials
= Subject
.doAs(subject
, new PrivilegedExceptionAction
<GSSCredential
>() {
284 public GSSCredential
run() throws GSSException
{
285 return manager
.createCredential(gssName
, GSSCredential
.INDEFINITE_LIFETIME
, KERBEROS_OID
,
286 GSSCredential
.ACCEPT_ONLY
);
291 if (log
.isDebugEnabled())
292 log
.debug("GSS acceptor configured for " + krb5Principal
);
293 return serverCredentials
;
294 } catch (Exception gsse
) {
295 throw new CmsException("Cannot create acceptor credentials for " + krb5Principal
, gsse
);
299 public GSSCredential
getAcceptorCredentials() {
300 return acceptorCredentials
;
303 public boolean isSingleUser() {
307 public final static Oid KERBEROS_OID
;
310 KERBEROS_OID
= new Oid("1.3.6.1.5.5.2");
311 } catch (GSSException e
) {
312 throw new IllegalStateException("Cannot create Kerberos OID", e
);