AuthLDAP

 AuthLdap

AuthLdap 是一個 PHP 的 LDAP 認證及用戶管理 class。 它提供一個簡易透過 LDAP 資料庫認證用戶、取得會員資料、更改密碼等功能
class.AuthLdap.php:

  1. <?php
  2. /**
  3.  * class.AuthLdap.php , version 0.2
  4.  * Mark Round, April 2002 - http://www.markround.com/unix
  5.  * Provides LDAP authentication and user functions.
  6.  *
  7.  * Not intended as a full-blown LDAP access class - but it does provide
  8.  * several useful functions for dealing with users.
  9.  * Note - this works out of the box on Sun's iPlanet Directory Server - but
  10.  * an ACL has to be defined giving all users the ability to change their
  11.  * password (userPassword attribute).
  12.  * See the README file for more information and examples.
  13.  *
  14.  * This program is free software; you can redistribute it and/or modify
  15.  * it under the terms of the GNU General Public License as published by
  16.  * the Free Software Foundation; either version 2 of the License, or
  17.  * (at your option) any later version.
  18.  * 
  19.  * This program is distributed in the hope that it will be useful,
  20.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22.  * GNU General Public License for more details.
  23.  * 
  24.  * You should have received a copy of the GNU General Public License
  25.  * along with this program; if not, write to the Free Software
  26.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  27.  *
  28.  * ChangeLog
  29.  * ---------
  30.  * version 0.2, 11.04.2003, Michael Joseph <[email protected]
  31.  * - Added switches and workarounds for Active Directory integration
  32.  * - Change documentation to phpdoc style (http://phpdocu.sourceforge.net)
  33.  * - Added a constructor
  34.  * - Added an attribute array parameter to the getUsers method
  35.  */
  36.  
  37. class AuthLdap {
  38.  
  39.     // 1.1 Public properties -----------------------------------------------------
  40.     /**
  41.      * Array of server IP address or hostnames
  42.      */
  43.     var $server;
  44.     /**
  45.      * The base DN (e.g. "dc=foo,dc=com")
  46.      */
  47.     var $dn;
  48.     /**
  49.      * the directory server, currently supports iPlanet and Active Directory
  50.      */
  51.     var $serverType;
  52.     /**
  53.      * Active Directory authenticates using user@domain
  54.      */
  55.     var $domain;
  56.     /**
  57.      * The user to authenticate with when searching
  58.      * Active Directory doesn't support anonymous access
  59.      */
  60.     var $searchUser;
  61.     /**
  62.      * The password to authenticate with when searching
  63.      * Active Directory doesn't support anonymous access
  64.      */
  65.     var $searchPassword;
  66.     /**
  67.      *  Where the user records are kept
  68.      */
  69.     var $people;
  70.     /**
  71.      * Where the group definitions are kept
  72.      */
  73.     var $groups;
  74.     /**
  75.      * The last error code returned by the LDAP server
  76.      */
  77.     var $ldapErrorCode;
  78.     /**
  79.      * Text of the error message
  80.      */
  81.     var $ldapErrorText;
  82.  
  83.     // 1.2 Private properties ----------------------------------------------------
  84.     /**
  85.      * The internal LDAP connection handle
  86.      */
  87.     var $connection;
  88.     /**
  89.      * Result of any connections etc.
  90.      */
  91.     var $result;
  92.  
  93.     /**
  94.      * Constructor- creates a new instance of the authentication class
  95.      *
  96.      * @param string the ldap server to connect to
  97.      * @param string the base dn
  98.      * @param string the server type- current supports iPlanet and ActiveDirectory
  99.      * @param string the domain to use when authenticating against Active Directory
  100.      * @param string the username to authenticate with when searching if anonymous binding is not supported
  101.      * @param string the password to authenticate with when searching if anonymous binding is not supported
  102.      */
  103.     function AuthLdap ($sLdapServer, $sBaseDN, $sServerType, $sDomain = "", $searchUser = "", $searchPassword = "") {
  104.         $this->server = array($sLdapServer);
  105.         $this->dn = $sBaseDN;
  106.         $this->serverType = $sServerType;
  107.         $this->domain = $sDomain;
  108.         $this->searchUser = $searchUser;
  109.         $this->searchPassword = $searchPassword;
  110.     }
  111.     
  112.     // 2.1 Connection handling methods -------------------------------------------
  113.     /**
  114.      * 2.1.1 : Connects to the server. Just creates a connection which is used
  115.      * in all later access to the LDAP server. If it can't connect and bind
  116.      * anonymously, it creates an error code of -1. Returns true if connected,
  117.      * false if failed. Takes an array of possible servers - if one doesn't work,
  118.      * it tries the next and so on.
  119.      */
  120.     function connect() {
  121.         foreach ($this->server as $key => $host) {
  122.             $this->connection = ldap_connect( $host);
  123.             if ( $this->connection) {
  124.                 if ($this->serverType == "ActiveDirectory") {
  125.                     return true;
  126.                 } else {
  127.                     // Connected, now try binding anonymously
  128.                     $this->result=@ldap_bind( $this->connection);
  129.                 }
  130.                 return true;
  131.             }
  132.         }
  133.  
  134.         $this->ldapErrorCode = -1;
  135.         $this->ldapErrorText = "Unable to connect to any server";
  136.         return false;
  137.     }
  138.  
  139.     /**
  140.      * 2.1.2 : Simply closes the connection set up earlier.
  141.      * Returns true if OK, false if there was an error.
  142.      */
  143.     function close() {
  144.         if ( !@ldap_close( $this->connection)) {
  145.             $this->ldapErrorCode = ldap_errno( $this->connection);
  146.             $this->ldapErrorText = ldap_error( $this->connection);
  147.             return false;
  148.         } else {
  149.             return true;
  150.         }
  151.     }
  152.  
  153.     /**
  154.      * 2.1.3 : Anonymously binds to the connection. After this is done,
  155.      * queries and searches can be done - but read-only.
  156.      */
  157.     function bind() {
  158.         if ( !$this->result=@ldap_bind( $this->connection)) {
  159.             $this->ldapErrorCode = ldap_errno( $this->connection);
  160.             $this->ldapErrorText = ldap_error( $this->connection);
  161.             return false;
  162.         } else {
  163.             return true;
  164.         }
  165.     }
  166.  
  167.  
  168.  
  169.     /**
  170.      * 2.1.4 : Binds as an authenticated user, which usually allows for write
  171.      * access. The FULL dn must be passed. For a directory manager, this is
  172.      * "cn=Directory Manager" under iPlanet. For a user, it will be something
  173.      * like "uid=jbloggs,ou=People,dc=foo,dc=com".
  174.      */    
  175.     function authBind( $bindDn,$pass) {
  176.         if ( !$this->result = @ldap_bind( $this->connection,$bindDn,$pass)) {
  177.             $this->ldapErrorCode = ldap_errno( $this->connection);
  178.             $this->ldapErrorText = ldap_error( $this->connection);
  179.             return false;
  180.         } else {
  181.             return true;
  182.         }
  183.     }
  184.  
  185.     // 2.2 Password methods ------------------------------------------------------
  186.     /**
  187.      * 2.2.1 : Checks a username and password - does this by logging on to the
  188.      * server as a user - specified in the DN. There are several reasons why
  189.      * this login could fail - these are listed below.
  190.      */
  191.     function checkPass( $uname,$pass) {
  192.         /* Construct the full DN, eg:-
  193.         ** "uid=username, ou=People, dc=orgname,dc=com"
  194.         */
  195.         if ($this->serverType == "ActiveDirectory") {
  196.             $checkDn = "$uname@$this->domain";
  197.         } else {
  198.             $checkDn = $this->getUserIdentifier() . "=$uname" . $this->setDn(true);
  199.         }
  200.         // Try and connect...
  201.         $this->result = @ldap_bind( $this->connection,$checkDn,$pass);
  202.         if ( $this->result) {
  203.             // Connected OK - login credentials are fine!
  204.             return true;
  205.         } else {
  206.             /* Login failed. Return false, together with the error code and text from
  207.             ** the LDAP server. The common error codes and reasons are listed below :
  208.             ** (for iPlanet, other servers may differ)
  209.             ** 19 - Account locked out (too many invalid login attempts)
  210.             ** 32 - User does not exist
  211.             ** 49 - Wrong password
  212.             ** 53 - Account inactive (manually locked out by administrator)
  213.             */
  214.             $this->ldapErrorCode = ldap_errno( $this->connection);
  215.             $this->ldapErrorText = ldap_error( $this->connection);
  216.             return false;
  217.         }
  218.     }
  219.  
  220.  
  221.     /**
  222.      * 2.2.2 : Allows a password to be changed. Note that on most LDAP servers,
  223.      * a new ACL must be defined giving users the ability to modify their
  224.      * password attribute (userPassword). Otherwise this will fail.
  225.      */
  226.     function changePass( $uname,$oldPass,$newPass) {
  227.         // builds the appropriate dn, based on whether $this->people and/or $this->group is set
  228.         if ($this->serverType == "ActiveDirectory") {
  229.             $checkDn = "$uname@$this->domain";
  230.         } else {
  231.             $checkDn = $this->getUserIdentifier() . "=$uname" . $this->setDn(true);
  232.         }
  233.         $this->result = @ldap_bind( $this->connection,$checkDn,$oldPass);
  234.  
  235.         if ( $this->result) {
  236.             // Connected OK - Now modify the password...
  237.             $info["userPassword"] = $newPass;
  238.             $this->result = @ldap_modify( $this->connection, $checkDn, $info);
  239.             if ( $this->result) {
  240.                 // Change went OK
  241.                 return true;
  242.             } else {
  243.                 // Couldn't change password...
  244.                 $this->ldapErrorCode = ldap_errno( $this->connection);
  245.                 $this->ldapErrorText = ldap_error( $this->connection);
  246.                 return false;
  247.             }
  248.         } else {
  249.             // Login failed - see checkPass method for common error codes
  250.             $this->ldapErrorCode = ldap_errno( $this->connection);
  251.             $this->ldapErrorText = ldap_error( $this->connection);
  252.             return false;
  253.         }
  254.     }
  255.  
  256.  
  257.     /**
  258.      * 2.2.3 : Returns days until the password will expire.
  259.      * We have to explicitly state this is what we want returned from the
  260.      * LDAP server - by default, it will only send back the "basic"
  261.      * attributes.
  262.      */
  263.     function checkPassAge ( $uname) {
  264.  
  265.         $results[0] = "passwordexpirationtime";
  266.         // builds the appropriate dn, based on whether $this->people and/or $this->group is set
  267.         $checkDn = $this->setDn(true);
  268.         $this->result = @ldap_search( $this->connection,$checkDn,$this->getUserIdentifier()."=$uname",$results);
  269.  
  270.         if ( !$info=@ldap_get_entries( $this->connection, $this->result)) {
  271.             $this->ldapErrorCode = ldap_errno( $this->connection);
  272.             $this->ldapErrorText = ldap_error( $this->connection);
  273.             return false;
  274.         } else {
  275.             /* Now work out how many days remaining....
  276.             ** Yes, it's very verbose code but I left it like this so it can easily 
  277.             ** be modified for your needs.
  278.             */
  279.             $date  = $info[0]["passwordexpirationtime"][0];
  280.             $year  = substr( $date,0,4);
  281.             $month = substr( $date,4,2);
  282.             $day   = substr( $date,6,2);
  283.             $hour  = substr( $date,8,2);
  284.             $min   = substr( $date,10,2);
  285.             $sec   = substr( $date,12,2);
  286.  
  287.             $timestamp = mktime( $hour,$min,$sec,$month,$day,$year);
  288.             $today  = mktime();
  289.             $diff   = $timestamp-$today;
  290.             return round( ( ( ( $diff/60)/60)/24));
  291.         }
  292.     }
  293.  
  294.     // 2.3 Group methods ---------------------------------------------------------
  295.     /**
  296.      * 2.3.1 : Checks to see if a user is in a given group. If so, it returns
  297.      * true, and returns false if the user isn't in the group, or any other
  298.      * error occurs (eg:- no such user, no group by that name etc.)
  299.      */
  300.     function checkGroup ( $uname,$group) {
  301.         // builds the appropriate dn, based on whether $this->people and/or $this->group is set
  302.         $checkDn = $this->setDn(false);
  303.  
  304.         // We need to search for the group in order to get it's entry.
  305.         $this->result = @ldap_search( $this->connection, $checkDn, "cn=" .$group);
  306.         $info = @ldap_get_entries( $this->connection, $this->result);
  307.  
  308.         // Only one entry should be returned(no groups will have the same name)
  309.         $entry = ldap_first_entry( $this->connection,$this->result);
  310.  
  311.         if ( !$entry) {
  312.             $this->ldapErrorCode = ldap_errno( $this->connection);
  313.             $this->ldapErrorText = ldap_error( $this->connection);
  314.             return false;  // Couldn't find the group...
  315.         }
  316.         // Get all the member DNs
  317.         if ( !$values = @ldap_get_values( $this->connection, $entry, "uniqueMember")) {
  318.             $this->ldapErrorCode = ldap_errno( $this->connection);
  319.             $this->ldapErrorText = ldap_error( $this->connection);
  320.             return false; // No users in the group
  321.         }
  322.  
  323.         foreach ( $values as $key => $value) {
  324.             /* Loop through all members - see if the uname is there...
  325.             ** Also check for sub-groups - this allows us to define a group as
  326.             ** having membership of another group.
  327.             ** FIXME:- This is pretty ugly code and unoptimised. It takes ages
  328.             ** to search if you have sub-groups.
  329.             */
  330.             list( $cn,$ou) = explode( ",",$value);
  331.             list( $ou_l,$ou_r) = explode( "=",$ou);
  332.  
  333.             if ( $this->groups==$ou_r) {
  334.                 list( $cn_l,$cn_r) = explode( "=",$cn);
  335.                 // OK, So we now check the sub-group...
  336.                 if ( $this->checkGroup ( $uname,$cn_r)) {
  337.                     return true;
  338.                 }
  339.             }
  340.  
  341.             if ( preg_match( "/$uname/i",$value)) {
  342.                 return true;
  343.             }
  344.         }
  345.     }
  346.  
  347.     // 2.4 Attribute methods -----------------------------------------------------
  348.     /**
  349.      * 2.4.1 : Returns an array containing a set of attribute values.
  350.      * For most searches, this will just be one row, but sometimes multiple
  351.      * results are returned (eg:- multiple email addresses)
  352.      */
  353.     function getAttribute ( $uname,$attribute) {
  354.         // builds the appropriate dn, based on whether $this->people and/or $this->group is set
  355.         $checkDn = $this->setDn( true);
  356.         $results[0] = $attribute;
  357.  
  358.         // We need to search for this user in order to get their entry.
  359.         $this->result = @ldap_search( $this->connection,$checkDn,$this->getUserIdentifier()."=$uname",$results);
  360.         $info = ldap_get_entries( $this->connection, $this->result);
  361.  
  362.         // Only one entry should ever be returned (no user will have the same uid)
  363.         $entry = ldap_first_entry( $this->connection, $this->result);
  364.  
  365.         if ( !$entry) {
  366.             $this->ldapErrorCode = -1;
  367.             $this->ldapErrorText = "Couldn't find user";
  368.             return false;  // Couldn't find the user...
  369.         }
  370.  
  371.         // Get all the member DNs
  372.         if ( !$values = @ldap_get_values( $this->connection, $entry, $attribute)) {
  373.             $this->ldapErrorCode = ldap_errno( $this->connection);
  374.             $this->ldapErrorText = ldap_error( $this->connection);
  375.             return false; // No matching attributes
  376.         }
  377.  
  378.         // Return an array containing the attributes.
  379.         return $values;
  380.     }
  381.  
  382.     /**
  383.      * 2.4.2 : Allows an attribute value to be set.
  384.      * This can only usually be done after an authenticated bind as a
  385.      * directory manager - otherwise, read/write access will not be granted.
  386.      */
  387.     function setAttribute( $uname, $attribute, $value) {
  388.         // Construct a full DN...
  389.         // builds the appropriate dn, based on whether $this->people and/or $this->group is set
  390.         $attrib_dn = $this->getUserIdentifier()."=$uname," . $this->setDn(true);
  391.  
  392.         $info[$attribute] = $value;
  393.         // Change attribute
  394.         $this->result = ldap_modify( $this->connection, $attrib_dn, $info);
  395.         if ( $this->result) {
  396.             // Change went OK
  397.             return true;
  398.         } else {
  399.             // Couldn't change password...
  400.             $this->ldapErrorCode = ldap_errno( $this->connection);
  401.             $this->ldapErrorText = ldap_error( $this->connection);
  402.             return false;
  403.         }
  404.     }
  405.  
  406.     // 2.5 User methods ----------------------------------------------------------
  407.     /**
  408.      * 2.5.1 : Returns an array containing a details of users, sorted by
  409.      * username. The search criteria is a standard LDAP query - * returns all
  410.      * users.  The $attributeArray variable contains the required user detail field names
  411.      */
  412.     function getUsers( $search, $attributeArray) {
  413.         // builds the appropriate dn, based on whether $this->people and/or $this->group is set
  414.         $checkDn = $this->setDn( true);
  415.  
  416.         // Perform the search and get the entry handles
  417.         
  418.         // if the directory is AD, then bind first with the search user first
  419.         if ($this->serverType == "ActiveDirectory") {
  420.             $this->authBind($this->searchUser, $this->searchPassword);
  421.         }
  422.         $this->result = ldap_search( $this->connection, $checkDn, $this->getUserIdentifier() . "=$search");
  423.         
  424.         $info = ldap_get_entries( $this->connection, $this->result);
  425.         for( $i = 0; $i < $info["count"]; $i++) {
  426.             // Get the username, and create an array indexed by it...
  427.             // Modify these as you see fit.
  428.             $uname = $info[$i][$this->getUserIdentifier()][0];
  429.             // add to the array for each attribute in my list
  430.             for ( $j = 0; $j < count( $attributeArray); $j++) {
  431.                 if (strtolower($attributeArray[$j]) == "dn") {
  432.                     $userslist["$uname"]["$attributeArray[$j]"]      = $info[$i][strtolower($attributeArray[$j])];
  433.                 } else {
  434.                     $userslist["$uname"]["$attributeArray[$j]"]      = $info[$i][strtolower($attributeArray[$j])][0];
  435.                 }
  436.             }
  437.         }
  438.  
  439.         if ( !@asort( $userslist)) {
  440.             /* Sort into alphabetical order. If this fails, it's because there
  441.             ** were no results returned (array is empty) - so just return false.
  442.             */
  443.             $this->ldapErrorCode = -1;
  444.             $this->ldapErrorText = "No users found matching search criteria ".$search;
  445.             return false;
  446.         }
  447.         return $userslist;
  448.     }
  449.  
  450.     // 2.6 helper methods
  451.     
  452.     /**
  453.      * Sets and returns the appropriate dn, based on whether there
  454.      * are values in $this->people and $this->groups.
  455.      *
  456.      * @param boolean specifies whether to build a groups dn or a people dn 
  457.      * @return string if true ou=$this->people,$this->dn, else ou=$this->groups,$this->dn
  458.      */
  459.     function setDn($peopleOrGroups) {
  460.  
  461.         if ($peopleOrGroups) {
  462.             if ( isset($this->people) && (strlen($this->people) > 0) ) {
  463.                 $checkDn = "ou=" .$this->people. ", " .$this->dn;
  464.             }
  465.         } else {
  466.             if ( isset($this->groups) && (strlen($this->groups) > 0) ) {
  467.                 $checkDn = "ou=" .$this->groups. ", " .$this->dn;
  468.             }
  469.         }
  470.  
  471.         if ( !isset($checkDn) ) {
  472.             $checkDn = $this->dn;
  473.         }
  474.         return $checkDn;
  475.     }
  476.     
  477.     /**
  478.      * Returns the correct user identifier to use, based on the ldap server type
  479.      */
  480.     function getUserIdentifier() {
  481.         if ($this->serverType == "ActiveDirectory") {
  482.             return "samaccountname";
  483.         } else {
  484.             return "uid";
  485.         }
  486.     }
  487. } // End of class
  488. ?>
<script type="text/javascript">document.write('
');</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章