176ce1169SAndreas Gohr<?php 276ce1169SAndreas Gohr/** 376ce1169SAndreas Gohr * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY 476ce1169SAndreas Gohr * Version 4.0.4 576ce1169SAndreas Gohr * 676ce1169SAndreas Gohr * PHP Version 5 with SSL and LDAP support 776ce1169SAndreas Gohr * 876ce1169SAndreas Gohr * Written by Scott Barnett, Richard Hyland 976ce1169SAndreas Gohr * email: scott@wiggumworld.com, adldap@richardhyland.com 1076ce1169SAndreas Gohr * http://adldap.sourceforge.net/ 1176ce1169SAndreas Gohr * 1276ce1169SAndreas Gohr * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland 1376ce1169SAndreas Gohr * 1476ce1169SAndreas Gohr * We'd appreciate any improvements or additions to be submitted back 1576ce1169SAndreas Gohr * to benefit the entire community :) 1676ce1169SAndreas Gohr * 1776ce1169SAndreas Gohr * This library is free software; you can redistribute it and/or 1876ce1169SAndreas Gohr * modify it under the terms of the GNU Lesser General Public 1976ce1169SAndreas Gohr * License as published by the Free Software Foundation; either 2076ce1169SAndreas Gohr * version 2.1 of the License. 2176ce1169SAndreas Gohr * 2276ce1169SAndreas Gohr * This library is distributed in the hope that it will be useful, 2376ce1169SAndreas Gohr * but WITHOUT ANY WARRANTY; without even the implied warranty of 2476ce1169SAndreas Gohr * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 2576ce1169SAndreas Gohr * Lesser General Public License for more details. 2676ce1169SAndreas Gohr * 2776ce1169SAndreas Gohr * @category ToolsAndUtilities 2876ce1169SAndreas Gohr * @package adLDAP 2976ce1169SAndreas Gohr * @subpackage User 3076ce1169SAndreas Gohr * @author Scott Barnett, Richard Hyland 3176ce1169SAndreas Gohr * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland 3276ce1169SAndreas Gohr * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1 3376ce1169SAndreas Gohr * @revision $Revision: 97 $ 3476ce1169SAndreas Gohr * @version 4.0.4 3576ce1169SAndreas Gohr * @link http://adldap.sourceforge.net/ 3676ce1169SAndreas Gohr */ 3776ce1169SAndreas Gohrrequire_once(dirname(__FILE__) . '/../adLDAP.php'); 3876ce1169SAndreas Gohrrequire_once(dirname(__FILE__) . '/../collections/adLDAPUserCollection.php'); 3976ce1169SAndreas Gohr 4076ce1169SAndreas Gohr/** 4176ce1169SAndreas Gohr* USER FUNCTIONS 4276ce1169SAndreas Gohr*/ 4376ce1169SAndreas Gohrclass adLDAPUsers { 4476ce1169SAndreas Gohr /** 4576ce1169SAndreas Gohr * The current adLDAP connection via dependency injection 4676ce1169SAndreas Gohr * 4776ce1169SAndreas Gohr * @var adLDAP 4876ce1169SAndreas Gohr */ 4976ce1169SAndreas Gohr protected $adldap; 5076ce1169SAndreas Gohr 5176ce1169SAndreas Gohr public function __construct(adLDAP $adldap) { 5276ce1169SAndreas Gohr $this->adldap = $adldap; 5376ce1169SAndreas Gohr } 5476ce1169SAndreas Gohr 5576ce1169SAndreas Gohr /** 5676ce1169SAndreas Gohr * Validate a user's login credentials 5776ce1169SAndreas Gohr * 5876ce1169SAndreas Gohr * @param string $username A user's AD username 5976ce1169SAndreas Gohr * @param string $password A user's AD password 6076ce1169SAndreas Gohr * @param bool optional $prevent_rebind 6176ce1169SAndreas Gohr * @return bool 6276ce1169SAndreas Gohr */ 6376ce1169SAndreas Gohr public function authenticate($username, $password, $preventRebind = false) { 6476ce1169SAndreas Gohr return $this->adldap->authenticate($username, $password, $preventRebind); 6576ce1169SAndreas Gohr } 6676ce1169SAndreas Gohr 6776ce1169SAndreas Gohr /** 6876ce1169SAndreas Gohr * Create a user 6976ce1169SAndreas Gohr * 7076ce1169SAndreas Gohr * If you specify a password here, this can only be performed over SSL 7176ce1169SAndreas Gohr * 7276ce1169SAndreas Gohr * @param array $attributes The attributes to set to the user account 7376ce1169SAndreas Gohr * @return bool 7476ce1169SAndreas Gohr */ 7576ce1169SAndreas Gohr public function create($attributes) 7676ce1169SAndreas Gohr { 7776ce1169SAndreas Gohr // Check for compulsory fields 7876ce1169SAndreas Gohr if (!array_key_exists("username", $attributes)){ return "Missing compulsory field [username]"; } 7976ce1169SAndreas Gohr if (!array_key_exists("firstname", $attributes)){ return "Missing compulsory field [firstname]"; } 8076ce1169SAndreas Gohr if (!array_key_exists("surname", $attributes)){ return "Missing compulsory field [surname]"; } 8176ce1169SAndreas Gohr if (!array_key_exists("email", $attributes)){ return "Missing compulsory field [email]"; } 8276ce1169SAndreas Gohr if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } 8376ce1169SAndreas Gohr if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } 8476ce1169SAndreas Gohr 8576ce1169SAndreas Gohr if (array_key_exists("password",$attributes) && (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS())){ 8676ce1169SAndreas Gohr throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); 8776ce1169SAndreas Gohr } 8876ce1169SAndreas Gohr 8976ce1169SAndreas Gohr if (!array_key_exists("display_name", $attributes)) { 9076ce1169SAndreas Gohr $attributes["display_name"] = $attributes["firstname"] . " " . $attributes["surname"]; 9176ce1169SAndreas Gohr } 9276ce1169SAndreas Gohr 9376ce1169SAndreas Gohr // Translate the schema 9476ce1169SAndreas Gohr $add = $this->adldap->adldap_schema($attributes); 9576ce1169SAndreas Gohr 9676ce1169SAndreas Gohr // Additional stuff only used for adding accounts 9776ce1169SAndreas Gohr $add["cn"][0] = $attributes["display_name"]; 9876ce1169SAndreas Gohr $add["samaccountname"][0] = $attributes["username"]; 9976ce1169SAndreas Gohr $add["objectclass"][0] = "top"; 10076ce1169SAndreas Gohr $add["objectclass"][1] = "person"; 10176ce1169SAndreas Gohr $add["objectclass"][2] = "organizationalPerson"; 10276ce1169SAndreas Gohr $add["objectclass"][3] = "user"; //person? 10376ce1169SAndreas Gohr //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; 10476ce1169SAndreas Gohr 10576ce1169SAndreas Gohr // Set the account control attribute 10676ce1169SAndreas Gohr $control_options = array("NORMAL_ACCOUNT"); 10776ce1169SAndreas Gohr if (!$attributes["enabled"]) { 10876ce1169SAndreas Gohr $control_options[] = "ACCOUNTDISABLE"; 10976ce1169SAndreas Gohr } 11076ce1169SAndreas Gohr $add["userAccountControl"][0] = $this->accountControl($control_options); 11176ce1169SAndreas Gohr 11276ce1169SAndreas Gohr // Determine the container 11376ce1169SAndreas Gohr $attributes["container"] = array_reverse($attributes["container"]); 11476ce1169SAndreas Gohr $container = "OU=" . implode(", OU=",$attributes["container"]); 11576ce1169SAndreas Gohr 11676ce1169SAndreas Gohr // Add the entry 11776ce1169SAndreas Gohr $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); 11876ce1169SAndreas Gohr if ($result != true) { 11976ce1169SAndreas Gohr return false; 12076ce1169SAndreas Gohr } 12176ce1169SAndreas Gohr 12276ce1169SAndreas Gohr return true; 12376ce1169SAndreas Gohr } 12476ce1169SAndreas Gohr 12576ce1169SAndreas Gohr /** 12676ce1169SAndreas Gohr * Account control options 12776ce1169SAndreas Gohr * 12876ce1169SAndreas Gohr * @param array $options The options to convert to int 12976ce1169SAndreas Gohr * @return int 13076ce1169SAndreas Gohr */ 13176ce1169SAndreas Gohr protected function accountControl($options) 13276ce1169SAndreas Gohr { 13376ce1169SAndreas Gohr $val=0; 13476ce1169SAndreas Gohr 13576ce1169SAndreas Gohr if (is_array($options)) { 13676ce1169SAndreas Gohr if (in_array("SCRIPT",$options)){ $val=$val+1; } 13776ce1169SAndreas Gohr if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; } 13876ce1169SAndreas Gohr if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; } 13976ce1169SAndreas Gohr if (in_array("LOCKOUT",$options)){ $val=$val+16; } 14076ce1169SAndreas Gohr if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; } 14176ce1169SAndreas Gohr //PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute. 14276ce1169SAndreas Gohr //For information about how to set the permission programmatically, see the "Property flag descriptions" section. 14376ce1169SAndreas Gohr if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; } 14476ce1169SAndreas Gohr if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; } 14576ce1169SAndreas Gohr if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; } 14676ce1169SAndreas Gohr if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; } 14776ce1169SAndreas Gohr if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; } 14876ce1169SAndreas Gohr if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; } 14976ce1169SAndreas Gohr if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; } 15076ce1169SAndreas Gohr if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; } 15176ce1169SAndreas Gohr if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; } 15276ce1169SAndreas Gohr if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; } 15376ce1169SAndreas Gohr if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; } 15476ce1169SAndreas Gohr if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; } 15576ce1169SAndreas Gohr if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; } 15676ce1169SAndreas Gohr if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; } 15776ce1169SAndreas Gohr if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){ $val=$val+16777216; } 15876ce1169SAndreas Gohr } 15976ce1169SAndreas Gohr return $val; 16076ce1169SAndreas Gohr } 16176ce1169SAndreas Gohr 16276ce1169SAndreas Gohr /** 16376ce1169SAndreas Gohr * Delete a user account 16476ce1169SAndreas Gohr * 16576ce1169SAndreas Gohr * @param string $username The username to delete (please be careful here!) 16676ce1169SAndreas Gohr * @param bool $isGUID Is the username a GUID or a samAccountName 16776ce1169SAndreas Gohr * @return array 16876ce1169SAndreas Gohr */ 16976ce1169SAndreas Gohr public function delete($username, $isGUID = false) 17076ce1169SAndreas Gohr { 17176ce1169SAndreas Gohr $userinfo = $this->info($username, array("*"), $isGUID); 17276ce1169SAndreas Gohr $dn = $userinfo[0]['distinguishedname'][0]; 17376ce1169SAndreas Gohr $result = $this->adldap->folder()->delete($dn); 17476ce1169SAndreas Gohr if ($result != true) { 17576ce1169SAndreas Gohr return false; 17676ce1169SAndreas Gohr } 17776ce1169SAndreas Gohr return true; 17876ce1169SAndreas Gohr } 17976ce1169SAndreas Gohr 18076ce1169SAndreas Gohr /** 18176ce1169SAndreas Gohr * Groups the user is a member of 18276ce1169SAndreas Gohr * 18376ce1169SAndreas Gohr * @param string $username The username to query 18476ce1169SAndreas Gohr * @param bool $recursive Recursive list of groups 18576ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 18676ce1169SAndreas Gohr * @return array 18776ce1169SAndreas Gohr */ 18876ce1169SAndreas Gohr public function groups($username, $recursive = NULL, $isGUID = false) 18976ce1169SAndreas Gohr { 19076ce1169SAndreas Gohr if ($username === NULL) { return false; } 19176ce1169SAndreas Gohr if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it 19276ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 19376ce1169SAndreas Gohr 19476ce1169SAndreas Gohr // Search the directory for their information 19576ce1169SAndreas Gohr $info = @$this->info($username, array("memberof", "primarygroupid"), $isGUID); 19676ce1169SAndreas Gohr $groups = $this->adldap->utilities()->niceNames($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames) 19776ce1169SAndreas Gohr 19876ce1169SAndreas Gohr if ($recursive === true){ 19976ce1169SAndreas Gohr foreach ($groups as $id => $groupName){ 20076ce1169SAndreas Gohr $extraGroups = $this->adldap->group()->recursiveGroups($groupName); 20176ce1169SAndreas Gohr $groups = array_merge($groups, $extraGroups); 20276ce1169SAndreas Gohr } 20376ce1169SAndreas Gohr } 20476ce1169SAndreas Gohr 20576ce1169SAndreas Gohr return $groups; 20676ce1169SAndreas Gohr } 20776ce1169SAndreas Gohr 20876ce1169SAndreas Gohr /** 20976ce1169SAndreas Gohr * Find information about the users. Returned in a raw array format from AD 21076ce1169SAndreas Gohr * 21176ce1169SAndreas Gohr * @param string $username The username to query 21276ce1169SAndreas Gohr * @param array $fields Array of parameters to query 21376ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 21476ce1169SAndreas Gohr * @return array 21576ce1169SAndreas Gohr */ 21676ce1169SAndreas Gohr public function info($username, $fields = NULL, $isGUID = false) 21776ce1169SAndreas Gohr { 21876ce1169SAndreas Gohr if ($username === NULL) { return false; } 21976ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 22076ce1169SAndreas Gohr 22176ce1169SAndreas Gohr if ($isGUID === true) { 22276ce1169SAndreas Gohr $username = $this->adldap->utilities()->strGuidToHex($username); 22376ce1169SAndreas Gohr $filter = "objectguid=" . $username; 22476ce1169SAndreas Gohr } 22576ce1169SAndreas Gohr else if (strstr($username, "@")) { 22676ce1169SAndreas Gohr $filter = "userPrincipalName=" . $username; 22776ce1169SAndreas Gohr } 22876ce1169SAndreas Gohr else { 22976ce1169SAndreas Gohr $filter = "samaccountname=" . $username; 23076ce1169SAndreas Gohr } 23176ce1169SAndreas Gohr $filter = "(&(objectCategory=person)({$filter}))"; 23276ce1169SAndreas Gohr if ($fields === NULL) { 23376ce1169SAndreas Gohr $fields = array("samaccountname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); 23476ce1169SAndreas Gohr } 23576ce1169SAndreas Gohr if (!in_array("objectsid", $fields)) { 23676ce1169SAndreas Gohr $fields[] = "objectsid"; 23776ce1169SAndreas Gohr } 23876ce1169SAndreas Gohr $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); 23976ce1169SAndreas Gohr $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); 24076ce1169SAndreas Gohr 24176ce1169SAndreas Gohr if (isset($entries[0])) { 24276ce1169SAndreas Gohr if ($entries[0]['count'] >= 1) { 24376ce1169SAndreas Gohr if (in_array("memberof", $fields)) { 24476ce1169SAndreas Gohr // AD does not return the primary group in the ldap query, we may need to fudge it 24576ce1169SAndreas Gohr if ($this->adldap->getRealPrimaryGroup() && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])){ 24676ce1169SAndreas Gohr //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]); 24776ce1169SAndreas Gohr $entries[0]["memberof"][] = $this->adldap->group()->getPrimaryGroup($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]); 24876ce1169SAndreas Gohr } else { 24976ce1169SAndreas Gohr $entries[0]["memberof"][] = "CN=Domain Users,CN=Users," . $this->adldap->getBaseDn(); 25076ce1169SAndreas Gohr } 25176ce1169SAndreas Gohr if (!isset($entries[0]["memberof"]["count"])) { 25276ce1169SAndreas Gohr $entries[0]["memberof"]["count"] = 0; 25376ce1169SAndreas Gohr } 25476ce1169SAndreas Gohr $entries[0]["memberof"]["count"]++; 25576ce1169SAndreas Gohr } 25676ce1169SAndreas Gohr } 25776ce1169SAndreas Gohr 25876ce1169SAndreas Gohr return $entries; 25976ce1169SAndreas Gohr } 26076ce1169SAndreas Gohr return false; 26176ce1169SAndreas Gohr } 26276ce1169SAndreas Gohr 26376ce1169SAndreas Gohr /** 26476ce1169SAndreas Gohr * Find information about the users. Returned in a raw array format from AD 26576ce1169SAndreas Gohr * 26676ce1169SAndreas Gohr * @param string $username The username to query 26776ce1169SAndreas Gohr * @param array $fields Array of parameters to query 26876ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 26976ce1169SAndreas Gohr * @return mixed 27076ce1169SAndreas Gohr */ 27176ce1169SAndreas Gohr public function infoCollection($username, $fields = NULL, $isGUID = false) 27276ce1169SAndreas Gohr { 27376ce1169SAndreas Gohr if ($username === NULL) { return false; } 27476ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 27576ce1169SAndreas Gohr 27676ce1169SAndreas Gohr $info = $this->info($username, $fields, $isGUID); 27776ce1169SAndreas Gohr 27876ce1169SAndreas Gohr if ($info !== false) { 27976ce1169SAndreas Gohr $collection = new adLDAPUserCollection($info, $this->adldap); 28076ce1169SAndreas Gohr return $collection; 28176ce1169SAndreas Gohr } 28276ce1169SAndreas Gohr return false; 28376ce1169SAndreas Gohr } 28476ce1169SAndreas Gohr 28576ce1169SAndreas Gohr /** 28676ce1169SAndreas Gohr * Determine if a user is in a specific group 28776ce1169SAndreas Gohr * 28876ce1169SAndreas Gohr * @param string $username The username to query 28976ce1169SAndreas Gohr * @param string $group The name of the group to check against 29076ce1169SAndreas Gohr * @param bool $recursive Check groups recursively 29176ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 29276ce1169SAndreas Gohr * @return bool 29376ce1169SAndreas Gohr */ 29476ce1169SAndreas Gohr public function inGroup($username, $group, $recursive = NULL, $isGUID = false) 29576ce1169SAndreas Gohr { 29676ce1169SAndreas Gohr if ($username === NULL) { return false; } 29776ce1169SAndreas Gohr if ($group === NULL) { return false; } 29876ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 29976ce1169SAndreas Gohr if ($recursive === NULL) { $recursive = $this->adldap->getRecursiveGroups(); } // Use the default option if they haven't set it 30076ce1169SAndreas Gohr 30176ce1169SAndreas Gohr // Get a list of the groups 30276ce1169SAndreas Gohr $groups = $this->groups($username, $recursive, $isGUID); 30376ce1169SAndreas Gohr 30476ce1169SAndreas Gohr // Return true if the specified group is in the group list 30576ce1169SAndreas Gohr if (in_array($group, $groups)) { 30676ce1169SAndreas Gohr return true; 30776ce1169SAndreas Gohr } 30876ce1169SAndreas Gohr 30976ce1169SAndreas Gohr return false; 31076ce1169SAndreas Gohr } 31176ce1169SAndreas Gohr 31276ce1169SAndreas Gohr /** 31376ce1169SAndreas Gohr * Determine a user's password expiry date 31476ce1169SAndreas Gohr * 31576ce1169SAndreas Gohr * @param string $username The username to query 31676ce1169SAndreas Gohr * @param book $isGUID Is the username passed a GUID or a samAccountName 317*59752844SAnders Sandblad * @requires bcmath http://php.net/manual/en/book.bc.php 31876ce1169SAndreas Gohr * @return array 31976ce1169SAndreas Gohr */ 32076ce1169SAndreas Gohr public function passwordExpiry($username, $isGUID = false) 32176ce1169SAndreas Gohr { 32276ce1169SAndreas Gohr if ($username === NULL) { return "Missing compulsory field [username]"; } 32376ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 324*59752844SAnders Sandblad if (!function_exists('bcmod')) { throw new adLDAPException("Missing function support [bcmod] http://php.net/manual/en/book.bc.php"); }; 32576ce1169SAndreas Gohr 32676ce1169SAndreas Gohr $userInfo = $this->info($username, array("pwdlastset", "useraccountcontrol"), $isGUID); 32776ce1169SAndreas Gohr $pwdLastSet = $userInfo[0]['pwdlastset'][0]; 32876ce1169SAndreas Gohr $status = array(); 32976ce1169SAndreas Gohr 33076ce1169SAndreas Gohr if ($userInfo[0]['useraccountcontrol'][0] == '66048') { 33176ce1169SAndreas Gohr // Password does not expire 33276ce1169SAndreas Gohr return "Does not expire"; 33376ce1169SAndreas Gohr } 33476ce1169SAndreas Gohr if ($pwdLastSet === '0') { 33576ce1169SAndreas Gohr // Password has already expired 33676ce1169SAndreas Gohr return "Password has expired"; 33776ce1169SAndreas Gohr } 33876ce1169SAndreas Gohr 33976ce1169SAndreas Gohr // Password expiry in AD can be calculated from TWO values: 34076ce1169SAndreas Gohr // - User's own pwdLastSet attribute: stores the last time the password was changed 34176ce1169SAndreas Gohr // - Domain's maxPwdAge attribute: how long passwords last in the domain 34276ce1169SAndreas Gohr // 34376ce1169SAndreas Gohr // Although Microsoft chose to use a different base and unit for time measurements. 34476ce1169SAndreas Gohr // This function will convert them to Unix timestamps 34576ce1169SAndreas Gohr $sr = ldap_read($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), 'objectclass=*', array('maxPwdAge')); 34676ce1169SAndreas Gohr if (!$sr) { 34776ce1169SAndreas Gohr return false; 34876ce1169SAndreas Gohr } 34976ce1169SAndreas Gohr $info = ldap_get_entries($this->adldap->getLdapConnection(), $sr); 35076ce1169SAndreas Gohr $maxPwdAge = $info[0]['maxpwdage'][0]; 35176ce1169SAndreas Gohr 35276ce1169SAndreas Gohr 35376ce1169SAndreas Gohr // See MSDN: http://msdn.microsoft.com/en-us/library/ms974598.aspx 35476ce1169SAndreas Gohr // 35576ce1169SAndreas Gohr // pwdLastSet contains the number of 100 nanosecond intervals since January 1, 1601 (UTC), 35676ce1169SAndreas Gohr // stored in a 64 bit integer. 35776ce1169SAndreas Gohr // 35876ce1169SAndreas Gohr // The number of seconds between this date and Unix epoch is 11644473600. 35976ce1169SAndreas Gohr // 36076ce1169SAndreas Gohr // maxPwdAge is stored as a large integer that represents the number of 100 nanosecond 36176ce1169SAndreas Gohr // intervals from the time the password was set before the password expires. 36276ce1169SAndreas Gohr // 36376ce1169SAndreas Gohr // We also need to scale this to seconds but also this value is a _negative_ quantity! 36476ce1169SAndreas Gohr // 36576ce1169SAndreas Gohr // If the low 32 bits of maxPwdAge are equal to 0 passwords do not expire 36676ce1169SAndreas Gohr // 36776ce1169SAndreas Gohr // Unfortunately the maths involved are too big for PHP integers, so I've had to require 36876ce1169SAndreas Gohr // BCMath functions to work with arbitrary precision numbers. 36976ce1169SAndreas Gohr if (bcmod($maxPwdAge, 4294967296) === '0') { 37076ce1169SAndreas Gohr return "Domain does not expire passwords"; 37176ce1169SAndreas Gohr } 37276ce1169SAndreas Gohr 37376ce1169SAndreas Gohr // Add maxpwdage and pwdlastset and we get password expiration time in Microsoft's 37476ce1169SAndreas Gohr // time units. Because maxpwd age is negative we need to subtract it. 37576ce1169SAndreas Gohr $pwdExpire = bcsub($pwdLastSet, $maxPwdAge); 37676ce1169SAndreas Gohr 37776ce1169SAndreas Gohr // Convert MS's time to Unix time 37876ce1169SAndreas Gohr $status['expiryts'] = bcsub(bcdiv($pwdExpire, '10000000'), '11644473600'); 37976ce1169SAndreas Gohr $status['expiryformat'] = date('Y-m-d H:i:s', bcsub(bcdiv($pwdExpire, '10000000'), '11644473600')); 38076ce1169SAndreas Gohr 38176ce1169SAndreas Gohr return $status; 38276ce1169SAndreas Gohr } 38376ce1169SAndreas Gohr 38476ce1169SAndreas Gohr /** 38576ce1169SAndreas Gohr * Modify a user 38676ce1169SAndreas Gohr * 38776ce1169SAndreas Gohr * @param string $username The username to query 38876ce1169SAndreas Gohr * @param array $attributes The attributes to modify. Note if you set the enabled attribute you must not specify any other attributes 38976ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 39076ce1169SAndreas Gohr * @return bool 39176ce1169SAndreas Gohr */ 39276ce1169SAndreas Gohr public function modify($username, $attributes, $isGUID = false) 39376ce1169SAndreas Gohr { 39476ce1169SAndreas Gohr if ($username === NULL) { return "Missing compulsory field [username]"; } 39576ce1169SAndreas Gohr if (array_key_exists("password", $attributes) && !$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) { 39676ce1169SAndreas Gohr throw new adLDAPException('SSL/TLS must be configured on your webserver and enabled in the class to set passwords.'); 39776ce1169SAndreas Gohr } 39876ce1169SAndreas Gohr 39976ce1169SAndreas Gohr // Find the dn of the user 40076ce1169SAndreas Gohr $userDn = $this->dn($username, $isGUID); 40176ce1169SAndreas Gohr if ($userDn === false) { 40276ce1169SAndreas Gohr return false; 40376ce1169SAndreas Gohr } 40476ce1169SAndreas Gohr 40576ce1169SAndreas Gohr // Translate the update to the LDAP schema 40676ce1169SAndreas Gohr $mod = $this->adldap->adldap_schema($attributes); 40776ce1169SAndreas Gohr 40876ce1169SAndreas Gohr // Check to see if this is an enabled status update 40976ce1169SAndreas Gohr if (!$mod && !array_key_exists("enabled", $attributes)){ 41076ce1169SAndreas Gohr return false; 41176ce1169SAndreas Gohr } 41276ce1169SAndreas Gohr 41376ce1169SAndreas Gohr // Set the account control attribute (only if specified) 41476ce1169SAndreas Gohr if (array_key_exists("enabled", $attributes)){ 41576ce1169SAndreas Gohr if ($attributes["enabled"]){ 41676ce1169SAndreas Gohr $controlOptions = array("NORMAL_ACCOUNT"); 41776ce1169SAndreas Gohr } 41876ce1169SAndreas Gohr else { 41976ce1169SAndreas Gohr $controlOptions = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE"); 42076ce1169SAndreas Gohr } 42176ce1169SAndreas Gohr $mod["userAccountControl"][0] = $this->accountControl($controlOptions); 42276ce1169SAndreas Gohr } 42376ce1169SAndreas Gohr 42476ce1169SAndreas Gohr // Do the update 42576ce1169SAndreas Gohr $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod); 42676ce1169SAndreas Gohr if ($result == false) { 42776ce1169SAndreas Gohr return false; 42876ce1169SAndreas Gohr } 42976ce1169SAndreas Gohr 43076ce1169SAndreas Gohr return true; 43176ce1169SAndreas Gohr } 43276ce1169SAndreas Gohr 43376ce1169SAndreas Gohr /** 43476ce1169SAndreas Gohr * Disable a user account 43576ce1169SAndreas Gohr * 43676ce1169SAndreas Gohr * @param string $username The username to disable 43776ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 43876ce1169SAndreas Gohr * @return bool 43976ce1169SAndreas Gohr */ 44076ce1169SAndreas Gohr public function disable($username, $isGUID = false) 44176ce1169SAndreas Gohr { 44276ce1169SAndreas Gohr if ($username === NULL) { return "Missing compulsory field [username]"; } 44376ce1169SAndreas Gohr $attributes = array("enabled" => 0); 44476ce1169SAndreas Gohr $result = $this->modify($username, $attributes, $isGUID); 44576ce1169SAndreas Gohr if ($result == false) { return false; } 44676ce1169SAndreas Gohr 44776ce1169SAndreas Gohr return true; 44876ce1169SAndreas Gohr } 44976ce1169SAndreas Gohr 45076ce1169SAndreas Gohr /** 45176ce1169SAndreas Gohr * Enable a user account 45276ce1169SAndreas Gohr * 45376ce1169SAndreas Gohr * @param string $username The username to enable 45476ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 45576ce1169SAndreas Gohr * @return bool 45676ce1169SAndreas Gohr */ 45776ce1169SAndreas Gohr public function enable($username, $isGUID = false) 45876ce1169SAndreas Gohr { 45976ce1169SAndreas Gohr if ($username === NULL) { return "Missing compulsory field [username]"; } 46076ce1169SAndreas Gohr $attributes = array("enabled" => 1); 46176ce1169SAndreas Gohr $result = $this->modify($username, $attributes, $isGUID); 46276ce1169SAndreas Gohr if ($result == false) { return false; } 46376ce1169SAndreas Gohr 46476ce1169SAndreas Gohr return true; 46576ce1169SAndreas Gohr } 46676ce1169SAndreas Gohr 46776ce1169SAndreas Gohr /** 46876ce1169SAndreas Gohr * Set the password of a user - This must be performed over SSL 46976ce1169SAndreas Gohr * 47076ce1169SAndreas Gohr * @param string $username The username to modify 47176ce1169SAndreas Gohr * @param string $password The new password 47276ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 47376ce1169SAndreas Gohr * @return bool 47476ce1169SAndreas Gohr */ 47576ce1169SAndreas Gohr public function password($username, $password, $isGUID = false) 47676ce1169SAndreas Gohr { 47776ce1169SAndreas Gohr if ($username === NULL) { return false; } 47876ce1169SAndreas Gohr if ($password === NULL) { return false; } 47976ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 48076ce1169SAndreas Gohr if (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS()) { 48176ce1169SAndreas Gohr throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); 48276ce1169SAndreas Gohr } 48376ce1169SAndreas Gohr 48476ce1169SAndreas Gohr $userDn = $this->dn($username, $isGUID); 48576ce1169SAndreas Gohr if ($userDn === false) { 48676ce1169SAndreas Gohr return false; 48776ce1169SAndreas Gohr } 48876ce1169SAndreas Gohr 48976ce1169SAndreas Gohr $add=array(); 49076ce1169SAndreas Gohr $add["unicodePwd"][0] = $this->encodePassword($password); 49176ce1169SAndreas Gohr 49276ce1169SAndreas Gohr $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $add); 49376ce1169SAndreas Gohr if ($result === false){ 49476ce1169SAndreas Gohr $err = ldap_errno($this->adldap->getLdapConnection()); 49576ce1169SAndreas Gohr if ($err) { 49676ce1169SAndreas Gohr $msg = 'Error ' . $err . ': ' . ldap_err2str($err) . '.'; 49776ce1169SAndreas Gohr if($err == 53) { 49876ce1169SAndreas Gohr $msg .= ' Your password might not match the password policy.'; 49976ce1169SAndreas Gohr } 50076ce1169SAndreas Gohr throw new adLDAPException($msg); 50176ce1169SAndreas Gohr } 50276ce1169SAndreas Gohr else { 50376ce1169SAndreas Gohr return false; 50476ce1169SAndreas Gohr } 50576ce1169SAndreas Gohr } 50676ce1169SAndreas Gohr 50776ce1169SAndreas Gohr return true; 50876ce1169SAndreas Gohr } 50976ce1169SAndreas Gohr 51076ce1169SAndreas Gohr /** 51176ce1169SAndreas Gohr * Encode a password for transmission over LDAP 51276ce1169SAndreas Gohr * 51376ce1169SAndreas Gohr * @param string $password The password to encode 51476ce1169SAndreas Gohr * @return string 51576ce1169SAndreas Gohr */ 51676ce1169SAndreas Gohr public function encodePassword($password) 51776ce1169SAndreas Gohr { 51876ce1169SAndreas Gohr $password="\"".$password."\""; 51976ce1169SAndreas Gohr $encoded=""; 52076ce1169SAndreas Gohr for ($i=0; $i <strlen($password); $i++){ $encoded.="{$password{$i}}\000"; } 52176ce1169SAndreas Gohr return $encoded; 52276ce1169SAndreas Gohr } 52376ce1169SAndreas Gohr 52476ce1169SAndreas Gohr /** 52576ce1169SAndreas Gohr * Obtain the user's distinguished name based on their userid 52676ce1169SAndreas Gohr * 52776ce1169SAndreas Gohr * 52876ce1169SAndreas Gohr * @param string $username The username 52976ce1169SAndreas Gohr * @param bool $isGUID Is the username passed a GUID or a samAccountName 53076ce1169SAndreas Gohr * @return string 53176ce1169SAndreas Gohr */ 53276ce1169SAndreas Gohr public function dn($username, $isGUID=false) 53376ce1169SAndreas Gohr { 53476ce1169SAndreas Gohr $user = $this->info($username, array("cn"), $isGUID); 53576ce1169SAndreas Gohr if ($user[0]["dn"] === NULL) { 53676ce1169SAndreas Gohr return false; 53776ce1169SAndreas Gohr } 53876ce1169SAndreas Gohr $userDn = $user[0]["dn"]; 53976ce1169SAndreas Gohr return $userDn; 54076ce1169SAndreas Gohr } 54176ce1169SAndreas Gohr 54276ce1169SAndreas Gohr /** 54376ce1169SAndreas Gohr * Return a list of all users in AD 54476ce1169SAndreas Gohr * 54576ce1169SAndreas Gohr * @param bool $includeDescription Return a description of the user 54676ce1169SAndreas Gohr * @param string $search Search parameter 54776ce1169SAndreas Gohr * @param bool $sorted Sort the user accounts 54876ce1169SAndreas Gohr * @return array 54976ce1169SAndreas Gohr */ 55076ce1169SAndreas Gohr public function all($includeDescription = false, $search = "*", $sorted = true) 55176ce1169SAndreas Gohr { 55276ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 55376ce1169SAndreas Gohr 55476ce1169SAndreas Gohr // Perform the search and grab all their details 55576ce1169SAndreas Gohr $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=" . $search . "))"; 55676ce1169SAndreas Gohr $fields = array("samaccountname","displayname"); 55776ce1169SAndreas Gohr $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); 55876ce1169SAndreas Gohr $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); 55976ce1169SAndreas Gohr 56076ce1169SAndreas Gohr $usersArray = array(); 56176ce1169SAndreas Gohr for ($i=0; $i<$entries["count"]; $i++){ 56276ce1169SAndreas Gohr if ($includeDescription && strlen($entries[$i]["displayname"][0])>0){ 56376ce1169SAndreas Gohr $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0]; 56476ce1169SAndreas Gohr } elseif ($includeDescription){ 56576ce1169SAndreas Gohr $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0]; 56676ce1169SAndreas Gohr } else { 56776ce1169SAndreas Gohr array_push($usersArray, $entries[$i]["samaccountname"][0]); 56876ce1169SAndreas Gohr } 56976ce1169SAndreas Gohr } 57076ce1169SAndreas Gohr if ($sorted) { 57176ce1169SAndreas Gohr asort($usersArray); 57276ce1169SAndreas Gohr } 57376ce1169SAndreas Gohr return $usersArray; 57476ce1169SAndreas Gohr } 57576ce1169SAndreas Gohr 57676ce1169SAndreas Gohr /** 57776ce1169SAndreas Gohr * Converts a username (samAccountName) to a GUID 57876ce1169SAndreas Gohr * 57976ce1169SAndreas Gohr * @param string $username The username to query 58076ce1169SAndreas Gohr * @return string 58176ce1169SAndreas Gohr */ 58276ce1169SAndreas Gohr public function usernameToGuid($username) 58376ce1169SAndreas Gohr { 58476ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()){ return false; } 58576ce1169SAndreas Gohr if ($username === null){ return "Missing compulsory field [username]"; } 58676ce1169SAndreas Gohr 58776ce1169SAndreas Gohr $filter = "samaccountname=" . $username; 58876ce1169SAndreas Gohr $fields = array("objectGUID"); 58976ce1169SAndreas Gohr $sr = @ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); 59076ce1169SAndreas Gohr if (ldap_count_entries($this->adldap->getLdapConnection(), $sr) > 0) { 59176ce1169SAndreas Gohr $entry = @ldap_first_entry($this->adldap->getLdapConnection(), $sr); 59276ce1169SAndreas Gohr $guid = @ldap_get_values_len($this->adldap->getLdapConnection(), $entry, 'objectGUID'); 59376ce1169SAndreas Gohr $strGUID = $this->adldap->utilities()->binaryToText($guid[0]); 59476ce1169SAndreas Gohr return $strGUID; 59576ce1169SAndreas Gohr } 59676ce1169SAndreas Gohr return false; 59776ce1169SAndreas Gohr } 59876ce1169SAndreas Gohr 59976ce1169SAndreas Gohr /** 60076ce1169SAndreas Gohr * Return a list of all users in AD that have a specific value in a field 60176ce1169SAndreas Gohr * 60276ce1169SAndreas Gohr * @param bool $includeDescription Return a description of the user 60376ce1169SAndreas Gohr * @param string $searchField Field to search search for 60476ce1169SAndreas Gohr * @param string $searchFilter Value to search for in the specified field 60576ce1169SAndreas Gohr * @param bool $sorted Sort the user accounts 60676ce1169SAndreas Gohr * @return array 60776ce1169SAndreas Gohr */ 60876ce1169SAndreas Gohr public function find($includeDescription = false, $searchField = false, $searchFilter = false, $sorted = true){ 60976ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()){ return false; } 61076ce1169SAndreas Gohr 61176ce1169SAndreas Gohr // Perform the search and grab all their details 61276ce1169SAndreas Gohr $searchParams = ""; 61376ce1169SAndreas Gohr if ($searchField) { 61476ce1169SAndreas Gohr $searchParams = "(" . $searchField . "=" . $searchFilter . ")"; 61576ce1169SAndreas Gohr } 61676ce1169SAndreas Gohr $filter = "(&(objectClass=user)(samaccounttype=" . adLDAP::ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)" . $searchParams . ")"; 61776ce1169SAndreas Gohr $fields = array("samaccountname","displayname"); 61876ce1169SAndreas Gohr $sr = ldap_search($this->adldap->getLdapConnection(), $this->adldap->getBaseDn(), $filter, $fields); 61976ce1169SAndreas Gohr $entries = ldap_get_entries($this->adldap->getLdapConnection(), $sr); 62076ce1169SAndreas Gohr 62176ce1169SAndreas Gohr $usersArray = array(); 62276ce1169SAndreas Gohr for ($i=0; $i < $entries["count"]; $i++) { 62376ce1169SAndreas Gohr if ($includeDescription && strlen($entries[$i]["displayname"][0]) > 0) { 62476ce1169SAndreas Gohr $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["displayname"][0]; 62576ce1169SAndreas Gohr } 62676ce1169SAndreas Gohr else if ($includeDescription) { 62776ce1169SAndreas Gohr $usersArray[$entries[$i]["samaccountname"][0]] = $entries[$i]["samaccountname"][0]; 62876ce1169SAndreas Gohr } 62976ce1169SAndreas Gohr else { 63076ce1169SAndreas Gohr array_push($usersArray, $entries[$i]["samaccountname"][0]); 63176ce1169SAndreas Gohr } 63276ce1169SAndreas Gohr } 63376ce1169SAndreas Gohr if ($sorted){ 63476ce1169SAndreas Gohr asort($usersArray); 63576ce1169SAndreas Gohr } 63676ce1169SAndreas Gohr return ($usersArray); 63776ce1169SAndreas Gohr } 63876ce1169SAndreas Gohr 63976ce1169SAndreas Gohr /** 64076ce1169SAndreas Gohr * Move a user account to a different OU 64176ce1169SAndreas Gohr * 64276ce1169SAndreas Gohr * @param string $username The username to move (please be careful here!) 64376ce1169SAndreas Gohr * @param array $container The container or containers to move the user to (please be careful here!). 64476ce1169SAndreas Gohr * accepts containers in 1. parent 2. child order 64576ce1169SAndreas Gohr * @return array 64676ce1169SAndreas Gohr */ 64776ce1169SAndreas Gohr public function move($username, $container) 64876ce1169SAndreas Gohr { 64976ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 65076ce1169SAndreas Gohr if ($username === null) { return "Missing compulsory field [username]"; } 65176ce1169SAndreas Gohr if ($container === null) { return "Missing compulsory field [container]"; } 65276ce1169SAndreas Gohr if (!is_array($container)) { return "Container must be an array"; } 65376ce1169SAndreas Gohr 65476ce1169SAndreas Gohr $userInfo = $this->info($username, array("*")); 65576ce1169SAndreas Gohr $dn = $userInfo[0]['distinguishedname'][0]; 65676ce1169SAndreas Gohr $newRDn = "cn=" . $username; 65776ce1169SAndreas Gohr $container = array_reverse($container); 65876ce1169SAndreas Gohr $newContainer = "ou=" . implode(",ou=",$container); 65976ce1169SAndreas Gohr $newBaseDn = strtolower($newContainer) . "," . $this->adldap->getBaseDn(); 66076ce1169SAndreas Gohr $result = @ldap_rename($this->adldap->getLdapConnection(), $dn, $newRDn, $newBaseDn, true); 66176ce1169SAndreas Gohr if ($result !== true) { 66276ce1169SAndreas Gohr return false; 66376ce1169SAndreas Gohr } 66476ce1169SAndreas Gohr return true; 66576ce1169SAndreas Gohr } 66676ce1169SAndreas Gohr 66776ce1169SAndreas Gohr /** 66876ce1169SAndreas Gohr * Get the last logon time of any user as a Unix timestamp 66976ce1169SAndreas Gohr * 67076ce1169SAndreas Gohr * @param string $username 67176ce1169SAndreas Gohr * @return long $unixTimestamp 67276ce1169SAndreas Gohr */ 67376ce1169SAndreas Gohr public function getLastLogon($username) { 67476ce1169SAndreas Gohr if (!$this->adldap->getLdapBind()) { return false; } 67576ce1169SAndreas Gohr if ($username === null) { return "Missing compulsory field [username]"; } 67676ce1169SAndreas Gohr $userInfo = $this->info($username, array("lastLogonTimestamp")); 67776ce1169SAndreas Gohr $lastLogon = adLDAPUtils::convertWindowsTimeToUnixTime($userInfo[0]['lastLogonTimestamp'][0]); 67876ce1169SAndreas Gohr return $lastLogon; 67976ce1169SAndreas Gohr } 68076ce1169SAndreas Gohr 68176ce1169SAndreas Gohr} 68276ce1169SAndreas Gohr?> 683