Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: An implementation of the AuthInterface interface that
4
 *             allows Metacat to use the LDAP protocol for
5
 *             directory services
6
 *  Copyright: 2000 Regents of the University of California and the
7
 *             National Center for Ecological Analysis and Synthesis
8
 *    Authors: Matt Jones
9
 *    Release: @release@
10
 *
11
 *   '$Author: sgarg $'
12
 *     '$Date: 2004-04-12 16:49:48 -0700 (Mon, 12 Apr 2004) $'
13
 * '$Revision: 2116 $'
14
 *
15
 * This program is free software; you can redistribute it and/or modify
16
 * it under the terms of the GNU General Public License as published by
17
 * the Free Software Foundation; either version 2 of the License, or
18
 * (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU General Public License
26
 * along with this program; if not, write to the Free Software
27
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28
 */
29

    
30
package edu.ucsb.nceas.metacat;
31

    
32
import java.net.ConnectException;
33
import javax.naming.AuthenticationException;
34
import javax.naming.Context;
35
import javax.naming.NamingEnumeration;
36
import javax.naming.NamingException;
37
import javax.naming.SizeLimitExceededException;
38
import javax.naming.InitialContext;
39
import javax.naming.directory.InvalidSearchFilterException;
40
import javax.naming.directory.Attribute;
41
import javax.naming.directory.Attributes;
42
import javax.naming.directory.BasicAttribute;
43
import javax.naming.directory.BasicAttributes;
44
import javax.naming.directory.DirContext;
45
import javax.naming.directory.InitialDirContext;
46
import javax.naming.directory.SearchResult;
47
import javax.naming.directory.SearchControls;
48
import javax.naming.ReferralException;
49
import javax.naming.ldap.*;
50
import java.net.URLDecoder;
51
import java.util.Iterator;
52
import java.util.HashMap;
53
import java.util.Hashtable;
54
import java.util.Enumeration;
55
import java.util.Set;
56
import java.util.Vector;
57

    
58
/**
59
 * An implementation of the AuthInterface interface that
60
 * allows Metacat to use the LDAP protocol for directory services.
61
 * The LDAP authentication service is used to determine if a user
62
 * is authenticated, and whether they are a member of a particular group.
63
 */
64
public class AuthLdap implements AuthInterface, Runnable {
65
  private MetaCatUtil util = new MetaCatUtil();
66
  private String ldapUrl;
67
  private String ldapsUrl;
68
  private String ldapBase;
69
  private String referral;
70
  private Context referralContext;
71
  Hashtable env = new Hashtable(11);
72
  private Context rContext;
73
  private String userName;
74
  private String userPassword;
75
  ReferralException refExc;
76
  //String uid=null;
77

    
78
  /**
79
   * Construct an AuthLdap
80
   */
81
  public AuthLdap() {
82
    // Read LDAP URI for directory service information
83
    this.ldapUrl = MetaCatUtil.getOption("ldapurl");
84
    this.ldapsUrl = MetaCatUtil.getOption("ldapsurl");
85
    this.ldapBase = MetaCatUtil.getOption("ldapbase");
86
    this.referral = MetaCatUtil.getOption("referral");
87
  }
88

    
89
  /**
90
   * Determine if a user/password are valid according to the authentication
91
   * service.
92
   *
93
   * @param user the name of the principal to authenticate
94
   * @param password the password to use for authentication
95
   * @returns boolean true if authentication successful, false otherwise
96
   */
97
  public boolean authenticate(String user, String password)
98
                    throws ConnectException
99
  {
100
    String ldapUrl = this.ldapUrl;
101
    String ldapsUrl = this.ldapsUrl;
102
    String ldapBase = this.ldapBase;
103
    boolean authenticated = false;
104
    String identifier = user;
105
    //get uid here.
106
    //uid=user.substring(0, user.indexOf(","));
107

    
108
    try {
109
        // Check the usename as passed in
110
        authenticated = ldapAuthenticate(identifier, password);
111
        // if not found, try looking up a valid DN then auth again
112
        if (!authenticated) {
113
            MetaCatUtil.debugMessage("Looking up DN for: " + identifier, 35);
114
            identifier = getIdentifyingName(identifier,ldapUrl,ldapBase);
115
            MetaCatUtil.debugMessage("DN found: " + identifier, 35);
116
            String decoded = URLDecoder.decode(identifier);
117
            MetaCatUtil.debugMessage("DN decoded: " + decoded, 35);
118
            identifier = decoded;
119
            String refUrl = "";
120
            String refBase = "";
121
            if (identifier.startsWith("ldap")) {
122
                refUrl = identifier.substring(0,
123
                               identifier.lastIndexOf("/")+1);
124
                MetaCatUtil.debugMessage("Ref ldapUrl: " + refUrl, 35);
125
                int position = identifier.indexOf(",");
126
                int position2 = identifier.indexOf(",", position+1);
127
                refBase = identifier.substring(position2+1);
128
                MetaCatUtil.debugMessage("Ref ldapBase: " + refBase, 35);
129
                identifier = identifier.substring(
130
                             identifier.lastIndexOf("/")+1);
131
                MetaCatUtil.debugMessage("Trying: " + identifier, 35);
132
                authenticated = ldapAuthenticate(identifier, password,
133
                                                 refUrl, refBase);
134
            } else {
135
                identifier = identifier+","+ldapBase;
136
                MetaCatUtil.debugMessage("Trying: " + identifier, 35);
137
                authenticated = ldapAuthenticate(identifier, password);
138
            }
139
            //authenticated = ldapAuthenticate(identifier, password);
140
        }
141

    
142
    } catch (NullPointerException e) {
143
      util.debugMessage("NullPointerException b' password is null", 30);
144
      util.debugMessage("NullPointerException while authenticating in " +
145
                        "AuthLdap.authenticate: " + e, 30);
146
      throw new ConnectException(
147
      "NullPointerException while authenticating in " +
148
                        "AuthLdap.authenticate: " + e);
149
    } catch (NamingException e) {
150
      util.debugMessage("Naming exception while authenticating in " +
151
                        "AuthLdap.authenticate: " + e, 30);
152
      e.printStackTrace();
153
    } catch (Exception e) {
154
      util.debugMessage(e.getMessage(), 30);
155
    }
156
    return authenticated;
157
  }
158

    
159
  /**
160
   * Connect to the LDAP directory and do the authentication using the
161
   * username and password as passed into the routine.
162
   *
163
   * @param identifier the distinguished name to check against LDAP
164
   * @param password the password for authentication
165
   */
166
  private boolean ldapAuthenticate(String identifier, String password)
167
            throws ConnectException, NamingException, NullPointerException
168
  {
169
      return ldapAuthenticate(identifier, password,
170
                              this.ldapsUrl, this.ldapBase);
171
  }
172

    
173
  /**
174
   * Connect to the LDAP directory and do the authentication using the
175
   * username and password as passed into the routine.
176
   *
177
   * @param identifier the distinguished name to check against LDAP
178
   * @param password the password for authentication
179
   */
180
  private boolean ldapAuthenticate(String identifier, String password,
181
            String directoryUrl, String searchBase)
182
            throws ConnectException, NamingException, NullPointerException
183
  {
184
    double totStartTime = System.currentTimeMillis();
185
    boolean authenticated = false;
186
    if (identifier != null && !password.equals("")) {
187

    
188
        //Pass the username and password to run() method
189
        userName=identifier;
190
        userPassword=password;
191
        // Identify service provider to use
192
        Hashtable env = new Hashtable(11);
193
        env.put(Context.INITIAL_CONTEXT_FACTORY,
194
              "com.sun.jndi.ldap.LdapCtxFactory");
195
        env.put(Context.REFERRAL, "throw");
196
        env.put(Context.PROVIDER_URL, directoryUrl + searchBase);
197
        util.debugMessage("PROVIDER_URL set to: " + directoryUrl + searchBase, 35);
198
        if ( !ldapsUrl.equals(ldapUrl) ) {
199
          // ldap is set on default port 389
200
          // ldaps is set on second port - 636 by default
201
          //env.put(Context.SECURITY_PROTOCOL, "ssl");
202
        }
203
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
204
        env.put(Context.SECURITY_PRINCIPAL, identifier);
205
        env.put(Context.SECURITY_CREDENTIALS, password);
206
        // If our auth credentials are invalid, an exception will be thrown
207
        DirContext ctx = null;
208
        try {
209
          double startTime = System.currentTimeMillis();
210
          //Here to check the authorization
211
          ctx = new InitialDirContext(env);
212
          double stopTime = System.currentTimeMillis();
213
          util.debugMessage("Connection time thru " + ldapsUrl + " was: " +
214
                             (stopTime-startTime)/1000 + " seconds.", 35);
215
          authenticated = true;
216
          //tls.close();
217
          ctx.close();
218
          this.ldapUrl = ldapUrl;
219
          this.ldapBase = ldapBase;
220
          //break;
221
        } catch (AuthenticationException ae) {
222
          authenticated = false;
223
          if ( ctx != null ) {
224
            ctx.close();
225
          }
226
        } catch (javax.naming.InvalidNameException ine) {
227
            util.debugMessage("An invalid DN was provided!", 30);
228
        } catch (javax.naming.ReferralException re) {
229
          util.debugMessage("referral during authentication", 30);
230
          util.debugMessage("Referral information: "+re.getReferralInfo(), 30);
231
          try {
232
            refExc = re;
233

    
234
            Thread t = new Thread(this);
235
            t.start();
236
            Thread.sleep(5000); //this is a manual override of ldap's
237
                                //hideously long time out period.
238
            util.debugMessage("Awake after 5 seconds.", 35);
239
            if (referralContext == null) {
240
              t.interrupt();
241
              authenticated = false;
242
            } else {
243
              authenticated = true;
244
            }
245
          } catch (Exception e) {
246
            authenticated = false;
247
          }
248
        }
249
    } else {
250
        util.debugMessage("User not found", 30);
251
    }
252
    double totStopTime = System.currentTimeMillis();
253
    util.debugMessage("total ldap authentication time: " +
254
                      (totStopTime - totStartTime)/1000 + " seconds", 35);
255
    return authenticated;
256
  }
257

    
258

    
259

    
260
  /**
261
   * Get the identifying name for a given userid or name.  This is the name
262
   * that is used in conjunction withthe LDAP BaseDN to create a
263
   * distinguished name (dn) for the record
264
   *
265
   * @param user the user for which the identifying name is requested
266
   * @returns String the identifying name for the user,
267
   *          or null if not found
268
   */
269
  private String getIdentifyingName(String user, String ldapUrl,
270
                                    String ldapBase) throws NamingException
271
  {
272
    String identifier = null;
273
    // Identify service provider to use
274
    Hashtable env = new Hashtable(11);
275
    env.put(Context.INITIAL_CONTEXT_FACTORY,
276
            "com.sun.jndi.ldap.LdapCtxFactory");
277
    util.debugMessage("setting referrals to: " + referral, 35);
278
    env.put(Context.REFERRAL, referral);
279
    env.put(Context.PROVIDER_URL, ldapUrl + ldapBase);
280
    //    non-secure LDAP context; dn are publicly readable
281
    try {
282

    
283
      // Bind to the LDAP server, in order to search for the right
284
      // distinguished name (dn) based on userid (uid) or common name (cn)
285
      DirContext ctx = new InitialDirContext(env);
286
      SearchControls ctls = new SearchControls();
287
      ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
288
      // Search for the user id or name using the uid, then cn and sn
289
      //attributes
290
      // If we find a record, determine the dn for the record
291
      // The following blocks need to be refactored into a subroutine
292
      // they have a ridiculous amount of redundancy
293

    
294
      // Parse out the uid and org components and look up the real DN
295
      // This assumes a dn like "uid=x,o=y,dc=someinst,dc=org"
296
      int position = user.indexOf(",");
297
      String comp1 = user.substring(0, position);
298
      MetaCatUtil.debugMessage("First comp is: " + comp1, 35);
299
      String comp2 = user.substring(position+1,
300
                                    user.indexOf(",", position+1));
301
      MetaCatUtil.debugMessage("Second comp is: " + comp2, 35);
302

    
303
      //String filter = "(&(" + comp1 + "))";
304
      String filter = "(&(" + comp1 + ")(" + comp2 + "))";
305
      MetaCatUtil.debugMessage("Filter is: " + filter, 35);
306
      MetaCatUtil.debugMessage("Provider URL is: " + ldapUrl + ldapBase, 35);
307
      NamingEnumeration answer;
308
      try {
309
        util.debugMessage("Trying search 1: " + filter, 35);
310
        answer = ctx.search("", filter, ctls);
311
        util.debugMessage("Search 1 complete", 35);
312
        if (answer == null) {
313
            util.debugMessage("Search 1 answer is null.", 35);
314
        }
315
        if (answer.hasMore()) {
316
          util.debugMessage("Search 1 has answers.", 35);
317
          SearchResult sr = (SearchResult)answer.next();
318
          identifier = sr.getName();
319
          util.debugMessage("Originally Found: " + identifier, 35);
320
          return identifier;
321
        }
322
      } catch (InvalidSearchFilterException e) {
323
          util.debugMessage("Invalid Filter exception thrown (if1)", 35);
324
      }
325

    
326
      // That failed, so check if it is just a username
327
      filter = "(" + user + ")";
328
      try {
329
        MetaCatUtil.debugMessage("Trying again: " + filter, 35);
330
        answer = ctx.search("", filter, ctls);
331
        if (answer.hasMore()) {
332
          SearchResult sr = (SearchResult)answer.next();
333
          identifier = sr.getName();
334
          if ( !sr.isRelative() ) {
335
            this.ldapUrl = identifier.substring(0,
336
                                                identifier.lastIndexOf("/")+1);
337
            this.ldapBase = identifier.substring(identifier.indexOf(",")+1);
338
            identifier = identifier.substring(identifier.lastIndexOf("/")+1,
339
                                              identifier.indexOf(","));
340
          }
341
          util.debugMessage("Found: " + identifier, 35);
342
          return identifier;
343
        }
344
      } catch (InvalidSearchFilterException e) {}
345

    
346
      // Maybe its a user id (uid)
347
      filter = "(uid=" + user + ")";
348
      MetaCatUtil.debugMessage("Trying again: " + filter, 35);
349
      answer = ctx.search("", filter, ctls);
350
      if (answer.hasMore()) {
351
        SearchResult sr = (SearchResult)answer.next();
352
        identifier = sr.getName();
353
        if ( !sr.isRelative() ) {
354
          this.ldapUrl = identifier.substring(0,identifier.lastIndexOf("/")+1);
355
          this.ldapBase = identifier.substring(identifier.indexOf(",")+1);
356
          identifier = identifier.substring(identifier.lastIndexOf("/")+1,
357
                                            identifier.indexOf(","));
358
        }
359
        util.debugMessage("Found: " + identifier, 35);
360
      } else {
361

    
362
        // maybe its just a common name
363
        filter = "(cn=" + user + ")";
364
        MetaCatUtil.debugMessage("Trying again: " + filter, 35);
365
        NamingEnumeration answer2 = ctx.search("", filter, ctls);
366
        if (answer2.hasMore()) {
367
          SearchResult sr = (SearchResult)answer2.next();
368
          identifier = sr.getName();
369
          if ( !sr.isRelative() ) {
370
            this.ldapUrl = identifier.substring(0,
371
                                                identifier.lastIndexOf("/")+1);
372
            this.ldapBase = identifier.substring(identifier.indexOf(",")+1);
373
            identifier = identifier.substring(identifier.lastIndexOf("/")+1,
374
                                              identifier.indexOf(","));
375
          }
376
          util.debugMessage("Found: " + identifier, 35);
377
        } else {
378

    
379
          // ok, last resort, is it a surname?
380
          filter = "(sn=" + user + ")";
381
          MetaCatUtil.debugMessage("Trying again: " + filter, 35);
382
          NamingEnumeration answer3 = ctx.search("", filter, ctls);
383
          if (answer3.hasMore()) {
384
            SearchResult sr = (SearchResult)answer3.next();
385
            identifier = sr.getName();
386
            if ( !sr.isRelative() ) {
387
              this.ldapUrl = identifier.substring(0,
388
                                                identifier.lastIndexOf("/")+1);
389
              this.ldapBase = identifier.substring(identifier.indexOf(",")+1);
390
              identifier = identifier.substring(identifier.lastIndexOf("/")+1,
391
                                                identifier.indexOf(","));
392
            }
393
            util.debugMessage("Found: " + identifier, 35);
394
          }
395
        }
396
      }
397
      // Close the context when we're done the initial search
398
      ctx.close();
399
    } catch (NamingException e) {
400
      util.debugMessage("Naming exception while getting dn: " + e, 35);
401
      throw new NamingException(
402
      "Naming exception in AuthLdap.getIdentifyingName: " + e);
403
    }
404
    MetaCatUtil.debugMessage("Returning found identifier as: " + identifier, 35);
405
    return identifier;
406
  }
407

    
408
  /**
409
   * Get all users from the authentication service
410
   *
411
   * @param user the user for authenticating against the service
412
   * @param password the password for authenticating against the service
413
   * @returns string array of all of the user names
414
   */
415
  public String[][] getUsers(String user, String password)
416
         throws ConnectException
417
  {
418
    String[][] users = null;
419

    
420
    // Identify service provider to use
421
    Hashtable env = new Hashtable(11);
422
    env.put(Context.INITIAL_CONTEXT_FACTORY,
423
            "com.sun.jndi.ldap.LdapCtxFactory");
424
    env.put(Context.REFERRAL, referral);
425
    env.put(Context.PROVIDER_URL, ldapUrl);
426

    
427
    try {
428

    
429
        // Create the initial directory context
430
        DirContext ctx = new InitialDirContext(env);
431

    
432
        // Specify the attributes to match.
433
        // Users are objects that have the attribute objectclass=InetOrgPerson.
434
        SearchControls ctls = new SearchControls();
435
        String[] attrIDs = {"dn", "cn", "o", "mail"};
436
        ctls.setReturningAttributes(attrIDs);
437
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
438
        //ctls.setCountLimit(1000);
439
        String filter = "(objectClass=inetOrgPerson)";
440
        NamingEnumeration enum = ctx.search(ldapBase, filter, ctls);
441

    
442
        // Store the users in a vector
443
        Vector uvec = new Vector();
444
        Vector uname = new Vector();
445
        Vector uorg = new Vector();
446
        Vector umail = new Vector();
447
        Attributes tempAttr = null;
448
        try {
449
            while (enum.hasMore()) {
450
                SearchResult sr = (SearchResult)enum.next();
451
                tempAttr = sr.getAttributes();
452

    
453
                if((tempAttr.get("cn")+"").startsWith("cn: ")){
454
                  uname.add( (tempAttr.get("cn") + "").substring(4));
455
                } else {
456
                  uname.add( tempAttr.get("cn") + "");
457
                }
458

    
459
                if((tempAttr.get("o")+"").startsWith("o: ")){
460
                  uorg.add( (tempAttr.get("o") + "").substring(3));
461
                } else {
462
                  uorg.add( tempAttr.get("o") + "");
463
                }
464

    
465
                if((tempAttr.get("mail")+"").startsWith("mail: ")){
466
                  umail.add((tempAttr.get("mail") + "").substring(6));
467
                } else {
468
                  umail.add(tempAttr.get("mail") + "");
469
                }
470

    
471
                uvec.add(sr.getName()+","+ldapBase);
472
            }
473
        } catch (SizeLimitExceededException slee) {
474
            util.debugMessage("LDAP Server size limit exceeded. " +
475
                    "Returning incomplete record set.", 35);
476
        }
477

    
478
        // initialize users[]; fill users[]
479
        users = new String[uvec.size()][4];
480
        for (int i=0; i < uvec.size(); i++) {
481
          users[i][0] = (String)uvec.elementAt(i);
482
          users[i][1] = (String)uname.elementAt(i);
483
          users[i][2]   = (String)uorg.elementAt(i);
484
          users[i][3] = (String)umail.elementAt(i);
485
        }
486

    
487
        // Close the context when we're done
488
        ctx.close();
489

    
490
    } catch (NamingException e) {
491
      util.debugMessage("Problem getting users in AuthLdap.getUsers:" + e, 35);
492
      //e.printStackTrace(System.err);
493
      throw new ConnectException(
494
      "Problem getting users in AuthLdap.getUsers:" + e);
495
    }
496

    
497
    return users;
498
  }
499

    
500
  /**
501
   * Get the users for a particular group from the authentication service
502
   *
503
   * @param user the user for authenticating against the service
504
   * @param password the password for authenticating against the service
505
   * @param group the group whose user list should be returned
506
   * @returns string array of the user names belonging to the group
507
   */
508
  public String[] getUsers(String user, String password, String group)
509
         throws ConnectException
510
  {
511
    String[] users = null;
512

    
513
    // Identify service provider to use
514
    Hashtable env = new Hashtable(11);
515
    env.put(Context.INITIAL_CONTEXT_FACTORY,
516
            "com.sun.jndi.ldap.LdapCtxFactory");
517
    env.put(Context.REFERRAL, referral);
518
    env.put(Context.PROVIDER_URL, ldapUrl);
519

    
520
    try {
521

    
522
        // Create the initial directory context
523
        DirContext ctx = new InitialDirContext(env);
524

    
525
        // Specify the ids of the attributes to return
526
        String[] attrIDs = {"uniqueMember"};
527

    
528
        Attributes answer = ctx.getAttributes(group, attrIDs);
529

    
530
        Vector uvec = new Vector();
531
        try {
532
            for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {
533
                Attribute attr = (Attribute)ae.next();
534
                for (NamingEnumeration e = attr.getAll();
535
                     e.hasMore();
536
                     uvec.add(e.next())
537
                    );
538
            }
539
        } catch (SizeLimitExceededException slee) {
540
            util.debugMessage("LDAP Server size limit exceeded. " +
541
                    "Returning incomplete record set.", 35);
542
        }
543

    
544
        // initialize users[]; fill users[]
545
        users = new String[uvec.size()];
546
        for (int i=0; i < uvec.size(); i++) {
547
          users[i] = (String)uvec.elementAt(i);
548
        }
549

    
550
        // Close the context when we're done
551
        ctx.close();
552

    
553
    } catch (NamingException e) {
554
      util.debugMessage("Problem getting users for a group in " +
555
              "AuthLdap.getUsers:" + e, 30);
556
      throw new ConnectException(
557
      "Problem getting users for a group in AuthLdap.getUsers:" + e);
558
    }
559

    
560
    return users;
561
  }
562

    
563
  /**
564
   * Get all groups from the authentication service
565
   *
566
   * @param user the user for authenticating against the service
567
   * @param password the password for authenticating against the service
568
   * @returns string array of the group names
569
   */
570
  public String[][] getGroups(String user, String password)
571
         throws ConnectException
572
  {
573
      return getGroups(user, password, null);
574
  }
575

    
576
  /**
577
   * Get the groups for a particular user from the authentication service
578
   *
579
   * @param user the user for authenticating against the service
580
   * @param password the password for authenticating against the service
581
   * @param foruser the user whose group list should be returned
582
   * @returns string array of the group names
583
   */
584
  public String[][] getGroups(String user, String password, String foruser)
585
         throws ConnectException
586
  {
587
    Vector gvec = new Vector();
588
    Vector desc = new Vector();
589
    Attributes tempAttr = null;
590

    
591
    //Pass the username and password to run() method
592
    userName=user;
593
    userPassword=password;
594
    // Identify service provider to use
595
    env.put(Context.INITIAL_CONTEXT_FACTORY,
596
            "com.sun.jndi.ldap.LdapCtxFactory");
597
    env.put(Context.REFERRAL, "throw");
598
    env.put(Context.PROVIDER_URL, ldapUrl);
599
    try {
600
        // Create the initial directory context
601
        DirContext ctx = new InitialDirContext(env);
602
        // Specify the ids of the attributes to return
603
        String[] attrIDs = {"cn", "o", "description"};
604
        // Specify the attributes to match.
605
        // Groups are objects with attribute objectclass=groupofuniquenames.
606
        // and have attribute uniquemember: uid=foruser,ldapbase.
607
        SearchControls ctls = new SearchControls();
608
        ctls.setReturningAttributes(attrIDs);
609
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
610

    
611
        String filter = null;
612
        String gfilter = "(objectClass=groupOfUniqueNames)";
613
        if (null == foruser) {
614
            filter = gfilter;
615
        } else {
616
            filter = "(& " + gfilter + "(uniqueMember=" + foruser + "))";
617
        }
618
        MetaCatUtil.debugMessage("searching for groups: " + filter, 35);
619
        NamingEnumeration enum = ctx.search(ldapBase, filter, ctls);
620

    
621
        // Print the groups
622
        MetaCatUtil.debugMessage("getting group results.", 50);
623
        while (enum.hasMore()) {
624
          SearchResult sr = (SearchResult)enum.next();
625
          tempAttr = sr.getAttributes();
626

    
627
          if((tempAttr.get("description")+"").startsWith("description: ")){
628
            desc.add( (tempAttr.get("description") + "").substring(13));
629
          } else {
630
            desc.add( tempAttr.get("description") + "");
631
          }
632

    
633
          gvec.add(sr.getName()+","+ldapBase);
634
          MetaCatUtil.debugMessage("group " + sr.getName() +
635
                                  " added to Group vector", 35);
636
        }
637
        // Close the context when we're done
638
        ctx.close();
639

    
640
    }
641
    catch (ReferralException re)
642
    {
643
      refExc = re;
644
      Thread t = new Thread(new GetGroup());
645
      util.debugMessage("Starting thread...", 50);
646
      t.start();
647
      util.debugMessage("sleeping for 5 seconds.", 50);
648
      try
649
      {
650
        Thread.sleep(5000);
651
      }
652
      catch(InterruptedException ie)
653
      {
654
        MetaCatUtil.debugMessage("main thread interrupted: " + ie.getMessage(), 30);
655
      }
656
      //this is a manual override of jndi's hideously long time
657
      //out period.
658
      util.debugMessage("Awake after 5 seconds.", 40);
659
      if (referralContext == null)
660
      {
661
        util.debugMessage("thread timed out...returning groups: " + gvec.toString(), 35);
662
        String groups[][] = new String[gvec.size()][2];
663
        for(int i=0; i<gvec.size(); i++)
664
        {
665
          groups[i][0] = (String)gvec.elementAt(i);
666
          groups[i][1] = (String)desc.elementAt(i);
667
        }
668
        t.interrupt();
669
        return groups;
670
      }
671
      DirContext dc = (DirContext)referralContext;
672
      String[] attrIDs = {"cn", "o", "description"};
673
      // Specify the attributes to match.
674
      // Groups are objects with attribute objectclass=groupofuniquenames.
675
      // and have attribute uniquemember: uid=foruser,ldapbase.
676
      SearchControls ctls = new SearchControls();
677
      ctls.setReturningAttributes(attrIDs);
678
      ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
679

    
680
      String filter = null;
681
      String gfilter = "(objectClass=groupOfUniqueNames)";
682
      if (null == foruser) {
683
          filter = gfilter;
684
      } else {
685
          filter = "(& " + gfilter + "(uniqueMember=" + foruser + "))";
686
      }
687

    
688
      try
689
      {
690
        NamingEnumeration enum = dc.search(ldapBase, filter, ctls);
691
        // Print the groups
692
        while (enum.hasMore()) {
693
          SearchResult sr = (SearchResult)enum.next();
694
          tempAttr = sr.getAttributes();
695

    
696
          if((tempAttr.get("description")+"").startsWith("description: ")){
697
            desc.add( (tempAttr.get("description") + "").substring(13));
698
          } else {
699
            desc.add( tempAttr.get("description") + "");
700
          }
701

    
702
          gvec.add(sr.getName()+","+ldapBase);
703
        }
704

    
705
        referralContext.close();
706
        dc.close();
707
      }
708
      catch(NamingException ne)
709
      {
710
        MetaCatUtil.debugMessage("Naming Exception in AuthLdap.getGroups" + ne.getExplanation() + ne.getMessage(), 30);
711
      }
712
    } catch (NamingException e) {
713
      e.printStackTrace(System.err);
714
      String groups[][] = new String[gvec.size()][2];
715
      for(int i=0; i<gvec.size(); i++)
716
      {
717
        groups[i][0] = (String)gvec.elementAt(i);
718
        groups[i][1] = (String)desc.elementAt(i);
719
      }
720
      return groups;
721
       /*throw new ConnectException(
722
      "Problem getting groups for a user in AuthLdap.getGroups:" + e);*/
723
    }
724

    
725
    MetaCatUtil.debugMessage("The user is in the following groups: " +
726
                              gvec.toString(), 35);
727
    String groups[][] = new String[gvec.size()][2];
728
    for(int i=0; i<gvec.size(); i++)
729
    {
730
      groups[i][0] = (String)gvec.elementAt(i);
731
      groups[i][1] = (String)desc.elementAt(i);
732
    }
733
    return groups;
734
  }
735

    
736
  /**
737
   * Get attributes describing a user or group
738
   *
739
   * @param foruser the user for which the attribute list is requested
740
   * @returns HashMap a map of attribute name to a Vector of values
741
   */
742
  public HashMap getAttributes(String foruser)
743
         throws ConnectException
744
  {
745
    return getAttributes(null, null, foruser);
746
  }
747

    
748
  /**
749
   * Get attributes describing a user or group
750
   *
751
   * @param user the user for authenticating against the service
752
   * @param password the password for authenticating against the service
753
   * @param foruser the user whose attributes should be returned
754
   * @returns HashMap a map of attribute name to a Vector of values
755
   */
756
  public HashMap getAttributes(String user, String password, String foruser)
757
         throws ConnectException
758
  {
759
    HashMap attributes = new HashMap();
760
    String ldapUrl = this.ldapUrl;
761
    String ldapBase = this.ldapBase;
762
    String userident = foruser;
763

    
764
    // Identify service provider to use
765
    Hashtable env = new Hashtable(11);
766
    env.put(Context.INITIAL_CONTEXT_FACTORY,
767
        "com.sun.jndi.ldap.LdapCtxFactory");
768
    env.put(Context.REFERRAL, referral);
769
    env.put(Context.PROVIDER_URL, ldapUrl);
770

    
771
    try {
772

    
773
      // Create the initial directory context
774
      DirContext ctx = new InitialDirContext(env);
775

    
776
      // Ask for all attributes of the user
777
      //Attributes attrs = ctx.getAttributes(userident);
778
      Attributes attrs = ctx.getAttributes(foruser);
779

    
780
      // Print all of the attributes
781
      NamingEnumeration en = attrs.getAll();
782
      while (en.hasMore()) {
783
        Attribute att = (Attribute)en.next();
784
        Vector values = new Vector();
785
        String attName = att.getID();
786
        NamingEnumeration attvalues = att.getAll();
787
        while (attvalues.hasMore()) {
788
          String value = (String)attvalues.next();
789
          values.add(value);
790
        }
791
        attributes.put(attName, values);
792
      }
793

    
794
      // Close the context when we're done
795
      ctx.close();
796
    } catch (NamingException e) {
797
      util.debugMessage("Problem getting attributes in " +
798
              "AuthLdap.getAttributes:" + e, 35);
799
      throw new ConnectException(
800
      "Problem getting attributes in AuthLdap.getAttributes:" + e);
801
    }
802

    
803
    return attributes;
804
  }
805

    
806
  /**
807
   * Get list of all subtrees holding Metacat's groups and users
808
   * starting from the Metacat LDAP root,
809
   * i.e. ldap://dev.nceas.ucsb.edu/dc=ecoinformatics,dc=org
810
   */
811
  private Hashtable getSubtrees(String user, String password,
812
                                String ldapUrl, String ldapBase)
813
                throws ConnectException
814
  {
815
    Hashtable trees = new Hashtable();
816

    
817
    // Identify service provider to use
818
    Hashtable env = new Hashtable(11);
819
    env.put(Context.INITIAL_CONTEXT_FACTORY,
820
            "com.sun.jndi.ldap.LdapCtxFactory");
821
    env.put(Context.REFERRAL, referral);
822
    env.put(Context.PROVIDER_URL, ldapUrl + ldapBase);
823

    
824
    try {
825

    
826
        // Create the initial directory context
827
        DirContext ctx = new InitialDirContext(env);
828

    
829
        // Specify the ids of the attributes to return
830
        String[] attrIDs = {"o","ref"};
831
        SearchControls ctls = new SearchControls();
832
        ctls.setReturningAttributes(attrIDs);
833
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
834

    
835
        // Specify the attributes to match.
836
        // Subtrees from the main server are found as objects with attribute
837
        // objectclass=organization or objectclass=referral to the subtree
838
        // resided on other server.
839
        String filter = "(|(objectclass=organization)(objectclass=referral))";
840

    
841
        // Search for objects in the current context
842
        NamingEnumeration enum = ctx.search("", filter, ctls);
843

    
844
        // Print the subtrees' <ldapURL, baseDN>
845
        while (enum.hasMore()) {
846
          SearchResult sr = (SearchResult)enum.next();
847
          Attributes attrs = sr.getAttributes();
848
          NamingEnumeration enum1 = attrs.getAll(); // "dc" and "ref" attrs
849
          if (enum1.hasMore()) {
850
            Attribute attr = (Attribute)enum1.next();
851
            String attrValue = (String)attr.get();
852
            String attrName = (String)attr.getID();
853

    
854
            if ( enum1.hasMore() ) {
855
              attr = (Attribute)enum1.next();
856
              String refValue = (String)attr.get();
857
              String refName = (String)attr.getID();
858
               if ( ldapBase.startsWith(refName + "=" + refValue) ) {
859
                trees.put(ldapBase,
860
                         attrValue.substring(0,attrValue.lastIndexOf("/")+1) );
861
              } else {
862
                trees.put(refName + "=" + refValue + "," + ldapBase,
863
                         attrValue.substring(0,attrValue.lastIndexOf("/")+1) );
864
              }
865

    
866
            } else if ( ldapBase.startsWith(attrName + "=" + attrValue) ) {
867
                trees.put(ldapBase, ldapUrl);
868
            } else {
869
                trees.put(attrName + "=" + attrValue + "," + ldapBase, ldapUrl);
870
            }
871
          }
872
        }
873

    
874
        // Close the context when we're done
875
        ctx.close();
876

    
877
    } catch (NamingException e) {
878
      util.debugMessage("Problem getting subtrees in AuthLdap.getSubtrees:"
879
                        + e, 30);
880
      throw new ConnectException(
881
      "Problem getting subtrees in AuthLdap.getSubtrees:" + e);
882
    }
883

    
884
    return trees;
885
  }
886

    
887
  /**
888
   * Get all groups and users from authentication scheme.
889
   * The output is formatted in XML.
890
   * @param user the user which requests the information
891
   * @param password the user's password
892
   */
893
  public String getPrincipals(String user, String password)
894
                throws ConnectException
895
  {
896
    StringBuffer out = new StringBuffer();
897
    Vector usersIn = new Vector();
898

    
899
    out.append("<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\n");
900
    out.append("<principals>\n");
901

    
902
    /*
903
     * get all subtrees first in the current dir context
904
     * and then the Metacat users under them
905
     */
906
    Hashtable subtrees = getSubtrees(user,password,this.ldapUrl,this.ldapBase);
907

    
908
    Enumeration enum = subtrees.keys();
909
    while ( enum.hasMoreElements() ) {
910
      this.ldapBase = (String)enum.nextElement();
911
      this.ldapUrl = (String)subtrees.get(ldapBase);
912

    
913
      String orgName = this.ldapBase;
914
      if(orgName != null && orgName.indexOf("o=") > -1){
915
        orgName = orgName.substring(orgName.indexOf("o=") +2);
916
        orgName = orgName.substring(0, orgName.indexOf(","));
917
      } else {
918
        orgName = "";
919
      }
920
      out.append(orgName);
921
      out.append("  <authSystem URI=\"" +
922
                 this.ldapUrl + this.ldapBase + "\" organization=\"" + orgName +"\">\n");
923

    
924
      // get all groups for directory context
925
      String[][] groups = getGroups(user, password);
926
      String[][] users = getUsers(user, password);
927
      int userIndex = 0;
928

    
929
      // for the groups and users that belong to them
930
      if ( groups!=null && groups.length > 0 ) {
931
        for (int i=0; i < groups.length; i++ ) {
932
          out.append("    <group>\n");
933
          out.append("      <groupname>" + groups[i][0] + "</groupname>\n");
934
          out.append("      <description>" + groups[i][1] + "</description>\n");
935
          String[] usersForGroup = getUsers(user,password,groups[i][0]);
936
          for (int j=0; j < usersForGroup.length; j++ ) {
937
            usersIn.addElement(usersForGroup[j]);
938

    
939
            userIndex = searchUser(usersForGroup[j], users);
940
            out.append("      <user>\n");
941

    
942
            if(userIndex < 0){
943
              out.append("        <username>" + usersForGroup[j] +
944
                         "</username>\n");
945
            } else {
946
              out.append("      <username>" + users[userIndex][0] + "</username>\n");
947
              out.append("      <name>" + users[userIndex][1] + "</name>\n");
948
              out.append("      <organization>" + users[userIndex][2] + "</organization>\n");
949
              out.append("      <email>" + users[userIndex][3] + "</email>\n");
950
            }
951

    
952
            out.append("      </user>\n");
953
          }
954
          out.append("    </group>\n");
955
        }
956
      }
957

    
958
      // for the users not belonging to any group
959
      for (int j=0; j < users.length; j++ ) {
960
        if ( !usersIn.contains(users[j][0]) ) {
961
          out.append("    <user>\n");
962
          out.append("      <username>" + users[j][0] + "</username>\n");
963
          out.append("      <name>" + users[j][1] + "</name>\n");
964
          out.append("      <organization>" + users[userIndex][2] + "</organization>\n");
965
          out.append("      <email>" + users[j][3] + "</email>\n");
966
          out.append("    </user>\n");
967
        }
968
      }
969

    
970
      out.append("  </authSystem>\n");
971
      if ( !usersIn.isEmpty() ) {
972
        usersIn.removeAllElements();
973
        usersIn.trimToSize();
974
      }
975

    
976
    }
977
    out.append("</principals>");
978
    return out.toString();
979
  }
980

    
981

    
982
  /**
983
   * Method for getting index of user DN in User info array
984
   */
985
  int searchUser(String user, String userGroup[][]){
986
    for (int j=0; j < userGroup.length; j++ ) {
987
      if (user.compareTo(userGroup[j][0]) == 0){
988
        return j;
989
      }
990
    }
991
    return -1;
992
  }
993

    
994
  /**
995
   * Test method for the class
996
   */
997
  public static void main(String[] args) {
998

    
999
    // Provide a user, such as: "Matt Jones", or "jones"
1000
    String user = args[0];
1001
    String password = args[1];
1002

    
1003
    MetaCatUtil.debugMessage("Creating session...", 20);
1004
    AuthLdap authservice = new AuthLdap();
1005
    MetaCatUtil.debugMessage("Session exists...", 20);
1006

    
1007
    boolean isValid = false;
1008
    try {
1009
      MetaCatUtil.debugMessage("Authenticating...", 20);
1010
      isValid = authservice.authenticate(user, password);
1011
      if (isValid) {
1012
        MetaCatUtil.debugMessage("Authentication successful for: " + user, 20 );
1013
      } else {
1014
        MetaCatUtil.debugMessage("Authentication failed for: " + user, 20);
1015
      }
1016

    
1017
      // Get attributes for the user
1018
      if (isValid) {
1019
        MetaCatUtil.debugMessage("\nGetting attributes for user....", 20);
1020
        HashMap userInfo = authservice.getAttributes(user, password, user);
1021
        // Print all of the attributes
1022
        Iterator attList = (Iterator)(((Set)userInfo.keySet()).iterator());
1023
        while (attList.hasNext()) {
1024
          String att = (String)attList.next();
1025
          Vector values = (Vector)userInfo.get(att);
1026
          Iterator attvalues = values.iterator();
1027
          while (attvalues.hasNext()) {
1028
            String value = (String)attvalues.next();
1029
            MetaCatUtil.debugMessage(att + ": " + value, 20);
1030
          }
1031
        }
1032
      }
1033

    
1034
      // get the groups
1035
      if (isValid) {
1036
        MetaCatUtil.debugMessage("\nGetting all groups....", 20);
1037
        String[][] groups = authservice.getGroups(user, password);
1038
        MetaCatUtil.debugMessage("Groups found: " + groups.length, 20);
1039
        for (int i=0; i < groups.length; i++) {
1040
            MetaCatUtil.debugMessage("Group " + i + ": " + groups[i][0], 20);
1041
        }
1042
      }
1043

    
1044
      // get the groups for the user
1045
      String savedGroup = null;
1046
      if (isValid) {
1047
        MetaCatUtil.debugMessage("\nGetting groups for user....", 20);
1048
        String[][] groups = authservice.getGroups(user, password, user);
1049
        MetaCatUtil.debugMessage("Groups found: " + groups.length, 20);
1050
        for (int i=0; i < groups.length; i++) {
1051
            MetaCatUtil.debugMessage("Group " + i + ": " + groups[i][0], 20);
1052
            savedGroup = groups[i][0];
1053
        }
1054
      }
1055

    
1056
      // get the users for a group
1057
      if (isValid) {
1058
        MetaCatUtil.debugMessage("\nGetting users for group....", 20);
1059
        MetaCatUtil.debugMessage("Group: " + savedGroup, 20);
1060
        String[] users = authservice.getUsers(user, password, savedGroup);
1061
        MetaCatUtil.debugMessage("Users found: " + users.length, 20);
1062
        for (int i=0; i < users.length; i++) {
1063
            MetaCatUtil.debugMessage("User " + i + ": " + users[i], 20);
1064
        }
1065
      }
1066

    
1067
      // get all users
1068
      if (isValid) {
1069
        MetaCatUtil.debugMessage("\nGetting all users ....", 20);
1070
        String[][] users = authservice.getUsers(user, password);
1071
        MetaCatUtil.debugMessage("Users found: " + users.length, 20);
1072

    
1073
      }
1074

    
1075
      // get the whole list groups and users in XML format
1076
      if (isValid) {
1077
        MetaCatUtil.debugMessage("\nTrying principals....", 20);
1078
        authservice = new AuthLdap();
1079
        String out = authservice.getPrincipals(user, password);
1080
        java.io.File f = new java.io.File("principals.xml");
1081
        java.io.FileWriter fw = new java.io.FileWriter(f);
1082
        java.io.BufferedWriter buff = new java.io.BufferedWriter(fw);
1083
        buff.write(out);
1084
        buff.flush();
1085
        buff.close();
1086
        fw.close();
1087
        MetaCatUtil.debugMessage("\nFinished getting principals.", 20);
1088
      }
1089

    
1090
    } catch (ConnectException ce) {
1091
      MetaCatUtil.debugMessage(ce.getMessage(), 30);
1092
    } catch (java.io.IOException ioe) {
1093
      MetaCatUtil.debugMessage("I/O Error writing to file principals.txt", 20);
1094
    }
1095
  }
1096

    
1097
  /**
1098
   * This method will be called by start a thread.
1099
   * It can handle if a referral exception happend.
1100
   */
1101
  public void run()
1102
  {
1103
    referralContext = null;
1104
    DirContext refDirContext=null;
1105
    boolean moreReferrals=true;
1106
    String referralInfo=null;
1107
    //set a while loop is because we don't know if a referral excption contains
1108
    //another referral exception
1109
    while (moreReferrals)
1110
    {
1111
      try
1112
      {
1113
        //revise environment variable
1114
        referralInfo=(String)refExc.getReferralInfo();
1115
        util.debugMessage("Processing referral (pr0): ", 35);
1116
        util.debugMessage("PROVIDER_URL set to (pr1): " + referralInfo, 35);
1117
        //env.put(Context.PROVIDER_URL,referralInfo);
1118
        //env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
1119
        //env.put(Context.SECURITY_PRINCIPAL, userName);
1120
        //env.put(Context.SECURITY_CREDENTIALS, userPassword);
1121
        //env.put(Context.REFERRAL, "throw");
1122
        //util.debugMessage("Processing referral (pr1.info): " + userName,35);
1123
        //util.debugMessage("Processing referral (pr2)",35);
1124
        //rContext = refExc.getReferralContext(env);
1125
        rContext = refExc.getReferralContext();
1126
        util.debugMessage("Processing referral (pr3)",35);
1127
        //casting the context to dircontext and it will create a
1128
        //autherntication or naming exception if DN and password is incorrect
1129
        referralContext=rContext;
1130
        refDirContext=(DirContext)rContext;
1131
        refDirContext.close();
1132
        //get context and jump out the while loop
1133
        moreReferrals=false;
1134
        util.debugMessage("Processing referral (pr4)",35);
1135
      }//try
1136
      //if referral have another referral excption
1137
      catch (ReferralException re)
1138
      {
1139
        util.debugMessage("GOT referral exception (re1): " + re.getMessage(),35);
1140
        util.debugMessage("RE details (re2): " + re.toString(true),35);
1141
        //keep running in while loop
1142
        moreReferrals=true;
1143
        //assign refExc to new referral exception re
1144
        refExc=re;
1145
      }
1146
      //catch a authentication exception
1147
      catch (AuthenticationException ae)
1148
      {
1149
        util.debugMessage("Error running referral handler thread (ae1): " +
1150
                          ae.getMessage(), 20);
1151
        //check if has another referral
1152
        moreReferrals=refExc.skipReferral();
1153
        //don't get the context
1154
        referralContext = null;
1155
      }
1156
      //catch a naming exception
1157
      catch (NamingException ne)
1158
      {
1159
        util.debugMessage("Error running referral handler thread (ne1): " +
1160
                          ne.getMessage(), 20);
1161
        //check if has another referral
1162
        moreReferrals=refExc.skipReferral();
1163
        //don't get context
1164
        referralContext = null;
1165
      }
1166
    }//while
1167
  }//run()
1168

    
1169
  private class GetGroup implements Runnable
1170
  {
1171
    public void run()
1172
    {
1173
      referralContext = null;
1174
      MetaCatUtil.debugMessage("getting groups context", 50);
1175
      DirContext refDirContext=null;
1176
      boolean moreReferrals=true;
1177
      //set a while loop is because we don't know if a referral excption
1178
      //contains another referral exception
1179
      while (moreReferrals)
1180
      {
1181
        try
1182
        {
1183
          //revise environment variable
1184
          String refInfo = null;
1185
          refInfo = (String)refExc.getReferralInfo();
1186
          if(refInfo != null)
1187
          {
1188
            MetaCatUtil.debugMessage("Referral in thread to: " +
1189
                              refInfo.toString(), 40);
1190
          }
1191
          else
1192
          {
1193
            MetaCatUtil.debugMessage("getting refInfo Manually", 50);
1194
            refInfo = (String)refExc.getReferralContext().getEnvironment().
1195
                                                  get(Context.PROVIDER_URL);
1196
          }
1197
          MetaCatUtil.debugMessage("refInfo: " + refInfo, 40);
1198

    
1199
          env.put(Context.INITIAL_CONTEXT_FACTORY,
1200
              "com.sun.jndi.ldap.LdapCtxFactory");
1201
          env.put(Context.REFERRAL, "throw");
1202
          env.put(Context.PROVIDER_URL, refInfo);
1203

    
1204
          MetaCatUtil.debugMessage("creating referralContext", 40);
1205
          referralContext = new InitialDirContext(env);
1206
          MetaCatUtil.debugMessage("referralContext created", 40);
1207
          //get context and jump out the while loop
1208
          moreReferrals=false;
1209
        }//try
1210
        catch (ReferralException re)
1211
        {
1212
          //keep running in while loop
1213
          moreReferrals=true;
1214
          //assign refExc to new referral exception re
1215
          refExc=re;
1216
        }
1217
        catch (AuthenticationException ae)
1218
        {
1219
          util.debugMessage("Error running referral handler thread (ae2): " +
1220
                          ae.getMessage(), 50);
1221
          //check if has another referral
1222
          moreReferrals=refExc.skipReferral();
1223
          //don't get the context
1224
          referralContext = null;
1225
        }
1226
        catch (NamingException ne)
1227
        {
1228
          util.debugMessage("Error running referral handler thread (ne2): " +
1229
                          ne.getMessage(), 50);
1230
          //check if has another referral
1231
          moreReferrals=refExc.skipReferral();
1232
          //don't get context
1233
          referralContext = null;
1234
        }
1235
      }//while
1236
    }//run()
1237
 }
1238
}
(12-12/61)