1 package org
.argeo
.cms
.auth
;
3 import static org
.argeo
.util
.naming
.LdapAttrs
.cn
;
5 import java
.io
.IOException
;
6 import java
.security
.PrivilegedAction
;
7 import java
.util
.Arrays
;
8 import java
.util
.HashSet
;
10 import java
.util
.Locale
;
14 import javax
.naming
.ldap
.LdapName
;
15 import javax
.security
.auth
.Subject
;
16 import javax
.security
.auth
.callback
.Callback
;
17 import javax
.security
.auth
.callback
.CallbackHandler
;
18 import javax
.security
.auth
.callback
.LanguageCallback
;
19 import javax
.security
.auth
.callback
.NameCallback
;
20 import javax
.security
.auth
.callback
.PasswordCallback
;
21 import javax
.security
.auth
.callback
.UnsupportedCallbackException
;
22 import javax
.security
.auth
.kerberos
.KerberosPrincipal
;
23 import javax
.security
.auth
.login
.CredentialNotFoundException
;
24 import javax
.security
.auth
.login
.LoginException
;
25 import javax
.security
.auth
.spi
.LoginModule
;
27 import org
.argeo
.api
.cms
.CmsConstants
;
28 import org
.argeo
.api
.cms
.CmsLog
;
29 import org
.argeo
.cms
.internal
.runtime
.CmsContextImpl
;
30 import org
.argeo
.cms
.security
.CryptoKeyring
;
31 import org
.argeo
.osgi
.useradmin
.AuthenticatingUser
;
32 import org
.argeo
.osgi
.useradmin
.TokenUtils
;
33 import org
.argeo
.util
.directory
.ldap
.IpaUtils
;
34 import org
.argeo
.util
.naming
.LdapAttrs
;
35 import org
.osgi
.framework
.BundleContext
;
36 import org
.osgi
.framework
.FrameworkUtil
;
37 import org
.osgi
.framework
.ServiceReference
;
38 import org
.osgi
.service
.useradmin
.Authorization
;
39 import org
.osgi
.service
.useradmin
.Group
;
40 import org
.osgi
.service
.useradmin
.User
;
41 import org
.osgi
.service
.useradmin
.UserAdmin
;
44 * Use the {@link UserAdmin} in the OSGi registry as the basis for
47 public class UserAdminLoginModule
implements LoginModule
{
48 private final static CmsLog log
= CmsLog
.getLog(UserAdminLoginModule
.class);
50 private Subject subject
;
51 private CallbackHandler callbackHandler
;
52 private Map
<String
, Object
> sharedState
= null;
54 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] { LdapAttrs
.mail
.name(),
55 LdapAttrs
.uid
.name(), LdapAttrs
.employeeNumber
.name(), LdapAttrs
.authPassword
.name() });
58 private BundleContext bc
;
59 private User authenticatedUser
= null;
60 private Locale locale
;
62 private Authorization bindAuthorization
= null;
64 // private boolean singleUser = Activator.isSingleUser();
66 @SuppressWarnings("unchecked")
68 public void initialize(Subject subject
, CallbackHandler callbackHandler
, Map
<String
, ?
> sharedState
,
69 Map
<String
, ?
> options
) {
70 this.subject
= subject
;
72 bc
= FrameworkUtil
.getBundle(UserAdminLoginModule
.class).getBundleContext();
73 this.callbackHandler
= callbackHandler
;
74 this.sharedState
= (Map
<String
, Object
>) sharedState
;
75 } catch (Exception e
) {
76 throw new IllegalStateException("Cannot initialize login module", e
);
81 public boolean login() throws LoginException
{
82 UserAdmin userAdmin
= CmsContextImpl
.getCmsContext().getUserAdmin();
83 final String username
;
84 final char[] password
;
85 Object certificateChain
= null;
86 boolean preauth
= false;
87 if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
88 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_PWD
)) {
89 // NB: required by Basic http auth
90 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
91 password
= (char[]) sharedState
.get(CmsAuthUtils
.SHARED_STATE_PWD
);
93 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
94 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_CERTIFICATE_CHAIN
)) {
95 String certDn
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
98 // ldapName = new LdapName(certificateName);
99 // } catch (InvalidNameException e) {
100 // e.printStackTrace();
103 // username = ldapName.getRdn(ldapName.size() - 1).getValue().toString();
105 certificateChain
= sharedState
.get(CmsAuthUtils
.SHARED_STATE_CERTIFICATE_CHAIN
);
107 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
108 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_REMOTE_ADDR
)
109 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_REMOTE_PORT
)) {// ident
110 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
113 // } else if (singleUser) {
114 // username = OsUserUtils.getOsUsername();
116 // // TODO retrieve from http session
117 // locale = Locale.getDefault();
120 // ask for username and password
121 NameCallback nameCallback
= new NameCallback("User");
122 PasswordCallback passwordCallback
= new PasswordCallback("Password", false);
123 LanguageCallback langCallback
= new LanguageCallback();
125 callbackHandler
.handle(new Callback
[] { nameCallback
, passwordCallback
, langCallback
});
126 } catch (IOException e
) {
127 throw new LoginException("Cannot handle callback: " + e
.getMessage());
128 } catch (UnsupportedCallbackException e
) {
133 locale
= langCallback
.getLocale();
135 locale
= Locale
.getDefault();
136 // FIXME add it to Subject
137 // Locale.setDefault(locale);
139 username
= nameCallback
.getName();
140 if (username
== null || username
.trim().equals("")) {
141 // authorization = userAdmin.getAuthorization(null);
142 throw new CredentialNotFoundException("No credentials provided");
144 if (passwordCallback
.getPassword() != null)
145 password
= passwordCallback
.getPassword();
147 throw new CredentialNotFoundException("No credentials provided");
148 sharedState
.put(CmsAuthUtils
.SHARED_STATE_NAME
, username
);
149 sharedState
.put(CmsAuthUtils
.SHARED_STATE_PWD
, password
);
151 User user
= searchForUser(userAdmin
, username
);
155 String token
= username
;
156 Group tokenGroup
= searchForToken(userAdmin
, token
);
157 if (tokenGroup
!= null) {
158 Authorization tokenAuthorization
= getAuthorizationFromToken(userAdmin
, tokenGroup
);
159 if (tokenAuthorization
!= null) {
160 bindAuthorization
= tokenAuthorization
;
161 authenticatedUser
= (User
) userAdmin
.getRole(bindAuthorization
.getName());
168 return true;// expect Kerberos
170 if (password
!= null) {
173 AuthenticatingUser authenticatingUser
= new AuthenticatingUser(user
.getName(), password
);
174 bindAuthorization
= userAdmin
.getAuthorization(authenticatingUser
);
175 // TODO check tokens as well
176 if (bindAuthorization
!= null) {
177 authenticatedUser
= user
;
180 } catch (Exception e
) {
182 if (log
.isTraceEnabled())
183 log
.trace("Bind failed", e
);
186 // works only if a connection password is provided
187 if (!user
.hasCredential(null, password
)) {
190 } else if (certificateChain
!= null) {
191 // TODO check CRLs/OSCP validity?
192 // NB: authorization in commit() will work only if an LDAP connection password
194 // } else if (singleUser) {
195 // // TODO verify IP address?
196 } else if (preauth
) {
199 throw new CredentialNotFoundException("No credentials provided");
202 authenticatedUser
= user
;
207 public boolean commit() throws LoginException
{
209 subject
.getPublicCredentials().add(locale
);
212 // OsUserUtils.loginAsSystemUser(subject);
214 UserAdmin userAdmin
= CmsContextImpl
.getCmsContext().getUserAdmin();
215 Authorization authorization
;
216 if (callbackHandler
== null) {// anonymous
217 authorization
= userAdmin
.getAuthorization(null);
218 } else if (bindAuthorization
!= null) {// bind
219 authorization
= bindAuthorization
;
221 User authenticatingUser
;
222 Set
<KerberosPrincipal
> kerberosPrincipals
= subject
.getPrincipals(KerberosPrincipal
.class);
223 if (kerberosPrincipals
.isEmpty()) {
224 if (authenticatedUser
== null) {
225 if (log
.isTraceEnabled())
226 log
.trace("Neither kerberos nor user admin login succeeded. Login failed.");
227 throw new CredentialNotFoundException("Bad credentials.");
229 authenticatingUser
= authenticatedUser
;
232 KerberosPrincipal kerberosPrincipal
= kerberosPrincipals
.iterator().next();
233 LdapName dn
= IpaUtils
.kerberosToDn(kerberosPrincipal
.getName());
234 authenticatingUser
= new AuthenticatingUser(dn
);
235 if (authenticatedUser
!= null && !authenticatingUser
.getName().equals(authenticatedUser
.getName()))
236 throw new LoginException("Kerberos login " + authenticatingUser
.getName()
237 + " is inconsistent with user admin login " + authenticatedUser
.getName());
239 if (log
.isTraceEnabled())
240 log
.trace("Retrieve authorization for " + authenticatingUser
+ "... ");
241 authorization
= Subject
.doAs(subject
, new PrivilegedAction
<Authorization
>() {
244 public Authorization
run() {
245 Authorization authorization
= userAdmin
.getAuthorization(authenticatingUser
);
246 return authorization
;
250 if (authorization
== null)
251 throw new LoginException(
252 "User admin found no authorization for authenticated user " + authenticatingUser
.getName());
255 // Log and monitor new login
256 RemoteAuthRequest request
= (RemoteAuthRequest
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_HTTP_REQUEST
);
257 CmsAuthUtils
.addAuthorization(subject
, authorization
);
259 // Unlock keyring (underlying login to the JCR repository)
260 char[] password
= (char[]) sharedState
.get(CmsAuthUtils
.SHARED_STATE_PWD
);
261 if (password
!= null) {
262 ServiceReference
<CryptoKeyring
> keyringSr
= bc
.getServiceReference(CryptoKeyring
.class);
263 if (keyringSr
!= null) {
264 CryptoKeyring keyring
= bc
.getService(keyringSr
);
265 Subject
.doAs(subject
, new PrivilegedAction
<Void
>() {
270 keyring
.unlock(password
);
271 } catch (Exception e
) {
273 log
.warn("Could not unlock keyring with the password provided by " + authorization
.getName()
274 + ": " + e
.getMessage());
283 // Register CmsSession with initial subject
284 CmsAuthUtils
.registerSessionAuthorization(request
, subject
, authorization
, locale
);
286 if (log
.isDebugEnabled())
287 log
.debug("Logged in to CMS: " + subject
);
292 public boolean abort() throws LoginException
{
297 public boolean logout() throws LoginException
{
298 if (log
.isTraceEnabled())
299 log
.trace("Logging out from CMS... " + subject
);
300 CmsAuthUtils
.cleanUp(subject
);
304 protected User
searchForUser(UserAdmin userAdmin
, String providedUsername
) {
306 // TODO check value null or empty
307 Set
<User
> collectedUsers
= new HashSet
<>();
312 for (String attr
: indexedUserProperties
) {
313 user
= userAdmin
.getUser(attr
, providedUsername
);
315 collectedUsers
.add(user
);
317 if (collectedUsers
.size() == 1) {
318 user
= collectedUsers
.iterator().next();
320 } else if (collectedUsers
.size() > 1) {
321 log
.warn(collectedUsers
.size() + " users for provided username" + providedUsername
);
323 // try DN as a last resort
325 user
= (User
) userAdmin
.getRole(providedUsername
);
328 } catch (Exception e
) {
332 } catch (Exception e
) {
333 if (log
.isTraceEnabled())
334 log
.warn("Cannot search for user " + providedUsername
, e
);
340 protected Group
searchForToken(UserAdmin userAdmin
, String token
) {
341 String dn
= cn
+ "=" + token
+ "," + CmsConstants
.TOKENS_BASEDN
;
342 Group tokenGroup
= (Group
) userAdmin
.getRole(dn
);
346 protected Authorization
getAuthorizationFromToken(UserAdmin userAdmin
, Group tokenGroup
) {
347 if (TokenUtils
.isExpired(tokenGroup
))
349 // String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
350 // if (expiryDateStr != null) {
351 // Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
352 // if (expiryDate.isBefore(Instant.now())) {
353 // if (log.isDebugEnabled())
354 // log.debug("Token " + tokenGroup.getName() + " has expired.");
358 String userDn
= TokenUtils
.userDn(tokenGroup
);
359 User user
= (User
) userAdmin
.getRole(userDn
);
360 Authorization auth
= userAdmin
.getAuthorization(user
);