1 package org
.argeo
.cms
.auth
;
3 import static org
.argeo
.naming
.LdapAttrs
.cn
;
4 import static org
.argeo
.naming
.LdapAttrs
.description
;
6 import java
.io
.IOException
;
7 import java
.security
.PrivilegedAction
;
8 import java
.security
.cert
.X509Certificate
;
9 import java
.time
.Instant
;
10 import java
.util
.Arrays
;
11 import java
.util
.HashSet
;
12 import java
.util
.List
;
13 import java
.util
.Locale
;
17 import javax
.naming
.ldap
.LdapName
;
18 import javax
.security
.auth
.Subject
;
19 import javax
.security
.auth
.callback
.Callback
;
20 import javax
.security
.auth
.callback
.CallbackHandler
;
21 import javax
.security
.auth
.callback
.LanguageCallback
;
22 import javax
.security
.auth
.callback
.NameCallback
;
23 import javax
.security
.auth
.callback
.PasswordCallback
;
24 import javax
.security
.auth
.callback
.UnsupportedCallbackException
;
25 import javax
.security
.auth
.kerberos
.KerberosPrincipal
;
26 import javax
.security
.auth
.login
.CredentialNotFoundException
;
27 import javax
.security
.auth
.login
.LoginException
;
28 import javax
.security
.auth
.spi
.LoginModule
;
29 import javax
.servlet
.http
.HttpServletRequest
;
31 import org
.apache
.commons
.logging
.Log
;
32 import org
.apache
.commons
.logging
.LogFactory
;
33 import org
.argeo
.cms
.CmsException
;
34 import org
.argeo
.cms
.internal
.kernel
.Activator
;
35 import org
.argeo
.naming
.LdapAttrs
;
36 import org
.argeo
.naming
.NamingUtils
;
37 import org
.argeo
.node
.NodeConstants
;
38 import org
.argeo
.node
.security
.CryptoKeyring
;
39 import org
.argeo
.osgi
.useradmin
.AuthenticatingUser
;
40 import org
.argeo
.osgi
.useradmin
.IpaUtils
;
41 import org
.argeo
.osgi
.useradmin
.OsUserUtils
;
42 import org
.osgi
.framework
.BundleContext
;
43 import org
.osgi
.framework
.FrameworkUtil
;
44 import org
.osgi
.framework
.ServiceReference
;
45 import org
.osgi
.service
.useradmin
.Authorization
;
46 import org
.osgi
.service
.useradmin
.Group
;
47 import org
.osgi
.service
.useradmin
.User
;
48 import org
.osgi
.service
.useradmin
.UserAdmin
;
50 public class UserAdminLoginModule
implements LoginModule
{
51 private final static Log log
= LogFactory
.getLog(UserAdminLoginModule
.class);
53 private Subject subject
;
54 private CallbackHandler callbackHandler
;
55 private Map
<String
, Object
> sharedState
= null;
57 private List
<String
> indexedUserProperties
= Arrays
58 .asList(new String
[] { LdapAttrs
.mail
.name(), LdapAttrs
.uid
.name(), LdapAttrs
.authPassword
.name() });
61 private BundleContext bc
;
62 private User authenticatedUser
= null;
63 private Locale locale
;
65 private Authorization bindAuthorization
= null;
67 private boolean singleUser
= Activator
.isSingleUser();
69 @SuppressWarnings("unchecked")
71 public void initialize(Subject subject
, CallbackHandler callbackHandler
, Map
<String
, ?
> sharedState
,
72 Map
<String
, ?
> options
) {
73 this.subject
= subject
;
75 bc
= FrameworkUtil
.getBundle(UserAdminLoginModule
.class).getBundleContext();
76 this.callbackHandler
= callbackHandler
;
77 this.sharedState
= (Map
<String
, Object
>) sharedState
;
78 } catch (Exception e
) {
79 throw new CmsException("Cannot initialize login module", e
);
84 public boolean login() throws LoginException
{
85 UserAdmin userAdmin
= Activator
.getUserAdmin();
86 final String username
;
87 final char[] password
;
88 X509Certificate
[] certificateChain
= null;
89 if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
90 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_PWD
)) {
91 // NB: required by Basic http auth
92 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
93 password
= (char[]) sharedState
.get(CmsAuthUtils
.SHARED_STATE_PWD
);
95 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
96 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_CERTIFICATE_CHAIN
)) {
97 // NB: required by Basic http auth
98 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
99 certificateChain
= (X509Certificate
[]) sharedState
.get(CmsAuthUtils
.SHARED_STATE_CERTIFICATE_CHAIN
);
101 } else if (singleUser
) {
102 username
= OsUserUtils
.getOsUsername();
106 // ask for username and password
107 NameCallback nameCallback
= new NameCallback("User");
108 PasswordCallback passwordCallback
= new PasswordCallback("Password", false);
109 LanguageCallback langCallback
= new LanguageCallback();
111 callbackHandler
.handle(new Callback
[] { nameCallback
, passwordCallback
, langCallback
});
112 } catch (IOException e
) {
113 throw new LoginException("Cannot handle callback: " + e
.getMessage());
114 } catch (UnsupportedCallbackException e
) {
119 locale
= langCallback
.getLocale();
121 locale
= Locale
.getDefault();
122 // FIXME add it to Subject
123 // Locale.setDefault(locale);
125 username
= nameCallback
.getName();
126 if (username
== null || username
.trim().equals("")) {
127 // authorization = userAdmin.getAuthorization(null);
128 throw new CredentialNotFoundException("No credentials provided");
130 if (passwordCallback
.getPassword() != null)
131 password
= passwordCallback
.getPassword();
133 throw new CredentialNotFoundException("No credentials provided");
134 sharedState
.put(CmsAuthUtils
.SHARED_STATE_NAME
, username
);
135 sharedState
.put(CmsAuthUtils
.SHARED_STATE_PWD
, password
);
137 User user
= searchForUser(userAdmin
, username
);
141 String token
= username
;
142 Group tokenGroup
= searchForToken(userAdmin
, token
);
143 if (tokenGroup
!= null) {
144 Authorization tokenAuthorization
= getAuthorizationFromToken(userAdmin
, tokenGroup
);
145 if (tokenAuthorization
!= null) {
146 bindAuthorization
= tokenAuthorization
;
147 authenticatedUser
= (User
) userAdmin
.getRole(bindAuthorization
.getName());
154 return true;// expect Kerberos
156 if (password
!= null) {
159 AuthenticatingUser authenticatingUser
= new AuthenticatingUser(user
.getName(), password
);
160 bindAuthorization
= userAdmin
.getAuthorization(authenticatingUser
);
161 // TODO check tokens as well
162 if (bindAuthorization
!= null) {
163 authenticatedUser
= user
;
166 } catch (Exception e
) {
168 if (log
.isTraceEnabled())
169 log
.trace("Bind failed", e
);
172 // works only if a connection password is provided
173 if (!user
.hasCredential(null, password
)) {
176 } else if (certificateChain
!= null) {
177 // TODO check CRLs/OSCP validity?
178 // NB: authorization in commit() will work only if an LDAP connection password
180 } else if (singleUser
) {
181 // TODO verify IP address?
183 throw new CredentialNotFoundException("No credentials provided");
186 authenticatedUser
= user
;
191 public boolean commit() throws LoginException
{
193 subject
.getPublicCredentials().add(Locale
.getDefault());
195 subject
.getPublicCredentials().add(locale
);
198 OsUserUtils
.loginAsSystemUser(subject
);
200 UserAdmin userAdmin
= Activator
.getUserAdmin();
201 Authorization authorization
;
202 if (callbackHandler
== null) {// anonymous
203 authorization
= userAdmin
.getAuthorization(null);
204 } else if (bindAuthorization
!= null) {// bind
205 authorization
= bindAuthorization
;
207 User authenticatingUser
;
208 Set
<KerberosPrincipal
> kerberosPrincipals
= subject
.getPrincipals(KerberosPrincipal
.class);
209 if (kerberosPrincipals
.isEmpty()) {
210 if (authenticatedUser
== null) {
211 if (log
.isTraceEnabled())
212 log
.trace("Neither kerberos nor user admin login succeeded. Login failed.");
215 authenticatingUser
= authenticatedUser
;
218 KerberosPrincipal kerberosPrincipal
= kerberosPrincipals
.iterator().next();
219 LdapName dn
= IpaUtils
.kerberosToDn(kerberosPrincipal
.getName());
220 authenticatingUser
= new AuthenticatingUser(dn
);
221 if (authenticatedUser
!= null && !authenticatingUser
.getName().equals(authenticatedUser
.getName()))
222 throw new LoginException("Kerberos login " + authenticatingUser
.getName()
223 + " is inconsistent with user admin login " + authenticatedUser
.getName());
225 authorization
= Subject
.doAs(subject
, new PrivilegedAction
<Authorization
>() {
228 public Authorization
run() {
229 Authorization authorization
= userAdmin
.getAuthorization(authenticatingUser
);
230 return authorization
;
234 if (authorization
== null)
235 throw new LoginException(
236 "User admin found no authorization for authenticated user " + authenticatingUser
.getName());
239 // Log and monitor new login
240 HttpServletRequest request
= (HttpServletRequest
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_HTTP_REQUEST
);
241 CmsAuthUtils
.addAuthorization(subject
, authorization
, locale
, request
);
243 // Unlock keyring (underlying login to the JCR repository)
244 char[] password
= (char[]) sharedState
.get(CmsAuthUtils
.SHARED_STATE_PWD
);
245 if (password
!= null) {
246 ServiceReference
<CryptoKeyring
> keyringSr
= bc
.getServiceReference(CryptoKeyring
.class);
247 if (keyringSr
!= null) {
248 CryptoKeyring keyring
= bc
.getService(keyringSr
);
249 Subject
.doAs(subject
, new PrivilegedAction
<Void
>() {
254 keyring
.unlock(password
);
255 } catch (Exception e
) {
257 log
.warn("Could not unlock keyring with the password provided by " + authorization
.getName()
258 + ": " + e
.getMessage());
267 // Register CmsSession with initial subject
268 CmsAuthUtils
.registerSessionAuthorization(request
, subject
, authorization
, locale
);
270 if (log
.isDebugEnabled())
271 log
.debug("Logged in to CMS: " + subject
);
276 public boolean abort() throws LoginException
{
281 public boolean logout() throws LoginException
{
282 if (log
.isTraceEnabled())
283 log
.trace("Logging out from CMS... " + subject
);
284 // boolean httpSessionLogoutOk = CmsAuthUtils.logoutSession(bc,
286 CmsAuthUtils
.cleanUp(subject
);
290 protected User
searchForUser(UserAdmin userAdmin
, String providedUsername
) {
292 // TODO check value null or empty
293 Set
<User
> collectedUsers
= new HashSet
<>();
297 for (String attr
: indexedUserProperties
) {
298 user
= userAdmin
.getUser(attr
, providedUsername
);
300 collectedUsers
.add(user
);
302 if (collectedUsers
.size() == 1) {
303 user
= collectedUsers
.iterator().next();
305 } else if (collectedUsers
.size() > 1) {
306 log
.warn(collectedUsers
.size() + " users for provided username" + providedUsername
);
308 // try DN as a last resort
310 user
= (User
) userAdmin
.getRole(providedUsername
);
313 } catch (Exception e
) {
317 } catch (Exception e
) {
318 if (log
.isTraceEnabled())
319 log
.warn("Cannot search for user " + providedUsername
, e
);
325 protected Group
searchForToken(UserAdmin userAdmin
, String token
) {
326 String dn
= cn
+ "=" + token
+ "," + NodeConstants
.TOKENS_BASEDN
;
327 Group tokenGroup
= (Group
) userAdmin
.getRole(dn
);
331 protected Authorization
getAuthorizationFromToken(UserAdmin userAdmin
, Group tokenGroup
) {
332 String expiryDateStr
= (String
) tokenGroup
.getProperties().get(description
.name());
333 if (expiryDateStr
!= null) {
334 Instant expiryDate
= NamingUtils
.ldapDateToInstant(expiryDateStr
);
335 if (expiryDate
.isBefore(Instant
.now())) {
336 if (log
.isDebugEnabled())
337 log
.debug("Token " + tokenGroup
.getName() + " has expired.");
341 Authorization auth
= userAdmin
.getAuthorization(tokenGroup
);