PageRenderTime 8ms CodeModel.GetById 5ms app.highlight 57ms RepoModel.GetById 1ms app.codeStats 0ms

/java/org/apache/catalina/realm/JNDIRealm.java

https://github.com/zongweiyang/tomcat
Java | 2427 lines | 1051 code | 509 blank | 867 comment | 261 complexity | 86207623f89127ae532c91a7a0250d70 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2 * Licensed to the Apache Software Foundation (ASF) under one or more
   3 * contributor license agreements.  See the NOTICE file distributed with
   4 * this work for additional information regarding copyright ownership.
   5 * The ASF licenses this file to You under the Apache License, Version 2.0
   6 * (the "License"); you may not use this file except in compliance with
   7 * the License.  You may obtain a copy of the License at
   8 *
   9 *      http://www.apache.org/licenses/LICENSE-2.0
  10 *
  11 * Unless required by applicable law or agreed to in writing, software
  12 * distributed under the License is distributed on an "AS IS" BASIS,
  13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 * See the License for the specific language governing permissions and
  15 * limitations under the License.
  16 */
  17
  18package org.apache.catalina.realm;
  19
  20import java.net.URI;
  21import java.net.URISyntaxException;
  22import java.security.Principal;
  23import java.text.MessageFormat;
  24import java.util.ArrayList;
  25import java.util.Arrays;
  26import java.util.Collections;
  27import java.util.HashMap;
  28import java.util.Hashtable;
  29import java.util.Iterator;
  30import java.util.List;
  31import java.util.Map;
  32import java.util.Map.Entry;
  33import java.util.Set;
  34
  35import javax.naming.AuthenticationException;
  36import javax.naming.CommunicationException;
  37import javax.naming.CompositeName;
  38import javax.naming.Context;
  39import javax.naming.InvalidNameException;
  40import javax.naming.Name;
  41import javax.naming.NameNotFoundException;
  42import javax.naming.NameParser;
  43import javax.naming.NamingEnumeration;
  44import javax.naming.NamingException;
  45import javax.naming.PartialResultException;
  46import javax.naming.ServiceUnavailableException;
  47import javax.naming.directory.Attribute;
  48import javax.naming.directory.Attributes;
  49import javax.naming.directory.DirContext;
  50import javax.naming.directory.InitialDirContext;
  51import javax.naming.directory.SearchControls;
  52import javax.naming.directory.SearchResult;
  53
  54import org.apache.catalina.LifecycleException;
  55import org.apache.tomcat.util.buf.B2CConverter;
  56import org.apache.tomcat.util.codec.binary.Base64;
  57import org.ietf.jgss.GSSCredential;
  58
  59/**
  60 * <p>Implementation of <strong>Realm</strong> that works with a directory
  61 * server accessed via the Java Naming and Directory Interface (JNDI) APIs.
  62 * The following constraints are imposed on the data structure in the
  63 * underlying directory server:</p>
  64 * <ul>
  65 *
  66 * <li>Each user that can be authenticated is represented by an individual
  67 *     element in the top level <code>DirContext</code> that is accessed
  68 *     via the <code>connectionURL</code> property.</li>
  69 *
  70 * <li>If a socket connection can not be made to the <code>connectURL</code>
  71 *     an attempt will be made to use the <code>alternateURL</code> if it
  72 *     exists.</li>
  73 *
  74 * <li>Each user element has a distinguished name that can be formed by
  75 *     substituting the presented username into a pattern configured by the
  76 *     <code>userPattern</code> property.</li>
  77 *
  78 * <li>Alternatively, if the <code>userPattern</code> property is not
  79 *     specified, a unique element can be located by searching the directory
  80 *     context. In this case:
  81 *     <ul>
  82 *     <li>The <code>userSearch</code> pattern specifies the search filter
  83 *         after substitution of the username.</li>
  84 *     <li>The <code>userBase</code> property can be set to the element that
  85 *         is the base of the subtree containing users.  If not specified,
  86 *         the search base is the top-level context.</li>
  87 *     <li>The <code>userSubtree</code> property can be set to
  88 *         <code>true</code> if you wish to search the entire subtree of the
  89 *         directory context.  The default value of <code>false</code>
  90 *         requests a search of only the current level.</li>
  91 *    </ul>
  92 * </li>
  93 *
  94 * <li>The user may be authenticated by binding to the directory with the
  95 *      username and password presented. This method is used when the
  96 *      <code>userPassword</code> property is not specified.</li>
  97 *
  98 * <li>The user may be authenticated by retrieving the value of an attribute
  99 *     from the directory and comparing it explicitly with the value presented
 100 *     by the user. This method is used when the <code>userPassword</code>
 101 *     property is specified, in which case:
 102 *     <ul>
 103 *     <li>The element for this user must contain an attribute named by the
 104 *         <code>userPassword</code> property.
 105 *     <li>The value of the user password attribute is either a cleartext
 106 *         String, or the result of passing a cleartext String through the
 107 *         <code>RealmBase.digest()</code> method (using the standard digest
 108 *         support included in <code>RealmBase</code>).
 109 *     <li>The user is considered to be authenticated if the presented
 110 *         credentials (after being passed through
 111 *         <code>RealmBase.digest()</code>) are equal to the retrieved value
 112 *         for the user password attribute.</li>
 113 *     </ul></li>
 114 *
 115 * <li>Each group of users that has been assigned a particular role may be
 116 *     represented by an individual element in the top level
 117 *     <code>DirContext</code> that is accessed via the
 118 *     <code>connectionURL</code> property.  This element has the following
 119 *     characteristics:
 120 *     <ul>
 121 *     <li>The set of all possible groups of interest can be selected by a
 122 *         search pattern configured by the <code>roleSearch</code>
 123 *         property.</li>
 124 *     <li>The <code>roleSearch</code> pattern optionally includes pattern
 125 *         replacements "{0}" for the distinguished name, and/or "{1}" for
 126 *         the username, and/or "{2}" the value of an attribute from the
 127 *         user's directory entry (the attribute is specified by the
 128 *         <code>userRoleAttribute</code> property), of the authenticated user
 129 *         for which roles will be retrieved.</li>
 130 *     <li>The <code>roleBase</code> property can be set to the element that
 131 *         is the base of the search for matching roles.  If not specified,
 132 *         the entire context will be searched.</li>
 133 *     <li>The <code>roleSubtree</code> property can be set to
 134 *         <code>true</code> if you wish to search the entire subtree of the
 135 *         directory context.  The default value of <code>false</code>
 136 *         requests a search of only the current level.</li>
 137 *     <li>The element includes an attribute (whose name is configured by
 138 *         the <code>roleName</code> property) containing the name of the
 139 *         role represented by this element.</li>
 140 *     </ul></li>
 141 *
 142 * <li>In addition, roles may be represented by the values of an attribute
 143 * in the user's element whose name is configured by the
 144 * <code>userRoleName</code> property.</li>
 145 *
 146 * <li>A default role can be assigned to each user that was successfully
 147 * authenticated by setting the <code>commonRole</code> property to the
 148 * name of this role. The role doesn't have to exist in the directory.</li>
 149 *
 150 * <li>If the directory server contains nested roles, you can search for them
 151 * by setting <code>roleNested</code> to <code>true</code>.
 152 * The default value is <code>false</code>, so role searches will not find
 153 * nested roles.</li>
 154 *
 155 * <li>Note that the standard <code>&lt;security-role-ref&gt;</code> element in
 156 *     the web application deployment descriptor allows applications to refer
 157 *     to roles programmatically by names other than those used in the
 158 *     directory server itself.</li>
 159 * </ul>
 160 *
 161 * <p><strong>TODO</strong> - Support connection pooling (including message
 162 * format objects) so that <code>authenticate()</code> does not have to be
 163 * synchronized.</p>
 164 *
 165 * <p><strong>WARNING</strong> - There is a reported bug against the Netscape
 166 * provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
 167 * successfully authenticated a non-existing user. The
 168 * report is here: http://issues.apache.org/bugzilla/show_bug.cgi?id=11210 .
 169 * With luck, Netscape has updated their provider code and this is not an
 170 * issue. </p>
 171 *
 172 * @author John Holman
 173 * @author Craig R. McClanahan
 174 * @version $Id$
 175 */
 176
 177public class JNDIRealm extends RealmBase {
 178
 179
 180    // ----------------------------------------------------- Instance Variables
 181
 182    /**
 183     *  The type of authentication to use
 184     */
 185    protected String authentication = null;
 186
 187    /**
 188     * The connection username for the server we will contact.
 189     */
 190    protected String connectionName = null;
 191
 192
 193    /**
 194     * The connection password for the server we will contact.
 195     */
 196    protected String connectionPassword = null;
 197
 198
 199    /**
 200     * The connection URL for the server we will contact.
 201     */
 202    protected String connectionURL = null;
 203
 204
 205    /**
 206     * The directory context linking us to our directory server.
 207     */
 208    protected DirContext context = null;
 209
 210
 211    /**
 212     * The JNDI context factory used to acquire our InitialContext.  By
 213     * default, assumes use of an LDAP server using the standard JNDI LDAP
 214     * provider.
 215     */
 216    protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
 217
 218
 219    /**
 220     * How aliases should be dereferenced during search operations.
 221     */
 222    protected String derefAliases = null;
 223
 224    /**
 225     * Constant that holds the name of the environment property for specifying
 226     * the manner in which aliases should be dereferenced.
 227     */
 228    public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
 229
 230
 231    /**
 232     * Descriptive information about this Realm implementation.
 233     */
 234    protected static final String name = "JNDIRealm";
 235
 236
 237    /**
 238     * The protocol that will be used in the communication with the
 239     * directory server.
 240     */
 241    protected String protocol = null;
 242
 243
 244    /**
 245     * Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
 246     * Microsoft Active Directory often returns referrals, which lead
 247     * to PartialResultExceptions. Unfortunately there's no stable way to detect,
 248     * if the Exceptions really come from an AD referral.
 249     * Set to true to ignore PartialResultExceptions.
 250     */
 251    protected boolean adCompat = false;
 252
 253
 254    /**
 255     * How should we handle referrals?  Microsoft Active Directory often returns
 256     * referrals. If you need to follow them set referrals to "follow".
 257     * Caution: if your DNS is not part of AD, the LDAP client lib might try
 258     * to resolve your domain name in DNS to find another LDAP server.
 259     */
 260    protected String referrals = null;
 261
 262
 263    /**
 264     * The base element for user searches.
 265     */
 266    protected String userBase = "";
 267
 268
 269    /**
 270     * The message format used to search for a user, with "{0}" marking
 271     * the spot where the username goes.
 272     */
 273    protected String userSearch = null;
 274
 275
 276    /**
 277     * The MessageFormat object associated with the current
 278     * <code>userSearch</code>.
 279     */
 280    protected MessageFormat userSearchFormat = null;
 281
 282
 283    /**
 284     * Should we search the entire subtree for matching users?
 285     */
 286    protected boolean userSubtree = false;
 287
 288
 289    /**
 290     * The attribute name used to retrieve the user password.
 291     */
 292    protected String userPassword = null;
 293
 294    /**
 295     * The name of the attribute inside the users
 296     * directory entry where the value will be
 297     * taken to search for roles
 298     * This attribute is not used during a nested search
 299     */
 300    protected String userRoleAttribute = null;
 301
 302
 303    /**
 304     * A string of LDAP user patterns or paths, ":"-separated
 305     * These will be used to form the distinguished name of a
 306     * user, with "{0}" marking the spot where the specified username
 307     * goes.
 308     * This is similar to userPattern, but allows for multiple searches
 309     * for a user.
 310     */
 311    protected String[] userPatternArray = null;
 312
 313
 314    /**
 315     * The message format used to form the distinguished name of a
 316     * user, with "{0}" marking the spot where the specified username
 317     * goes.
 318     */
 319    protected String userPattern = null;
 320
 321
 322    /**
 323     * An array of MessageFormat objects associated with the current
 324     * <code>userPatternArray</code>.
 325     */
 326    protected MessageFormat[] userPatternFormatArray = null;
 327
 328    /**
 329     * The base element for role searches.
 330     */
 331    protected String roleBase = "";
 332
 333
 334    /**
 335     * The MessageFormat object associated with the current
 336     * <code>roleBase</code>.
 337     */
 338    protected MessageFormat roleBaseFormat = null;
 339
 340
 341    /**
 342     * The MessageFormat object associated with the current
 343     * <code>roleSearch</code>.
 344     */
 345    protected MessageFormat roleFormat = null;
 346
 347
 348    /**
 349     * The name of an attribute in the user's entry containing
 350     * roles for that user
 351     */
 352    protected String userRoleName = null;
 353
 354
 355    /**
 356     * The name of the attribute containing roles held elsewhere
 357     */
 358    protected String roleName = null;
 359
 360
 361    /**
 362     * The message format used to select roles for a user, with "{0}" marking
 363     * the spot where the distinguished name of the user goes. The "{1}"
 364     * and "{2}" are described in the Configuration Reference.
 365     */
 366    protected String roleSearch = null;
 367
 368
 369    /**
 370     * Should we search the entire subtree for matching memberships?
 371     */
 372    protected boolean roleSubtree = false;
 373
 374    /**
 375     * Should we look for nested group in order to determine roles?
 376     */
 377    protected boolean roleNested = false;
 378
 379    /**
 380     * When searching for user roles, should the search be performed as the user
 381     * currently being authenticated? If false, {@link #connectionName} and
 382     * {@link #connectionPassword} will be used if specified, else an anonymous
 383     * connection will be used.
 384     */
 385    protected boolean roleSearchAsUser = false;
 386
 387    /**
 388     * An alternate URL, to which, we should connect if connectionURL fails.
 389     */
 390    protected String alternateURL;
 391
 392    /**
 393     * The number of connection attempts.  If greater than zero we use the
 394     * alternate url.
 395     */
 396    protected int connectionAttempt = 0;
 397
 398    /**
 399     *  Add this role to every authenticated user
 400     */
 401    protected String commonRole = null;
 402
 403
 404    /**
 405     * The timeout, in milliseconds, to use when trying to create a connection
 406     * to the directory. The default is 5000 (5 seconds).
 407     */
 408    protected String connectionTimeout = "5000";
 409
 410    /**
 411     * The sizeLimit (also known as the countLimit) to use when the realm is
 412     * configured with {@link #userSearch}. Zero for no limit.
 413     */
 414    protected long sizeLimit = 0;
 415
 416    /**
 417     * The timeLimit (in milliseconds) to use when the realm is configured with
 418     * {@link #userSearch}. Zero for no limit.
 419     */
 420    protected int timeLimit = 0;
 421
 422
 423    /**
 424     * Should delegated credentials from the SPNEGO authenticator be used if
 425     * available
 426     */
 427    protected boolean useDelegatedCredential = true;
 428
 429
 430    // ------------------------------------------------------------- Properties
 431
 432    /**
 433     * Return the type of authentication to use.
 434     */
 435    public String getAuthentication() {
 436
 437        return authentication;
 438
 439    }
 440
 441    /**
 442     * Set the type of authentication to use.
 443     *
 444     * @param authentication The authentication
 445     */
 446    public void setAuthentication(String authentication) {
 447
 448        this.authentication = authentication;
 449
 450    }
 451
 452    /**
 453     * Return the connection username for this Realm.
 454     */
 455    public String getConnectionName() {
 456
 457        return (this.connectionName);
 458
 459    }
 460
 461
 462    /**
 463     * Set the connection username for this Realm.
 464     *
 465     * @param connectionName The new connection username
 466     */
 467    public void setConnectionName(String connectionName) {
 468
 469        this.connectionName = connectionName;
 470
 471    }
 472
 473
 474    /**
 475     * Return the connection password for this Realm.
 476     */
 477    public String getConnectionPassword() {
 478
 479        return (this.connectionPassword);
 480
 481    }
 482
 483
 484    /**
 485     * Set the connection password for this Realm.
 486     *
 487     * @param connectionPassword The new connection password
 488     */
 489    public void setConnectionPassword(String connectionPassword) {
 490
 491        this.connectionPassword = connectionPassword;
 492
 493    }
 494
 495
 496    /**
 497     * Return the connection URL for this Realm.
 498     */
 499    public String getConnectionURL() {
 500
 501        return (this.connectionURL);
 502
 503    }
 504
 505
 506    /**
 507     * Set the connection URL for this Realm.
 508     *
 509     * @param connectionURL The new connection URL
 510     */
 511    public void setConnectionURL(String connectionURL) {
 512
 513        this.connectionURL = connectionURL;
 514
 515    }
 516
 517
 518    /**
 519     * Return the JNDI context factory for this Realm.
 520     */
 521    public String getContextFactory() {
 522
 523        return (this.contextFactory);
 524
 525    }
 526
 527
 528    /**
 529     * Set the JNDI context factory for this Realm.
 530     *
 531     * @param contextFactory The new context factory
 532     */
 533    public void setContextFactory(String contextFactory) {
 534
 535        this.contextFactory = contextFactory;
 536
 537    }
 538
 539    /**
 540     * Return the derefAliases setting to be used.
 541     */
 542    public java.lang.String getDerefAliases() {
 543        return derefAliases;
 544    }
 545
 546    /**
 547     * Set the value for derefAliases to be used when searching the directory.
 548     *
 549     * @param derefAliases New value of property derefAliases.
 550     */
 551    public void setDerefAliases(java.lang.String derefAliases) {
 552      this.derefAliases = derefAliases;
 553    }
 554
 555    /**
 556     * Return the protocol to be used.
 557     */
 558    public String getProtocol() {
 559
 560        return protocol;
 561
 562    }
 563
 564    /**
 565     * Set the protocol for this Realm.
 566     *
 567     * @param protocol The new protocol.
 568     */
 569    public void setProtocol(String protocol) {
 570
 571        this.protocol = protocol;
 572
 573    }
 574
 575
 576    /**
 577     * Returns the current settings for handling PartialResultExceptions
 578     */
 579    public boolean getAdCompat () {
 580        return adCompat;
 581    }
 582
 583
 584    /**
 585     * How do we handle PartialResultExceptions?
 586     * True: ignore all PartialResultExceptions.
 587     */
 588    public void setAdCompat (boolean adCompat) {
 589        this.adCompat = adCompat;
 590    }
 591
 592
 593    /**
 594     * Returns the current settings for handling JNDI referrals.
 595     */
 596    public String getReferrals () {
 597        return referrals;
 598    }
 599
 600
 601    /**
 602     * How do we handle JNDI referrals? ignore, follow, or throw
 603     * (see javax.naming.Context.REFERRAL for more information).
 604     */
 605    public void setReferrals (String referrals) {
 606        this.referrals = referrals;
 607    }
 608
 609
 610    /**
 611     * Return the base element for user searches.
 612     */
 613    public String getUserBase() {
 614
 615        return (this.userBase);
 616
 617    }
 618
 619
 620    /**
 621     * Set the base element for user searches.
 622     *
 623     * @param userBase The new base element
 624     */
 625    public void setUserBase(String userBase) {
 626
 627        this.userBase = userBase;
 628
 629    }
 630
 631
 632    /**
 633     * Return the message format pattern for selecting users in this Realm.
 634     */
 635    public String getUserSearch() {
 636
 637        return (this.userSearch);
 638
 639    }
 640
 641
 642    /**
 643     * Set the message format pattern for selecting users in this Realm.
 644     *
 645     * @param userSearch The new user search pattern
 646     */
 647    public void setUserSearch(String userSearch) {
 648
 649        this.userSearch = userSearch;
 650        if (userSearch == null)
 651            userSearchFormat = null;
 652        else
 653            userSearchFormat = new MessageFormat(userSearch);
 654
 655    }
 656
 657
 658    /**
 659     * Return the "search subtree for users" flag.
 660     */
 661    public boolean getUserSubtree() {
 662
 663        return (this.userSubtree);
 664
 665    }
 666
 667
 668    /**
 669     * Set the "search subtree for users" flag.
 670     *
 671     * @param userSubtree The new search flag
 672     */
 673    public void setUserSubtree(boolean userSubtree) {
 674
 675        this.userSubtree = userSubtree;
 676
 677    }
 678
 679
 680    /**
 681     * Return the user role name attribute name for this Realm.
 682     */
 683    public String getUserRoleName() {
 684
 685        return userRoleName;
 686    }
 687
 688
 689    /**
 690     * Set the user role name attribute name for this Realm.
 691     *
 692     * @param userRoleName The new userRole name attribute name
 693     */
 694    public void setUserRoleName(String userRoleName) {
 695
 696        this.userRoleName = userRoleName;
 697
 698    }
 699
 700
 701    /**
 702     * Return the base element for role searches.
 703     */
 704    public String getRoleBase() {
 705
 706        return (this.roleBase);
 707
 708    }
 709
 710
 711    /**
 712     * Set the base element for role searches.
 713     *
 714     * @param roleBase The new base element
 715     */
 716    public void setRoleBase(String roleBase) {
 717
 718        this.roleBase = roleBase;
 719        if (roleBase == null)
 720            roleBaseFormat = null;
 721        else
 722            roleBaseFormat = new MessageFormat(roleBase);
 723
 724    }
 725
 726
 727    /**
 728     * Return the role name attribute name for this Realm.
 729     */
 730    public String getRoleName() {
 731
 732        return (this.roleName);
 733
 734    }
 735
 736
 737    /**
 738     * Set the role name attribute name for this Realm.
 739     *
 740     * @param roleName The new role name attribute name
 741     */
 742    public void setRoleName(String roleName) {
 743
 744        this.roleName = roleName;
 745
 746    }
 747
 748
 749    /**
 750     * Return the message format pattern for selecting roles in this Realm.
 751     */
 752    public String getRoleSearch() {
 753
 754        return (this.roleSearch);
 755
 756    }
 757
 758
 759    /**
 760     * Set the message format pattern for selecting roles in this Realm.
 761     *
 762     * @param roleSearch The new role search pattern
 763     */
 764    public void setRoleSearch(String roleSearch) {
 765
 766        this.roleSearch = roleSearch;
 767        if (roleSearch == null)
 768            roleFormat = null;
 769        else
 770            roleFormat = new MessageFormat(roleSearch);
 771
 772    }
 773
 774
 775    public boolean isRoleSearchAsUser() {
 776        return roleSearchAsUser;
 777    }
 778
 779    public void setRoleSearchAsUser(boolean roleSearchAsUser) {
 780        this.roleSearchAsUser = roleSearchAsUser;
 781    }
 782
 783    /**
 784     * Return the "search subtree for roles" flag.
 785     */
 786    public boolean getRoleSubtree() {
 787
 788        return (this.roleSubtree);
 789
 790    }
 791
 792
 793    /**
 794     * Set the "search subtree for roles" flag.
 795     *
 796     * @param roleSubtree The new search flag
 797     */
 798    public void setRoleSubtree(boolean roleSubtree) {
 799
 800        this.roleSubtree = roleSubtree;
 801
 802    }
 803
 804    /**
 805     * Return the "The nested group search flag" flag.
 806     */
 807    public boolean getRoleNested() {
 808
 809        return (this.roleNested);
 810
 811    }
 812
 813
 814    /**
 815     * Set the "search subtree for roles" flag.
 816     *
 817     * @param roleNested The nested group search flag
 818     */
 819    public void setRoleNested(boolean roleNested) {
 820
 821        this.roleNested = roleNested;
 822
 823    }
 824
 825
 826    /**
 827     * Return the password attribute used to retrieve the user password.
 828     */
 829    public String getUserPassword() {
 830
 831        return (this.userPassword);
 832
 833    }
 834
 835
 836    /**
 837     * Set the password attribute used to retrieve the user password.
 838     *
 839     * @param userPassword The new password attribute
 840     */
 841    public void setUserPassword(String userPassword) {
 842
 843        this.userPassword = userPassword;
 844
 845    }
 846
 847
 848    public String getUserRoleAttribute() {
 849        return userRoleAttribute;
 850    }
 851
 852    public void setUserRoleAttribute(String userRoleAttribute) {
 853        this.userRoleAttribute = userRoleAttribute;
 854    }
 855
 856    /**
 857     * Return the message format pattern for selecting users in this Realm.
 858     */
 859    public String getUserPattern() {
 860
 861        return (this.userPattern);
 862
 863    }
 864
 865
 866
 867
 868    /**
 869     * Set the message format pattern for selecting users in this Realm.
 870     * This may be one simple pattern, or multiple patterns to be tried,
 871     * separated by parentheses. (for example, either "cn={0}", or
 872     * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
 873     * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
 874     * also valid. Complex search strings with &, etc are NOT supported.
 875     *
 876     * @param userPattern The new user pattern
 877     */
 878    public void setUserPattern(String userPattern) {
 879
 880        this.userPattern = userPattern;
 881        if (userPattern == null)
 882            userPatternArray = null;
 883        else {
 884            userPatternArray = parseUserPatternString(userPattern);
 885            int len = this.userPatternArray.length;
 886            userPatternFormatArray = new MessageFormat[len];
 887            for (int i=0; i < len; i++) {
 888                userPatternFormatArray[i] =
 889                    new MessageFormat(userPatternArray[i]);
 890            }
 891        }
 892    }
 893
 894
 895    /**
 896     * Getter for property alternateURL.
 897     *
 898     * @return Value of property alternateURL.
 899     */
 900    public String getAlternateURL() {
 901
 902        return this.alternateURL;
 903
 904    }
 905
 906
 907    /**
 908     * Setter for property alternateURL.
 909     *
 910     * @param alternateURL New value of property alternateURL.
 911     */
 912    public void setAlternateURL(String alternateURL) {
 913
 914        this.alternateURL = alternateURL;
 915
 916    }
 917
 918
 919    /**
 920     * Return the common role
 921     */
 922    public String getCommonRole() {
 923
 924        return commonRole;
 925
 926    }
 927
 928
 929    /**
 930     * Set the common role
 931     *
 932     * @param commonRole The common role
 933     */
 934    public void setCommonRole(String commonRole) {
 935
 936        this.commonRole = commonRole;
 937
 938    }
 939
 940
 941    /**
 942     * Return the connection timeout.
 943     */
 944    public String getConnectionTimeout() {
 945
 946        return connectionTimeout;
 947
 948    }
 949
 950
 951    /**
 952     * Set the connection timeout.
 953     *
 954     * @param timeout The new connection timeout
 955     */
 956    public void setConnectionTimeout(String timeout) {
 957
 958        this.connectionTimeout = timeout;
 959
 960    }
 961
 962
 963    public long getSizeLimit() {
 964        return sizeLimit;
 965    }
 966
 967
 968    public void setSizeLimit(long sizeLimit) {
 969        this.sizeLimit = sizeLimit;
 970    }
 971
 972
 973    public int getTimeLimit() {
 974        return timeLimit;
 975    }
 976
 977
 978    public void setTimeLimit(int timeLimit) {
 979        this.timeLimit = timeLimit;
 980    }
 981
 982
 983
 984    public boolean isUseDelegatedCredential() {
 985        return useDelegatedCredential;
 986    }
 987
 988    public void setUseDelegatedCredential(boolean useDelegatedCredential) {
 989        this.useDelegatedCredential = useDelegatedCredential;
 990    }
 991
 992
 993    // ---------------------------------------------------------- Realm Methods
 994
 995    /**
 996     * Return the Principal associated with the specified username and
 997     * credentials, if there is one; otherwise return <code>null</code>.
 998     *
 999     * If there are any errors with the JDBC connection, executing
1000     * the query or anything we return null (don't authenticate). This
1001     * event is also logged, and the connection will be closed so that
1002     * a subsequent request will automatically re-open it.
1003     *
1004     * @param username Username of the Principal to look up
1005     * @param credentials Password or other credentials to use in
1006     *  authenticating this username
1007     */
1008    @Override
1009    public Principal authenticate(String username, String credentials) {
1010
1011        DirContext context = null;
1012        Principal principal = null;
1013
1014        try {
1015
1016            // Ensure that we have a directory context available
1017            context = open();
1018
1019            // Occassionally the directory context will timeout.  Try one more
1020            // time before giving up.
1021            try {
1022
1023                // Authenticate the specified username if possible
1024                principal = authenticate(context, username, credentials);
1025
1026            } catch (NullPointerException e) {
1027                /* BZ 42449 - Kludge Sun's LDAP provider
1028                   with broken SSL
1029                */
1030                // log the exception so we know it's there.
1031                containerLog.warn(sm.getString("jndiRealm.exception"), e);
1032
1033                // close the connection so we know it will be reopened.
1034                if (context != null)
1035                    close(context);
1036
1037                // open a new directory context.
1038                context = open();
1039
1040                // Try the authentication again.
1041                principal = authenticate(context, username, credentials);
1042
1043            } catch (CommunicationException e) {
1044
1045                // log the exception so we know it's there.
1046                containerLog.warn(sm.getString("jndiRealm.exception"), e);
1047
1048                // close the connection so we know it will be reopened.
1049                if (context != null)
1050                    close(context);
1051
1052                // open a new directory context.
1053                context = open();
1054
1055                // Try the authentication again.
1056                principal = authenticate(context, username, credentials);
1057
1058            } catch (ServiceUnavailableException e) {
1059
1060                // log the exception so we know it's there.
1061                containerLog.warn(sm.getString("jndiRealm.exception"), e);
1062
1063                // close the connection so we know it will be reopened.
1064                if (context != null)
1065                    close(context);
1066
1067                // open a new directory context.
1068                context = open();
1069
1070                // Try the authentication again.
1071                principal = authenticate(context, username, credentials);
1072
1073            }
1074
1075
1076            // Release this context
1077            release(context);
1078
1079            // Return the authenticated Principal (if any)
1080            return (principal);
1081
1082        } catch (NamingException e) {
1083
1084            // Log the problem for posterity
1085            containerLog.error(sm.getString("jndiRealm.exception"), e);
1086
1087            // Close the connection so that it gets reopened next time
1088            if (context != null)
1089                close(context);
1090
1091            // Return "not authenticated" for this request
1092            if (containerLog.isDebugEnabled())
1093                containerLog.debug("Returning null principal.");
1094            return (null);
1095
1096        }
1097
1098    }
1099
1100
1101    // -------------------------------------------------------- Package Methods
1102
1103
1104    // ------------------------------------------------------ Protected Methods
1105
1106
1107    /**
1108     * Return the Principal associated with the specified username and
1109     * credentials, if there is one; otherwise return <code>null</code>.
1110     *
1111     * @param context The directory context
1112     * @param username Username of the Principal to look up
1113     * @param credentials Password or other credentials to use in
1114     *  authenticating this username
1115     *
1116     * @exception NamingException if a directory server error occurs
1117     */
1118    public synchronized Principal authenticate(DirContext context,
1119                                               String username,
1120                                               String credentials)
1121        throws NamingException {
1122
1123        if (username == null || username.equals("")
1124            || credentials == null || credentials.equals("")) {
1125            if (containerLog.isDebugEnabled())
1126                containerLog.debug("username null or empty: returning null principal.");
1127            return (null);
1128        }
1129
1130        if (userPatternArray != null) {
1131            for (int curUserPattern = 0;
1132                 curUserPattern < userPatternFormatArray.length;
1133                 curUserPattern++) {
1134                // Retrieve user information
1135                User user = getUser(context, username, credentials, curUserPattern);
1136                if (user != null) {
1137                    try {
1138                        // Check the user's credentials
1139                        if (checkCredentials(context, user, credentials)) {
1140                            // Search for additional roles
1141                            List<String> roles = getRoles(context, user);
1142                            if (containerLog.isDebugEnabled()) {
1143                                Iterator<String> it = roles.iterator();
1144                                // TODO: Use a single log message
1145                                while (it.hasNext()) {
1146                                    containerLog.debug("Found role: " + it.next());
1147                                }
1148                            }
1149                            return (new GenericPrincipal(username,
1150                                                         credentials,
1151                                                         roles));
1152                        }
1153                    } catch (InvalidNameException ine) {
1154                        // Log the problem for posterity
1155                        containerLog.warn(sm.getString("jndiRealm.exception"), ine);
1156                        // ignore; this is probably due to a name not fitting
1157                        // the search path format exactly, as in a fully-
1158                        // qualified name being munged into a search path
1159                        // that already contains cn= or vice-versa
1160                    }
1161                }
1162            }
1163            return null;
1164        } else {
1165            // Retrieve user information
1166            User user = getUser(context, username, credentials);
1167            if (user == null)
1168                return (null);
1169
1170            // Check the user's credentials
1171            if (!checkCredentials(context, user, credentials))
1172                return (null);
1173
1174            // Search for additional roles
1175            List<String> roles = getRoles(context, user);
1176            if (containerLog.isDebugEnabled()) {
1177                Iterator<String> it = roles.iterator();
1178                // TODO: Use a single log message
1179                while (it.hasNext()) {
1180                    containerLog.debug("Found role: " + it.next());
1181                }
1182            }
1183
1184            // Create and return a suitable Principal for this user
1185            return (new GenericPrincipal(username, credentials, roles));
1186        }
1187    }
1188
1189
1190    /**
1191     * Return a User object containing information about the user
1192     * with the specified username, if found in the directory;
1193     * otherwise return <code>null</code>.
1194     *
1195     * @param context The directory context
1196     * @param username Username to be looked up
1197     *
1198     * @exception NamingException if a directory server error occurs
1199     *
1200     * @see #getUser(DirContext, String, String, int)
1201     */
1202    protected User getUser(DirContext context, String username)
1203        throws NamingException {
1204
1205        return getUser(context, username, null, -1);
1206    }
1207
1208
1209    /**
1210     * Return a User object containing information about the user
1211     * with the specified username, if found in the directory;
1212     * otherwise return <code>null</code>.
1213     *
1214     * @param context The directory context
1215     * @param username Username to be looked up
1216     * @param credentials User credentials (optional)
1217     *
1218     * @exception NamingException if a directory server error occurs
1219     *
1220     * @see #getUser(DirContext, String, String, int)
1221     */
1222    protected User getUser(DirContext context, String username, String credentials)
1223        throws NamingException {
1224
1225        return getUser(context, username, credentials, -1);
1226    }
1227
1228
1229    /**
1230     * Return a User object containing information about the user
1231     * with the specified username, if found in the directory;
1232     * otherwise return <code>null</code>.
1233     *
1234     * If the <code>userPassword</code> configuration attribute is
1235     * specified, the value of that attribute is retrieved from the
1236     * user's directory entry. If the <code>userRoleName</code>
1237     * configuration attribute is specified, all values of that
1238     * attribute are retrieved from the directory entry.
1239     *
1240     * @param context The directory context
1241     * @param username Username to be looked up
1242     * @param credentials User credentials (optional)
1243     * @param curUserPattern Index into userPatternFormatArray
1244     *
1245     * @exception NamingException if a directory server error occurs
1246     */
1247    protected User getUser(DirContext context, String username,
1248                           String credentials, int curUserPattern)
1249        throws NamingException {
1250
1251        User user = null;
1252
1253        // Get attributes to retrieve from user entry
1254        ArrayList<String> list = new ArrayList<>();
1255        if (userPassword != null)
1256            list.add(userPassword);
1257        if (userRoleName != null)
1258            list.add(userRoleName);
1259        if (userRoleAttribute != null) {
1260            list.add(userRoleAttribute);
1261        }
1262        String[] attrIds = new String[list.size()];
1263        list.toArray(attrIds);
1264
1265        // Use pattern or search for user entry
1266        if (userPatternFormatArray != null && curUserPattern >= 0) {
1267            user = getUserByPattern(context, username, credentials, attrIds, curUserPattern);
1268        } else {
1269            user = getUserBySearch(context, username, attrIds);
1270        }
1271
1272        return user;
1273    }
1274
1275
1276    /**
1277     * Use the distinguished name to locate the directory
1278     * entry for the user with the specified username and
1279     * return a User object; otherwise return <code>null</code>.
1280     *
1281     * @param context The directory context
1282     * @param username The username
1283     * @param attrIds String[]containing names of attributes to
1284     * @param dn Distinguished name of the user
1285     * retrieve.
1286     *
1287     * @exception NamingException if a directory server error occurs
1288     */
1289    protected User getUserByPattern(DirContext context,
1290                                    String username,
1291                                    String[] attrIds,
1292                                    String dn)
1293        throws NamingException {
1294
1295        // If no attributes are requested, no need to look for them
1296        if (attrIds == null || attrIds.length == 0) {
1297            return new User(username, dn, null, null,null);
1298        }
1299
1300        // Get required attributes from user entry
1301        Attributes attrs = null;
1302        try {
1303            attrs = context.getAttributes(dn, attrIds);
1304        } catch (NameNotFoundException e) {
1305            return (null);
1306        }
1307        if (attrs == null)
1308            return (null);
1309
1310        // Retrieve value of userPassword
1311        String password = null;
1312        if (userPassword != null)
1313            password = getAttributeValue(userPassword, attrs);
1314
1315        String userRoleAttrValue = null;
1316        if (userRoleAttribute != null) {
1317            userRoleAttrValue = getAttributeValue(userRoleAttribute, attrs);
1318        }
1319
1320        // Retrieve values of userRoleName attribute
1321        ArrayList<String> roles = null;
1322        if (userRoleName != null)
1323            roles = addAttributeValues(userRoleName, attrs, roles);
1324
1325        return new User(username, dn, password, roles, userRoleAttrValue);
1326    }
1327
1328
1329    /**
1330     * Use the <code>UserPattern</code> configuration attribute to
1331     * locate the directory entry for the user with the specified
1332     * username and return a User object; otherwise return
1333     * <code>null</code>.
1334     *
1335     * @param context The directory context
1336     * @param username The username
1337     * @param credentials User credentials (optional)
1338     * @param attrIds String[]containing names of attributes to
1339     * @param curUserPattern Index into userPatternFormatArray
1340     *
1341     * @exception NamingException if a directory server error occurs
1342     * @see #getUserByPattern(DirContext, String, String[], String)
1343     */
1344    protected User getUserByPattern(DirContext context,
1345                                    String username,
1346                                    String credentials,
1347                                    String[] attrIds,
1348                                    int curUserPattern)
1349        throws NamingException {
1350
1351        User user = null;
1352
1353        if (username == null || userPatternFormatArray[curUserPattern] == null)
1354            return (null);
1355
1356        // Form the dn from the user pattern
1357        String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
1358
1359        try {
1360            user = getUserByPattern(context, username, attrIds, dn);
1361        } catch (NameNotFoundException e) {
1362            return (null);
1363        } catch (NamingException e) {
1364            // If the getUserByPattern() call fails, try it again with the
1365            // credentials of the user that we're searching for
1366            try {
1367                userCredentialsAdd(context, dn, credentials);
1368
1369                user = getUserByPattern(context, username, attrIds, dn);
1370            } finally {
1371                userCredentialsRemove(context);
1372            }
1373        }
1374        return user;
1375    }
1376
1377
1378    /**
1379     * Search the directory to return a User object containing
1380     * information about the user with the specified username, if
1381     * found in the directory; otherwise return <code>null</code>.
1382     *
1383     * @param context The directory context
1384     * @param username The username
1385     * @param attrIds String[]containing names of attributes to retrieve.
1386     *
1387     * @exception NamingException if a directory server error occurs
1388     */
1389    protected User getUserBySearch(DirContext context,
1390                                   String username,
1391                                   String[] attrIds)
1392        throws NamingException {
1393
1394        if (username == null || userSearchFormat == null)
1395            return (null);
1396
1397        // Form the search filter
1398        String filter = userSearchFormat.format(new String[] { username });
1399
1400        // Set up the search controls
1401        SearchControls constraints = new SearchControls();
1402
1403        if (userSubtree) {
1404            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
1405        }
1406        else {
1407            constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1408        }
1409
1410        constraints.setCountLimit(sizeLimit);
1411        constraints.setTimeLimit(timeLimit);
1412
1413        // Specify the attributes to be retrieved
1414        if (attrIds == null)
1415            attrIds = new String[0];
1416        constraints.setReturningAttributes(attrIds);
1417
1418        NamingEnumeration<SearchResult> results =
1419            context.search(userBase, filter, constraints);
1420
1421
1422        // Fail if no entries found
1423        try {
1424            if (results == null || !results.hasMore()) {
1425                return (null);
1426            }
1427        } catch (PartialResultException ex) {
1428            if (!adCompat)
1429                throw ex;
1430            else
1431                return (null);
1432        }
1433
1434        // Get result for the first entry found
1435        SearchResult result = results.next();
1436
1437        // Check no further entries were found
1438        try {
1439            if (results.hasMore()) {
1440                if(containerLog.isInfoEnabled())
1441                    containerLog.info("username " + username + " has multiple entries");
1442                return (null);
1443            }
1444        } catch (PartialResultException ex) {
1445            if (!adCompat)
1446                throw ex;
1447        }
1448
1449        String dn = getDistinguishedName(context, userBase, result);
1450
1451        if (containerLog.isTraceEnabled())
1452            containerLog.trace("  entry found for " + username + " with dn " + dn);
1453
1454        // Get the entry's attributes
1455        Attributes attrs = result.getAttributes();
1456        if (attrs == null)
1457            return null;
1458
1459        // Retrieve value of userPassword
1460        String password = null;
1461        if (userPassword != null)
1462            password = getAttributeValue(userPassword, attrs);
1463
1464        String userRoleAttrValue = null;
1465        if (userRoleAttribute != null) {
1466            userRoleAttrValue = getAttributeValue(userRoleAttribute, attrs);
1467        }
1468
1469        // Retrieve values of userRoleName attribute
1470        ArrayList<String> roles = null;
1471        if (userRoleName != null)
1472            roles = addAttributeValues(userRoleName, attrs, roles);
1473
1474        return new User(username, dn, password, roles, userRoleAttrValue);
1475    }
1476
1477
1478    /**
1479     * Check whether the given User can be authenticated with the
1480     * given credentials. If the <code>userPassword</code>
1481     * configuration attribute is specified, the credentials
1482     * previously retrieved from the directory are compared explicitly
1483     * with those presented by the user. Otherwise the presented
1484     * credentials are checked by binding to the directory as the
1485     * user.
1486     *
1487     * @param context The directory context
1488     * @param user The User to be authenticated
1489     * @param credentials The credentials presented by the user
1490     *
1491     * @exception NamingException if a directory server error occurs
1492     */
1493    protected boolean checkCredentials(DirContext context,
1494                                     User user,
1495                                     String credentials)
1496         throws NamingException {
1497
1498         boolean validated = false;
1499
1500         if (userPassword == null) {
1501             validated = bindAsUser(context, user, credentials);
1502         } else {
1503             validated = compareCredentials(context, user, credentials);
1504         }
1505
1506         if (containerLog.isTraceEnabled()) {
1507             if (validated) {
1508                 containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
1509                                  user.getUserName()));
1510             } else {
1511                 containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
1512                                  user.getUserName()));
1513             }
1514         }
1515         return (validated);
1516     }
1517
1518
1519    /**
1520     * Check whether the credentials presented by the user match those
1521     * retrieved from the directory.
1522     *
1523     * @param context The directory context
1524     * @param info The User to be authenticated
1525     * @param credentials Authentication credentials
1526     *
1527     * @exception NamingException if a directory server error occurs
1528     */
1529    protected boolean compareCredentials(DirContext context,
1530                                         User info,
1531                                         String credentials)
1532        throws NamingException {
1533
1534        if (info == null || credentials == null)
1535            return (false);
1536
1537        String password = info.getPassword();
1538        if (password == null)
1539            return (false);
1540
1541        // Validate the credentials specified by the user
1542        if (containerLog.isTraceEnabled())
1543            containerLog.trace("  validating credentials");
1544
1545        boolean validated = false;
1546        if (hasMessageDigest()) {
1547            // Some directories prefix the password with the hash type
1548            // The string is in a format compatible with Base64.encode not
1549            // the Hex encoding of the parent class.
1550            if (password.startsWith("{MD5}") || password.startsWith("{SHA}")) {
1551                /* sync since super.digest() does this same thing */
1552                synchronized (this) {
1553                    password = password.substring(5);
1554                    md.reset();
1555                    md.update(credentials.getBytes(B2CConverter.ISO_8859_1));
1556                    byte[] encoded = Base64.encodeBase64(md.digest());
1557                    String digestedPassword =
1558                            new String(encoded, B2CConverter.ISO_8859_1);
1559                    validated = password.equals(digestedPassword);
1560                }
1561            } else if (password.startsWith("{SSHA}")) {
1562                // Bugzilla 32938
1563                /* sync since super.digest() does this same thing */
1564                synchronized (this) {
1565                    password = password.substring(6);
1566
1567                    md.reset();
1568                    md.update(credentials.getBytes(B2CConverter.ISO_8859_1));
1569
1570                    // Decode stored password.
1571                    byte[] decoded = Base64.decodeBase64(password);
1572
1573                    // Split decoded password into hash and salt.
1574                    final int saltpos = 20;
1575                    byte[] hash = new byte[saltpos];
1576                    System.arraycopy(decoded, 0, hash, 0, saltpos);
1577
1578                    md.update(decoded, saltpos, decoded.length - saltpos);
1579
1580                    byte[] dp = md.digest();
1581
1582                    validated = Arrays.equals(dp, hash);
1583                } // End synchronized(this) block
1584            } else {
1585                // Hex hashes should be compared case-insensitive
1586                validated = (digest(credentials).equalsIgnoreCase(password));
1587            }
1588        } else
1589            validated = (digest(credentials).equals(password));
1590        return (validated);
1591
1592    }
1593
1594
1595    /**
1596     * Check credentials by binding to the directory as the user
1597     *
1598     * @param context The directory context
1599     * @param user The User to be authenticated
1600     * @param credentials Authentication credentials
1601     *
1602     * @exception NamingException if a directory server error occurs
1603     */
1604     protected boolean bindAsUser(DirContext context,
1605                                  User user,
1606                                  String credentials)
1607         throws NamingException {
1608
1609         if (credentials == null || user == null)
1610             return (false);
1611
1612         String dn = user.getDN();
1613         if (dn == null)
1614             return (false);
1615
1616         // Validate the credentials specified by the user
1617         if (containerLog.isTraceEnabled()) {
1618             containerLog.trace("  validating credentials by binding as the user");
1619        }
1620
1621        userCredentialsAdd(context, dn, credentials);
1622
1623        // Elicit an LDAP bind operation
1624        boolean validated = false;
1625        try {
1626            if (containerLog.isTraceEnabled()) {
1627                containerLog.trace("  binding as "  + dn);
1628            }
1629            context.getAttributes("", null);
1630            validated = true;
1631        }
1632        catch (AuthenticationException e) {
1633            if (containerLog.isTraceEnabled()) {
1634                containerLog.trace("  bind attempt failed");
1635            }
1636        }
1637
1638        userCredentialsRemove(context);
1639
1640        return (validated);
1641    }
1642
1643     /**
1644      * Configure the context to use the provided credentials for
1645      * authentication.
1646      *
1647      * @param context      DirContext to configure
1648      * @param dn           Distinguished name of user
1649      * @param credentials  Credentials of user
1650      */
1651    private void userCredentialsAdd(DirContext context, String dn,
1652            String credentials) throws NamingException {
1653        // Set up security environment to bind as the user
1654        context.add

Large files files are truncated, but you can click here to view the full file