1 package org
.argeo
.cms
.internal
.runtime
;
3 import java
.io
.IOException
;
4 import java
.net
.Inet6Address
;
5 import java
.net
.InetAddress
;
7 import java
.net
.URISyntaxException
;
9 import java
.nio
.file
.Files
;
10 import java
.nio
.file
.Path
;
11 import java
.security
.PrivilegedExceptionAction
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Dictionary
;
14 import java
.util
.Iterator
;
15 import java
.util
.List
;
16 import java
.util
.Optional
;
19 import javax
.security
.auth
.Subject
;
20 import javax
.security
.auth
.callback
.Callback
;
21 import javax
.security
.auth
.callback
.CallbackHandler
;
22 import javax
.security
.auth
.callback
.NameCallback
;
23 import javax
.security
.auth
.callback
.UnsupportedCallbackException
;
24 import javax
.security
.auth
.kerberos
.KerberosPrincipal
;
25 import javax
.security
.auth
.login
.LoginContext
;
26 import javax
.security
.auth
.login
.LoginException
;
28 import org
.apache
.commons
.httpclient
.auth
.AuthPolicy
;
29 import org
.apache
.commons
.httpclient
.auth
.CredentialsProvider
;
30 import org
.apache
.commons
.httpclient
.params
.DefaultHttpParams
;
31 import org
.apache
.commons
.httpclient
.params
.HttpMethodParams
;
32 import org
.apache
.commons
.httpclient
.params
.HttpParams
;
33 import org
.argeo
.api
.cms
.CmsAuth
;
34 import org
.argeo
.api
.cms
.CmsConstants
;
35 import org
.argeo
.api
.cms
.CmsLog
;
36 import org
.argeo
.api
.cms
.CmsState
;
37 import org
.argeo
.cms
.internal
.http
.client
.HttpCredentialProvider
;
38 import org
.argeo
.cms
.internal
.http
.client
.SpnegoAuthScheme
;
39 import org
.argeo
.osgi
.useradmin
.AggregatingUserAdmin
;
40 import org
.argeo
.osgi
.useradmin
.DirectoryUserAdmin
;
41 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
42 import org
.argeo
.util
.directory
.DirectoryConf
;
43 import org
.argeo
.util
.naming
.dns
.DnsBrowser
;
44 import org
.argeo
.util
.transaction
.WorkControl
;
45 import org
.argeo
.util
.transaction
.WorkTransaction
;
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
;
56 * Aggregates multiple {@link UserDirectory} and integrates them with system
59 public class CmsUserAdmin
extends AggregatingUserAdmin
{
60 private final static CmsLog log
= CmsLog
.getLog(CmsUserAdmin
.class);
63 private Path nodeKeyTab
= KernelUtils
.getOsgiInstancePath(KernelConstants
.NODE_KEY_TAB_PATH
);
64 private GSSCredential acceptorCredentials
;
66 private boolean singleUser
= false;
68 private WorkControl transactionManager
;
69 private WorkTransaction userTransaction
;
71 private CmsState cmsState
;
73 public CmsUserAdmin() {
74 super(CmsConstants
.ROLES_BASEDN
, CmsConstants
.TOKENS_BASEDN
);
79 List
<Dictionary
<String
, Object
>> configs
= InitUtils
.getUserDirectoryConfigs();
80 for (Dictionary
<String
, Object
> config
: configs
) {
81 UserDirectory userDirectory
= enableUserDirectory(config
);
82 if (userDirectory
.getRealm().isPresent())
83 loadIpaJaasConfiguration();
85 log
.debug(() -> "CMS user admin available");
89 // for (UserDirectory userDirectory : getUserDirectories()) {
90 // removeUserDirectory(userDirectory);
95 public UserDirectory
enableUserDirectory(Dictionary
<String
, ?
> properties
) {
96 String uri
= (String
) properties
.get(DirectoryConf
.uri
.name());
97 Object realm
= properties
.get(DirectoryConf
.realm
.name());
101 String baseDn
= (String
) properties
.get(DirectoryConf
.baseDn
.name());
102 u
= KernelUtils
.getOsgiInstanceUri(KernelConstants
.DIR_NODE
+ '/' + baseDn
+ ".ldif");
103 } else if (realm
!= null) {
108 } catch (URISyntaxException e
) {
109 throw new IllegalArgumentException("Badly formatted URI " + uri
, e
);
113 UserDirectory userDirectory
= new DirectoryUserAdmin(u
, properties
);
114 // if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
115 // || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
116 // userDirectory = new LdapUserAdmin(properties);
117 // } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
118 // userDirectory = new LdifUserAdmin(u, properties);
119 // } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
120 // userDirectory = new OsUserDirectory(u, properties);
121 // singleUser = true;
123 // throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
125 String basePath
= userDirectory
.getContext();
127 addUserDirectory(userDirectory
);
128 if (isSystemRolesBaseDn(basePath
)) {
129 addStandardSystemRoles();
131 if (log
.isDebugEnabled()) {
132 log
.debug("User directory " + userDirectory
.getContext() + (u
!= null ?
" [" + u
.getScheme() + "]" : "")
133 + " enabled." + (realm
!= null ?
" " + realm
+ " realm." : ""));
135 return userDirectory
;
138 protected void addStandardSystemRoles() {
139 // we assume UserTransaction is already available (TODO make it more robust)
141 userTransaction
.begin();
142 Role adminRole
= getRole(CmsConstants
.ROLE_ADMIN
);
143 if (adminRole
== null) {
144 adminRole
= createRole(CmsConstants
.ROLE_ADMIN
, Role
.GROUP
);
146 if (getRole(CmsConstants
.ROLE_USER_ADMIN
) == null) {
147 Group userAdminRole
= (Group
) createRole(CmsConstants
.ROLE_USER_ADMIN
, Role
.GROUP
);
148 userAdminRole
.addMember(adminRole
);
150 userTransaction
.commit();
151 } catch (Exception e
) {
153 userTransaction
.rollback();
154 } catch (Exception e1
) {
157 throw new IllegalStateException("Cannot add standard system roles", e
);
162 protected void addAbstractSystemRoles(Authorization rawAuthorization
, Set
<String
> sysRoles
) {
163 if (rawAuthorization
.getName() == null) {
164 sysRoles
.add(CmsConstants
.ROLE_ANONYMOUS
);
166 sysRoles
.add(CmsConstants
.ROLE_USER
);
171 protected void postAdd(UserDirectory userDirectory
) {
172 userDirectory
.setTransactionControl(transactionManager
);
174 Optional
<String
> realm
= userDirectory
.getRealm();
175 if (realm
.isPresent()) {
176 if (Files
.exists(nodeKeyTab
)) {
177 String servicePrincipal
= getKerberosServicePrincipal(realm
.get());
178 if (servicePrincipal
!= null) {
179 CallbackHandler callbackHandler
= new CallbackHandler() {
181 public void handle(Callback
[] callbacks
) throws IOException
, UnsupportedCallbackException
{
182 for (Callback callback
: callbacks
)
183 if (callback
instanceof NameCallback
)
184 ((NameCallback
) callback
).setName(servicePrincipal
);
189 LoginContext nodeLc
= new LoginContext(CmsAuth
.LOGIN_CONTEXT_NODE
, callbackHandler
);
191 acceptorCredentials
= logInAsAcceptor(nodeLc
.getSubject(), servicePrincipal
);
192 } catch (LoginException e
) {
193 throw new IllegalStateException("Cannot log in kernel", e
);
198 // Register client-side SPNEGO auth scheme
199 AuthPolicy
.registerAuthScheme(SpnegoAuthScheme
.NAME
, SpnegoAuthScheme
.class);
200 HttpParams params
= DefaultHttpParams
.getDefaultParams();
201 ArrayList
<String
> schemes
= new ArrayList
<>();
202 schemes
.add(SpnegoAuthScheme
.NAME
);// SPNEGO preferred
203 // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
204 params
.setParameter(AuthPolicy
.AUTH_SCHEME_PRIORITY
, schemes
);
205 params
.setParameter(CredentialsProvider
.PROVIDER
, new HttpCredentialProvider());
206 params
.setParameter(HttpMethodParams
.COOKIE_POLICY
, KernelConstants
.COOKIE_POLICY_BROWSER_COMPATIBILITY
);
207 // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
212 protected void preDestroy(UserDirectory userDirectory
) {
213 Optional
<String
> realm
= userDirectory
.getRealm();
214 if (realm
.isPresent()) {
215 if (acceptorCredentials
!= null) {
217 acceptorCredentials
.dispose();
218 } catch (GSSException e
) {
221 acceptorCredentials
= null;
226 private void loadIpaJaasConfiguration() {
227 if (System
.getProperty(KernelConstants
.JAAS_CONFIG_PROP
) == null) {
228 String jaasConfig
= KernelConstants
.JAAS_CONFIG_IPA
;
229 URL url
= getClass().getClassLoader().getResource(jaasConfig
);
230 KernelUtils
.setJaasConfiguration(url
);
231 log
.debug("Set IPA JAAS configuration.");
235 private String
getKerberosServicePrincipal(String realm
) {
237 try (DnsBrowser dnsBrowser
= new DnsBrowser()) {
238 InetAddress localhost
= InetAddress
.getLocalHost();
239 hostname
= localhost
.getHostName();
240 String dnsZone
= hostname
.substring(hostname
.indexOf('.') + 1);
241 String ipfromDns
= dnsBrowser
.getRecord(hostname
, localhost
instanceof Inet6Address ?
"AAAA" : "A");
242 boolean consistentIp
= localhost
.getHostAddress().equals(ipfromDns
);
243 String kerberosDomain
= dnsBrowser
.getRecord("_kerberos." + dnsZone
, "TXT");
244 if (consistentIp
&& kerberosDomain
!= null && kerberosDomain
.equals(realm
) && Files
.exists(nodeKeyTab
)) {
245 return KernelConstants
.DEFAULT_KERBEROS_SERVICE
+ "/" + hostname
+ "@" + kerberosDomain
;
248 } catch (Exception e
) {
249 log
.warn("Exception when determining kerberos principal", e
);
254 private GSSCredential
logInAsAcceptor(Subject subject
, String servicePrincipal
) {
255 // not static because class is not supported by Android
256 final Oid KERBEROS_OID
;
258 KERBEROS_OID
= new Oid("1.3.6.1.5.5.2");
259 } catch (GSSException e
) {
260 throw new IllegalStateException("Cannot create Kerberos OID", e
);
263 Iterator
<KerberosPrincipal
> krb5It
= subject
.getPrincipals(KerberosPrincipal
.class).iterator();
264 if (!krb5It
.hasNext())
266 KerberosPrincipal krb5Principal
= null;
267 while (krb5It
.hasNext()) {
268 KerberosPrincipal principal
= krb5It
.next();
269 if (principal
.getName().equals(servicePrincipal
))
270 krb5Principal
= principal
;
273 if (krb5Principal
== null)
276 GSSManager manager
= GSSManager
.getInstance();
278 GSSName gssName
= manager
.createName(krb5Principal
.getName(), null);
279 GSSCredential serverCredentials
= Subject
.doAs(subject
, new PrivilegedExceptionAction
<GSSCredential
>() {
282 public GSSCredential
run() throws GSSException
{
283 return manager
.createCredential(gssName
, GSSCredential
.INDEFINITE_LIFETIME
, KERBEROS_OID
,
284 GSSCredential
.ACCEPT_ONLY
);
289 if (log
.isDebugEnabled())
290 log
.debug("GSS acceptor configured for " + krb5Principal
);
291 return serverCredentials
;
292 } catch (Exception gsse
) {
293 throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal
, gsse
);
297 public GSSCredential
getAcceptorCredentials() {
298 return acceptorCredentials
;
301 public boolean hasAcceptorCredentials() {
302 return acceptorCredentials
!= null;
305 public boolean isSingleUser() {
309 public void setTransactionManager(WorkControl transactionManager
) {
310 this.transactionManager
= transactionManager
;
313 public void setUserTransaction(WorkTransaction userTransaction
) {
314 this.userTransaction
= userTransaction
;
317 public void setCmsState(CmsState cmsState
) {
318 this.cmsState
= cmsState
;