1 package org
.argeo
.cms
.auth
;
3 import static org
.argeo
.api
.acr
.ldap
.LdapAttr
.cn
;
5 import java
.io
.IOException
;
6 import java
.security
.Principal
;
7 import java
.security
.PrivilegedAction
;
8 import java
.util
.Arrays
;
9 import java
.util
.HashSet
;
10 import java
.util
.List
;
11 import java
.util
.Locale
;
15 import javax
.naming
.ldap
.LdapName
;
16 import javax
.security
.auth
.Subject
;
17 import javax
.security
.auth
.callback
.Callback
;
18 import javax
.security
.auth
.callback
.CallbackHandler
;
19 import javax
.security
.auth
.callback
.LanguageCallback
;
20 import javax
.security
.auth
.callback
.NameCallback
;
21 import javax
.security
.auth
.callback
.PasswordCallback
;
22 import javax
.security
.auth
.callback
.UnsupportedCallbackException
;
23 import javax
.security
.auth
.kerberos
.KerberosPrincipal
;
24 import javax
.security
.auth
.login
.CredentialNotFoundException
;
25 import javax
.security
.auth
.login
.LoginException
;
26 import javax
.security
.auth
.spi
.LoginModule
;
28 import org
.argeo
.api
.acr
.ldap
.LdapAttr
;
29 import org
.argeo
.api
.cms
.CmsConstants
;
30 import org
.argeo
.api
.cms
.CmsLog
;
31 import org
.argeo
.cms
.directory
.ldap
.IpaUtils
;
32 import org
.argeo
.cms
.internal
.runtime
.CmsContextImpl
;
33 import org
.argeo
.cms
.osgi
.useradmin
.AuthenticatingUser
;
34 import org
.argeo
.cms
.osgi
.useradmin
.TokenUtils
;
35 import org
.osgi
.service
.useradmin
.Authorization
;
36 import org
.osgi
.service
.useradmin
.Group
;
37 import org
.osgi
.service
.useradmin
.User
;
38 import org
.osgi
.service
.useradmin
.UserAdmin
;
41 * Use the {@link UserAdmin} in the OSGi registry as the basis for
44 public class UserAdminLoginModule
implements LoginModule
{
45 private final static CmsLog log
= CmsLog
.getLog(UserAdminLoginModule
.class);
47 private Subject subject
;
48 private CallbackHandler callbackHandler
;
49 private Map
<String
, Object
> sharedState
= null;
51 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] { LdapAttr
.mail
.name(), LdapAttr
.uid
.name(),
52 LdapAttr
.employeeNumber
.name(), LdapAttr
.authPassword
.name() });
55 // private BundleContext bc;
56 private User authenticatedUser
= null;
57 private Locale locale
;
59 private Authorization bindAuthorization
= null;
61 // private boolean singleUser = Activator.isSingleUser();
63 @SuppressWarnings("unchecked")
65 public void initialize(Subject subject
, CallbackHandler callbackHandler
, Map
<String
, ?
> sharedState
,
66 Map
<String
, ?
> options
) {
67 this.subject
= subject
;
69 // bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext();
70 this.callbackHandler
= callbackHandler
;
71 this.sharedState
= (Map
<String
, Object
>) sharedState
;
72 } catch (Exception e
) {
73 throw new IllegalStateException("Cannot initialize login module", e
);
78 public boolean login() throws LoginException
{
79 UserAdmin userAdmin
= CmsContextImpl
.getCmsContext().getUserAdmin();
80 final String username
;
81 final char[] password
;
82 Object certificateChain
= null;
83 boolean preauth
= false;
84 if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
85 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_PWD
)) {
86 // NB: required by Basic http auth
87 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
88 password
= (char[]) sharedState
.get(CmsAuthUtils
.SHARED_STATE_PWD
);
90 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
91 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_SPNEGO_TOKEN
)) {
92 // SPNEGO login has succeeded, that's enough for us at this stage
94 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
95 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_CERTIFICATE_CHAIN
)) {
96 String certDn
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
98 certificateChain
= sharedState
.get(CmsAuthUtils
.SHARED_STATE_CERTIFICATE_CHAIN
);
100 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_NAME
)
101 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_REMOTE_ADDR
)
102 && sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_REMOTE_PORT
)) {// ident
103 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_NAME
);
106 } else if (sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_OS_USERNAME
)) {
107 // single user, we assume Kerberos or other mean for commit
108 username
= (String
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_OS_USERNAME
);
113 // ask for username and password
114 NameCallback nameCallback
= new NameCallback("User");
115 PasswordCallback passwordCallback
= new PasswordCallback("Password", false);
116 LanguageCallback langCallback
= new LanguageCallback();
118 callbackHandler
.handle(new Callback
[] { nameCallback
, passwordCallback
, langCallback
});
119 } catch (IOException e
) {
120 throw new LoginException("Cannot handle callback: " + e
.getMessage());
121 } catch (UnsupportedCallbackException e
) {
126 locale
= langCallback
.getLocale();
128 locale
= Locale
.getDefault();
129 // FIXME add it to Subject
130 // Locale.setDefault(locale);
132 username
= nameCallback
.getName();
133 if (username
== null || username
.trim().equals("")) {
134 // authorization = userAdmin.getAuthorization(null);
135 throw new CredentialNotFoundException("No credentials provided");
137 if (passwordCallback
.getPassword() != null)
138 password
= passwordCallback
.getPassword();
140 throw new CredentialNotFoundException("No credentials provided");
141 sharedState
.put(CmsAuthUtils
.SHARED_STATE_NAME
, username
);
142 sharedState
.put(CmsAuthUtils
.SHARED_STATE_PWD
, password
);
144 User user
= searchForUser(userAdmin
, username
);
148 String token
= username
;
149 Group tokenGroup
= searchForToken(userAdmin
, token
);
150 if (tokenGroup
!= null) {
151 Authorization tokenAuthorization
= getAuthorizationFromToken(userAdmin
, tokenGroup
);
152 if (tokenAuthorization
!= null) {
153 bindAuthorization
= tokenAuthorization
;
154 authenticatedUser
= (User
) userAdmin
.getRole(bindAuthorization
.getName());
161 return true;// expect Kerberos
163 if (password
!= null) {
164 // TODO disabling bind for the time being,
165 // as it requires authorisations to be set at LDAP level
166 boolean tryBind
= false;
170 AuthenticatingUser authenticatingUser
= new AuthenticatingUser(user
.getName(), password
);
171 bindAuthorization
= userAdmin
.getAuthorization(authenticatingUser
);
172 // TODO check tokens as well
173 if (bindAuthorization
!= null) {
174 authenticatedUser
= user
;
177 } catch (Exception e
) {
179 if (log
.isTraceEnabled())
180 log
.trace("Bind failed", e
);
183 // works only if a connection password is provided
184 if (!user
.hasCredential(null, password
)) {
187 } else if (certificateChain
!= null) {
188 // TODO check CRLs/OSCP validity?
189 // NB: authorization in commit() will work only if an LDAP connection password
191 // } else if (singleUser) {
192 // // TODO verify IP address?
193 } else if (preauth
) {
196 throw new CredentialNotFoundException("No credentials provided");
199 authenticatedUser
= user
;
204 public boolean commit() throws LoginException
{
206 subject
.getPublicCredentials().add(locale
);
209 // OsUserUtils.loginAsSystemUser(subject);
211 UserAdmin userAdmin
= CmsContextImpl
.getCmsContext().getUserAdmin();
212 Authorization authorization
;
213 if (callbackHandler
== null && !sharedState
.containsKey(CmsAuthUtils
.SHARED_STATE_OS_USERNAME
)) {// anonymous
214 authorization
= userAdmin
.getAuthorization(null);
215 } else if (bindAuthorization
!= null) {// bind
216 authorization
= bindAuthorization
;
218 User authenticatingUser
;
219 Set
<KerberosPrincipal
> kerberosPrincipals
= subject
.getPrincipals(KerberosPrincipal
.class);
220 if (kerberosPrincipals
.isEmpty()) {
221 if (authenticatedUser
== null) {
222 if (log
.isTraceEnabled())
223 log
.trace("Neither kerberos nor user admin login succeeded. Login failed.");
224 throw new CredentialNotFoundException("Bad credentials.");
226 authenticatingUser
= authenticatedUser
;
229 KerberosPrincipal kerberosPrincipal
= kerberosPrincipals
.iterator().next();
230 LdapName dn
= IpaUtils
.kerberosToDn(kerberosPrincipal
.getName());
231 authenticatingUser
= new AuthenticatingUser(dn
);
232 if (authenticatedUser
!= null && !authenticatingUser
.getName().equals(authenticatedUser
.getName()))
233 throw new LoginException("Kerberos login " + authenticatingUser
.getName()
234 + " is inconsistent with user admin login " + authenticatedUser
.getName());
236 if (log
.isTraceEnabled())
237 log
.trace("Retrieve authorization for " + authenticatingUser
+ "... ");
238 authorization
= Subject
.doAs(subject
, new PrivilegedAction
<Authorization
>() {
241 public Authorization
run() {
242 Authorization authorization
= userAdmin
.getAuthorization(authenticatingUser
);
243 return authorization
;
247 if (authorization
== null)
248 throw new LoginException(
249 "User admin found no authorization for authenticated user " + authenticatingUser
.getName());
252 // Log and monitor new login
253 RemoteAuthRequest request
= (RemoteAuthRequest
) sharedState
.get(CmsAuthUtils
.SHARED_STATE_HTTP_REQUEST
);
254 CmsAuthUtils
.addAuthorization(subject
, authorization
);
256 // Unlock keyring (underlying login to the JCR repository)
257 // char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
258 // if (password != null) {
259 // ServiceReference<CryptoKeyring> keyringSr = bc.getServiceReference(CryptoKeyring.class);
260 // if (keyringSr != null) {
261 // CryptoKeyring keyring = bc.getService(keyringSr);
262 // Subject.doAs(subject, new PrivilegedAction<Void>() {
265 // public Void run() {
267 // keyring.unlock(password);
268 // } catch (Exception e) {
269 // e.printStackTrace();
270 // log.warn("Could not unlock keyring with the password provided by " + authorization.getName()
271 // + ": " + e.getMessage());
280 // Register CmsSession with initial subject
281 CmsAuthUtils
.registerSessionAuthorization(request
, subject
, authorization
, locale
);
283 if (log
.isDebugEnabled()) {
284 StringBuilder msg
= new StringBuilder();
285 msg
.append("Logged in to CMS: " + authorization
.getName() + "(" + authorization
+ ")\n");
286 for (Principal principal
: subject
.getPrincipals()) {
287 msg
.append(" Principal: " + principal
.getName()).append(" (")
288 .append(principal
.getClass().getSimpleName()).append(")\n");
290 for (Object credential
: subject
.getPublicCredentials()) {
291 msg
.append(" Public Credential: " + credential
).append(" (")
292 .append(credential
.getClass().getSimpleName()).append(")\n");
296 // if (log.isTraceEnabled())
297 // log.trace(" Subject: " + subject);
302 public boolean abort() throws LoginException
{
307 public boolean logout() throws LoginException
{
308 if (log
.isTraceEnabled())
309 log
.trace("Logging out from CMS... " + subject
);
310 CmsAuthUtils
.cleanUp(subject
);
314 protected User
searchForUser(UserAdmin userAdmin
, String providedUsername
) {
316 // TODO check value null or empty
317 Set
<User
> collectedUsers
= new HashSet
<>();
322 for (String attr
: indexedUserProperties
) {
323 user
= userAdmin
.getUser(attr
, providedUsername
);
325 collectedUsers
.add(user
);
327 if (collectedUsers
.size() == 1) {
328 user
= collectedUsers
.iterator().next();
330 } else if (collectedUsers
.size() > 1) {
331 log
.warn(collectedUsers
.size() + " users for provided username" + providedUsername
);
333 // try DN as a last resort
335 user
= (User
) userAdmin
.getRole(providedUsername
);
338 } catch (Exception e
) {
342 } catch (Exception e
) {
343 if (log
.isTraceEnabled())
344 log
.warn("Cannot search for user " + providedUsername
, e
);
350 protected Group
searchForToken(UserAdmin userAdmin
, String token
) {
351 String dn
= cn
+ "=" + token
+ "," + CmsConstants
.TOKENS_BASEDN
;
352 Group tokenGroup
= (Group
) userAdmin
.getRole(dn
);
356 protected Authorization
getAuthorizationFromToken(UserAdmin userAdmin
, Group tokenGroup
) {
357 if (TokenUtils
.isExpired(tokenGroup
))
359 // String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
360 // if (expiryDateStr != null) {
361 // Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
362 // if (expiryDate.isBefore(Instant.now())) {
363 // if (log.isDebugEnabled())
364 // log.debug("Token " + tokenGroup.getName() + " has expired.");
368 String userDn
= TokenUtils
.userDn(tokenGroup
);
369 User user
= (User
) userAdmin
.getRole(userDn
);
370 Authorization auth
= userAdmin
.getAuthorization(user
);