* @copyright Copyright (c) 2008, Ian White * @version 0.8 * */ class LDAP { // This is a singleton class, the instance is stored here static private $ldap; // Various LDAP references private $ds; private $r; private $base = ""; // An array of OpenDirectory Servers holding user information. private $servers = array(); /* * username_valid takes a username as an argument and returns true if * the username meets the criteria for a valid username * * @access public * @static */ public static function username_valid($username) { $username_pattern = '/^[a-zA-Z0-9]+$/'; $illegal_pattern = '/[&;`\'\\"|*?~<>^()[\]}{$\n\r]/'; return (preg_match($username_pattern, $username) && !preg_match($illegal_pattern, $username)); } /* * password_valid takes a password as an argument and returns true if the * password is the correct length and contains no illegal characters. The * criteria here are more stringent than those of the OpenDirectory server, * but this has been useful for us in protecing against PHP/MySQL vulnerabilities. * * @access public * @static */ public static function password_valid($password) { // This is more strict than the OpenDirectory requirements, but it has worked well for // our projects. $password_pattern = '/^[-!@#%_+=\ \/?a-zA-Z0-9]{8,}$/'; $illegal_pattern = '/[&;`\'\\"|*?~<>^()[\]}{$\n\r]/'; return (preg_match($password_pattern, $password) && !preg_match($illegal_pattern, $password)); } /* * authenticate takes a username, password, and optional group. It verifies that the username * and password are valid, checks to see if the user/pass combination successfully authenticates, * and if the optional group is provided, ensures that the user is a member of the group * specified. Note: this relies on the pam_auth module to be installed and configured. It also * depends on the underlying server's PAM mechanism to actually perform the authenatication. This * was used to enable kerberos to be the method of authentication. This function could be re- * written to rely only on LDAP authentication if need be. * * @access public * @static */ public static function authenticate($user, $pass, $group=NULL) { if(LDAP::username_valid($user) && LDAP::password_valid($pass)) { if(pam_auth($user, $pass)) { if($group) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "(&(memberUid={$user})(cn={$group}))"); return (ldap_count_entries(LDAP::$ldap->ds, $sr)); } else { return true; } } else { return false ; } } else { return false; } } /* * get_user_by_id takes an id and returns an array with user information for that user. * * @access public * @static */ public static function get_user_by_id($id) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $sr=ldap_search(LDAP::$ldap->ds, "cn=Users,{LDAP::$ldap->base}", "uidNumber={$id}"); if(ldap_count_entries(LDAP::$ldap->ds, $sr)) { $user_array = ldap_get_entries(LDAP::$ldap->ds, $sr); return LDAP::convert_user($user_array[0]); } else { throw new Exception("Invalid user or user not found."); } } /* * get_user_by_username takes an username and returns an array with user information for that user. * * @access public * @static */ public static function get_user_by_username($username) { if(LDAP::username_valid($username) ) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $sr=ldap_search(LDAP::$ldap->ds, "cn=Users,{LDAP::$ldap->base}", "uid={$username}"); if(ldap_count_entries(LDAP::$ldap->ds, $sr)) { $user_array = ldap_get_entries(LDAP::$ldap->ds, $sr); return LDAP::convert_user($user_array[0]); } } throw new Exception("Invalid user or user not found."); } /* * get_group_by_id takes a group id and returns an array with group information for that group. * * @access public * @static */ public static function get_group_by_id($id) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "gidNumber={$id}"); if(ldap_count_entries(LDAP::$ldap->ds, $sr)) { $group_array = ldap_get_entries(LDAP::$ldap->ds, $sr); return LDAP::convert_group($group_array[0]); } } /* * get_group_by_groupname takes a groupname and returns an array with group information for that group. * * @access public * @static */ public static function get_group_by_groupname($groupname) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "cn={$groupname}"); if(ldap_count_entries(LDAP::$ldap->ds, $sr)) { $group_array = ldap_get_entries(LDAP::$ldap->ds, $sr); return LDAP::convert_group($group_array[0]); } } /* * get_group_membership takes a groupname and returns an array user arrays representing the * users who are a member of that group. * * @access public * @static */ public static function get_group_members($groupname) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $new_users = array(); $group = LDAP::group_by_groupname($groupname); // Search for secondary group members if($group['members']) { foreach($group['members'] as $username) { try { $tmp = LDAP::user_by_username($username); $new_users[$tmp['uid']] = $tmp; } catch (Exception $e) { } } } $id = $group['gid']; // Search for users who have this group as their primary group id. $sr=ldap_search(LDAP::$ldap->ds, "cn=Users,{LDAP::$ldap->base}", "gidNumber={$id}"); if(ldap_count_entries(LDAP::$ldap->ds, $sr)) { $user_array = ldap_get_entries(LDAP::$ldap->ds, $sr); $user_array = LDAP::clean_array($user_array); foreach($user_array as $user) { $tmp = LDAP::convert_user($user); $new_users[$tmp['uid']] = $tmp; } } return $new_users; } /* * get_groups_for takes a user and an optional sort and primary argument. It returns * an array of group arrays representing the groups that the specified user is a member * of. If sort is not set, or set to true, the groups will be sorted by id. If primary * is set to true, the primary group of the user will also be returned. * * @access public * @static */ public static function get_groups_for($username, $sort=true, $primary=false) { if(LDAP::username_valid($username) ) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $new_groups = array(); $user = LDAP::get_user_by_username($username); if($primary) { $new_groups[$user['primary_gid']] = LDAP::get_group_by_id($user['primary_gid']); } $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "memberUid={$username}"); if(ldap_count_entries(LDAP::$ldap->ds, $sr)) { $group_array = ldap_get_entries(LDAP::$ldap->ds, $sr); $group_array = LDAP::clean_array($group_array); foreach($group_array as $group) { $tmp = LDAP::convert_group($group); $new_groups[$tmp['gid']] = $tmp; } } if($sort) { $new_groups = array_reverse($new_groups, true); } return $new_groups; } else { throw new Exception("Invalid user or user not found."); } } /* * is_member takes a username and groupname. It returns true if the user is a member of the * group specified. This does not check the primary group, but could easily be modified to * check the primary group. * * @access public * @static */ public static function is_member($username, $groupname) { if(LDAP::username_valid($username) ) { if(!LDAP::$ldap) LDAP::$ldap = new LDAP(); $sr=ldap_search(LDAP::$ldap->ds, "cn=Groups,{LDAP::$ldap->base}", "(&(memberUid={$username})(cn={$groupname}))"); return (ldap_count_entries(LDAP::$ldap->ds, $sr)); } else { throw new Exception("Invalid user or user not found."); } } /* * __construct calls reconnect to establish a connection to the OpenDirectory server. * * @access private * @static */ private function __construct() { $this->reconnect(); } /* * reconnect opens the connection to the OpenDirectory server. * * @access private * @static */ private function reconnect() { $index = mt_rand(0, count($this->servers) - 1); $i = 0; while( !$this->ds && $i < count($this->servers) ) { $this->ds = ldap_connect($this->servers[($index % count($this->servers))]); $index++; $i++; } $server = $this->servers[($index % count($this->servers))]; if($this->ds) { ldap_set_option($this->ds, LDAP_OPT_PROTOCOL_VERSION, 3); $this->r = ldap_bind($this->ds); } else { throw new Exception("Could not connect to any LDAP server."); } } /* * clear_array takes an array object returned by the OpenDirectory server * to remove all of the cruft. It returns a new array with only named keys * and empty attributes are removed. * * @access private * @static */ private static function clean_array($array) { $return = array(); if($array) { foreach($array as $key => $item) { if($key === 'count') continue; $return[$key] = $item; } } return $return; } /* * convert_user takes an OpenDirectory user array and converts it into a more * user friendly array. * * @access private * @static */ private static function convert_user($user_array) { $user = array(); $user['username' ] = $user_array['uid' ][0]; $user['uid' ] = $user_array['uidnumber' ][0]; $user['first_name' ] = $user_array['givenname' ][0]; $user['last_name' ] = $user_array['sn' ][0]; $user['primary_gid' ] = $user_array['gidnumber' ][0]; $user['room' ] = $user_array['street' ][0]; $user['lab_website' ] = $user_array['labeleduri' ][0]; $user['description' ] = $user_array['description' ][0]; $user['position' ] = $user_array['apple-keyword'][0]; $user['email' ] = LDAP::clean_array($user_array['mail' ]); $user['phone']['work'] = LDAP::clean_array($user_array['telephonenumber' ]); $user['phone']['cell'] = LDAP::clean_array($user_array['mobile' ]); $user['phone']['fax' ] = LDAP::clean_array($user_array['facsimiletelephonenumber']); return $user; } /* * convert_group takes an OpenDirectory group array and converts it into a more * group friendly array. * * @access private * @static */ private static function convert_group($group_array) { $group = array(); $group['groupname' ] = $group_array['cn' ][0]; $group['gid' ] = $group_array['gidnumber' ][0]; $group['real_name' ] = $group_array['apple-group-realname'][0]; $group['members' ] = LDAP::clean_array($group_array['memberuid']); $group['description'] = $group_array['description'][0]; return $group; } } ?>