1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package net.sf.jguard.ext.authentication.loginmodules;
29
30 import java.text.MessageFormat;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Hashtable;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39
40 import java.util.logging.Level;
41 import javax.naming.CompositeName;
42 import javax.naming.Context;
43 import javax.naming.InitialContext;
44 import javax.naming.Name;
45 import javax.naming.NameParser;
46 import javax.naming.NamingEnumeration;
47 import javax.naming.NamingException;
48 import javax.naming.directory.Attribute;
49 import javax.naming.directory.Attributes;
50 import javax.naming.directory.DirContext;
51 import javax.naming.directory.SearchControls;
52 import javax.naming.directory.SearchResult;
53 import javax.naming.ldap.Control;
54 import javax.naming.ldap.InitialLdapContext;
55 import javax.security.auth.Subject;
56 import javax.security.auth.callback.CallbackHandler;
57 import javax.security.auth.login.FailedLoginException;
58 import javax.security.auth.login.LoginException;
59 import javax.security.auth.spi.LoginModule;
60
61 import net.sf.jguard.core.CoreConstants;
62 import net.sf.jguard.core.authentication.credentials.JGuardCredential;
63 import net.sf.jguard.ext.SecurityConstants;
64 import net.sf.jguard.ext.util.FastBindConnectionControl;
65 import net.sf.jguard.ext.util.JNDIUtils;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69
70
71
72
73
74 public class JNDILoginModule extends UserLoginModule implements LoginModule {
75
76 private static final String USER_DN = "userDN";
77 private static final String CONTEXTFORCOMMIT = "contextforcommit";
78 private static final String JNDI = "jndi";
79 private static final String TIMELIMIT = "timelimit";
80 private static final String SEARCHSCOPE = "searchscope";
81 private static final String RETURNINGOBJFLAG = "returningobjflag";
82 private static final String RETURNINGATTRIBUTES = "returningattributes";
83 private static final String DEREFLINKFLAG = "dereflinkflag";
84 private static final String COUNTLIMIT = "countlimit";
85 private static final String SEARCHCONTROLS = "searchcontrols.";
86
87 private static final String PREAUTH = "preauth.";
88 private static final String AUTH = "auth.";
89 private static final String FAST_BIND_CONNECTION = "fastBindConnection";
90 private static final String SEARCH_FILTER = "search.filter";
91 private static final String SEARCH_BASE_DN = "search.base.dn";
92
93
94 private static final Logger logger = LoggerFactory.getLogger(JNDILoginModule.class.getName());
95
96
97 private DirContext preAuthContext = null;
98 private DirContext authContext = null;
99
100
101 private SearchControls preAuthSearchControls = null;
102
103 private Map authOpts = null;
104 private Map preAuthOpts = null;
105 private Map preAuthSearchControlsOpts = null;
106
107 private Set credentials = null;
108
109
110
111
112
113
114
115
116 @Override
117 public void initialize(Subject subj, CallbackHandler cbkHandler, Map sState, Map opts) {
118 super.initialize(subj, cbkHandler, sState, opts);
119 preAuthOpts = new HashMap();
120 preAuthSearchControlsOpts = new HashMap();
121 authOpts = new HashMap();
122
123
124 fillOptions();
125
126 }
127
128 private DirContext getContext(Map opts) throws LoginException{
129 DirContext context = null;
130 if(opts.containsKey(JNDILoginModule.JNDI)){
131 try {
132 Context initDirContext = new InitialContext();
133 context = (DirContext) initDirContext.lookup((String)opts.get(JNDILoginModule.JNDI));
134 } catch (NamingException e) {
135 throw new LoginException(" we cannot grab the default initial context ");
136 }
137
138 }else{
139 Control[] LDAPcontrols = getLDAPControls(opts);
140 try {
141 context = new InitialLdapContext(new Hashtable(opts),LDAPcontrols);
142 } catch (NamingException e) {
143 throw new LoginException(e.getMessage());
144 }
145 }
146 if(context == null){
147 throw new LoginException(" we cannot grab the default initial context ");
148 }
149 return context;
150
151 }
152
153
154
155
156
157
158
159
160
161 private void fillOptions() {
162 Iterator entriesIterator = options.entrySet().iterator();
163 while(entriesIterator.hasNext()){
164 Map.Entry entry = (Map.Entry)entriesIterator.next();
165 String key = (String)entry.getKey();
166 String value = (String)entry.getValue();
167 if(key.startsWith(JNDILoginModule.PREAUTH)){
168 key = key.substring(8, key.length());
169 if(key.startsWith(JNDILoginModule.SEARCHCONTROLS)){
170 key = key.substring(15, key.length());
171 preAuthSearchControlsOpts.put(key, value);
172 }else{
173 preAuthOpts.put(key, value);
174 }
175 }else if(key.startsWith(JNDILoginModule.AUTH)){
176 key = key.substring(5, key.length());
177 authOpts.put(key, value);
178 }
179 }
180 }
181
182
183
184
185
186
187 @Override
188 public boolean login() throws LoginException {
189 super.login();
190 if(CoreConstants.GUEST.equals(login)){
191
192 loginOK = false;
193 return false;
194 }
195
196
197
198
199
200
201 String userDN = (String)authOpts.get(USER_DN);
202 if(preAuthOpts.size()==0 &&(userDN==null || userDN.equals(""))){
203 throw new IllegalArgumentException(" you've configured the JNDILoginmodule in 'auth' mode (options starting by 'preauth.' are not present).\n 'auth.userDN' option used to find the user LDAP Entry is lacking or is empty ");
204 }
205
206
207 userDN = getuserDN(userDN, login);
208
209 if (userDN != null && !equals("")) {
210 authOpts.put(Context.SECURITY_PRINCIPAL, userDN);
211 authOpts.put(Context.SECURITY_CREDENTIALS, new String(password));
212 try {
213 authContext = getContext(authOpts);
214 } finally {
215 try {
216 if(authContext!= null){
217 authContext.close();
218 }
219 } catch (NamingException e) {
220 throw new FailedLoginException(e.getMessage());
221 }
222 }
223
224 }else{
225 loginOK = false;
226 throw new LoginException(" Distinguished name is null or empty ");
227 }
228
229
230 sharedState.put(SecurityConstants.SKIP_PASSWORD_CHECK, "true");
231 logger.info( " JNDI login phase succeed for user "+login);
232 return true;
233 }
234
235
236
237
238
239
240
241
242
243
244 private String getuserDN(String userDN, String login) throws LoginException {
245
246 String escapedLogin = JNDIUtils.escapeDn(login);
247 Object[] args = {escapedLogin};
248 if(preAuthOpts.size()>0){
249
250
251 try {
252 preAuthContext = getContext(preAuthOpts);
253 } catch (LoginException e) {
254 loginOK = false;
255 throw new IllegalArgumentException(e.getMessage());
256 }
257 preAuthSearchControlsOpts.put(COUNTLIMIT, "1");
258 preAuthSearchControls = getSearchControls(preAuthSearchControlsOpts);
259
260 try{
261 userDN = preAuthSearch(preAuthContext, preAuthSearchControls);
262 }catch(LoginException e){
263 loginOK = false;
264 throw e;
265 }finally{
266 try {
267 preAuthContext.close();
268 } catch (NamingException e) {
269 logger.error(e.getMessage());
270 }
271 }
272 }else{
273 userDN = MessageFormat.format(userDN, args);
274 userDN = JNDIUtils.escapeDn(userDN);
275 }
276 return userDN;
277 }
278
279
280
281
282
283
284
285 @Override
286 public boolean commit() throws LoginException {
287 if(!loginOK){
288 return false;
289 }
290 if(options.containsKey(JNDILoginModule.CONTEXTFORCOMMIT)&&options.get(JNDILoginModule.CONTEXTFORCOMMIT).equals("true")){
291 credentials = grabAttributes(getContext(authOpts),(String)authOpts.get(USER_DN));
292 }
293
294 if(credentials!=null){
295 Set privateCredentials = subject.getPrivateCredentials();
296 privateCredentials.addAll(credentials);
297 }
298 return true;
299 }
300
301
302
303
304
305
306
307
308
309 private Set grabAttributes(DirContext contextUsedForCommit,String userDN) throws LoginException {
310 DirContext userCtxt = null;
311 Set creds = new HashSet();
312 try {
313 userCtxt = (DirContext) contextUsedForCommit.lookup(getuserDN(userDN,login));
314 if(userCtxt==null){
315 throw new FailedLoginException("login.user.does.not.exist");
316 }
317
318 Attributes attributes = userCtxt.getAttributes("");
319 creds = grabCredentials(attributes);
320 } catch (NamingException e) {
321 throw new LoginException(e.getMessage());
322 }finally{
323 try {
324 if(userCtxt!=null){
325 userCtxt.close();
326 }
327 } catch (NamingException e) {
328 throw new LoginException(e.getMessage());
329 }
330 }
331
332 return creds;
333 }
334
335
336
337
338
339
340
341
342 private Set grabCredentials(Attributes atts) throws NamingException {
343 Set credentialSet = new HashSet();
344 NamingEnumeration enumeration = atts.getAll();
345
346 while(enumeration.hasMore()){
347 Attribute attribute = (Attribute)enumeration.next();
348 String key = attribute.getID();
349 String value= JNDIUtils.getAttributeValue(attribute);
350 JGuardCredential credential = new JGuardCredential();
351 credential.setName(key);
352 credential.setValue(value);
353 credentialSet.add(credential);
354 }
355
356 return credentialSet;
357 }
358
359
360
361
362
363
364 private String preAuthSearch(DirContext context,SearchControls controls) throws LoginException {
365 NamingEnumeration results = null;
366 String dn = null;
367 String baseDN = null;
368 String searchFilter = null;
369 try {
370 String[] filterArgs = new String[]{super.login};
371 Hashtable opts = context.getEnvironment();
372 baseDN = (String)opts.get(JNDILoginModule.SEARCH_BASE_DN);
373 searchFilter = (String)opts.get(JNDILoginModule.SEARCH_FILTER);
374
375 results = context.search(baseDN,searchFilter,filterArgs, controls);
376 int userFound = 0;
377 boolean grabInformations = false;
378 String contextforcommit = (String)options.get(JNDILoginModule.CONTEXTFORCOMMIT);
379 if (contextforcommit!=null && "preauth".equals(contextforcommit)){
380 grabInformations = true;
381 }
382 while(results.hasMore()){
383 SearchResult result = (SearchResult) results.next();
384
385 dn = result.getName();
386
387 NameParser pn = context.getNameParser("");
388
389 CompositeName compName = new CompositeName(result.getName());
390
391
392 Name entryName = pn.parse(compName.get(0));
393
394
395
396
397 dn = entryName.toString();
398
399 if(grabInformations){
400 credentials = grabCredentials(result.getAttributes());
401 }
402
403 userFound++;
404 }
405 if(userFound>1){
406 logger.warn("more than one Distinguished Name has been found in the Directory for the user="+login);
407 throw new FailedLoginException("login.error");
408 }
409 } catch (NamingException e) {
410 throw new LoginException(" a naming exception has been raised when we are looking for the user Distinguished Name "+e.getMessage());
411 }finally{
412 try {
413 context.close();
414 } catch (NamingException e) {
415 throw new LoginException(e.getMessage());
416 }
417 }
418 if(dn==null){
419 throw new FailedLoginException("login.error");
420 }
421 return dn;
422 }
423
424 private SearchControls getSearchControls(Map opts){
425 SearchControls controls = new SearchControls();
426 Iterator itEntries = opts.entrySet().iterator();
427 while(itEntries.hasNext()){
428 Map.Entry entry = (Map.Entry)itEntries.next();
429 String key = (String)entry.getKey();
430 String value = (String)entry.getValue();
431 if(JNDILoginModule.COUNTLIMIT.equals(key)){
432 long countLimit = Long.parseLong(value);
433 controls.setCountLimit(countLimit);
434 }else if(JNDILoginModule.DEREFLINKFLAG.equals(key)){
435 boolean derefLinkFlag = Boolean.valueOf(value).booleanValue();
436 controls.setDerefLinkFlag(derefLinkFlag);
437 }else if(JNDILoginModule.RETURNINGATTRIBUTES.equals(key)){
438 String[] returningAttributes = value.split("#");
439 controls.setReturningAttributes(returningAttributes);
440 }else if(JNDILoginModule.RETURNINGOBJFLAG.equals(key)){
441 boolean returningobjflag = Boolean.valueOf(value).booleanValue();
442 controls.setReturningObjFlag(returningobjflag);
443 }else if(JNDILoginModule.SEARCHSCOPE.equals(key)){
444 int scope = Integer.parseInt(value);
445 controls.setSearchScope(scope);
446 }else if(JNDILoginModule.TIMELIMIT.equals(key)){
447 int timelimit = Integer.parseInt(value);
448 controls.setTimeLimit(timelimit);
449 }
450 }
451
452 return controls;
453 }
454
455
456 private Control[] getLDAPControls(Map opts){
457 List ldapControls = new ArrayList();
458 if(opts.containsKey(JNDILoginModule.FAST_BIND_CONNECTION)
459 && "true".equalsIgnoreCase((String)opts.get(JNDILoginModule.FAST_BIND_CONNECTION))){
460 ldapControls.add(new FastBindConnectionControl());
461 }
462 return (Control[]) ldapControls.toArray(new Control[ldapControls.size()]);
463
464 }
465
466 }