| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | /**
 | 
        
           |  |  | 18 |  * Authentication Plugin: LDAP Authentication
 | 
        
           |  |  | 19 |  * Authentication using LDAP (Lightweight Directory Access Protocol).
 | 
        
           |  |  | 20 |  *
 | 
        
           |  |  | 21 |  * @package auth_ldap
 | 
        
           |  |  | 22 |  * @author Martin Dougiamas
 | 
        
           |  |  | 23 |  * @author Iñaki Arenaza
 | 
        
           |  |  | 24 |  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
 | 
        
           |  |  | 25 |  */
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 28 |   | 
        
           |  |  | 29 | // See http://support.microsoft.com/kb/305144 to interprete these values.
 | 
        
           |  |  | 30 | if (!defined('AUTH_AD_ACCOUNTDISABLE')) {
 | 
        
           |  |  | 31 |     define('AUTH_AD_ACCOUNTDISABLE', 0x0002);
 | 
        
           |  |  | 32 | }
 | 
        
           |  |  | 33 | if (!defined('AUTH_AD_NORMAL_ACCOUNT')) {
 | 
        
           |  |  | 34 |     define('AUTH_AD_NORMAL_ACCOUNT', 0x0200);
 | 
        
           |  |  | 35 | }
 | 
        
           |  |  | 36 | if (!defined('AUTH_NTLMTIMEOUT')) {  // timewindow for the NTLM SSO process, in secs...
 | 
        
           |  |  | 37 |     define('AUTH_NTLMTIMEOUT', 10);
 | 
        
           |  |  | 38 | }
 | 
        
           |  |  | 39 |   | 
        
           |  |  | 40 | // UF_DONT_EXPIRE_PASSWD value taken from MSDN directly
 | 
        
           |  |  | 41 | if (!defined('UF_DONT_EXPIRE_PASSWD')) {
 | 
        
           |  |  | 42 |     define ('UF_DONT_EXPIRE_PASSWD', 0x00010000);
 | 
        
           |  |  | 43 | }
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 | // The Posix uid and gid of the 'nobody' account and 'nogroup' group.
 | 
        
           |  |  | 46 | if (!defined('AUTH_UID_NOBODY')) {
 | 
        
           |  |  | 47 |     define('AUTH_UID_NOBODY', -2);
 | 
        
           |  |  | 48 | }
 | 
        
           |  |  | 49 | if (!defined('AUTH_GID_NOGROUP')) {
 | 
        
           |  |  | 50 |     define('AUTH_GID_NOGROUP', -2);
 | 
        
           |  |  | 51 | }
 | 
        
           |  |  | 52 |   | 
        
           |  |  | 53 | // Regular expressions for a valid NTLM username and domain name.
 | 
        
           |  |  | 54 | if (!defined('AUTH_NTLM_VALID_USERNAME')) {
 | 
        
           |  |  | 55 |     define('AUTH_NTLM_VALID_USERNAME', '[^/\\\\\\\\\[\]:;|=,+*?<>@"]+');
 | 
        
           |  |  | 56 | }
 | 
        
           |  |  | 57 | if (!defined('AUTH_NTLM_VALID_DOMAINNAME')) {
 | 
        
           |  |  | 58 |     define('AUTH_NTLM_VALID_DOMAINNAME', '[^\\\\\\\\\/:*?"<>|]+');
 | 
        
           |  |  | 59 | }
 | 
        
           |  |  | 60 | // Default format for remote users if using NTLM SSO
 | 
        
           |  |  | 61 | if (!defined('AUTH_NTLM_DEFAULT_FORMAT')) {
 | 
        
           |  |  | 62 |     define('AUTH_NTLM_DEFAULT_FORMAT', '%domain%\\%username%');
 | 
        
           |  |  | 63 | }
 | 
        
           |  |  | 64 | if (!defined('AUTH_NTLM_FASTPATH_ATTEMPT')) {
 | 
        
           |  |  | 65 |     define('AUTH_NTLM_FASTPATH_ATTEMPT', 0);
 | 
        
           |  |  | 66 | }
 | 
        
           |  |  | 67 | if (!defined('AUTH_NTLM_FASTPATH_YESFORM')) {
 | 
        
           |  |  | 68 |     define('AUTH_NTLM_FASTPATH_YESFORM', 1);
 | 
        
           |  |  | 69 | }
 | 
        
           |  |  | 70 | if (!defined('AUTH_NTLM_FASTPATH_YESATTEMPT')) {
 | 
        
           |  |  | 71 |     define('AUTH_NTLM_FASTPATH_YESATTEMPT', 2);
 | 
        
           |  |  | 72 | }
 | 
        
           |  |  | 73 |   | 
        
           |  |  | 74 | // Allows us to retrieve a diagnostic message in case of LDAP operation error
 | 
        
           |  |  | 75 | if (!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
 | 
        
           |  |  | 76 |     define('LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032);
 | 
        
           |  |  | 77 | }
 | 
        
           |  |  | 78 |   | 
        
           |  |  | 79 | require_once($CFG->libdir.'/authlib.php');
 | 
        
           |  |  | 80 | require_once($CFG->libdir.'/ldaplib.php');
 | 
        
           |  |  | 81 | require_once($CFG->dirroot.'/user/lib.php');
 | 
        
           |  |  | 82 | require_once($CFG->dirroot.'/auth/ldap/locallib.php');
 | 
        
           |  |  | 83 |   | 
        
           |  |  | 84 | /**
 | 
        
           |  |  | 85 |  * LDAP authentication plugin.
 | 
        
           |  |  | 86 |  */
 | 
        
           |  |  | 87 | class auth_plugin_ldap extends auth_plugin_base {
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |     /** @var string */
 | 
        
           |  |  | 90 |     protected $roleauth;
 | 
        
           |  |  | 91 |   | 
        
           |  |  | 92 |     /** @var string */
 | 
        
           |  |  | 93 |     public $pluginconfig;
 | 
        
           |  |  | 94 |   | 
        
           |  |  | 95 |     /** @var LDAP\Connection LDAP connection. */
 | 
        
           |  |  | 96 |     protected $ldapconnection;
 | 
        
           |  |  | 97 |   | 
        
           |  |  | 98 |     /** @var int */
 | 
        
           |  |  | 99 |     protected $ldapconns = 0;
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |     /**
 | 
        
           |  |  | 102 |      * Init plugin config from database settings depending on the plugin auth type.
 | 
        
           |  |  | 103 |      */
 | 
        
           |  |  | 104 |     function init_plugin($authtype) {
 | 
        
           |  |  | 105 |         $this->pluginconfig = 'auth_'.$authtype;
 | 
        
           |  |  | 106 |         $this->config = get_config($this->pluginconfig);
 | 
        
           |  |  | 107 |         if (empty($this->config->ldapencoding)) {
 | 
        
           |  |  | 108 |             $this->config->ldapencoding = 'utf-8';
 | 
        
           |  |  | 109 |         }
 | 
        
           |  |  | 110 |         if (empty($this->config->user_type)) {
 | 
        
           |  |  | 111 |             $this->config->user_type = 'default';
 | 
        
           |  |  | 112 |         }
 | 
        
           |  |  | 113 |   | 
        
           |  |  | 114 |         $ldap_usertypes = ldap_supported_usertypes();
 | 
        
           |  |  | 115 |         $this->config->user_type_name = $ldap_usertypes[$this->config->user_type];
 | 
        
           |  |  | 116 |         unset($ldap_usertypes);
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |         $default = ldap_getdefaults();
 | 
        
           |  |  | 119 |   | 
        
           |  |  | 120 |         // Use defaults if values not given
 | 
        
           |  |  | 121 |         foreach ($default as $key => $value) {
 | 
        
           |  |  | 122 |             // watch out - 0, false are correct values too
 | 
        
           |  |  | 123 |             if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
 | 
        
           |  |  | 124 |                 $this->config->{$key} = $value[$this->config->user_type];
 | 
        
           |  |  | 125 |             }
 | 
        
           |  |  | 126 |         }
 | 
        
           |  |  | 127 |   | 
        
           |  |  | 128 |         // Hack prefix to objectclass
 | 
        
           |  |  | 129 |         $this->config->objectclass = ldap_normalise_objectclass($this->config->objectclass);
 | 
        
           |  |  | 130 |     }
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |     /**
 | 
        
           |  |  | 133 |      * Constructor with initialisation.
 | 
        
           |  |  | 134 |      */
 | 
        
           |  |  | 135 |     public function __construct() {
 | 
        
           |  |  | 136 |         $this->authtype = 'ldap';
 | 
        
           |  |  | 137 |         $this->roleauth = 'auth_ldap';
 | 
        
           |  |  | 138 |         $this->errorlogtag = '[AUTH LDAP] ';
 | 
        
           |  |  | 139 |         $this->init_plugin($this->authtype);
 | 
        
           |  |  | 140 |     }
 | 
        
           |  |  | 141 |   | 
        
           |  |  | 142 |     /**
 | 
        
           |  |  | 143 |      * Old syntax of class constructor. Deprecated in PHP7.
 | 
        
           |  |  | 144 |      *
 | 
        
           |  |  | 145 |      * @deprecated since Moodle 3.1
 | 
        
           |  |  | 146 |      */
 | 
        
           |  |  | 147 |     public function auth_plugin_ldap() {
 | 
        
           |  |  | 148 |         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 | 
        
           |  |  | 149 |         self::__construct();
 | 
        
           |  |  | 150 |     }
 | 
        
           |  |  | 151 |   | 
        
           |  |  | 152 |     /**
 | 
        
           |  |  | 153 |      * Returns true if the username and password work and false if they are
 | 
        
           |  |  | 154 |      * wrong or don't exist.
 | 
        
           |  |  | 155 |      *
 | 
        
           |  |  | 156 |      * @param string $username The username (without system magic quotes)
 | 
        
           |  |  | 157 |      * @param string $password The password (without system magic quotes)
 | 
        
           |  |  | 158 |      *
 | 
        
           |  |  | 159 |      * @return bool Authentication success or failure.
 | 
        
           |  |  | 160 |      */
 | 
        
           |  |  | 161 |     function user_login($username, $password) {
 | 
        
           |  |  | 162 |         if (! function_exists('ldap_bind')) {
 | 
        
           |  |  | 163 |             throw new \moodle_exception('auth_ldapnotinstalled', 'auth_ldap');
 | 
        
           |  |  | 164 |             return false;
 | 
        
           |  |  | 165 |         }
 | 
        
           |  |  | 166 |   | 
        
           |  |  | 167 |         if (!$username or !$password) {    // Don't allow blank usernames or passwords
 | 
        
           |  |  | 168 |             return false;
 | 
        
           |  |  | 169 |         }
 | 
        
           |  |  | 170 |   | 
        
           |  |  | 171 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 172 |         $extpassword = core_text::convert($password, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 173 |   | 
        
           |  |  | 174 |         // Before we connect to LDAP, check if this is an AD SSO login
 | 
        
           |  |  | 175 |         // if we succeed in this block, we'll return success early.
 | 
        
           |  |  | 176 |         //
 | 
        
           |  |  | 177 |         $key = sesskey();
 | 
        
           |  |  | 178 |         if (!empty($this->config->ntlmsso_enabled) && $key === $password) {
 | 
        
           |  |  | 179 |             $sessusername = get_cache_flag($this->pluginconfig.'/ntlmsess', $key);
 | 
        
           |  |  | 180 |             // We only get the cache flag if we retrieve it before
 | 
        
           |  |  | 181 |             // it expires (AUTH_NTLMTIMEOUT seconds).
 | 
        
           |  |  | 182 |             if (empty($sessusername)) {
 | 
        
           |  |  | 183 |                 return false;
 | 
        
           |  |  | 184 |             }
 | 
        
           |  |  | 185 |   | 
        
           |  |  | 186 |             if ($username === $sessusername) {
 | 
        
           |  |  | 187 |                 unset($sessusername);
 | 
        
           |  |  | 188 |   | 
        
           |  |  | 189 |                 // Check that the user is inside one of the configured LDAP contexts
 | 
        
           |  |  | 190 |                 $validuser = false;
 | 
        
           |  |  | 191 |                 $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 192 |                 // if the user is not inside the configured contexts,
 | 
        
           |  |  | 193 |                 // ldap_find_userdn returns false.
 | 
        
           |  |  | 194 |                 if ($this->ldap_find_userdn($ldapconnection, $extusername)) {
 | 
        
           |  |  | 195 |                     $validuser = true;
 | 
        
           |  |  | 196 |                 }
 | 
        
           |  |  | 197 |                 $this->ldap_close();
 | 
        
           |  |  | 198 |   | 
        
           |  |  | 199 |                 // Shortcut here - SSO confirmed
 | 
        
           |  |  | 200 |                 return $validuser;
 | 
        
           |  |  | 201 |             }
 | 
        
           |  |  | 202 |         } // End SSO processing
 | 
        
           |  |  | 203 |         unset($key);
 | 
        
           |  |  | 204 |   | 
        
           |  |  | 205 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 206 |         $ldap_user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
 | 
        
           |  |  | 207 |   | 
        
           |  |  | 208 |         // If ldap_user_dn is empty, user does not exist
 | 
        
           |  |  | 209 |         if (!$ldap_user_dn) {
 | 
        
           |  |  | 210 |             $this->ldap_close();
 | 
        
           |  |  | 211 |             return false;
 | 
        
           |  |  | 212 |         }
 | 
        
           |  |  | 213 |   | 
        
           |  |  | 214 |         // Try to bind with current username and password
 | 
        
           |  |  | 215 |         $ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, $extpassword);
 | 
        
           |  |  | 216 |   | 
        
           |  |  | 217 |         // If login fails and we are using MS Active Directory, retrieve the diagnostic
 | 
        
           |  |  | 218 |         // message to see if this is due to an expired password, or that the user is forced to
 | 
        
           |  |  | 219 |         // change the password on first login. If it is, only proceed if we can change
 | 
        
           |  |  | 220 |         // password from Moodle (otherwise we'll get stuck later in the login process).
 | 
        
           |  |  | 221 |         if (!$ldap_login && ($this->config->user_type == 'ad')
 | 
        
           |  |  | 222 |             && $this->can_change_password()
 | 
        
           |  |  | 223 |             && (!empty($this->config->expiration) and ($this->config->expiration == 1))) {
 | 
        
           |  |  | 224 |   | 
        
           |  |  | 225 |             // We need to get the diagnostic message right after the call to ldap_bind(),
 | 
        
           |  |  | 226 |             // before any other LDAP operation.
 | 
        
           |  |  | 227 |             ldap_get_option($ldapconnection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diagmsg);
 | 
        
           |  |  | 228 |   | 
        
           |  |  | 229 |             if ($this->ldap_ad_pwdexpired_from_diagmsg($diagmsg)) {
 | 
        
           |  |  | 230 |                 // If login failed because user must change the password now or the
 | 
        
           |  |  | 231 |                 // password has expired, let the user in. We'll catch this later in the
 | 
        
           |  |  | 232 |                 // login process when we explicitly check for expired passwords.
 | 
        
           |  |  | 233 |                 $ldap_login = true;
 | 
        
           |  |  | 234 |             }
 | 
        
           |  |  | 235 |         }
 | 
        
           |  |  | 236 |         $this->ldap_close();
 | 
        
           |  |  | 237 |         return $ldap_login;
 | 
        
           |  |  | 238 |     }
 | 
        
           |  |  | 239 |   | 
        
           |  |  | 240 |     /**
 | 
        
           |  |  | 241 |      * Reads user information from ldap and returns it in array()
 | 
        
           |  |  | 242 |      *
 | 
        
           |  |  | 243 |      * Function should return all information available. If you are saving
 | 
        
           |  |  | 244 |      * this information to moodle user-table you should honor syncronization flags
 | 
        
           |  |  | 245 |      *
 | 
        
           |  |  | 246 |      * @param string $username username
 | 
        
           |  |  | 247 |      *
 | 
        
           |  |  | 248 |      * @return mixed array with no magic quotes or false on error
 | 
        
           |  |  | 249 |      */
 | 
        
           |  |  | 250 |     function get_userinfo($username) {
 | 
        
           |  |  | 251 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 252 |   | 
        
           |  |  | 253 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 254 |         if(!($user_dn = $this->ldap_find_userdn($ldapconnection, $extusername))) {
 | 
        
           |  |  | 255 |             $this->ldap_close();
 | 
        
           |  |  | 256 |             return false;
 | 
        
           |  |  | 257 |         }
 | 
        
           |  |  | 258 |   | 
        
           |  |  | 259 |         $search_attribs = array();
 | 
        
           |  |  | 260 |         $attrmap = $this->ldap_attributes();
 | 
        
           |  |  | 261 |         foreach ($attrmap as $key => $values) {
 | 
        
           |  |  | 262 |             if (!is_array($values)) {
 | 
        
           |  |  | 263 |                 $values = array($values);
 | 
        
           |  |  | 264 |             }
 | 
        
           |  |  | 265 |             foreach ($values as $value) {
 | 
        
           |  |  | 266 |                 if (!in_array($value, $search_attribs)) {
 | 
        
           |  |  | 267 |                     array_push($search_attribs, $value);
 | 
        
           |  |  | 268 |                 }
 | 
        
           |  |  | 269 |             }
 | 
        
           |  |  | 270 |         }
 | 
        
           |  |  | 271 |   | 
        
           |  |  | 272 |         if (!$user_info_result = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs)) {
 | 
        
           |  |  | 273 |             $this->ldap_close();
 | 
        
           |  |  | 274 |             return false; // error!
 | 
        
           |  |  | 275 |         }
 | 
        
           |  |  | 276 |   | 
        
           |  |  | 277 |         $user_entry = ldap_get_entries_moodle($ldapconnection, $user_info_result);
 | 
        
           |  |  | 278 |         if (empty($user_entry)) {
 | 
        
           |  |  | 279 |             $this->ldap_close();
 | 
        
           |  |  | 280 |             return false; // entry not found
 | 
        
           |  |  | 281 |         }
 | 
        
           |  |  | 282 |   | 
        
           |  |  | 283 |         $result = array();
 | 
        
           |  |  | 284 |         foreach ($attrmap as $key => $values) {
 | 
        
           |  |  | 285 |             if (!is_array($values)) {
 | 
        
           |  |  | 286 |                 $values = array($values);
 | 
        
           |  |  | 287 |             }
 | 
        
           |  |  | 288 |             $ldapval = NULL;
 | 
        
           |  |  | 289 |             foreach ($values as $value) {
 | 
        
           |  |  | 290 |                 $entry = $user_entry[0];
 | 
        
           |  |  | 291 |                 if (($value == 'dn') || ($value == 'distinguishedname')) {
 | 
        
           |  |  | 292 |                     $result[$key] = $user_dn;
 | 
        
           |  |  | 293 |                     continue;
 | 
        
           |  |  | 294 |                 }
 | 
        
           |  |  | 295 |                 if (!array_key_exists($value, $entry)) {
 | 
        
           |  |  | 296 |                     continue; // wrong data mapping!
 | 
        
           |  |  | 297 |                 }
 | 
        
           |  |  | 298 |                 if (is_array($entry[$value])) {
 | 
        
           |  |  | 299 |                     $newval = core_text::convert($entry[$value][0], $this->config->ldapencoding, 'utf-8');
 | 
        
           |  |  | 300 |                 } else {
 | 
        
           |  |  | 301 |                     $newval = core_text::convert($entry[$value], $this->config->ldapencoding, 'utf-8');
 | 
        
           |  |  | 302 |                 }
 | 
        
           |  |  | 303 |                 if (!empty($newval)) { // favour ldap entries that are set
 | 
        
           |  |  | 304 |                     $ldapval = $newval;
 | 
        
           |  |  | 305 |                 }
 | 
        
           |  |  | 306 |             }
 | 
        
           |  |  | 307 |             if (!is_null($ldapval)) {
 | 
        
           |  |  | 308 |                 $result[$key] = $ldapval;
 | 
        
           |  |  | 309 |             }
 | 
        
           |  |  | 310 |         }
 | 
        
           |  |  | 311 |   | 
        
           |  |  | 312 |         $this->ldap_close();
 | 
        
           |  |  | 313 |         return $result;
 | 
        
           |  |  | 314 |     }
 | 
        
           |  |  | 315 |   | 
        
           |  |  | 316 |     /**
 | 
        
           |  |  | 317 |      * Reads user information from ldap and returns it in an object
 | 
        
           |  |  | 318 |      *
 | 
        
           |  |  | 319 |      * @param string $username username (with system magic quotes)
 | 
        
           |  |  | 320 |      * @return mixed object or false on error
 | 
        
           |  |  | 321 |      */
 | 
        
           |  |  | 322 |     function get_userinfo_asobj($username) {
 | 
        
           |  |  | 323 |         $user_array = $this->get_userinfo($username);
 | 
        
           |  |  | 324 |         if ($user_array == false) {
 | 
        
           |  |  | 325 |             return false; //error or not found
 | 
        
           |  |  | 326 |         }
 | 
        
           |  |  | 327 |         $user_array = truncate_userinfo($user_array);
 | 
        
           |  |  | 328 |         $user = new stdClass();
 | 
        
           |  |  | 329 |         foreach ($user_array as $key=>$value) {
 | 
        
           |  |  | 330 |             $user->{$key} = $value;
 | 
        
           |  |  | 331 |         }
 | 
        
           |  |  | 332 |         return $user;
 | 
        
           |  |  | 333 |     }
 | 
        
           |  |  | 334 |   | 
        
           |  |  | 335 |     /**
 | 
        
           |  |  | 336 |      * Returns all usernames from LDAP
 | 
        
           |  |  | 337 |      *
 | 
        
           |  |  | 338 |      * get_userlist returns all usernames from LDAP
 | 
        
           |  |  | 339 |      *
 | 
        
           |  |  | 340 |      * @return array
 | 
        
           |  |  | 341 |      */
 | 
        
           |  |  | 342 |     function get_userlist() {
 | 
        
           |  |  | 343 |         return $this->ldap_get_userlist("({$this->config->user_attribute}=*)");
 | 
        
           |  |  | 344 |     }
 | 
        
           |  |  | 345 |   | 
        
           |  |  | 346 |     /**
 | 
        
           |  |  | 347 |      * Checks if user exists on LDAP
 | 
        
           |  |  | 348 |      *
 | 
        
           |  |  | 349 |      * @param string $username
 | 
        
           |  |  | 350 |      */
 | 
        
           |  |  | 351 |     function user_exists($username) {
 | 
        
           |  |  | 352 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 353 |   | 
        
           |  |  | 354 |         // Returns true if given username exists on ldap
 | 
        
           |  |  | 355 |         $users = $this->ldap_get_userlist('('.$this->config->user_attribute.'='.ldap_filter_addslashes($extusername).')');
 | 
        
           |  |  | 356 |         return count($users);
 | 
        
           |  |  | 357 |     }
 | 
        
           |  |  | 358 |   | 
        
           |  |  | 359 |     /**
 | 
        
           |  |  | 360 |      * Creates a new user on LDAP.
 | 
        
           |  |  | 361 |      * By using information in userobject
 | 
        
           |  |  | 362 |      * Use user_exists to prevent duplicate usernames
 | 
        
           |  |  | 363 |      *
 | 
        
           |  |  | 364 |      * @param mixed $userobject  Moodle userobject
 | 
        
           |  |  | 365 |      * @param mixed $plainpass   Plaintext password
 | 
        
           |  |  | 366 |      */
 | 
        
           |  |  | 367 |     function user_create($userobject, $plainpass) {
 | 
        
           |  |  | 368 |         $extusername = core_text::convert($userobject->username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 369 |         $extpassword = core_text::convert($plainpass, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 370 |   | 
        
           |  |  | 371 |         switch ($this->config->passtype) {
 | 
        
           |  |  | 372 |             case 'md5':
 | 
        
           |  |  | 373 |                 $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword)));
 | 
        
           |  |  | 374 |                 break;
 | 
        
           |  |  | 375 |             case 'sha1':
 | 
        
           |  |  | 376 |                 $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword)));
 | 
        
           |  |  | 377 |                 break;
 | 
        
           |  |  | 378 |             case 'plaintext':
 | 
        
           |  |  | 379 |             default:
 | 
        
           |  |  | 380 |                 break; // plaintext
 | 
        
           |  |  | 381 |         }
 | 
        
           |  |  | 382 |   | 
        
           |  |  | 383 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 384 |         $attrmap = $this->ldap_attributes();
 | 
        
           |  |  | 385 |   | 
        
           |  |  | 386 |         $newuser = array();
 | 
        
           |  |  | 387 |   | 
        
           |  |  | 388 |         foreach ($attrmap as $key => $values) {
 | 
        
           |  |  | 389 |             if (!is_array($values)) {
 | 
        
           |  |  | 390 |                 $values = array($values);
 | 
        
           |  |  | 391 |             }
 | 
        
           |  |  | 392 |             foreach ($values as $value) {
 | 
        
           |  |  | 393 |                 if (!empty($userobject->$key) ) {
 | 
        
           |  |  | 394 |                     $newuser[$value] = core_text::convert($userobject->$key, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 395 |                 }
 | 
        
           |  |  | 396 |             }
 | 
        
           |  |  | 397 |         }
 | 
        
           |  |  | 398 |   | 
        
           |  |  | 399 |         //Following sets all mandatory and other forced attribute values
 | 
        
           |  |  | 400 |         //User should be creted as login disabled untill email confirmation is processed
 | 
        
           |  |  | 401 |         //Feel free to add your user type and send patches to paca@sci.fi to add them
 | 
        
           |  |  | 402 |         //Moodle distribution
 | 
        
           |  |  | 403 |   | 
        
           |  |  | 404 |         switch ($this->config->user_type)  {
 | 
        
           |  |  | 405 |             case 'edir':
 | 
        
           |  |  | 406 |                 $newuser['objectClass']   = array('inetOrgPerson', 'organizationalPerson', 'person', 'top');
 | 
        
           |  |  | 407 |                 $newuser['uniqueId']      = $extusername;
 | 
        
           |  |  | 408 |                 $newuser['logindisabled'] = 'TRUE';
 | 
        
           |  |  | 409 |                 $newuser['userpassword']  = $extpassword;
 | 
        
           |  |  | 410 |                 $uadd = ldap_add($ldapconnection, $this->config->user_attribute.'='.ldap_addslashes($extusername).','.$this->config->create_context, $newuser);
 | 
        
           |  |  | 411 |                 break;
 | 
        
           |  |  | 412 |             case 'rfc2307':
 | 
        
           |  |  | 413 |             case 'rfc2307bis':
 | 
        
           |  |  | 414 |                 // posixAccount object class forces us to specify a uidNumber
 | 
        
           |  |  | 415 |                 // and a gidNumber. That is quite complicated to generate from
 | 
        
           |  |  | 416 |                 // Moodle without colliding with existing numbers and without
 | 
        
           |  |  | 417 |                 // race conditions. As this user is supposed to be only used
 | 
        
           |  |  | 418 |                 // with Moodle (otherwise the user would exist beforehand) and
 | 
        
           |  |  | 419 |                 // doesn't need to login into a operating system, we assign the
 | 
        
           |  |  | 420 |                 // user the uid of user 'nobody' and gid of group 'nogroup'. In
 | 
        
           |  |  | 421 |                 // addition to that, we need to specify a home directory. We
 | 
        
           |  |  | 422 |                 // use the root directory ('/') as the home directory, as this
 | 
        
           |  |  | 423 |                 // is the only one can always be sure exists. Finally, even if
 | 
        
           |  |  | 424 |                 // it's not mandatory, we specify '/bin/false' as the login
 | 
        
           |  |  | 425 |                 // shell, to prevent the user from login in at the operating
 | 
        
           |  |  | 426 |                 // system level (Moodle ignores this).
 | 
        
           |  |  | 427 |   | 
        
           |  |  | 428 |                 $newuser['objectClass']   = array('posixAccount', 'inetOrgPerson', 'organizationalPerson', 'person', 'top');
 | 
        
           |  |  | 429 |                 $newuser['cn']            = $extusername;
 | 
        
           |  |  | 430 |                 $newuser['uid']           = $extusername;
 | 
        
           |  |  | 431 |                 $newuser['uidNumber']     = AUTH_UID_NOBODY;
 | 
        
           |  |  | 432 |                 $newuser['gidNumber']     = AUTH_GID_NOGROUP;
 | 
        
           |  |  | 433 |                 $newuser['homeDirectory'] = '/';
 | 
        
           |  |  | 434 |                 $newuser['loginShell']    = '/bin/false';
 | 
        
           |  |  | 435 |   | 
        
           |  |  | 436 |                 // IMPORTANT:
 | 
        
           |  |  | 437 |                 // We have to create the account locked, but posixAccount has
 | 
        
           |  |  | 438 |                 // no attribute to achive this reliably. So we are going to
 | 
        
           |  |  | 439 |                 // modify the password in a reversable way that we can later
 | 
        
           |  |  | 440 |                 // revert in user_activate().
 | 
        
           |  |  | 441 |                 //
 | 
        
           |  |  | 442 |                 // Beware that this can be defeated by the user if we are not
 | 
        
           |  |  | 443 |                 // using MD5 or SHA-1 passwords. After all, the source code of
 | 
        
           |  |  | 444 |                 // Moodle is available, and the user can see the kind of
 | 
        
           |  |  | 445 |                 // modification we are doing and 'undo' it by hand (but only
 | 
        
           |  |  | 446 |                 // if we are using plain text passwords).
 | 
        
           |  |  | 447 |                 //
 | 
        
           |  |  | 448 |                 // Also bear in mind that you need to use a binding user that
 | 
        
           |  |  | 449 |                 // can create accounts and has read/write privileges on the
 | 
        
           |  |  | 450 |                 // 'userPassword' attribute for this to work.
 | 
        
           |  |  | 451 |   | 
        
           |  |  | 452 |                 $newuser['userPassword']  = '*'.$extpassword;
 | 
        
           |  |  | 453 |                 $uadd = ldap_add($ldapconnection, $this->config->user_attribute.'='.ldap_addslashes($extusername).','.$this->config->create_context, $newuser);
 | 
        
           |  |  | 454 |                 break;
 | 
        
           |  |  | 455 |             case 'ad':
 | 
        
           |  |  | 456 |                 // User account creation is a two step process with AD. First you
 | 
        
           |  |  | 457 |                 // create the user object, then you set the password. If you try
 | 
        
           |  |  | 458 |                 // to set the password while creating the user, the operation
 | 
        
           |  |  | 459 |                 // fails.
 | 
        
           |  |  | 460 |   | 
        
           |  |  | 461 |                 // Passwords in Active Directory must be encoded as Unicode
 | 
        
           |  |  | 462 |                 // strings (UCS-2 Little Endian format) and surrounded with
 | 
        
           |  |  | 463 |                 // double quotes. See http://support.microsoft.com/?kbid=269190
 | 
        
           |  |  | 464 |                 if (!function_exists('mb_convert_encoding')) {
 | 
        
           |  |  | 465 |                     throw new \moodle_exception('auth_ldap_no_mbstring', 'auth_ldap');
 | 
        
           |  |  | 466 |                 }
 | 
        
           |  |  | 467 |   | 
        
           |  |  | 468 |                 // Check for invalid sAMAccountName characters.
 | 
        
           |  |  | 469 |                 if (preg_match('#[/\\[\]:;|=,+*?<>@"]#', $extusername)) {
 | 
        
           |  |  | 470 |                     throw new \moodle_exception ('auth_ldap_ad_invalidchars', 'auth_ldap');
 | 
        
           |  |  | 471 |                 }
 | 
        
           |  |  | 472 |   | 
        
           |  |  | 473 |                 // First create the user account, and mark it as disabled.
 | 
        
           |  |  | 474 |                 $newuser['objectClass'] = array('top', 'person', 'user', 'organizationalPerson');
 | 
        
           |  |  | 475 |                 $newuser['sAMAccountName'] = $extusername;
 | 
        
           |  |  | 476 |                 $newuser['userAccountControl'] = AUTH_AD_NORMAL_ACCOUNT |
 | 
        
           |  |  | 477 |                                                  AUTH_AD_ACCOUNTDISABLE;
 | 
        
           |  |  | 478 |                 $userdn = 'cn='.ldap_addslashes($extusername).','.$this->config->create_context;
 | 
        
           |  |  | 479 |                 if (!ldap_add($ldapconnection, $userdn, $newuser)) {
 | 
        
           |  |  | 480 |                     throw new \moodle_exception('auth_ldap_ad_create_req', 'auth_ldap');
 | 
        
           |  |  | 481 |                 }
 | 
        
           |  |  | 482 |   | 
        
           |  |  | 483 |                 // Now set the password
 | 
        
           |  |  | 484 |                 unset($newuser);
 | 
        
           |  |  | 485 |                 $newuser['unicodePwd'] = mb_convert_encoding('"' . $extpassword . '"',
 | 
        
           |  |  | 486 |                                                              'UCS-2LE', 'UTF-8');
 | 
        
           |  |  | 487 |                 if(!ldap_modify($ldapconnection, $userdn, $newuser)) {
 | 
        
           |  |  | 488 |                     // Something went wrong: delete the user account and error out
 | 
        
           |  |  | 489 |                     ldap_delete ($ldapconnection, $userdn);
 | 
        
           |  |  | 490 |                     throw new \moodle_exception('auth_ldap_ad_create_req', 'auth_ldap');
 | 
        
           |  |  | 491 |                 }
 | 
        
           |  |  | 492 |                 $uadd = true;
 | 
        
           |  |  | 493 |                 break;
 | 
        
           |  |  | 494 |             default:
 | 
        
           |  |  | 495 |                throw new \moodle_exception('auth_ldap_unsupportedusertype', 'auth_ldap', '', $this->config->user_type_name);
 | 
        
           |  |  | 496 |         }
 | 
        
           |  |  | 497 |         $this->ldap_close();
 | 
        
           |  |  | 498 |         return $uadd;
 | 
        
           |  |  | 499 |     }
 | 
        
           |  |  | 500 |   | 
        
           |  |  | 501 |     /**
 | 
        
           |  |  | 502 |      * Returns true if plugin allows resetting of password from moodle.
 | 
        
           |  |  | 503 |      *
 | 
        
           |  |  | 504 |      * @return bool
 | 
        
           |  |  | 505 |      */
 | 
        
           |  |  | 506 |     function can_reset_password() {
 | 
        
           |  |  | 507 |         return !empty($this->config->stdchangepassword);
 | 
        
           |  |  | 508 |     }
 | 
        
           |  |  | 509 |   | 
        
           |  |  | 510 |     /**
 | 
        
           |  |  | 511 |      * Returns true if plugin can be manually set.
 | 
        
           |  |  | 512 |      *
 | 
        
           |  |  | 513 |      * @return bool
 | 
        
           |  |  | 514 |      */
 | 
        
           |  |  | 515 |     function can_be_manually_set() {
 | 
        
           |  |  | 516 |         return true;
 | 
        
           |  |  | 517 |     }
 | 
        
           |  |  | 518 |   | 
        
           |  |  | 519 |     /**
 | 
        
           |  |  | 520 |      * Returns true if plugin allows signup and user creation.
 | 
        
           |  |  | 521 |      *
 | 
        
           |  |  | 522 |      * @return bool
 | 
        
           |  |  | 523 |      */
 | 
        
           |  |  | 524 |     function can_signup() {
 | 
        
           |  |  | 525 |         return (!empty($this->config->auth_user_create) and !empty($this->config->create_context));
 | 
        
           |  |  | 526 |     }
 | 
        
           |  |  | 527 |   | 
        
           |  |  | 528 |     /**
 | 
        
           |  |  | 529 |      * Sign up a new user ready for confirmation.
 | 
        
           |  |  | 530 |      * Password is passed in plaintext.
 | 
        
           |  |  | 531 |      *
 | 
        
           |  |  | 532 |      * @param object $user new user object
 | 
        
           |  |  | 533 |      * @param boolean $notify print notice with link and terminate
 | 
        
           |  |  | 534 |      * @return boolean success
 | 
        
           |  |  | 535 |      */
 | 
        
           |  |  | 536 |     function user_signup($user, $notify=true) {
 | 
        
           |  |  | 537 |         global $CFG, $DB, $PAGE, $OUTPUT;
 | 
        
           |  |  | 538 |   | 
        
           |  |  | 539 |         require_once($CFG->dirroot.'/user/profile/lib.php');
 | 
        
           |  |  | 540 |         require_once($CFG->dirroot.'/user/lib.php');
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 |         if ($this->user_exists($user->username)) {
 | 
        
           |  |  | 543 |             throw new \moodle_exception('auth_ldap_user_exists', 'auth_ldap');
 | 
        
           |  |  | 544 |         }
 | 
        
           |  |  | 545 |   | 
        
           |  |  | 546 |         $plainslashedpassword = $user->password;
 | 
        
           |  |  | 547 |         unset($user->password);
 | 
        
           |  |  | 548 |   | 
        
           |  |  | 549 |         if (! $this->user_create($user, $plainslashedpassword)) {
 | 
        
           |  |  | 550 |             throw new \moodle_exception('auth_ldap_create_error', 'auth_ldap');
 | 
        
           |  |  | 551 |         }
 | 
        
           |  |  | 552 |   | 
        
           |  |  | 553 |         $user->id = user_create_user($user, false, false);
 | 
        
           |  |  | 554 |   | 
        
           |  |  | 555 |         user_add_password_history($user->id, $plainslashedpassword);
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 |         // Save any custom profile field information
 | 
        
           |  |  | 558 |         profile_save_data($user);
 | 
        
           |  |  | 559 |   | 
        
           |  |  | 560 |         $userinfo = $this->get_userinfo($user->username);
 | 
        
           |  |  | 561 |         $this->update_user_record($user->username, false, false, $this->is_user_suspended((object) $userinfo));
 | 
        
           |  |  | 562 |   | 
        
           |  |  | 563 |         // This will also update the stored hash to the latest algorithm
 | 
        
           |  |  | 564 |         // if the existing hash is using an out-of-date algorithm (or the
 | 
        
           |  |  | 565 |         // legacy md5 algorithm).
 | 
        
           |  |  | 566 |         update_internal_user_password($user, $plainslashedpassword);
 | 
        
           |  |  | 567 |   | 
        
           |  |  | 568 |         $user = $DB->get_record('user', array('id'=>$user->id));
 | 
        
           |  |  | 569 |   | 
        
           |  |  | 570 |         \core\event\user_created::create_from_userid($user->id)->trigger();
 | 
        
           |  |  | 571 |   | 
        
           |  |  | 572 |         if (! send_confirmation_email($user)) {
 | 
        
           |  |  | 573 |             throw new \moodle_exception('noemail', 'auth_ldap');
 | 
        
           |  |  | 574 |         }
 | 
        
           |  |  | 575 |   | 
        
           |  |  | 576 |         if ($notify) {
 | 
        
           |  |  | 577 |             $emailconfirm = get_string('emailconfirm');
 | 
        
           |  |  | 578 |             $PAGE->set_url('/auth/ldap/auth.php');
 | 
        
           |  |  | 579 |             $PAGE->navbar->add($emailconfirm);
 | 
        
           |  |  | 580 |             $PAGE->set_title($emailconfirm);
 | 
        
           |  |  | 581 |             $PAGE->set_heading($emailconfirm);
 | 
        
           |  |  | 582 |             echo $OUTPUT->header();
 | 
        
           |  |  | 583 |             notice(get_string('emailconfirmsent', '', $user->email), "{$CFG->wwwroot}/index.php");
 | 
        
           |  |  | 584 |         } else {
 | 
        
           |  |  | 585 |             return true;
 | 
        
           |  |  | 586 |         }
 | 
        
           |  |  | 587 |     }
 | 
        
           |  |  | 588 |   | 
        
           |  |  | 589 |     /**
 | 
        
           |  |  | 590 |      * Returns true if plugin allows confirming of new users.
 | 
        
           |  |  | 591 |      *
 | 
        
           |  |  | 592 |      * @return bool
 | 
        
           |  |  | 593 |      */
 | 
        
           |  |  | 594 |     function can_confirm() {
 | 
        
           |  |  | 595 |         return $this->can_signup();
 | 
        
           |  |  | 596 |     }
 | 
        
           |  |  | 597 |   | 
        
           |  |  | 598 |     /**
 | 
        
           |  |  | 599 |      * Confirm the new user as registered.
 | 
        
           |  |  | 600 |      *
 | 
        
           |  |  | 601 |      * @param string $username
 | 
        
           |  |  | 602 |      * @param string $confirmsecret
 | 
        
           |  |  | 603 |      */
 | 
        
           |  |  | 604 |     function user_confirm($username, $confirmsecret) {
 | 
        
           |  |  | 605 |         global $DB;
 | 
        
           |  |  | 606 |   | 
        
           |  |  | 607 |         $user = get_complete_user_data('username', $username);
 | 
        
           |  |  | 608 |   | 
        
           |  |  | 609 |         if (!empty($user)) {
 | 
        
           |  |  | 610 |             if ($user->auth != $this->authtype) {
 | 
        
           |  |  | 611 |                 return AUTH_CONFIRM_ERROR;
 | 
        
           |  |  | 612 |   | 
        
           |  |  | 613 |             } else if ($user->secret === $confirmsecret && $user->confirmed) {
 | 
        
           |  |  | 614 |                 return AUTH_CONFIRM_ALREADY;
 | 
        
           |  |  | 615 |   | 
        
           |  |  | 616 |             } else if ($user->secret === $confirmsecret) {   // They have provided the secret key to get in
 | 
        
           |  |  | 617 |                 if (!$this->user_activate($username)) {
 | 
        
           |  |  | 618 |                     return AUTH_CONFIRM_FAIL;
 | 
        
           |  |  | 619 |                 }
 | 
        
           |  |  | 620 |                 $user->confirmed = 1;
 | 
        
           |  |  | 621 |                 user_update_user($user, false);
 | 
        
           |  |  | 622 |                 return AUTH_CONFIRM_OK;
 | 
        
           |  |  | 623 |             }
 | 
        
           |  |  | 624 |         } else {
 | 
        
           |  |  | 625 |             return AUTH_CONFIRM_ERROR;
 | 
        
           |  |  | 626 |         }
 | 
        
           |  |  | 627 |     }
 | 
        
           |  |  | 628 |   | 
        
           |  |  | 629 |     /**
 | 
        
           |  |  | 630 |      * Return number of days to user password expires
 | 
        
           |  |  | 631 |      *
 | 
        
           |  |  | 632 |      * If userpassword does not expire it should return 0. If password is already expired
 | 
        
           |  |  | 633 |      * it should return negative value.
 | 
        
           |  |  | 634 |      *
 | 
        
           |  |  | 635 |      * @param mixed $username username
 | 
        
           |  |  | 636 |      * @return integer
 | 
        
           |  |  | 637 |      */
 | 
        
           |  |  | 638 |     function password_expire($username) {
 | 
        
           |  |  | 639 |         $result = 0;
 | 
        
           |  |  | 640 |   | 
        
           |  |  | 641 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 642 |   | 
        
           |  |  | 643 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 644 |         $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
 | 
        
           |  |  | 645 |         $search_attribs = array($this->config->expireattr);
 | 
        
           |  |  | 646 |         $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
 | 
        
           |  |  | 647 |         if ($sr)  {
 | 
        
           |  |  | 648 |             $info = ldap_get_entries_moodle($ldapconnection, $sr);
 | 
        
           |  |  | 649 |             if (!empty ($info)) {
 | 
        
           |  |  | 650 |                 $info = $info[0];
 | 
        
           |  |  | 651 |                 if (isset($info[$this->config->expireattr][0])) {
 | 
        
           |  |  | 652 |                     $expiretime = $this->ldap_expirationtime2unix($info[$this->config->expireattr][0], $ldapconnection, $user_dn);
 | 
        
           |  |  | 653 |                     if ($expiretime != 0) {
 | 
        
           |  |  | 654 |                         $now = time();
 | 
        
           |  |  | 655 |                         if ($expiretime > $now) {
 | 
        
           |  |  | 656 |                             $result = ceil(($expiretime - $now) / DAYSECS);
 | 
        
           |  |  | 657 |                         } else {
 | 
        
           |  |  | 658 |                             $result = floor(($expiretime - $now) / DAYSECS);
 | 
        
           |  |  | 659 |                         }
 | 
        
           |  |  | 660 |                     }
 | 
        
           |  |  | 661 |                 }
 | 
        
           |  |  | 662 |             }
 | 
        
           |  |  | 663 |         } else {
 | 
        
           |  |  | 664 |             error_log($this->errorlogtag.get_string('didtfindexpiretime', 'auth_ldap'));
 | 
        
           |  |  | 665 |         }
 | 
        
           |  |  | 666 |   | 
        
           |  |  | 667 |         return $result;
 | 
        
           |  |  | 668 |     }
 | 
        
           |  |  | 669 |   | 
        
           |  |  | 670 |     /**
 | 
        
           |  |  | 671 |      * Synchronise users from the external LDAP server to Moodle's user table.
 | 
        
           |  |  | 672 |      *
 | 
        
           |  |  | 673 |      * Calls sync_users_update_callback() with default callback if appropriate.
 | 
        
           |  |  | 674 |      *
 | 
        
           |  |  | 675 |      * @param bool $doupdates will do pull in data updates from LDAP if relevant
 | 
        
           |  |  | 676 |      * @return bool success
 | 
        
           |  |  | 677 |      */
 | 
        
           |  |  | 678 |     public function sync_users($doupdates = true) {
 | 
        
           |  |  | 679 |         return $this->sync_users_update_callback($doupdates ? [$this, 'update_users'] : null);
 | 
        
           |  |  | 680 |     }
 | 
        
           |  |  | 681 |   | 
        
           |  |  | 682 |     /**
 | 
        
           |  |  | 683 |      * Synchronise users from the external LDAP server to Moodle's user table (callback).
 | 
        
           |  |  | 684 |      *
 | 
        
           |  |  | 685 |      * Sync is now using username attribute.
 | 
        
           |  |  | 686 |      *
 | 
        
           |  |  | 687 |      * Syncing users removes or suspends users that dont exists anymore in external LDAP.
 | 
        
           |  |  | 688 |      * Creates new users and updates coursecreator status of users.
 | 
        
           |  |  | 689 |      *
 | 
        
           |  |  | 690 |      * @param callable|null $updatecallback will do pull in data updates from LDAP if relevant
 | 
        
           |  |  | 691 |      * @return bool success
 | 
        
           |  |  | 692 |      */
 | 
        
           |  |  | 693 |     public function sync_users_update_callback(?callable $updatecallback = null): bool {
 | 
        
           |  |  | 694 |         global $CFG, $DB;
 | 
        
           |  |  | 695 |   | 
        
           |  |  | 696 |         require_once($CFG->dirroot . '/user/profile/lib.php');
 | 
        
           |  |  | 697 |   | 
        
           |  |  | 698 |         print_string('connectingldap', 'auth_ldap');
 | 
        
           |  |  | 699 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 700 |   | 
        
           |  |  | 701 |         $dbman = $DB->get_manager();
 | 
        
           |  |  | 702 |   | 
        
           |  |  | 703 |     /// Define table user to be created
 | 
        
           |  |  | 704 |         $table = new xmldb_table('tmp_extuser');
 | 
        
           |  |  | 705 |         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 | 
        
           |  |  | 706 |         $table->add_field('username', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
 | 
        
           |  |  | 707 |         $table->add_field('mnethostid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
 | 
        
           |  |  | 708 |         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 | 
        
           |  |  | 709 |         $table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username'));
 | 
        
           |  |  | 710 |   | 
        
           |  |  | 711 |         print_string('creatingtemptable', 'auth_ldap', 'tmp_extuser');
 | 
        
           |  |  | 712 |         $dbman->create_temp_table($table);
 | 
        
           |  |  | 713 |   | 
        
           |  |  | 714 |         ////
 | 
        
           |  |  | 715 |         //// get user's list from ldap to sql in a scalable fashion
 | 
        
           |  |  | 716 |         ////
 | 
        
           |  |  | 717 |         // prepare some data we'll need
 | 
        
           |  |  | 718 |         $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')';
 | 
        
           |  |  | 719 |         $servercontrols = array();
 | 
        
           |  |  | 720 |   | 
        
           |  |  | 721 |         $contexts = explode(';', $this->config->contexts);
 | 
        
           |  |  | 722 |   | 
        
           |  |  | 723 |         if (!empty($this->config->create_context)) {
 | 
        
           |  |  | 724 |             array_push($contexts, $this->config->create_context);
 | 
        
           |  |  | 725 |         }
 | 
        
           |  |  | 726 |   | 
        
           |  |  | 727 |         $ldappagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection);
 | 
        
           |  |  | 728 |         $ldapcookie = '';
 | 
        
           |  |  | 729 |         foreach ($contexts as $context) {
 | 
        
           |  |  | 730 |             $context = trim($context);
 | 
        
           |  |  | 731 |             if (empty($context)) {
 | 
        
           |  |  | 732 |                 continue;
 | 
        
           |  |  | 733 |             }
 | 
        
           |  |  | 734 |   | 
        
           |  |  | 735 |             do {
 | 
        
           |  |  | 736 |                 if ($ldappagedresults) {
 | 
        
           |  |  | 737 |                     $servercontrols = array(array(
 | 
        
           |  |  | 738 |                         'oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => array(
 | 
        
           |  |  | 739 |                             'size' => $this->config->pagesize, 'cookie' => $ldapcookie)));
 | 
        
           |  |  | 740 |                 }
 | 
        
           |  |  | 741 |                 if ($this->config->search_sub) {
 | 
        
           |  |  | 742 |                     // Use ldap_search to find first user from subtree.
 | 
        
           |  |  | 743 |                     $ldapresult = ldap_search($ldapconnection, $context, $filter, array($this->config->user_attribute),
 | 
        
           |  |  | 744 |                         0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
 | 
        
           |  |  | 745 |                 } else {
 | 
        
           |  |  | 746 |                     // Search only in this context.
 | 
        
           |  |  | 747 |                     $ldapresult = ldap_list($ldapconnection, $context, $filter, array($this->config->user_attribute),
 | 
        
           |  |  | 748 |                         0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
 | 
        
           |  |  | 749 |                 }
 | 
        
           |  |  | 750 |                 if (!$ldapresult) {
 | 
        
           |  |  | 751 |                     continue;
 | 
        
           |  |  | 752 |                 }
 | 
        
           |  |  | 753 |                 if ($ldappagedresults) {
 | 
        
           |  |  | 754 |                     // Get next server cookie to know if we'll need to continue searching.
 | 
        
           |  |  | 755 |                     $ldapcookie = '';
 | 
        
           |  |  | 756 |                     // Get next cookie from controls.
 | 
        
           |  |  | 757 |                     ldap_parse_result($ldapconnection, $ldapresult, $errcode, $matcheddn,
 | 
        
           |  |  | 758 |                         $errmsg, $referrals, $controls);
 | 
        
           |  |  | 759 |                     if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
 | 
        
           |  |  | 760 |                         $ldapcookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
 | 
        
           |  |  | 761 |                     }
 | 
        
           |  |  | 762 |                 }
 | 
        
           |  |  | 763 |                 if ($entry = @ldap_first_entry($ldapconnection, $ldapresult)) {
 | 
        
           |  |  | 764 |                     do {
 | 
        
           |  |  | 765 |                         $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute);
 | 
        
           |  |  | 766 |                         $value = core_text::convert($value[0], $this->config->ldapencoding, 'utf-8');
 | 
        
           |  |  | 767 |                         $value = trim($value);
 | 
        
           |  |  | 768 |                         $this->ldap_bulk_insert($value);
 | 
        
           |  |  | 769 |                     } while ($entry = ldap_next_entry($ldapconnection, $entry));
 | 
        
           |  |  | 770 |                 }
 | 
        
           |  |  | 771 |                 unset($ldapresult); // Free mem.
 | 
        
           |  |  | 772 |             } while ($ldappagedresults && $ldapcookie !== null && $ldapcookie != '');
 | 
        
           |  |  | 773 |         }
 | 
        
           |  |  | 774 |   | 
        
           |  |  | 775 |         // If LDAP paged results were used, the current connection must be completely
 | 
        
           |  |  | 776 |         // closed and a new one created, to work without paged results from here on.
 | 
        
           |  |  | 777 |         if ($ldappagedresults) {
 | 
        
           |  |  | 778 |             $this->ldap_close(true);
 | 
        
           |  |  | 779 |             $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 780 |         }
 | 
        
           |  |  | 781 |   | 
        
           |  |  | 782 |         /// preserve our user database
 | 
        
           |  |  | 783 |         /// if the temp table is empty, it probably means that something went wrong, exit
 | 
        
           |  |  | 784 |         /// so as to avoid mass deletion of users; which is hard to undo
 | 
        
           |  |  | 785 |         $count = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}');
 | 
        
           |  |  | 786 |         if ($count < 1) {
 | 
        
           |  |  | 787 |             print_string('didntgetusersfromldap', 'auth_ldap');
 | 
        
           |  |  | 788 |             $dbman->drop_table($table);
 | 
        
           |  |  | 789 |             $this->ldap_close();
 | 
        
           |  |  | 790 |             return false;
 | 
        
           |  |  | 791 |         } else {
 | 
        
           |  |  | 792 |             print_string('gotcountrecordsfromldap', 'auth_ldap', $count);
 | 
        
           |  |  | 793 |         }
 | 
        
           |  |  | 794 |   | 
        
           |  |  | 795 |   | 
        
           |  |  | 796 | /// User removal
 | 
        
           |  |  | 797 |         // Find users in DB that aren't in ldap -- to be removed!
 | 
        
           |  |  | 798 |         // this is still not as scalable (but how often do we mass delete?)
 | 
        
           |  |  | 799 |   | 
        
           |  |  | 800 |         if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
 | 
        
           |  |  | 801 |             $sql = "SELECT u.*
 | 
        
           |  |  | 802 |                       FROM {user} u
 | 
        
           |  |  | 803 |                  LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
 | 
        
           |  |  | 804 |                      WHERE u.auth = :auth
 | 
        
           |  |  | 805 |                            AND u.deleted = 0
 | 
        
           |  |  | 806 |                            AND e.username IS NULL";
 | 
        
           |  |  | 807 |             $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));
 | 
        
           |  |  | 808 |   | 
        
           |  |  | 809 |             if (!empty($remove_users)) {
 | 
        
           |  |  | 810 |                 print_string('userentriestoremove', 'auth_ldap', count($remove_users));
 | 
        
           |  |  | 811 |                 foreach ($remove_users as $user) {
 | 
        
           |  |  | 812 |                     if (delete_user($user)) {
 | 
        
           |  |  | 813 |                         echo "\t"; print_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
 | 
        
           |  |  | 814 |                     } else {
 | 
        
           |  |  | 815 |                         echo "\t"; print_string('auth_dbdeleteusererror', 'auth_db', $user->username); echo "\n";
 | 
        
           |  |  | 816 |                     }
 | 
        
           |  |  | 817 |                 }
 | 
        
           |  |  | 818 |             } else {
 | 
        
           |  |  | 819 |                 print_string('nouserentriestoremove', 'auth_ldap');
 | 
        
           |  |  | 820 |             }
 | 
        
           |  |  | 821 |             unset($remove_users); // Free mem!
 | 
        
           |  |  | 822 |   | 
        
           |  |  | 823 |         } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
 | 
        
           |  |  | 824 |             $sql = "SELECT u.*
 | 
        
           |  |  | 825 |                       FROM {user} u
 | 
        
           |  |  | 826 |                  LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
 | 
        
           |  |  | 827 |                      WHERE u.auth = :auth
 | 
        
           |  |  | 828 |                            AND u.deleted = 0
 | 
        
           |  |  | 829 |                            AND u.suspended = 0
 | 
        
           |  |  | 830 |                            AND e.username IS NULL";
 | 
        
           |  |  | 831 |             $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));
 | 
        
           |  |  | 832 |   | 
        
           |  |  | 833 |             if (!empty($remove_users)) {
 | 
        
           |  |  | 834 |                 print_string('userentriestoremove', 'auth_ldap', count($remove_users));
 | 
        
           |  |  | 835 |   | 
        
           |  |  | 836 |                 foreach ($remove_users as $user) {
 | 
        
           |  |  | 837 |                     $updateuser = new stdClass();
 | 
        
           |  |  | 838 |                     $updateuser->id = $user->id;
 | 
        
           |  |  | 839 |                     $updateuser->suspended = 1;
 | 
        
           |  |  | 840 |                     user_update_user($updateuser, false);
 | 
        
           |  |  | 841 |                     echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
 | 
        
           |  |  | 842 |                     \core\session\manager::kill_user_sessions($user->id);
 | 
        
           |  |  | 843 |                 }
 | 
        
           |  |  | 844 |             } else {
 | 
        
           |  |  | 845 |                 print_string('nouserentriestoremove', 'auth_ldap');
 | 
        
           |  |  | 846 |             }
 | 
        
           |  |  | 847 |             unset($remove_users); // Free mem!
 | 
        
           |  |  | 848 |         }
 | 
        
           |  |  | 849 |   | 
        
           |  |  | 850 | /// Revive suspended users
 | 
        
           |  |  | 851 |         if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
 | 
        
           |  |  | 852 |             $sql = "SELECT u.id, u.username
 | 
        
           |  |  | 853 |                       FROM {user} u
 | 
        
           |  |  | 854 |                       JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
 | 
        
           |  |  | 855 |                      WHERE (u.auth = 'nologin' OR (u.auth = ? AND u.suspended = 1)) AND u.deleted = 0";
 | 
        
           |  |  | 856 |             // Note: 'nologin' is there for backwards compatibility.
 | 
        
           |  |  | 857 |             $revive_users = $DB->get_records_sql($sql, array($this->authtype));
 | 
        
           |  |  | 858 |   | 
        
           |  |  | 859 |             if (!empty($revive_users)) {
 | 
        
           |  |  | 860 |                 print_string('userentriestorevive', 'auth_ldap', count($revive_users));
 | 
        
           |  |  | 861 |   | 
        
           |  |  | 862 |                 foreach ($revive_users as $user) {
 | 
        
           |  |  | 863 |                     $updateuser = new stdClass();
 | 
        
           |  |  | 864 |                     $updateuser->id = $user->id;
 | 
        
           |  |  | 865 |                     $updateuser->auth = $this->authtype;
 | 
        
           |  |  | 866 |                     $updateuser->suspended = 0;
 | 
        
           |  |  | 867 |                     user_update_user($updateuser, false);
 | 
        
           |  |  | 868 |                     echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n";
 | 
        
           |  |  | 869 |                 }
 | 
        
           |  |  | 870 |             } else {
 | 
        
           |  |  | 871 |                 print_string('nouserentriestorevive', 'auth_ldap');
 | 
        
           |  |  | 872 |             }
 | 
        
           |  |  | 873 |   | 
        
           |  |  | 874 |             unset($revive_users);
 | 
        
           |  |  | 875 |         }
 | 
        
           |  |  | 876 |   | 
        
           |  |  | 877 | /// User Updates - time-consuming (optional)
 | 
        
           |  |  | 878 |         if ($updatecallback && $updatekeys = $this->get_profile_keys()) { // Run updates only if relevant.
 | 
        
           |  |  | 879 |             $users = $DB->get_records_sql('SELECT u.username, u.id
 | 
        
           |  |  | 880 |                                              FROM {user} u
 | 
        
           |  |  | 881 |                                             WHERE u.deleted = 0 AND u.auth = ? AND u.mnethostid = ?',
 | 
        
           |  |  | 882 |                                           array($this->authtype, $CFG->mnet_localhost_id));
 | 
        
           |  |  | 883 |             if (!empty($users)) {
 | 
        
           |  |  | 884 |                 // Update users in chunks as specified in sync_updateuserchunk.
 | 
        
           |  |  | 885 |                 if (!empty($this->config->sync_updateuserchunk)) {
 | 
        
           |  |  | 886 |                     foreach (array_chunk($users, $this->config->sync_updateuserchunk) as $chunk) {
 | 
        
           |  |  | 887 |                         call_user_func($updatecallback, $chunk, $updatekeys);
 | 
        
           |  |  | 888 |                     }
 | 
        
           |  |  | 889 |                 } else {
 | 
        
           |  |  | 890 |                     call_user_func($updatecallback, $users, $updatekeys);
 | 
        
           |  |  | 891 |                 }
 | 
        
           |  |  | 892 |                 unset($users); // Free mem.
 | 
        
           |  |  | 893 |             }
 | 
        
           |  |  | 894 |         } else {
 | 
        
           |  |  | 895 |             print_string('noupdatestobedone', 'auth_ldap');
 | 
        
           |  |  | 896 |         }
 | 
        
           |  |  | 897 |   | 
        
           |  |  | 898 | /// User Additions
 | 
        
           |  |  | 899 |         // Find users missing in DB that are in LDAP
 | 
        
           |  |  | 900 |         // and gives me a nifty object I don't want.
 | 
        
           |  |  | 901 |         // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin
 | 
        
           |  |  | 902 |         $sql = 'SELECT e.id, e.username
 | 
        
           |  |  | 903 |                   FROM {tmp_extuser} e
 | 
        
           |  |  | 904 |                   LEFT JOIN {user} u ON (e.username = u.username AND e.mnethostid = u.mnethostid)
 | 
        
           |  |  | 905 |                  WHERE u.id IS NULL';
 | 
        
           |  |  | 906 |         $add_users = $DB->get_records_sql($sql);
 | 
        
           |  |  | 907 |   | 
        
           |  |  | 908 |         if (!empty($add_users)) {
 | 
        
           |  |  | 909 |             print_string('userentriestoadd', 'auth_ldap', count($add_users));
 | 
        
           |  |  | 910 |             $errors = 0;
 | 
        
           |  |  | 911 |   | 
        
           |  |  | 912 |             foreach ($add_users as $user) {
 | 
        
           |  |  | 913 |                 $transaction = $DB->start_delegated_transaction();
 | 
        
           |  |  | 914 |                 $user = $this->get_userinfo_asobj($user->username);
 | 
        
           |  |  | 915 |   | 
        
           |  |  | 916 |                 // Prep a few params
 | 
        
           |  |  | 917 |                 $user->modified   = time();
 | 
        
           |  |  | 918 |                 $user->confirmed  = 1;
 | 
        
           |  |  | 919 |                 $user->auth       = $this->authtype;
 | 
        
           |  |  | 920 |                 $user->mnethostid = $CFG->mnet_localhost_id;
 | 
        
           |  |  | 921 |                 // get_userinfo_asobj() might have replaced $user->username with the value
 | 
        
           |  |  | 922 |                 // from the LDAP server (which can be mixed-case). Make sure it's lowercase
 | 
        
           |  |  | 923 |                 $user->username = trim(core_text::strtolower($user->username));
 | 
        
           |  |  | 924 |                 // It isn't possible to just rely on the configured suspension attribute since
 | 
        
           |  |  | 925 |                 // things like active directory use bit masks, other things using LDAP might
 | 
        
           |  |  | 926 |                 // do different stuff as well.
 | 
        
           |  |  | 927 |                 //
 | 
        
           |  |  | 928 |                 // The cast to int is a workaround for MDL-53959.
 | 
        
           |  |  | 929 |                 $user->suspended = (int)$this->is_user_suspended($user);
 | 
        
           |  |  | 930 |   | 
        
           |  |  | 931 |                 if (empty($user->calendartype)) {
 | 
        
           |  |  | 932 |                     $user->calendartype = $CFG->calendartype;
 | 
        
           |  |  | 933 |                 }
 | 
        
           |  |  | 934 |   | 
        
           |  |  | 935 |                 // $id = user_create_user($user, false);
 | 
        
           |  |  | 936 |                 try {
 | 
        
           |  |  | 937 |                     $id = user_create_user($user, false);
 | 
        
           |  |  | 938 |                 } catch (Exception $e) {
 | 
        
           |  |  | 939 |                     print_string('invaliduserexception', 'auth_ldap', print_r($user, true) .  $e->getMessage());
 | 
        
           |  |  | 940 |                     $errors++;
 | 
        
           |  |  | 941 |                     continue;
 | 
        
           |  |  | 942 |                 }
 | 
        
           |  |  | 943 |                 echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n";
 | 
        
           |  |  | 944 |                 $euser = $DB->get_record('user', array('id' => $id));
 | 
        
           |  |  | 945 |   | 
        
           |  |  | 946 |                 if (!empty($this->config->forcechangepassword)) {
 | 
        
           |  |  | 947 |                     set_user_preference('auth_forcepasswordchange', 1, $id);
 | 
        
           |  |  | 948 |                 }
 | 
        
           |  |  | 949 |   | 
        
           |  |  | 950 |                 // Save custom profile fields.
 | 
        
           |  |  | 951 |                 $this->update_user_record($user->username, $this->get_profile_keys(true), false);
 | 
        
           |  |  | 952 |   | 
        
           |  |  | 953 |                 // Add roles if needed.
 | 
        
           |  |  | 954 |                 $this->sync_roles($euser);
 | 
        
           |  |  | 955 |                 $transaction->allow_commit();
 | 
        
           |  |  | 956 |             }
 | 
        
           |  |  | 957 |   | 
        
           |  |  | 958 |             // Display number of user creation errors, if any.
 | 
        
           |  |  | 959 |             if ($errors) {
 | 
        
           |  |  | 960 |                 print_string('invalidusererrors', 'auth_ldap', $errors);
 | 
        
           |  |  | 961 |             }
 | 
        
           |  |  | 962 |   | 
        
           |  |  | 963 |             unset($add_users); // free mem
 | 
        
           |  |  | 964 |         } else {
 | 
        
           |  |  | 965 |             print_string('nouserstobeadded', 'auth_ldap');
 | 
        
           |  |  | 966 |         }
 | 
        
           |  |  | 967 |   | 
        
           |  |  | 968 |         $dbman->drop_table($table);
 | 
        
           |  |  | 969 |         $this->ldap_close();
 | 
        
           |  |  | 970 |   | 
        
           |  |  | 971 |         return true;
 | 
        
           |  |  | 972 |     }
 | 
        
           |  |  | 973 |   | 
        
           |  |  | 974 |     /**
 | 
        
           |  |  | 975 |      * Update users from the external LDAP server into Moodle's user table.
 | 
        
           |  |  | 976 |      *
 | 
        
           |  |  | 977 |      * Sync helper
 | 
        
           |  |  | 978 |      *
 | 
        
           |  |  | 979 |      * @param array $users chunk of users to update
 | 
        
           |  |  | 980 |      * @param array $updatekeys fields to update
 | 
        
           |  |  | 981 |      */
 | 
        
           |  |  | 982 |     public function update_users(array $users, array $updatekeys): void {
 | 
        
           |  |  | 983 |         global $DB;
 | 
        
           |  |  | 984 |   | 
        
           |  |  | 985 |         print_string('userentriestoupdate', 'auth_ldap', count($users));
 | 
        
           |  |  | 986 |   | 
        
           |  |  | 987 |         foreach ($users as $user) {
 | 
        
           |  |  | 988 |             $transaction = $DB->start_delegated_transaction();
 | 
        
           |  |  | 989 |             echo "\t";
 | 
        
           |  |  | 990 |             print_string('auth_dbupdatinguser', 'auth_db', ['name' => $user->username, 'id' => $user->id]);
 | 
        
           |  |  | 991 |             $userinfo = $this->get_userinfo($user->username);
 | 
        
           |  |  | 992 |             if (!$this->update_user_record($user->username, $updatekeys, true,
 | 
        
           |  |  | 993 |                     $this->is_user_suspended((object) $userinfo))) {
 | 
        
           |  |  | 994 |                 echo ' - '.get_string('skipped');
 | 
        
           |  |  | 995 |             }
 | 
        
           |  |  | 996 |             echo "\n";
 | 
        
           |  |  | 997 |   | 
        
           |  |  | 998 |             // Update system roles, if needed.
 | 
        
           |  |  | 999 |             $this->sync_roles($user);
 | 
        
           |  |  | 1000 |             $transaction->allow_commit();
 | 
        
           |  |  | 1001 |         }
 | 
        
           |  |  | 1002 |     }
 | 
        
           |  |  | 1003 |   | 
        
           |  |  | 1004 |     /**
 | 
        
           |  |  | 1005 |      * Bulk insert in SQL's temp table
 | 
        
           |  |  | 1006 |      */
 | 
        
           |  |  | 1007 |     function ldap_bulk_insert($username) {
 | 
        
           |  |  | 1008 |         global $DB, $CFG;
 | 
        
           |  |  | 1009 |   | 
        
           |  |  | 1010 |         $username = core_text::strtolower($username); // usernames are __always__ lowercase.
 | 
        
           |  |  | 1011 |         $DB->insert_record_raw('tmp_extuser', array('username'=>$username,
 | 
        
           |  |  | 1012 |                                                     'mnethostid'=>$CFG->mnet_localhost_id), false, true);
 | 
        
           |  |  | 1013 |         echo '.';
 | 
        
           |  |  | 1014 |     }
 | 
        
           |  |  | 1015 |   | 
        
           |  |  | 1016 |     /**
 | 
        
           |  |  | 1017 |      * Activates (enables) user in external LDAP so user can login
 | 
        
           |  |  | 1018 |      *
 | 
        
           |  |  | 1019 |      * @param mixed $username
 | 
        
           |  |  | 1020 |      * @return boolean result
 | 
        
           |  |  | 1021 |      */
 | 
        
           |  |  | 1022 |     function user_activate($username) {
 | 
        
           |  |  | 1023 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1024 |   | 
        
           |  |  | 1025 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 1026 |   | 
        
           |  |  | 1027 |         $userdn = $this->ldap_find_userdn($ldapconnection, $extusername);
 | 
        
           |  |  | 1028 |         switch ($this->config->user_type)  {
 | 
        
           |  |  | 1029 |             case 'edir':
 | 
        
           |  |  | 1030 |                 $newinfo['loginDisabled'] = 'FALSE';
 | 
        
           |  |  | 1031 |                 break;
 | 
        
           |  |  | 1032 |             case 'rfc2307':
 | 
        
           |  |  | 1033 |             case 'rfc2307bis':
 | 
        
           |  |  | 1034 |                 // Remember that we add a '*' character in front of the
 | 
        
           |  |  | 1035 |                 // external password string to 'disable' the account. We just
 | 
        
           |  |  | 1036 |                 // need to remove it.
 | 
        
           |  |  | 1037 |                 $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)',
 | 
        
           |  |  | 1038 |                                 array('userPassword'));
 | 
        
           |  |  | 1039 |                 $info = ldap_get_entries($ldapconnection, $sr);
 | 
        
           |  |  | 1040 |                 $info[0] = array_change_key_case($info[0], CASE_LOWER);
 | 
        
           |  |  | 1041 |                 $newinfo['userPassword'] = ltrim($info[0]['userpassword'][0], '*');
 | 
        
           |  |  | 1042 |                 break;
 | 
        
           |  |  | 1043 |             case 'ad':
 | 
        
           |  |  | 1044 |                 // We need to unset the ACCOUNTDISABLE bit in the
 | 
        
           |  |  | 1045 |                 // userAccountControl attribute ( see
 | 
        
           |  |  | 1046 |                 // http://support.microsoft.com/kb/305144 )
 | 
        
           |  |  | 1047 |                 $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)',
 | 
        
           |  |  | 1048 |                                 array('userAccountControl'));
 | 
        
           |  |  | 1049 |                 $info = ldap_get_entries($ldapconnection, $sr);
 | 
        
           |  |  | 1050 |                 $info[0] = array_change_key_case($info[0], CASE_LOWER);
 | 
        
           |  |  | 1051 |                 $newinfo['userAccountControl'] = $info[0]['useraccountcontrol'][0]
 | 
        
           |  |  | 1052 |                                                  & (~AUTH_AD_ACCOUNTDISABLE);
 | 
        
           |  |  | 1053 |                 break;
 | 
        
           |  |  | 1054 |             default:
 | 
        
           |  |  | 1055 |                 throw new \moodle_exception('user_activatenotsupportusertype', 'auth_ldap', '', $this->config->user_type_name);
 | 
        
           |  |  | 1056 |         }
 | 
        
           |  |  | 1057 |         $result = ldap_modify($ldapconnection, $userdn, $newinfo);
 | 
        
           |  |  | 1058 |         $this->ldap_close();
 | 
        
           |  |  | 1059 |         return $result;
 | 
        
           |  |  | 1060 |     }
 | 
        
           |  |  | 1061 |   | 
        
           |  |  | 1062 |     /**
 | 
        
           |  |  | 1063 |      * Returns true if user should be coursecreator.
 | 
        
           |  |  | 1064 |      *
 | 
        
           |  |  | 1065 |      * @param mixed $username    username (without system magic quotes)
 | 
        
           |  |  | 1066 |      * @return mixed result      null if course creators is not configured, boolean otherwise.
 | 
        
           |  |  | 1067 |      *
 | 
        
           |  |  | 1068 |      * @deprecated since Moodle 3.4 MDL-30634 - please do not use this function any more.
 | 
        
           |  |  | 1069 |      */
 | 
        
           |  |  | 1070 |     function iscreator($username) {
 | 
        
           |  |  | 1071 |         debugging('iscreator() is deprecated. Please use auth_plugin_ldap::is_role() instead.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 1072 |   | 
        
           |  |  | 1073 |         if (empty($this->config->creators) or empty($this->config->memberattribute)) {
 | 
        
           |  |  | 1074 |             return null;
 | 
        
           |  |  | 1075 |         }
 | 
        
           |  |  | 1076 |   | 
        
           |  |  | 1077 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1078 |   | 
        
           |  |  | 1079 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 1080 |   | 
        
           |  |  | 1081 |         if ($this->config->memberattribute_isdn) {
 | 
        
           |  |  | 1082 |             if(!($userid = $this->ldap_find_userdn($ldapconnection, $extusername))) {
 | 
        
           |  |  | 1083 |                 return false;
 | 
        
           |  |  | 1084 |             }
 | 
        
           |  |  | 1085 |         } else {
 | 
        
           |  |  | 1086 |             $userid = $extusername;
 | 
        
           |  |  | 1087 |         }
 | 
        
           |  |  | 1088 |   | 
        
           |  |  | 1089 |         $group_dns = explode(';', $this->config->creators);
 | 
        
           |  |  | 1090 |         $creator = ldap_isgroupmember($ldapconnection, $userid, $group_dns, $this->config->memberattribute);
 | 
        
           |  |  | 1091 |   | 
        
           |  |  | 1092 |         $this->ldap_close();
 | 
        
           |  |  | 1093 |   | 
        
           |  |  | 1094 |         return $creator;
 | 
        
           |  |  | 1095 |     }
 | 
        
           |  |  | 1096 |   | 
        
           |  |  | 1097 |     /**
 | 
        
           |  |  | 1098 |      * Check if user has LDAP group membership.
 | 
        
           |  |  | 1099 |      *
 | 
        
           |  |  | 1100 |      * Returns true if user should be assigned role.
 | 
        
           |  |  | 1101 |      *
 | 
        
           |  |  | 1102 |      * @param mixed $username username (without system magic quotes).
 | 
        
           |  |  | 1103 |      * @param array $role Array of role's shortname, localname, and settingname for the config value.
 | 
        
           |  |  | 1104 |      * @return mixed result null if role/LDAP context is not configured, boolean otherwise.
 | 
        
           |  |  | 1105 |      */
 | 
        
           |  |  | 1106 |     private function is_role($username, $role) {
 | 
        
           |  |  | 1107 |         if (empty($this->config->{$role['settingname']}) or empty($this->config->memberattribute)) {
 | 
        
           |  |  | 1108 |             return null;
 | 
        
           |  |  | 1109 |         }
 | 
        
           |  |  | 1110 |   | 
        
           |  |  | 1111 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1112 |   | 
        
           |  |  | 1113 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 1114 |   | 
        
           |  |  | 1115 |         if ($this->config->memberattribute_isdn) {
 | 
        
           |  |  | 1116 |             if (!($userid = $this->ldap_find_userdn($ldapconnection, $extusername))) {
 | 
        
           |  |  | 1117 |                 return false;
 | 
        
           |  |  | 1118 |             }
 | 
        
           |  |  | 1119 |         } else {
 | 
        
           |  |  | 1120 |             $userid = $extusername;
 | 
        
           |  |  | 1121 |         }
 | 
        
           |  |  | 1122 |   | 
        
           |  |  | 1123 |         $groupdns = explode(';', $this->config->{$role['settingname']});
 | 
        
           |  |  | 1124 |         $isrole = ldap_isgroupmember($ldapconnection, $userid, $groupdns, $this->config->memberattribute);
 | 
        
           |  |  | 1125 |   | 
        
           |  |  | 1126 |         $this->ldap_close();
 | 
        
           |  |  | 1127 |   | 
        
           |  |  | 1128 |         return $isrole;
 | 
        
           |  |  | 1129 |     }
 | 
        
           |  |  | 1130 |   | 
        
           |  |  | 1131 |     /**
 | 
        
           |  |  | 1132 |      * Called when the user record is updated.
 | 
        
           |  |  | 1133 |      *
 | 
        
           |  |  | 1134 |      * Modifies user in external LDAP server. It takes olduser (before
 | 
        
           |  |  | 1135 |      * changes) and newuser (after changes) compares information and
 | 
        
           |  |  | 1136 |      * saves modified information to external LDAP server.
 | 
        
           |  |  | 1137 |      *
 | 
        
           |  |  | 1138 |      * @param mixed $olduser     Userobject before modifications    (without system magic quotes)
 | 
        
           |  |  | 1139 |      * @param mixed $newuser     Userobject new modified userobject (without system magic quotes)
 | 
        
           |  |  | 1140 |      * @return boolean result
 | 
        
           |  |  | 1141 |      *
 | 
        
           |  |  | 1142 |      */
 | 
        
           |  |  | 1143 |     function user_update($olduser, $newuser) {
 | 
        
           |  |  | 1144 |         global $CFG;
 | 
        
           |  |  | 1145 |   | 
        
           |  |  | 1146 |         require_once($CFG->dirroot . '/user/profile/lib.php');
 | 
        
           |  |  | 1147 |   | 
        
           |  |  | 1148 |         if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
 | 
        
           |  |  | 1149 |             error_log($this->errorlogtag.get_string('renamingnotallowed', 'auth_ldap'));
 | 
        
           |  |  | 1150 |             return false;
 | 
        
           |  |  | 1151 |         }
 | 
        
           |  |  | 1152 |   | 
        
           |  |  | 1153 |         if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
 | 
        
           |  |  | 1154 |             return true; // just change auth and skip update
 | 
        
           |  |  | 1155 |         }
 | 
        
           |  |  | 1156 |   | 
        
           |  |  | 1157 |         $attrmap = $this->ldap_attributes();
 | 
        
           |  |  | 1158 |         // Before doing anything else, make sure we really need to update anything
 | 
        
           |  |  | 1159 |         // in the external LDAP server.
 | 
        
           |  |  | 1160 |         $update_external = false;
 | 
        
           |  |  | 1161 |         foreach ($attrmap as $key => $ldapkeys) {
 | 
        
           |  |  | 1162 |             if (!empty($this->config->{'field_updateremote_'.$key})) {
 | 
        
           |  |  | 1163 |                 $update_external = true;
 | 
        
           |  |  | 1164 |                 break;
 | 
        
           |  |  | 1165 |             }
 | 
        
           |  |  | 1166 |         }
 | 
        
           |  |  | 1167 |         if (!$update_external) {
 | 
        
           |  |  | 1168 |             return true;
 | 
        
           |  |  | 1169 |         }
 | 
        
           |  |  | 1170 |   | 
        
           |  |  | 1171 |         $extoldusername = core_text::convert($olduser->username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1172 |   | 
        
           |  |  | 1173 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 1174 |   | 
        
           |  |  | 1175 |         $search_attribs = array();
 | 
        
           |  |  | 1176 |         foreach ($attrmap as $key => $values) {
 | 
        
           |  |  | 1177 |             if (!is_array($values)) {
 | 
        
           |  |  | 1178 |                 $values = array($values);
 | 
        
           |  |  | 1179 |             }
 | 
        
           |  |  | 1180 |             foreach ($values as $value) {
 | 
        
           |  |  | 1181 |                 if (!in_array($value, $search_attribs)) {
 | 
        
           |  |  | 1182 |                     array_push($search_attribs, $value);
 | 
        
           |  |  | 1183 |                 }
 | 
        
           |  |  | 1184 |             }
 | 
        
           |  |  | 1185 |         }
 | 
        
           |  |  | 1186 |   | 
        
           |  |  | 1187 |         if(!($user_dn = $this->ldap_find_userdn($ldapconnection, $extoldusername))) {
 | 
        
           |  |  | 1188 |             return false;
 | 
        
           |  |  | 1189 |         }
 | 
        
           |  |  | 1190 |   | 
        
           |  |  | 1191 |         // Load old custom fields.
 | 
        
           |  |  | 1192 |         $olduserprofilefields = (array) profile_user_record($olduser->id, false);
 | 
        
           |  |  | 1193 |   | 
        
           |  |  | 1194 |         $fields = array();
 | 
        
           |  |  | 1195 |         foreach (profile_get_custom_fields(false) as $field) {
 | 
        
           |  |  | 1196 |             $fields[$field->shortname] = $field;
 | 
        
           |  |  | 1197 |         }
 | 
        
           |  |  | 1198 |   | 
        
           |  |  | 1199 |         $success = true;
 | 
        
           |  |  | 1200 |         $user_info_result = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
 | 
        
           |  |  | 1201 |         if ($user_info_result) {
 | 
        
           |  |  | 1202 |             $user_entry = ldap_get_entries_moodle($ldapconnection, $user_info_result);
 | 
        
           |  |  | 1203 |             if (empty($user_entry)) {
 | 
        
           |  |  | 1204 |                 $attribs = join (', ', $search_attribs);
 | 
        
           |  |  | 1205 |                 error_log($this->errorlogtag.get_string('updateusernotfound', 'auth_ldap',
 | 
        
           |  |  | 1206 |                                                           array('userdn'=>$user_dn,
 | 
        
           |  |  | 1207 |                                                                 'attribs'=>$attribs)));
 | 
        
           |  |  | 1208 |                 return false; // old user not found!
 | 
        
           |  |  | 1209 |             } else if (count($user_entry) > 1) {
 | 
        
           |  |  | 1210 |                 error_log($this->errorlogtag.get_string('morethanoneuser', 'auth_ldap'));
 | 
        
           |  |  | 1211 |                 return false;
 | 
        
           |  |  | 1212 |             }
 | 
        
           |  |  | 1213 |   | 
        
           |  |  | 1214 |             $user_entry = $user_entry[0];
 | 
        
           |  |  | 1215 |   | 
        
           |  |  | 1216 |             foreach ($attrmap as $key => $ldapkeys) {
 | 
        
           |  |  | 1217 |                 if (preg_match('/^profile_field_(.*)$/', $key, $match)) {
 | 
        
           |  |  | 1218 |                     // Custom field.
 | 
        
           |  |  | 1219 |                     $fieldname = $match[1];
 | 
        
           |  |  | 1220 |                     if (isset($fields[$fieldname])) {
 | 
        
           |  |  | 1221 |                         $class = 'profile_field_' . $fields[$fieldname]->datatype;
 | 
        
           |  |  | 1222 |                         $formfield = new $class($fields[$fieldname]->id, $olduser->id);
 | 
        
           |  |  | 1223 |                         $oldvalue = isset($olduserprofilefields[$fieldname]) ? $olduserprofilefields[$fieldname] : null;
 | 
        
           |  |  | 1224 |                     } else {
 | 
        
           |  |  | 1225 |                         $oldvalue = null;
 | 
        
           |  |  | 1226 |                     }
 | 
        
           |  |  | 1227 |                     $newvalue = $formfield->edit_save_data_preprocess($newuser->{$formfield->inputname}, new stdClass);
 | 
        
           |  |  | 1228 |                 } else {
 | 
        
           |  |  | 1229 |                     // Standard field.
 | 
        
           |  |  | 1230 |                     $oldvalue = isset($olduser->$key) ? $olduser->$key : null;
 | 
        
           |  |  | 1231 |                     $newvalue = isset($newuser->$key) ? $newuser->$key : null;
 | 
        
           |  |  | 1232 |                 }
 | 
        
           |  |  | 1233 |   | 
        
           |  |  | 1234 |                 if ($newvalue !== null and $newvalue !== $oldvalue and !empty($this->config->{'field_updateremote_' . $key})) {
 | 
        
           |  |  | 1235 |                     // For ldap values that could be in more than one
 | 
        
           |  |  | 1236 |                     // ldap key, we will do our best to match
 | 
        
           |  |  | 1237 |                     // where they came from
 | 
        
           |  |  | 1238 |                     $ambiguous = true;
 | 
        
           |  |  | 1239 |                     $changed   = false;
 | 
        
           |  |  | 1240 |                     if (!is_array($ldapkeys)) {
 | 
        
           |  |  | 1241 |                         $ldapkeys = array($ldapkeys);
 | 
        
           |  |  | 1242 |                     }
 | 
        
           |  |  | 1243 |                     if (count($ldapkeys) < 2) {
 | 
        
           |  |  | 1244 |                         $ambiguous = false;
 | 
        
           |  |  | 1245 |                     }
 | 
        
           |  |  | 1246 |   | 
        
           |  |  | 1247 |                     $nuvalue = core_text::convert($newvalue, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1248 |                     empty($nuvalue) ? $nuvalue = array() : $nuvalue;
 | 
        
           |  |  | 1249 |                     $ouvalue = core_text::convert($oldvalue, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1250 |                     foreach ($ldapkeys as $ldapkey) {
 | 
        
           |  |  | 1251 |                         // If the field is empty in LDAP there are two options:
 | 
        
           |  |  | 1252 |                         // 1. We get the LDAP field using ldap_first_attribute.
 | 
        
           |  |  | 1253 |                         // 2. LDAP don't send the field using  ldap_first_attribute.
 | 
        
           |  |  | 1254 |                         // So, for option 1 we check the if the field is retrieve it.
 | 
        
           |  |  | 1255 |                         // And get the original value of field in LDAP if the field.
 | 
        
           |  |  | 1256 |                         // Otherwise, let value in blank and delegate the check in ldap_modify.
 | 
        
           |  |  | 1257 |                         if (isset($user_entry[$ldapkey][0])) {
 | 
        
           |  |  | 1258 |                             $ldapvalue = $user_entry[$ldapkey][0];
 | 
        
           |  |  | 1259 |                         } else {
 | 
        
           |  |  | 1260 |                             $ldapvalue = '';
 | 
        
           |  |  | 1261 |                         }
 | 
        
           |  |  | 1262 |   | 
        
           |  |  | 1263 |                         if (!$ambiguous) {
 | 
        
           |  |  | 1264 |                             // Skip update if the values already match
 | 
        
           |  |  | 1265 |                             if ($nuvalue !== $ldapvalue) {
 | 
        
           |  |  | 1266 |                                 // This might fail due to schema validation
 | 
        
           |  |  | 1267 |                                 if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
 | 
        
           |  |  | 1268 |                                     $changed = true;
 | 
        
           |  |  | 1269 |                                     continue;
 | 
        
           |  |  | 1270 |                                 } else {
 | 
        
           |  |  | 1271 |                                     $success = false;
 | 
        
           |  |  | 1272 |                                     error_log($this->errorlogtag.get_string ('updateremfail', 'auth_ldap',
 | 
        
           |  |  | 1273 |                                                                              array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1274 |                                                                                    'errstring'=>ldap_err2str(ldap_errno($ldapconnection)),
 | 
        
           |  |  | 1275 |                                                                                    'key'=>$key,
 | 
        
           |  |  | 1276 |                                                                                    'ouvalue'=>$ouvalue,
 | 
        
           |  |  | 1277 |                                                                                    'nuvalue'=>$nuvalue)));
 | 
        
           |  |  | 1278 |                                     continue;
 | 
        
           |  |  | 1279 |                                 }
 | 
        
           |  |  | 1280 |                             }
 | 
        
           |  |  | 1281 |                         } else {
 | 
        
           |  |  | 1282 |                             // Ambiguous. Value empty before in Moodle (and LDAP) - use
 | 
        
           |  |  | 1283 |                             // 1st ldap candidate field, no need to guess
 | 
        
           |  |  | 1284 |                             if ($ouvalue === '') { // value empty before - use 1st ldap candidate
 | 
        
           |  |  | 1285 |                                 // This might fail due to schema validation
 | 
        
           |  |  | 1286 |                                 if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
 | 
        
           |  |  | 1287 |                                     $changed = true;
 | 
        
           |  |  | 1288 |                                     continue;
 | 
        
           |  |  | 1289 |                                 } else {
 | 
        
           |  |  | 1290 |                                     $success = false;
 | 
        
           |  |  | 1291 |                                     error_log($this->errorlogtag.get_string ('updateremfail', 'auth_ldap',
 | 
        
           |  |  | 1292 |                                                                              array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1293 |                                                                                    'errstring'=>ldap_err2str(ldap_errno($ldapconnection)),
 | 
        
           |  |  | 1294 |                                                                                    'key'=>$key,
 | 
        
           |  |  | 1295 |                                                                                    'ouvalue'=>$ouvalue,
 | 
        
           |  |  | 1296 |                                                                                    'nuvalue'=>$nuvalue)));
 | 
        
           |  |  | 1297 |                                     continue;
 | 
        
           |  |  | 1298 |                                 }
 | 
        
           |  |  | 1299 |                             }
 | 
        
           |  |  | 1300 |   | 
        
           |  |  | 1301 |                             // We found which ldap key to update!
 | 
        
           |  |  | 1302 |                             if ($ouvalue !== '' and $ouvalue === $ldapvalue ) {
 | 
        
           |  |  | 1303 |                                 // This might fail due to schema validation
 | 
        
           |  |  | 1304 |                                 if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) {
 | 
        
           |  |  | 1305 |                                     $changed = true;
 | 
        
           |  |  | 1306 |                                     continue;
 | 
        
           |  |  | 1307 |                                 } else {
 | 
        
           |  |  | 1308 |                                     $success = false;
 | 
        
           |  |  | 1309 |                                     error_log($this->errorlogtag.get_string ('updateremfail', 'auth_ldap',
 | 
        
           |  |  | 1310 |                                                                              array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1311 |                                                                                    'errstring'=>ldap_err2str(ldap_errno($ldapconnection)),
 | 
        
           |  |  | 1312 |                                                                                    'key'=>$key,
 | 
        
           |  |  | 1313 |                                                                                    'ouvalue'=>$ouvalue,
 | 
        
           |  |  | 1314 |                                                                                    'nuvalue'=>$nuvalue)));
 | 
        
           |  |  | 1315 |                                     continue;
 | 
        
           |  |  | 1316 |                                 }
 | 
        
           |  |  | 1317 |                             }
 | 
        
           |  |  | 1318 |                         }
 | 
        
           |  |  | 1319 |                     }
 | 
        
           |  |  | 1320 |   | 
        
           |  |  | 1321 |                     if ($ambiguous and !$changed) {
 | 
        
           |  |  | 1322 |                         $success = false;
 | 
        
           |  |  | 1323 |                         error_log($this->errorlogtag.get_string ('updateremfailamb', 'auth_ldap',
 | 
        
           |  |  | 1324 |                                                                  array('key'=>$key,
 | 
        
           |  |  | 1325 |                                                                        'ouvalue'=>$ouvalue,
 | 
        
           |  |  | 1326 |                                                                        'nuvalue'=>$nuvalue)));
 | 
        
           |  |  | 1327 |                     }
 | 
        
           |  |  | 1328 |                 }
 | 
        
           |  |  | 1329 |             }
 | 
        
           |  |  | 1330 |         } else {
 | 
        
           |  |  | 1331 |             error_log($this->errorlogtag.get_string ('usernotfound', 'auth_ldap'));
 | 
        
           |  |  | 1332 |             $success = false;
 | 
        
           |  |  | 1333 |         }
 | 
        
           |  |  | 1334 |   | 
        
           |  |  | 1335 |         $this->ldap_close();
 | 
        
           |  |  | 1336 |         return $success;
 | 
        
           |  |  | 1337 |   | 
        
           |  |  | 1338 |     }
 | 
        
           |  |  | 1339 |   | 
        
           |  |  | 1340 |     /**
 | 
        
           |  |  | 1341 |      * Changes userpassword in LDAP
 | 
        
           |  |  | 1342 |      *
 | 
        
           |  |  | 1343 |      * Called when the user password is updated. It assumes it is
 | 
        
           |  |  | 1344 |      * called by an admin or that you've otherwise checked the user's
 | 
        
           |  |  | 1345 |      * credentials
 | 
        
           |  |  | 1346 |      *
 | 
        
           |  |  | 1347 |      * @param  object  $user        User table object
 | 
        
           |  |  | 1348 |      * @param  string  $newpassword Plaintext password (not crypted/md5'ed)
 | 
        
           |  |  | 1349 |      * @return boolean result
 | 
        
           |  |  | 1350 |      *
 | 
        
           |  |  | 1351 |      */
 | 
        
           |  |  | 1352 |     function user_update_password($user, $newpassword) {
 | 
        
           |  |  | 1353 |         global $USER;
 | 
        
           |  |  | 1354 |   | 
        
           |  |  | 1355 |         $result = false;
 | 
        
           |  |  | 1356 |         $username = $user->username;
 | 
        
           |  |  | 1357 |   | 
        
           |  |  | 1358 |         $extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1359 |         $extpassword = core_text::convert($newpassword, 'utf-8', $this->config->ldapencoding);
 | 
        
           |  |  | 1360 |   | 
        
           |  |  | 1361 |         switch ($this->config->passtype) {
 | 
        
           |  |  | 1362 |             case 'md5':
 | 
        
           |  |  | 1363 |                 $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword)));
 | 
        
           |  |  | 1364 |                 break;
 | 
        
           |  |  | 1365 |             case 'sha1':
 | 
        
           |  |  | 1366 |                 $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword)));
 | 
        
           |  |  | 1367 |                 break;
 | 
        
           |  |  | 1368 |             case 'plaintext':
 | 
        
           |  |  | 1369 |             default:
 | 
        
           |  |  | 1370 |                 break; // plaintext
 | 
        
           |  |  | 1371 |         }
 | 
        
           |  |  | 1372 |   | 
        
           |  |  | 1373 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 1374 |   | 
        
           |  |  | 1375 |         $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
 | 
        
           |  |  | 1376 |   | 
        
           |  |  | 1377 |         if (!$user_dn) {
 | 
        
           |  |  | 1378 |             error_log($this->errorlogtag.get_string ('nodnforusername', 'auth_ldap', $user->username));
 | 
        
           |  |  | 1379 |             return false;
 | 
        
           |  |  | 1380 |         }
 | 
        
           |  |  | 1381 |   | 
        
           |  |  | 1382 |         switch ($this->config->user_type) {
 | 
        
           |  |  | 1383 |             case 'edir':
 | 
        
           |  |  | 1384 |                 // Change password
 | 
        
           |  |  | 1385 |                 $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $extpassword));
 | 
        
           |  |  | 1386 |                 if (!$result) {
 | 
        
           |  |  | 1387 |                     error_log($this->errorlogtag.get_string ('updatepasserror', 'auth_ldap',
 | 
        
           |  |  | 1388 |                                                                array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1389 |                                                                      'errstring'=>ldap_err2str(ldap_errno($ldapconnection)))));
 | 
        
           |  |  | 1390 |                 }
 | 
        
           |  |  | 1391 |                 // Update password expiration time, grace logins count
 | 
        
           |  |  | 1392 |                 $search_attribs = array($this->config->expireattr, 'passwordExpirationInterval', 'loginGraceLimit');
 | 
        
           |  |  | 1393 |                 $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs);
 | 
        
           |  |  | 1394 |                 if ($sr) {
 | 
        
           |  |  | 1395 |                     $entry = ldap_get_entries_moodle($ldapconnection, $sr);
 | 
        
           |  |  | 1396 |                     $info = $entry[0];
 | 
        
           |  |  | 1397 |                     $newattrs = array();
 | 
        
           |  |  | 1398 |                     if (!empty($info[$this->config->expireattr][0])) {
 | 
        
           |  |  | 1399 |                         // Set expiration time only if passwordExpirationInterval is defined
 | 
        
           |  |  | 1400 |                         if (!empty($info['passwordexpirationinterval'][0])) {
 | 
        
           |  |  | 1401 |                            $expirationtime = time() + $info['passwordexpirationinterval'][0];
 | 
        
           |  |  | 1402 |                            $ldapexpirationtime = $this->ldap_unix2expirationtime($expirationtime);
 | 
        
           |  |  | 1403 |                            $newattrs['passwordExpirationTime'] = $ldapexpirationtime;
 | 
        
           |  |  | 1404 |                         }
 | 
        
           |  |  | 1405 |   | 
        
           |  |  | 1406 |                         // Set gracelogin count
 | 
        
           |  |  | 1407 |                         if (!empty($info['logingracelimit'][0])) {
 | 
        
           |  |  | 1408 |                            $newattrs['loginGraceRemaining']= $info['logingracelimit'][0];
 | 
        
           |  |  | 1409 |                         }
 | 
        
           |  |  | 1410 |   | 
        
           |  |  | 1411 |                         // Store attribute changes in LDAP
 | 
        
           |  |  | 1412 |                         $result = ldap_modify($ldapconnection, $user_dn, $newattrs);
 | 
        
           |  |  | 1413 |                         if (!$result) {
 | 
        
           |  |  | 1414 |                             error_log($this->errorlogtag.get_string ('updatepasserrorexpiregrace', 'auth_ldap',
 | 
        
           |  |  | 1415 |                                                                        array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1416 |                                                                              'errstring'=>ldap_err2str(ldap_errno($ldapconnection)))));
 | 
        
           |  |  | 1417 |                         }
 | 
        
           |  |  | 1418 |                     }
 | 
        
           |  |  | 1419 |                 }
 | 
        
           |  |  | 1420 |                 else {
 | 
        
           |  |  | 1421 |                     error_log($this->errorlogtag.get_string ('updatepasserrorexpire', 'auth_ldap',
 | 
        
           |  |  | 1422 |                                                              array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1423 |                                                                    'errstring'=>ldap_err2str(ldap_errno($ldapconnection)))));
 | 
        
           |  |  | 1424 |                 }
 | 
        
           |  |  | 1425 |                 break;
 | 
        
           |  |  | 1426 |   | 
        
           |  |  | 1427 |             case 'ad':
 | 
        
           |  |  | 1428 |                 // Passwords in Active Directory must be encoded as Unicode
 | 
        
           |  |  | 1429 |                 // strings (UCS-2 Little Endian format) and surrounded with
 | 
        
           |  |  | 1430 |                 // double quotes. See http://support.microsoft.com/?kbid=269190
 | 
        
           |  |  | 1431 |                 if (!function_exists('mb_convert_encoding')) {
 | 
        
           |  |  | 1432 |                     error_log($this->errorlogtag.get_string ('needmbstring', 'auth_ldap'));
 | 
        
           |  |  | 1433 |                     return false;
 | 
        
           |  |  | 1434 |                 }
 | 
        
           |  |  | 1435 |                 $extpassword = mb_convert_encoding('"'.$extpassword.'"', "UCS-2LE", $this->config->ldapencoding);
 | 
        
           |  |  | 1436 |                 $result = ldap_modify($ldapconnection, $user_dn, array('unicodePwd' => $extpassword));
 | 
        
           |  |  | 1437 |                 if (!$result) {
 | 
        
           |  |  | 1438 |                     error_log($this->errorlogtag.get_string ('updatepasserror', 'auth_ldap',
 | 
        
           |  |  | 1439 |                                                              array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1440 |                                                                    'errstring'=>ldap_err2str(ldap_errno($ldapconnection)))));
 | 
        
           |  |  | 1441 |                 }
 | 
        
           |  |  | 1442 |                 break;
 | 
        
           |  |  | 1443 |   | 
        
           |  |  | 1444 |             default:
 | 
        
           |  |  | 1445 |                 // Send LDAP the password in cleartext, it will md5 it itself
 | 
        
           |  |  | 1446 |                 $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $extpassword));
 | 
        
           |  |  | 1447 |                 if (!$result) {
 | 
        
           |  |  | 1448 |                     error_log($this->errorlogtag.get_string ('updatepasserror', 'auth_ldap',
 | 
        
           |  |  | 1449 |                                                              array('errno'=>ldap_errno($ldapconnection),
 | 
        
           |  |  | 1450 |                                                                    'errstring'=>ldap_err2str(ldap_errno($ldapconnection)))));
 | 
        
           |  |  | 1451 |                 }
 | 
        
           |  |  | 1452 |   | 
        
           |  |  | 1453 |         }
 | 
        
           |  |  | 1454 |   | 
        
           |  |  | 1455 |         $this->ldap_close();
 | 
        
           |  |  | 1456 |         return $result;
 | 
        
           |  |  | 1457 |     }
 | 
        
           |  |  | 1458 |   | 
        
           |  |  | 1459 |     /**
 | 
        
           |  |  | 1460 |      * Take expirationtime and return it as unix timestamp in seconds
 | 
        
           |  |  | 1461 |      *
 | 
        
           |  |  | 1462 |      * Takes expiration timestamp as read from LDAP and returns it as unix timestamp in seconds
 | 
        
           |  |  | 1463 |      * Depends on $this->config->user_type variable
 | 
        
           |  |  | 1464 |      *
 | 
        
           |  |  | 1465 |      * @param mixed time   Time stamp read from LDAP as it is.
 | 
        
           |  |  | 1466 |      * @param string $ldapconnection Only needed for Active Directory.
 | 
        
           |  |  | 1467 |      * @param string $user_dn User distinguished name for the user we are checking password expiration (only needed for Active Directory).
 | 
        
           |  |  | 1468 |      * @return timestamp
 | 
        
           |  |  | 1469 |      */
 | 
        
           |  |  | 1470 |     function ldap_expirationtime2unix($time, $ldapconnection, $user_dn) {
 | 
        
           |  |  | 1471 |         $result = false;
 | 
        
           |  |  | 1472 |         switch ($this->config->user_type) {
 | 
        
           |  |  | 1473 |             case 'edir':
 | 
        
           |  |  | 1474 |                 $yr=substr($time, 0, 4);
 | 
        
           |  |  | 1475 |                 $mo=substr($time, 4, 2);
 | 
        
           |  |  | 1476 |                 $dt=substr($time, 6, 2);
 | 
        
           |  |  | 1477 |                 $hr=substr($time, 8, 2);
 | 
        
           |  |  | 1478 |                 $min=substr($time, 10, 2);
 | 
        
           |  |  | 1479 |                 $sec=substr($time, 12, 2);
 | 
        
           |  |  | 1480 |                 $result = mktime($hr, $min, $sec, $mo, $dt, $yr);
 | 
        
           |  |  | 1481 |                 break;
 | 
        
           |  |  | 1482 |             case 'rfc2307':
 | 
        
           |  |  | 1483 |             case 'rfc2307bis':
 | 
        
           |  |  | 1484 |                 $result = $time * DAYSECS; // The shadowExpire contains the number of DAYS between 01/01/1970 and the actual expiration date
 | 
        
           |  |  | 1485 |                 break;
 | 
        
           |  |  | 1486 |             case 'ad':
 | 
        
           |  |  | 1487 |                 $result = $this->ldap_get_ad_pwdexpire($time, $ldapconnection, $user_dn);
 | 
        
           |  |  | 1488 |                 break;
 | 
        
           |  |  | 1489 |             default:
 | 
        
           |  |  | 1490 |                 throw new \moodle_exception('auth_ldap_usertypeundefined', 'auth_ldap');
 | 
        
           |  |  | 1491 |         }
 | 
        
           |  |  | 1492 |         return $result;
 | 
        
           |  |  | 1493 |     }
 | 
        
           |  |  | 1494 |   | 
        
           |  |  | 1495 |     /**
 | 
        
           |  |  | 1496 |      * Takes unix timestamp and returns it formated for storing in LDAP
 | 
        
           |  |  | 1497 |      *
 | 
        
           |  |  | 1498 |      * @param integer unix time stamp
 | 
        
           |  |  | 1499 |      */
 | 
        
           |  |  | 1500 |     function ldap_unix2expirationtime($time) {
 | 
        
           |  |  | 1501 |         $result = false;
 | 
        
           |  |  | 1502 |         switch ($this->config->user_type) {
 | 
        
           |  |  | 1503 |             case 'edir':
 | 
        
           |  |  | 1504 |                 $result=date('YmdHis', $time).'Z';
 | 
        
           |  |  | 1505 |                 break;
 | 
        
           |  |  | 1506 |             case 'rfc2307':
 | 
        
           |  |  | 1507 |             case 'rfc2307bis':
 | 
        
           |  |  | 1508 |                 $result = $time ; // Already in correct format
 | 
        
           |  |  | 1509 |                 break;
 | 
        
           |  |  | 1510 |             default:
 | 
        
           |  |  | 1511 |                 throw new \moodle_exception('auth_ldap_usertypeundefined2', 'auth_ldap');
 | 
        
           |  |  | 1512 |         }
 | 
        
           |  |  | 1513 |         return $result;
 | 
        
           |  |  | 1514 |   | 
        
           |  |  | 1515 |     }
 | 
        
           |  |  | 1516 |   | 
        
           |  |  | 1517 |     /**
 | 
        
           |  |  | 1518 |      * Returns user attribute mappings between moodle and LDAP
 | 
        
           |  |  | 1519 |      *
 | 
        
           |  |  | 1520 |      * @return array
 | 
        
           |  |  | 1521 |      */
 | 
        
           |  |  | 1522 |   | 
        
           |  |  | 1523 |     function ldap_attributes() {
 | 
        
           |  |  | 1524 |         $moodleattributes = array();
 | 
        
           |  |  | 1525 |         // If we have custom fields then merge them with user fields.
 | 
        
           |  |  | 1526 |         $customfields = $this->get_custom_user_profile_fields();
 | 
        
           |  |  | 1527 |         if (!empty($customfields) && !empty($this->userfields)) {
 | 
        
           |  |  | 1528 |             $userfields = array_merge($this->userfields, $customfields);
 | 
        
           |  |  | 1529 |         } else {
 | 
        
           |  |  | 1530 |             $userfields = $this->userfields;
 | 
        
           |  |  | 1531 |         }
 | 
        
           |  |  | 1532 |   | 
        
           |  |  | 1533 |         foreach ($userfields as $field) {
 | 
        
           |  |  | 1534 |             if (!empty($this->config->{"field_map_$field"})) {
 | 
        
           |  |  | 1535 |                 $moodleattributes[$field] = core_text::strtolower(trim($this->config->{"field_map_$field"}));
 | 
        
           |  |  | 1536 |                 if (preg_match('/,/', $moodleattributes[$field])) {
 | 
        
           |  |  | 1537 |                     $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ?
 | 
        
           |  |  | 1538 |                 }
 | 
        
           |  |  | 1539 |             }
 | 
        
           |  |  | 1540 |         }
 | 
        
           |  |  | 1541 |         $moodleattributes['username'] = core_text::strtolower(trim($this->config->user_attribute));
 | 
        
           |  |  | 1542 |         $moodleattributes['suspended'] = core_text::strtolower(trim($this->config->suspended_attribute));
 | 
        
           |  |  | 1543 |         return $moodleattributes;
 | 
        
           |  |  | 1544 |     }
 | 
        
           |  |  | 1545 |   | 
        
           |  |  | 1546 |     /**
 | 
        
           |  |  | 1547 |      * Returns all usernames from LDAP
 | 
        
           |  |  | 1548 |      *
 | 
        
           |  |  | 1549 |      * @param $filter An LDAP search filter to select desired users
 | 
        
           |  |  | 1550 |      * @return array of LDAP user names converted to UTF-8
 | 
        
           |  |  | 1551 |      */
 | 
        
           |  |  | 1552 |     function ldap_get_userlist($filter='*') {
 | 
        
           |  |  | 1553 |         $fresult = array();
 | 
        
           |  |  | 1554 |   | 
        
           |  |  | 1555 |         $ldapconnection = $this->ldap_connect();
 | 
        
           |  |  | 1556 |   | 
        
           |  |  | 1557 |         if ($filter == '*') {
 | 
        
           |  |  | 1558 |            $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')';
 | 
        
           |  |  | 1559 |         }
 | 
        
           |  |  | 1560 |         $servercontrols = array();
 | 
        
           |  |  | 1561 |   | 
        
           |  |  | 1562 |         $contexts = explode(';', $this->config->contexts);
 | 
        
           |  |  | 1563 |         if (!empty($this->config->create_context)) {
 | 
        
           |  |  | 1564 |             array_push($contexts, $this->config->create_context);
 | 
        
           |  |  | 1565 |         }
 | 
        
           |  |  | 1566 |   | 
        
           |  |  | 1567 |         $ldap_cookie = '';
 | 
        
           |  |  | 1568 |         $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection);
 | 
        
           |  |  | 1569 |         foreach ($contexts as $context) {
 | 
        
           |  |  | 1570 |             $context = trim($context);
 | 
        
           |  |  | 1571 |             if (empty($context)) {
 | 
        
           |  |  | 1572 |                 continue;
 | 
        
           |  |  | 1573 |             }
 | 
        
           |  |  | 1574 |   | 
        
           |  |  | 1575 |             do {
 | 
        
           |  |  | 1576 |                 if ($ldap_pagedresults) {
 | 
        
           |  |  | 1577 |                     $servercontrols = array(array(
 | 
        
           |  |  | 1578 |                         'oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => array(
 | 
        
           |  |  | 1579 |                             'size' => $this->config->pagesize, 'cookie' => $ldap_cookie)));
 | 
        
           |  |  | 1580 |                 }
 | 
        
           |  |  | 1581 |                 if ($this->config->search_sub) {
 | 
        
           |  |  | 1582 |                     // Use ldap_search to find first user from subtree.
 | 
        
           |  |  | 1583 |                     $ldap_result = ldap_search($ldapconnection, $context, $filter, array($this->config->user_attribute),
 | 
        
           |  |  | 1584 |                         0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
 | 
        
           |  |  | 1585 |                 } else {
 | 
        
           |  |  | 1586 |                     // Search only in this context.
 | 
        
           |  |  | 1587 |                     $ldap_result = ldap_list($ldapconnection, $context, $filter, array($this->config->user_attribute),
 | 
        
           |  |  | 1588 |                         0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
 | 
        
           |  |  | 1589 |                 }
 | 
        
           |  |  | 1590 |                 if(!$ldap_result) {
 | 
        
           |  |  | 1591 |                     continue;
 | 
        
           |  |  | 1592 |                 }
 | 
        
           |  |  | 1593 |                 if ($ldap_pagedresults) {
 | 
        
           |  |  | 1594 |                     // Get next server cookie to know if we'll need to continue searching.
 | 
        
           |  |  | 1595 |                     $ldap_cookie = '';
 | 
        
           |  |  | 1596 |                     // Get next cookie from controls.
 | 
        
           |  |  | 1597 |                     ldap_parse_result($ldapconnection, $ldap_result, $errcode, $matcheddn,
 | 
        
           |  |  | 1598 |                         $errmsg, $referrals, $controls);
 | 
        
           |  |  | 1599 |                     if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
 | 
        
           |  |  | 1600 |                         $ldap_cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
 | 
        
           |  |  | 1601 |                     }
 | 
        
           |  |  | 1602 |                 }
 | 
        
           |  |  | 1603 |                 $users = ldap_get_entries_moodle($ldapconnection, $ldap_result);
 | 
        
           |  |  | 1604 |                 // Add found users to list.
 | 
        
           |  |  | 1605 |                 for ($i = 0; $i < count($users); $i++) {
 | 
        
           |  |  | 1606 |                     $extuser = core_text::convert($users[$i][$this->config->user_attribute][0],
 | 
        
           |  |  | 1607 |                                                 $this->config->ldapencoding, 'utf-8');
 | 
        
           |  |  | 1608 |                     array_push($fresult, $extuser);
 | 
        
           |  |  | 1609 |                 }
 | 
        
           |  |  | 1610 |                 unset($ldap_result); // Free mem.
 | 
        
           |  |  | 1611 |             } while ($ldap_pagedresults && !empty($ldap_cookie));
 | 
        
           |  |  | 1612 |         }
 | 
        
           |  |  | 1613 |   | 
        
           |  |  | 1614 |         // If paged results were used, make sure the current connection is completely closed
 | 
        
           |  |  | 1615 |         $this->ldap_close($ldap_pagedresults);
 | 
        
           |  |  | 1616 |         return $fresult;
 | 
        
           |  |  | 1617 |     }
 | 
        
           |  |  | 1618 |   | 
        
           |  |  | 1619 |     /**
 | 
        
           |  |  | 1620 |      * Indicates if password hashes should be stored in local moodle database.
 | 
        
           |  |  | 1621 |      *
 | 
        
           |  |  | 1622 |      * @return bool true means flag 'not_cached' stored instead of password hash
 | 
        
           |  |  | 1623 |      */
 | 
        
           |  |  | 1624 |     function prevent_local_passwords() {
 | 
        
           |  |  | 1625 |         return !empty($this->config->preventpassindb);
 | 
        
           |  |  | 1626 |     }
 | 
        
           |  |  | 1627 |   | 
        
           |  |  | 1628 |     /**
 | 
        
           |  |  | 1629 |      * Returns true if this authentication plugin is 'internal'.
 | 
        
           |  |  | 1630 |      *
 | 
        
           |  |  | 1631 |      * @return bool
 | 
        
           |  |  | 1632 |      */
 | 
        
           |  |  | 1633 |     function is_internal() {
 | 
        
           |  |  | 1634 |         return false;
 | 
        
           |  |  | 1635 |     }
 | 
        
           |  |  | 1636 |   | 
        
           |  |  | 1637 |     /**
 | 
        
           |  |  | 1638 |      * Returns true if this authentication plugin can change the user's
 | 
        
           |  |  | 1639 |      * password.
 | 
        
           |  |  | 1640 |      *
 | 
        
           |  |  | 1641 |      * @return bool
 | 
        
           |  |  | 1642 |      */
 | 
        
           |  |  | 1643 |     function can_change_password() {
 | 
        
           |  |  | 1644 |         return !empty($this->config->stdchangepassword) or !empty($this->config->changepasswordurl);
 | 
        
           |  |  | 1645 |     }
 | 
        
           |  |  | 1646 |   | 
        
           |  |  | 1647 |     /**
 | 
        
           |  |  | 1648 |      * Returns the URL for changing the user's password, or empty if the default can
 | 
        
           |  |  | 1649 |      * be used.
 | 
        
           |  |  | 1650 |      *
 | 
        
           |  |  | 1651 |      * @return moodle_url
 | 
        
           |  |  | 1652 |      */
 | 
        
           |  |  | 1653 |     function change_password_url() {
 | 
        
           |  |  | 1654 |         if (empty($this->config->stdchangepassword)) {
 | 
        
           |  |  | 1655 |             if (!empty($this->config->changepasswordurl)) {
 | 
        
           |  |  | 1656 |                 return new moodle_url($this->config->changepasswordurl);
 | 
        
           |  |  | 1657 |             } else {
 | 
        
           |  |  | 1658 |                 return null;
 | 
        
           |  |  | 1659 |             }
 | 
        
           |  |  | 1660 |         } else {
 | 
        
           |  |  | 1661 |             return null;
 | 
        
           |  |  | 1662 |         }
 | 
        
           |  |  | 1663 |     }
 | 
        
           |  |  | 1664 |   | 
        
           |  |  | 1665 |     /**
 | 
        
           |  |  | 1666 |      * Will get called before the login page is shownr. Ff NTLM SSO
 | 
        
           |  |  | 1667 |      * is enabled, and the user is in the right network, we'll redirect
 | 
        
           |  |  | 1668 |      * to the magic NTLM page for SSO...
 | 
        
           |  |  | 1669 |      *
 | 
        
           |  |  | 1670 |      */
 | 
        
           |  |  | 1671 |     function loginpage_hook() {
 | 
        
           |  |  | 1672 |         global $CFG, $SESSION;
 | 
        
           |  |  | 1673 |   | 
        
           |  |  | 1674 |         // HTTPS is potentially required
 | 
        
           |  |  | 1675 |         //httpsrequired(); - this must be used before setting the URL, it is already done on the login/index.php
 | 
        
           |  |  | 1676 |   | 
        
           |  |  | 1677 |         if (($_SERVER['REQUEST_METHOD'] === 'GET'         // Only on initial GET of loginpage
 | 
        
           |  |  | 1678 |              || ($_SERVER['REQUEST_METHOD'] === 'POST'
 | 
        
           |  |  | 1679 |                  && (get_local_referer() != strip_querystring(qualified_me()))))
 | 
        
           |  |  | 1680 |                                                           // Or when POSTed from another place
 | 
        
           |  |  | 1681 |                                                           // See MDL-14071
 | 
        
           |  |  | 1682 |             && !empty($this->config->ntlmsso_enabled)     // SSO enabled
 | 
        
           |  |  | 1683 |             && !empty($this->config->ntlmsso_subnet)      // have a subnet to test for
 | 
        
           |  |  | 1684 |             && empty($_GET['authldap_skipntlmsso'])       // haven't failed it yet
 | 
        
           |  |  | 1685 |             && (isguestuser() || !isloggedin())           // guestuser or not-logged-in users
 | 
        
           |  |  | 1686 |             && address_in_subnet(getremoteaddr(), $this->config->ntlmsso_subnet)) {
 | 
        
           |  |  | 1687 |   | 
        
           |  |  | 1688 |             // First, let's remember where we were trying to get to before we got here
 | 
        
           |  |  | 1689 |             if (empty($SESSION->wantsurl)) {
 | 
        
           |  |  | 1690 |                 $SESSION->wantsurl = null;
 | 
        
           |  |  | 1691 |                 $referer = get_local_referer(false);
 | 
        
           |  |  | 1692 |                 if ($referer &&
 | 
        
           |  |  | 1693 |                         $referer != $CFG->wwwroot &&
 | 
        
           |  |  | 1694 |                         $referer != $CFG->wwwroot . '/' &&
 | 
        
           |  |  | 1695 |                         $referer != $CFG->wwwroot . '/login/' &&
 | 
        
           |  |  | 1696 |                         $referer != $CFG->wwwroot . '/login/index.php') {
 | 
        
           |  |  | 1697 |                     $SESSION->wantsurl = $referer;
 | 
        
           |  |  | 1698 |                 }
 | 
        
           |  |  | 1699 |             }
 | 
        
           |  |  | 1700 |   | 
        
           |  |  | 1701 |             // Now start the whole NTLM machinery.
 | 
        
           |  |  | 1702 |             if($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESATTEMPT ||
 | 
        
           |  |  | 1703 |                 $this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
 | 
        
           |  |  | 1704 |                 if (core_useragent::is_ie()) {
 | 
        
           |  |  | 1705 |                     $sesskey = sesskey();
 | 
        
           |  |  | 1706 |                     redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_magic.php?sesskey='.$sesskey);
 | 
        
           |  |  | 1707 |                 } else if ($this->config->ntlmsso_ie_fastpath == AUTH_NTLM_FASTPATH_YESFORM) {
 | 
        
           |  |  | 1708 |                     redirect($CFG->wwwroot.'/login/index.php?authldap_skipntlmsso=1');
 | 
        
           |  |  | 1709 |                 }
 | 
        
           |  |  | 1710 |             }
 | 
        
           |  |  | 1711 |             redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_attempt.php');
 | 
        
           |  |  | 1712 |         }
 | 
        
           |  |  | 1713 |   | 
        
           |  |  | 1714 |         // No NTLM SSO, Use the normal login page instead.
 | 
        
           |  |  | 1715 |   | 
        
           |  |  | 1716 |         // If $SESSION->wantsurl is empty and we have a 'Referer:' header, the login
 | 
        
           |  |  | 1717 |         // page insists on redirecting us to that page after user validation. If
 | 
        
           |  |  | 1718 |         // we clicked on the redirect link at the ntlmsso_finish.php page (instead
 | 
        
           |  |  | 1719 |         // of waiting for the redirection to happen) then we have a 'Referer:' header
 | 
        
           |  |  | 1720 |         // we don't want to use at all. As we can't get rid of it, just point
 | 
        
           |  |  | 1721 |         // $SESSION->wantsurl to $CFG->wwwroot (after all, we came from there).
 | 
        
           |  |  | 1722 |         if (empty($SESSION->wantsurl)
 | 
        
           |  |  | 1723 |             && (get_local_referer() == $CFG->wwwroot.'/auth/ldap/ntlmsso_finish.php')) {
 | 
        
           |  |  | 1724 |   | 
        
           |  |  | 1725 |             $SESSION->wantsurl = $CFG->wwwroot;
 | 
        
           |  |  | 1726 |         }
 | 
        
           |  |  | 1727 |     }
 | 
        
           |  |  | 1728 |   | 
        
           |  |  | 1729 |     /**
 | 
        
           |  |  | 1730 |      * To be called from a page running under NTLM's
 | 
        
           |  |  | 1731 |      * "Integrated Windows Authentication".
 | 
        
           |  |  | 1732 |      *
 | 
        
           |  |  | 1733 |      * If successful, it will set a special "cookie" (not an HTTP cookie!)
 | 
        
           |  |  | 1734 |      * in cache_flags under the $this->pluginconfig/ntlmsess "plugin" and return true.
 | 
        
           |  |  | 1735 |      * The "cookie" will be picked up by ntlmsso_finish() to complete the
 | 
        
           |  |  | 1736 |      * process.
 | 
        
           |  |  | 1737 |      *
 | 
        
           |  |  | 1738 |      * On failure it will return false for the caller to display an appropriate
 | 
        
           |  |  | 1739 |      * error message (probably saying that Integrated Windows Auth isn't enabled!)
 | 
        
           |  |  | 1740 |      *
 | 
        
           |  |  | 1741 |      * NOTE that this code will execute under the OS user credentials,
 | 
        
           |  |  | 1742 |      * so we MUST avoid dealing with files -- such as session files.
 | 
        
           |  |  | 1743 |      * (The caller should define('NO_MOODLE_COOKIES', true) before including config.php)
 | 
        
           |  |  | 1744 |      *
 | 
        
           |  |  | 1745 |      */
 | 
        
           |  |  | 1746 |     function ntlmsso_magic($sesskey) {
 | 
        
           |  |  | 1747 |         if (isset($_SERVER['REMOTE_USER']) && !empty($_SERVER['REMOTE_USER'])) {
 | 
        
           |  |  | 1748 |   | 
        
           |  |  | 1749 |             // HTTP __headers__ seem to be sent in ISO-8859-1 encoding
 | 
        
           |  |  | 1750 |             // (according to my reading of RFC-1945, RFC-2616 and RFC-2617 and
 | 
        
           |  |  | 1751 |             // my local tests), so we need to convert the REMOTE_USER value
 | 
        
           |  |  | 1752 |             // (i.e., what we got from the HTTP WWW-Authenticate header) into UTF-8
 | 
        
           |  |  | 1753 |             $username = core_text::convert($_SERVER['REMOTE_USER'], 'iso-8859-1', 'utf-8');
 | 
        
           |  |  | 1754 |   | 
        
           |  |  | 1755 |             switch ($this->config->ntlmsso_type) {
 | 
        
           |  |  | 1756 |                 case 'ntlm':
 | 
        
           |  |  | 1757 |                     // The format is now configurable, so try to extract the username
 | 
        
           |  |  | 1758 |                     $username = $this->get_ntlm_remote_user($username);
 | 
        
           |  |  | 1759 |                     if (empty($username)) {
 | 
        
           |  |  | 1760 |                         return false;
 | 
        
           |  |  | 1761 |                     }
 | 
        
           |  |  | 1762 |                     break;
 | 
        
           |  |  | 1763 |                 case 'kerberos':
 | 
        
           |  |  | 1764 |                     // Format is username@DOMAIN
 | 
        
           |  |  | 1765 |                     $username = substr($username, 0, strpos($username, '@'));
 | 
        
           |  |  | 1766 |                     break;
 | 
        
           |  |  | 1767 |                 default:
 | 
        
           |  |  | 1768 |                     error_log($this->errorlogtag.get_string ('ntlmsso_unknowntype', 'auth_ldap'));
 | 
        
           |  |  | 1769 |                     return false; // Should never happen!
 | 
        
           |  |  | 1770 |             }
 | 
        
           |  |  | 1771 |   | 
        
           |  |  | 1772 |             $username = core_text::strtolower($username); // Compatibility hack
 | 
        
           |  |  | 1773 |             set_cache_flag($this->pluginconfig.'/ntlmsess', $sesskey, $username, AUTH_NTLMTIMEOUT);
 | 
        
           |  |  | 1774 |             return true;
 | 
        
           |  |  | 1775 |         }
 | 
        
           |  |  | 1776 |         return false;
 | 
        
           |  |  | 1777 |     }
 | 
        
           |  |  | 1778 |   | 
        
           |  |  | 1779 |     /**
 | 
        
           |  |  | 1780 |      * Find the session set by ntlmsso_magic(), validate it and
 | 
        
           |  |  | 1781 |      * call authenticate_user_login() to authenticate the user through
 | 
        
           |  |  | 1782 |      * the auth machinery.
 | 
        
           |  |  | 1783 |      *
 | 
        
           |  |  | 1784 |      * It is complemented by a similar check in user_login().
 | 
        
           |  |  | 1785 |      *
 | 
        
           |  |  | 1786 |      * If it succeeds, it never returns.
 | 
        
           |  |  | 1787 |      *
 | 
        
           |  |  | 1788 |      */
 | 
        
           |  |  | 1789 |     function ntlmsso_finish() {
 | 
        
           |  |  | 1790 |         global $CFG, $USER, $SESSION;
 | 
        
           |  |  | 1791 |   | 
        
           |  |  | 1792 |         $key = sesskey();
 | 
        
           |  |  | 1793 |         $username = get_cache_flag($this->pluginconfig.'/ntlmsess', $key);
 | 
        
           |  |  | 1794 |         if (empty($username)) {
 | 
        
           |  |  | 1795 |             return false;
 | 
        
           |  |  | 1796 |         }
 | 
        
           |  |  | 1797 |   | 
        
           |  |  | 1798 |         // Here we want to trigger the whole authentication machinery
 | 
        
           |  |  | 1799 |         // to make sure no step is bypassed...
 | 
        
           |  |  | 1800 |         $reason = null;
 | 
        
           |  |  | 1801 |         $user = authenticate_user_login($username, $key, false, $reason, false);
 | 
        
           |  |  | 1802 |         if ($user) {
 | 
        
           |  |  | 1803 |             complete_user_login($user);
 | 
        
           |  |  | 1804 |   | 
        
           |  |  | 1805 |             // Cleanup the key to prevent reuse...
 | 
        
           |  |  | 1806 |             // and to allow re-logins with normal credentials
 | 
        
           |  |  | 1807 |             unset_cache_flag($this->pluginconfig.'/ntlmsess', $key);
 | 
        
           |  |  | 1808 |   | 
        
           |  |  | 1809 |             // Redirection
 | 
        
           |  |  | 1810 |             if (user_not_fully_set_up($USER, true)) {
 | 
        
           |  |  | 1811 |                 $urltogo = $CFG->wwwroot.'/user/edit.php';
 | 
        
           |  |  | 1812 |                 // We don't delete $SESSION->wantsurl yet, so we get there later
 | 
        
           |  |  | 1813 |             } else if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0)) {
 | 
        
           |  |  | 1814 |                 $urltogo = $SESSION->wantsurl;    // Because it's an address in this site
 | 
        
           |  |  | 1815 |                 unset($SESSION->wantsurl);
 | 
        
           |  |  | 1816 |             } else {
 | 
        
           |  |  | 1817 |                 // No wantsurl stored or external - go to homepage
 | 
        
           |  |  | 1818 |                 $urltogo = $CFG->wwwroot.'/';
 | 
        
           |  |  | 1819 |                 unset($SESSION->wantsurl);
 | 
        
           |  |  | 1820 |             }
 | 
        
           |  |  | 1821 |             // We do not want to redirect if we are in a PHPUnit test.
 | 
        
           |  |  | 1822 |             if (!PHPUNIT_TEST) {
 | 
        
           |  |  | 1823 |                 redirect($urltogo);
 | 
        
           |  |  | 1824 |             }
 | 
        
           |  |  | 1825 |         }
 | 
        
           |  |  | 1826 |         // Should never reach here.
 | 
        
           |  |  | 1827 |         return false;
 | 
        
           |  |  | 1828 |     }
 | 
        
           |  |  | 1829 |   | 
        
           |  |  | 1830 |     /**
 | 
        
           |  |  | 1831 |      * Sync roles for this user.
 | 
        
           |  |  | 1832 |      *
 | 
        
           |  |  | 1833 |      * @param object $user The user to sync (without system magic quotes).
 | 
        
           |  |  | 1834 |      */
 | 
        
           |  |  | 1835 |     function sync_roles($user) {
 | 
        
           |  |  | 1836 |         global $DB;
 | 
        
           |  |  | 1837 |   | 
        
           |  |  | 1838 |         $roles = get_ldap_assignable_role_names(2); // Admin user.
 | 
        
           |  |  | 1839 |   | 
        
           |  |  | 1840 |         foreach ($roles as $role) {
 | 
        
           |  |  | 1841 |             $isrole = $this->is_role($user->username, $role);
 | 
        
           |  |  | 1842 |             if ($isrole === null) {
 | 
        
           |  |  | 1843 |                 continue; // Nothing to sync - role/LDAP contexts not configured.
 | 
        
           |  |  | 1844 |             }
 | 
        
           |  |  | 1845 |   | 
        
           |  |  | 1846 |             // Sync user.
 | 
        
           |  |  | 1847 |             $systemcontext = context_system::instance();
 | 
        
           |  |  | 1848 |             if ($isrole) {
 | 
        
           |  |  | 1849 |                 // Following calls will not create duplicates.
 | 
        
           |  |  | 1850 |                 role_assign($role['id'], $user->id, $systemcontext->id, $this->roleauth);
 | 
        
           |  |  | 1851 |             } else {
 | 
        
           |  |  | 1852 |                 // Unassign only if previously assigned by this plugin.
 | 
        
           |  |  | 1853 |                 role_unassign($role['id'], $user->id, $systemcontext->id, $this->roleauth);
 | 
        
           |  |  | 1854 |             }
 | 
        
           |  |  | 1855 |         }
 | 
        
           |  |  | 1856 |     }
 | 
        
           |  |  | 1857 |   | 
        
           |  |  | 1858 |     /**
 | 
        
           |  |  | 1859 |      * Get password expiration time for a given user from Active Directory
 | 
        
           |  |  | 1860 |      *
 | 
        
           |  |  | 1861 |      * @param string $pwdlastset The time last time we changed the password.
 | 
        
           |  |  | 1862 |      * @param resource $lcapconn The open LDAP connection.
 | 
        
           |  |  | 1863 |      * @param string $user_dn The distinguished name of the user we are checking.
 | 
        
           |  |  | 1864 |      *
 | 
        
           |  |  | 1865 |      * @return string $unixtime
 | 
        
           |  |  | 1866 |      */
 | 
        
           |  |  | 1867 |     function ldap_get_ad_pwdexpire($pwdlastset, $ldapconn, $user_dn){
 | 
        
           |  |  | 1868 |         global $CFG;
 | 
        
           |  |  | 1869 |   | 
        
           |  |  | 1870 |         if (!function_exists('bcsub')) {
 | 
        
           |  |  | 1871 |             error_log($this->errorlogtag.get_string ('needbcmath', 'auth_ldap'));
 | 
        
           |  |  | 1872 |             return 0;
 | 
        
           |  |  | 1873 |         }
 | 
        
           |  |  | 1874 |   | 
        
           |  |  | 1875 |         // If UF_DONT_EXPIRE_PASSWD flag is set in user's
 | 
        
           |  |  | 1876 |         // userAccountControl attribute, the password doesn't expire.
 | 
        
           |  |  | 1877 |         $sr = ldap_read($ldapconn, $user_dn, '(objectClass=*)',
 | 
        
           |  |  | 1878 |                         array('userAccountControl'));
 | 
        
           |  |  | 1879 |         if (!$sr) {
 | 
        
           |  |  | 1880 |             error_log($this->errorlogtag.get_string ('useracctctrlerror', 'auth_ldap', $user_dn));
 | 
        
           |  |  | 1881 |             // Don't expire password, as we are not sure if it has to be
 | 
        
           |  |  | 1882 |             // expired or not.
 | 
        
           |  |  | 1883 |             return 0;
 | 
        
           |  |  | 1884 |         }
 | 
        
           |  |  | 1885 |   | 
        
           |  |  | 1886 |         $entry = ldap_get_entries_moodle($ldapconn, $sr);
 | 
        
           |  |  | 1887 |         $info = $entry[0];
 | 
        
           |  |  | 1888 |         $useraccountcontrol = $info['useraccountcontrol'][0];
 | 
        
           |  |  | 1889 |         if ($useraccountcontrol & UF_DONT_EXPIRE_PASSWD) {
 | 
        
           |  |  | 1890 |             // Password doesn't expire.
 | 
        
           |  |  | 1891 |             return 0;
 | 
        
           |  |  | 1892 |         }
 | 
        
           |  |  | 1893 |   | 
        
           |  |  | 1894 |         // If pwdLastSet is zero, the user must change his/her password now
 | 
        
           |  |  | 1895 |         // (unless UF_DONT_EXPIRE_PASSWD flag is set, but we already
 | 
        
           |  |  | 1896 |         // tested this above)
 | 
        
           |  |  | 1897 |         if ($pwdlastset === '0') {
 | 
        
           |  |  | 1898 |             // Password has expired
 | 
        
           |  |  | 1899 |             return -1;
 | 
        
           |  |  | 1900 |         }
 | 
        
           |  |  | 1901 |   | 
        
           |  |  | 1902 |         // ----------------------------------------------------------------
 | 
        
           |  |  | 1903 |         // Password expiration time in Active Directory is the composition of
 | 
        
           |  |  | 1904 |         // two values:
 | 
        
           |  |  | 1905 |         //
 | 
        
           |  |  | 1906 |         //   - User's pwdLastSet attribute, that stores the last time
 | 
        
           |  |  | 1907 |         //     the password was changed.
 | 
        
           |  |  | 1908 |         //
 | 
        
           |  |  | 1909 |         //   - Domain's maxPwdAge attribute, that sets how long
 | 
        
           |  |  | 1910 |         //     passwords last in this domain.
 | 
        
           |  |  | 1911 |         //
 | 
        
           |  |  | 1912 |         // We already have the first value (passed in as a parameter). We
 | 
        
           |  |  | 1913 |         // need to get the second one. As we don't know the domain DN, we
 | 
        
           |  |  | 1914 |         // have to query rootDSE's defaultNamingContext attribute to get
 | 
        
           |  |  | 1915 |         // it. Then we have to query that DN's maxPwdAge attribute to get
 | 
        
           |  |  | 1916 |         // the real value.
 | 
        
           |  |  | 1917 |         //
 | 
        
           |  |  | 1918 |         // Once we have both values, we just need to combine them. But MS
 | 
        
           |  |  | 1919 |         // chose to use a different base and unit for time measurements.
 | 
        
           |  |  | 1920 |         // So we need to convert the values to Unix timestamps (see
 | 
        
           |  |  | 1921 |         // details below).
 | 
        
           |  |  | 1922 |         // ----------------------------------------------------------------
 | 
        
           |  |  | 1923 |   | 
        
           |  |  | 1924 |         $sr = ldap_read($ldapconn, ROOTDSE, '(objectClass=*)',
 | 
        
           |  |  | 1925 |                         array('defaultNamingContext'));
 | 
        
           |  |  | 1926 |         if (!$sr) {
 | 
        
           |  |  | 1927 |             error_log($this->errorlogtag.get_string ('rootdseerror', 'auth_ldap'));
 | 
        
           |  |  | 1928 |             return 0;
 | 
        
           |  |  | 1929 |         }
 | 
        
           |  |  | 1930 |   | 
        
           |  |  | 1931 |         $entry = ldap_get_entries_moodle($ldapconn, $sr);
 | 
        
           |  |  | 1932 |         $info = $entry[0];
 | 
        
           |  |  | 1933 |         $domaindn = $info['defaultnamingcontext'][0];
 | 
        
           |  |  | 1934 |   | 
        
           |  |  | 1935 |         $sr = ldap_read ($ldapconn, $domaindn, '(objectClass=*)',
 | 
        
           |  |  | 1936 |                          array('maxPwdAge'));
 | 
        
           |  |  | 1937 |         $entry = ldap_get_entries_moodle($ldapconn, $sr);
 | 
        
           |  |  | 1938 |         $info = $entry[0];
 | 
        
           |  |  | 1939 |         $maxpwdage = $info['maxpwdage'][0];
 | 
        
           |  |  | 1940 |         if ($sr = ldap_read($ldapconn, $user_dn, '(objectClass=*)', array('msDS-ResultantPSO'))) {
 | 
        
           |  |  | 1941 |             if ($entry = ldap_get_entries_moodle($ldapconn, $sr)) {
 | 
        
           |  |  | 1942 |                 $info = $entry[0];
 | 
        
           |  |  | 1943 |                 $userpso = $info['msds-resultantpso'][0];
 | 
        
           |  |  | 1944 |   | 
        
           |  |  | 1945 |                 // If a PSO exists, FGPP is being utilized.
 | 
        
           |  |  | 1946 |                 // Grab the new maxpwdage from the msDS-MaximumPasswordAge attribute of the PSO.
 | 
        
           |  |  | 1947 |                 if (!empty($userpso)) {
 | 
        
           |  |  | 1948 |                     $sr = ldap_read($ldapconn, $userpso, '(objectClass=*)', array('msDS-MaximumPasswordAge'));
 | 
        
           |  |  | 1949 |                     if ($entry = ldap_get_entries_moodle($ldapconn, $sr)) {
 | 
        
           |  |  | 1950 |                         $info = $entry[0];
 | 
        
           |  |  | 1951 |                         // Default value of msds-maximumpasswordage is 42 and is always set.
 | 
        
           |  |  | 1952 |                         $maxpwdage = $info['msds-maximumpasswordage'][0];
 | 
        
           |  |  | 1953 |                     }
 | 
        
           |  |  | 1954 |                 }
 | 
        
           |  |  | 1955 |             }
 | 
        
           |  |  | 1956 |         }
 | 
        
           |  |  | 1957 |         // ----------------------------------------------------------------
 | 
        
           |  |  | 1958 |         // MSDN says that "pwdLastSet contains the number of 100 nanosecond
 | 
        
           |  |  | 1959 |         // intervals since January 1, 1601 (UTC), stored in a 64 bit integer".
 | 
        
           |  |  | 1960 |         //
 | 
        
           |  |  | 1961 |         // According to Perl's Date::Manip, the number of seconds between
 | 
        
           |  |  | 1962 |         // this date and Unix epoch is 11644473600. So we have to
 | 
        
           |  |  | 1963 |         // substract this value to calculate a Unix time, once we have
 | 
        
           |  |  | 1964 |         // scaled pwdLastSet to seconds. This is the script used to
 | 
        
           |  |  | 1965 |         // calculate the value shown above:
 | 
        
           |  |  | 1966 |         //
 | 
        
           |  |  | 1967 |         //    #!/usr/bin/perl -w
 | 
        
           |  |  | 1968 |         //
 | 
        
           |  |  | 1969 |         //    use Date::Manip;
 | 
        
           |  |  | 1970 |         //
 | 
        
           |  |  | 1971 |         //    $date1 = ParseDate ("160101010000 UTC");
 | 
        
           |  |  | 1972 |         //    $date2 = ParseDate ("197001010000 UTC");
 | 
        
           |  |  | 1973 |         //    $delta = DateCalc($date1, $date2, \$err);
 | 
        
           |  |  | 1974 |         //    $secs = Delta_Format($delta, 0, "%st");
 | 
        
           |  |  | 1975 |         //    print "$secs \n";
 | 
        
           |  |  | 1976 |         //
 | 
        
           |  |  | 1977 |         // MSDN also says that "maxPwdAge is stored as a large integer that
 | 
        
           |  |  | 1978 |         // represents the number of 100 nanosecond intervals from the time
 | 
        
           |  |  | 1979 |         // the password was set before the password expires." We also need
 | 
        
           |  |  | 1980 |         // to scale this to seconds. Bear in mind that this value is stored
 | 
        
           |  |  | 1981 |         // as a _negative_ quantity (at least in my AD domain).
 | 
        
           |  |  | 1982 |         //
 | 
        
           |  |  | 1983 |         // As a last remark, if the low 32 bits of maxPwdAge are equal to 0,
 | 
        
           |  |  | 1984 |         // the maximum password age in the domain is set to 0, which means
 | 
        
           |  |  | 1985 |         // passwords do not expire (see
 | 
        
           |  |  | 1986 |         // http://msdn2.microsoft.com/en-us/library/ms974598.aspx)
 | 
        
           |  |  | 1987 |         //
 | 
        
           |  |  | 1988 |         // As the quantities involved are too big for PHP integers, we
 | 
        
           |  |  | 1989 |         // need to use BCMath functions to work with arbitrary precision
 | 
        
           |  |  | 1990 |         // numbers.
 | 
        
           |  |  | 1991 |         // ----------------------------------------------------------------
 | 
        
           |  |  | 1992 |   | 
        
           |  |  | 1993 |         // If the low order 32 bits are 0, then passwords do not expire in
 | 
        
           |  |  | 1994 |         // the domain. Just do '$maxpwdage mod 2^32' and check the result
 | 
        
           |  |  | 1995 |         // (2^32 = 4294967296)
 | 
        
           |  |  | 1996 |         if (bcmod ($maxpwdage, 4294967296) === '0') {
 | 
        
           |  |  | 1997 |             return 0;
 | 
        
           |  |  | 1998 |         }
 | 
        
           |  |  | 1999 |   | 
        
           |  |  | 2000 |         // Add up pwdLastSet and maxPwdAge to get password expiration
 | 
        
           |  |  | 2001 |         // time, in MS time units. Remember maxPwdAge is stored as a
 | 
        
           |  |  | 2002 |         // _negative_ quantity, so we need to substract it in fact.
 | 
        
           |  |  | 2003 |         $pwdexpire = bcsub ($pwdlastset, $maxpwdage);
 | 
        
           |  |  | 2004 |   | 
        
           |  |  | 2005 |         // Scale the result to convert it to Unix time units and return
 | 
        
           |  |  | 2006 |         // that value.
 | 
        
           |  |  | 2007 |         return bcsub( bcdiv($pwdexpire, '10000000'), '11644473600');
 | 
        
           |  |  | 2008 |     }
 | 
        
           |  |  | 2009 |   | 
        
           |  |  | 2010 |     /**
 | 
        
           |  |  | 2011 |      * Connect to the LDAP server, using the plugin configured
 | 
        
           |  |  | 2012 |      * settings. It's actually a wrapper around ldap_connect_moodle()
 | 
        
           |  |  | 2013 |      *
 | 
        
           |  |  | 2014 |      * @return resource A valid LDAP connection (or dies if it can't connect)
 | 
        
           |  |  | 2015 |      */
 | 
        
           |  |  | 2016 |     function ldap_connect() {
 | 
        
           |  |  | 2017 |         // Cache ldap connections. They are expensive to set up
 | 
        
           |  |  | 2018 |         // and can drain the TCP/IP ressources on the server if we
 | 
        
           |  |  | 2019 |         // are syncing a lot of users (as we try to open a new connection
 | 
        
           |  |  | 2020 |         // to get the user details). This is the least invasive way
 | 
        
           |  |  | 2021 |         // to reuse existing connections without greater code surgery.
 | 
        
           |  |  | 2022 |         if(!empty($this->ldapconnection)) {
 | 
        
           |  |  | 2023 |             $this->ldapconns++;
 | 
        
           |  |  | 2024 |             return $this->ldapconnection;
 | 
        
           |  |  | 2025 |         }
 | 
        
           |  |  | 2026 |   | 
        
           |  |  | 2027 |         if($ldapconnection = ldap_connect_moodle($this->config->host_url, $this->config->ldap_version,
 | 
        
           |  |  | 2028 |                                                  $this->config->user_type, $this->config->bind_dn,
 | 
        
           |  |  | 2029 |                                                  $this->config->bind_pw, $this->config->opt_deref,
 | 
        
           |  |  | 2030 |                                                  $debuginfo, $this->config->start_tls)) {
 | 
        
           |  |  | 2031 |             $this->ldapconns = 1;
 | 
        
           |  |  | 2032 |             $this->ldapconnection = $ldapconnection;
 | 
        
           |  |  | 2033 |             return $ldapconnection;
 | 
        
           |  |  | 2034 |         }
 | 
        
           |  |  | 2035 |   | 
        
           |  |  | 2036 |         throw new \moodle_exception('auth_ldap_noconnect_all', 'auth_ldap', '', $debuginfo);
 | 
        
           |  |  | 2037 |     }
 | 
        
           |  |  | 2038 |   | 
        
           |  |  | 2039 |     /**
 | 
        
           |  |  | 2040 |      * Disconnects from a LDAP server
 | 
        
           |  |  | 2041 |      *
 | 
        
           |  |  | 2042 |      * @param force boolean Forces closing the real connection to the LDAP server, ignoring any
 | 
        
           |  |  | 2043 |      *                      cached connections. This is needed when we've used paged results
 | 
        
           |  |  | 2044 |      *                      and want to use normal results again.
 | 
        
           |  |  | 2045 |      */
 | 
        
           |  |  | 2046 |     function ldap_close($force=false) {
 | 
        
           |  |  | 2047 |         $this->ldapconns--;
 | 
        
           |  |  | 2048 |         if (($this->ldapconns == 0) || ($force)) {
 | 
        
           |  |  | 2049 |             $this->ldapconns = 0;
 | 
        
           |  |  | 2050 |             @ldap_close($this->ldapconnection);
 | 
        
           |  |  | 2051 |             unset($this->ldapconnection);
 | 
        
           |  |  | 2052 |         }
 | 
        
           |  |  | 2053 |     }
 | 
        
           |  |  | 2054 |   | 
        
           |  |  | 2055 |     /**
 | 
        
           |  |  | 2056 |      * Search specified contexts for username and return the user dn
 | 
        
           |  |  | 2057 |      * like: cn=username,ou=suborg,o=org. It's actually a wrapper
 | 
        
           |  |  | 2058 |      * around ldap_find_userdn().
 | 
        
           |  |  | 2059 |      *
 | 
        
           |  |  | 2060 |      * @param resource $ldapconnection a valid LDAP connection
 | 
        
           |  |  | 2061 |      * @param string $extusername the username to search (in external LDAP encoding, no db slashes)
 | 
        
           |  |  | 2062 |      * @return mixed the user dn (external LDAP encoding) or false
 | 
        
           |  |  | 2063 |      */
 | 
        
           |  |  | 2064 |     function ldap_find_userdn($ldapconnection, $extusername) {
 | 
        
           |  |  | 2065 |         $ldap_contexts = explode(';', $this->config->contexts);
 | 
        
           |  |  | 2066 |         if (!empty($this->config->create_context)) {
 | 
        
           |  |  | 2067 |             array_push($ldap_contexts, $this->config->create_context);
 | 
        
           |  |  | 2068 |         }
 | 
        
           |  |  | 2069 |   | 
        
           |  |  | 2070 |         return ldap_find_userdn($ldapconnection, $extusername, $ldap_contexts, $this->config->objectclass,
 | 
        
           |  |  | 2071 |                                 $this->config->user_attribute, $this->config->search_sub);
 | 
        
           |  |  | 2072 |     }
 | 
        
           |  |  | 2073 |   | 
        
           |  |  | 2074 |     /**
 | 
        
           |  |  | 2075 |      * When using NTLM SSO, the format of the remote username we get in
 | 
        
           |  |  | 2076 |      * $_SERVER['REMOTE_USER'] may vary, depending on where from and how the web
 | 
        
           |  |  | 2077 |      * server gets the data. So we let the admin configure the format using two
 | 
        
           |  |  | 2078 |      * place holders (%domain% and %username%). This function tries to extract
 | 
        
           |  |  | 2079 |      * the username (stripping the domain part and any separators if they are
 | 
        
           |  |  | 2080 |      * present) from the value present in $_SERVER['REMOTE_USER'], using the
 | 
        
           |  |  | 2081 |      * configured format.
 | 
        
           |  |  | 2082 |      *
 | 
        
           |  |  | 2083 |      * @param string $remoteuser The value from $_SERVER['REMOTE_USER'] (converted to UTF-8)
 | 
        
           |  |  | 2084 |      *
 | 
        
           |  |  | 2085 |      * @return string The remote username (without domain part or
 | 
        
           |  |  | 2086 |      *                separators). Empty string if we can't extract the username.
 | 
        
           |  |  | 2087 |      */
 | 
        
           |  |  | 2088 |     protected function get_ntlm_remote_user($remoteuser) {
 | 
        
           |  |  | 2089 |         if (empty($this->config->ntlmsso_remoteuserformat)) {
 | 
        
           |  |  | 2090 |             $format = AUTH_NTLM_DEFAULT_FORMAT;
 | 
        
           |  |  | 2091 |         } else {
 | 
        
           |  |  | 2092 |             $format = $this->config->ntlmsso_remoteuserformat;
 | 
        
           |  |  | 2093 |         }
 | 
        
           |  |  | 2094 |   | 
        
           |  |  | 2095 |         $format = preg_quote($format);
 | 
        
           |  |  | 2096 |         $formatregex = preg_replace(array('#%domain%#', '#%username%#'),
 | 
        
           |  |  | 2097 |                                     array('('.AUTH_NTLM_VALID_DOMAINNAME.')', '('.AUTH_NTLM_VALID_USERNAME.')'),
 | 
        
           |  |  | 2098 |                                     $format);
 | 
        
           |  |  | 2099 |         if (preg_match('#^'.$formatregex.'$#', $remoteuser, $matches)) {
 | 
        
           |  |  | 2100 |             $user = end($matches);
 | 
        
           |  |  | 2101 |             return $user;
 | 
        
           |  |  | 2102 |         }
 | 
        
           |  |  | 2103 |   | 
        
           |  |  | 2104 |         /* We are unable to extract the username with the configured format. Probably
 | 
        
           |  |  | 2105 |          * the format specified is wrong, so log a warning for the admin and return
 | 
        
           |  |  | 2106 |          * an empty username.
 | 
        
           |  |  | 2107 |          */
 | 
        
           |  |  | 2108 |         error_log($this->errorlogtag.get_string ('auth_ntlmsso_maybeinvalidformat', 'auth_ldap'));
 | 
        
           |  |  | 2109 |         return '';
 | 
        
           |  |  | 2110 |     }
 | 
        
           |  |  | 2111 |   | 
        
           |  |  | 2112 |     /**
 | 
        
           |  |  | 2113 |      * Check if the diagnostic message for the LDAP login error tells us that the
 | 
        
           |  |  | 2114 |      * login is denied because the user password has expired or the password needs
 | 
        
           |  |  | 2115 |      * to be changed on first login (using interactive SMB/Windows logins, not
 | 
        
           |  |  | 2116 |      * LDAP logins).
 | 
        
           |  |  | 2117 |      *
 | 
        
           |  |  | 2118 |      * @param string the diagnostic message for the LDAP login error
 | 
        
           |  |  | 2119 |      * @return bool true if the password has expired or the password must be changed on first login
 | 
        
           |  |  | 2120 |      */
 | 
        
           |  |  | 2121 |     protected function ldap_ad_pwdexpired_from_diagmsg($diagmsg) {
 | 
        
           |  |  | 2122 |         // The format of the diagnostic message is (actual examples from W2003 and W2008):
 | 
        
           |  |  | 2123 |         // "80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 52e, vece"  (W2003)
 | 
        
           |  |  | 2124 |         // "80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 773, vece"  (W2003)
 | 
        
           |  |  | 2125 |         // "80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 52e, v1771" (W2008)
 | 
        
           |  |  | 2126 |         // "80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 773, v1771" (W2008)
 | 
        
           |  |  | 2127 |         // We are interested in the 'data nnn' part.
 | 
        
           |  |  | 2128 |         //   if nnn == 773 then user must change password on first login
 | 
        
           |  |  | 2129 |         //   if nnn == 532 then user password has expired
 | 
        
           |  |  | 2130 |         $diagmsg = explode(',', $diagmsg);
 | 
        
           |  |  | 2131 |         if (preg_match('/data (773|532)/i', trim($diagmsg[2]))) {
 | 
        
           |  |  | 2132 |             return true;
 | 
        
           |  |  | 2133 |         }
 | 
        
           |  |  | 2134 |         return false;
 | 
        
           |  |  | 2135 |     }
 | 
        
           |  |  | 2136 |   | 
        
           |  |  | 2137 |     /**
 | 
        
           |  |  | 2138 |      * Check if a user is suspended. This function is intended to be used after calling
 | 
        
           |  |  | 2139 |      * get_userinfo_asobj. This is needed because LDAP doesn't have a notion of disabled
 | 
        
           |  |  | 2140 |      * users, however things like MS Active Directory support it and expose information
 | 
        
           |  |  | 2141 |      * through a field.
 | 
        
           |  |  | 2142 |      *
 | 
        
           |  |  | 2143 |      * @param object $user the user object returned by get_userinfo_asobj
 | 
        
           |  |  | 2144 |      * @return boolean
 | 
        
           |  |  | 2145 |      */
 | 
        
           |  |  | 2146 |     protected function is_user_suspended($user) {
 | 
        
           |  |  | 2147 |         if (!$this->config->suspended_attribute || !isset($user->suspended)) {
 | 
        
           |  |  | 2148 |             return false;
 | 
        
           |  |  | 2149 |         }
 | 
        
           |  |  | 2150 |         if ($this->config->suspended_attribute == 'useraccountcontrol' && $this->config->user_type == 'ad') {
 | 
        
           |  |  | 2151 |             return (bool)($user->suspended & AUTH_AD_ACCOUNTDISABLE);
 | 
        
           |  |  | 2152 |         }
 | 
        
           |  |  | 2153 |   | 
        
           |  |  | 2154 |         return (bool)$user->suspended;
 | 
        
           |  |  | 2155 |     }
 | 
        
           |  |  | 2156 |   | 
        
           |  |  | 2157 |     /**
 | 
        
           |  |  | 2158 |      * Test a DN
 | 
        
           |  |  | 2159 |      *
 | 
        
           |  |  | 2160 |      * @param resource $ldapconn
 | 
        
           |  |  | 2161 |      * @param string $dn The DN to check for existence
 | 
        
           |  |  | 2162 |      * @param string $message The identifier of a string as in get_string()
 | 
        
           |  |  | 2163 |      * @param string|object|array $a An object, string or number that can be used
 | 
        
           |  |  | 2164 |      *      within translation strings as in get_string()
 | 
        
           |  |  | 2165 |      * @return true or a message in case of error
 | 
        
           |  |  | 2166 |      */
 | 
        
           |  |  | 2167 |     private function test_dn($ldapconn, $dn, $message, $a = null) {
 | 
        
           |  |  | 2168 |         $ldapresult = @ldap_read($ldapconn, $dn, '(objectClass=*)', array());
 | 
        
           |  |  | 2169 |         if (!$ldapresult) {
 | 
        
           |  |  | 2170 |             if (ldap_errno($ldapconn) == 32) {
 | 
        
           |  |  | 2171 |                 // No such object.
 | 
        
           |  |  | 2172 |                 return get_string($message, 'auth_ldap', $a);
 | 
        
           |  |  | 2173 |             }
 | 
        
           |  |  | 2174 |   | 
        
           |  |  | 2175 |             $a = array('code' => ldap_errno($ldapconn), 'subject' => $a, 'message' => ldap_error($ldapconn));
 | 
        
           |  |  | 2176 |             return get_string('diag_genericerror', 'auth_ldap', $a);
 | 
        
           |  |  | 2177 |         }
 | 
        
           |  |  | 2178 |   | 
        
           |  |  | 2179 |         return true;
 | 
        
           |  |  | 2180 |     }
 | 
        
           |  |  | 2181 |   | 
        
           |  |  | 2182 |     /**
 | 
        
           |  |  | 2183 |      * Test if settings are correct, print info to output.
 | 
        
           |  |  | 2184 |      */
 | 
        
           |  |  | 2185 |     public function test_settings() {
 | 
        
           |  |  | 2186 |         global $OUTPUT;
 | 
        
           |  |  | 2187 |   | 
        
           |  |  | 2188 |         if (!function_exists('ldap_connect')) { // Is php-ldap really there?
 | 
        
           |  |  | 2189 |             echo $OUTPUT->notification(get_string('auth_ldap_noextension', 'auth_ldap'), \core\output\notification::NOTIFY_ERROR);
 | 
        
           |  |  | 2190 |             return;
 | 
        
           |  |  | 2191 |         }
 | 
        
           |  |  | 2192 |   | 
        
           |  |  | 2193 |         // Check to see if this is actually configured.
 | 
        
           |  |  | 2194 |         if (empty($this->config->host_url)) {
 | 
        
           |  |  | 2195 |             // LDAP is not even configured.
 | 
        
           |  |  | 2196 |             echo $OUTPUT->notification(get_string('ldapnotconfigured', 'auth_ldap'), \core\output\notification::NOTIFY_ERROR);
 | 
        
           |  |  | 2197 |             return;
 | 
        
           |  |  | 2198 |         }
 | 
        
           |  |  | 2199 |   | 
        
           |  |  | 2200 |         if ($this->config->ldap_version != 3) {
 | 
        
           |  |  | 2201 |             echo $OUTPUT->notification(get_string('diag_toooldversion', 'auth_ldap'), \core\output\notification::NOTIFY_WARNING);
 | 
        
           |  |  | 2202 |         }
 | 
        
           |  |  | 2203 |   | 
        
           |  |  | 2204 |         try {
 | 
        
           |  |  | 2205 |             $ldapconn = $this->ldap_connect();
 | 
        
           |  |  | 2206 |         } catch (Exception $e) {
 | 
        
           |  |  | 2207 |             echo $OUTPUT->notification($e->getMessage(), \core\output\notification::NOTIFY_ERROR);
 | 
        
           |  |  | 2208 |             return;
 | 
        
           |  |  | 2209 |         }
 | 
        
           |  |  | 2210 |   | 
        
           |  |  | 2211 |         // Display paged file results.
 | 
        
           |  |  | 2212 |         if (!ldap_paged_results_supported($this->config->ldap_version, $ldapconn)) {
 | 
        
           |  |  | 2213 |             echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'), \core\output\notification::NOTIFY_INFO);
 | 
        
           |  |  | 2214 |         }
 | 
        
           |  |  | 2215 |   | 
        
           |  |  | 2216 |         // Check contexts.
 | 
        
           |  |  | 2217 |         foreach (explode(';', $this->config->contexts) as $context) {
 | 
        
           |  |  | 2218 |             $context = trim($context);
 | 
        
           |  |  | 2219 |             if (empty($context)) {
 | 
        
           |  |  | 2220 |                 echo $OUTPUT->notification(get_string('diag_emptycontext', 'auth_ldap'), \core\output\notification::NOTIFY_WARNING);
 | 
        
           |  |  | 2221 |                 continue;
 | 
        
           |  |  | 2222 |             }
 | 
        
           |  |  | 2223 |   | 
        
           |  |  | 2224 |             $message = $this->test_dn($ldapconn, $context, 'diag_contextnotfound', $context);
 | 
        
           |  |  | 2225 |             if ($message !== true) {
 | 
        
           |  |  | 2226 |                 echo $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
 | 
        
           |  |  | 2227 |             }
 | 
        
           |  |  | 2228 |         }
 | 
        
           |  |  | 2229 |   | 
        
           |  |  | 2230 |         // Create system role mapping field for each assignable system role.
 | 
        
           |  |  | 2231 |         $roles = get_ldap_assignable_role_names();
 | 
        
           |  |  | 2232 |         foreach ($roles as $role) {
 | 
        
           |  |  | 2233 |             foreach (explode(';', $this->config->{$role['settingname']}) as $groupdn) {
 | 
        
           |  |  | 2234 |                 if (empty($groupdn)) {
 | 
        
           |  |  | 2235 |                     continue;
 | 
        
           |  |  | 2236 |                 }
 | 
        
           |  |  | 2237 |   | 
        
           |  |  | 2238 |                 $role['group'] = $groupdn;
 | 
        
           |  |  | 2239 |                 $message = $this->test_dn($ldapconn, $groupdn, 'diag_rolegroupnotfound', $role);
 | 
        
           |  |  | 2240 |                 if ($message !== true) {
 | 
        
           |  |  | 2241 |                     echo $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING);
 | 
        
           |  |  | 2242 |                 }
 | 
        
           |  |  | 2243 |             }
 | 
        
           |  |  | 2244 |         }
 | 
        
           |  |  | 2245 |   | 
        
           |  |  | 2246 |         $this->ldap_close(true);
 | 
        
           |  |  | 2247 |         // We were able to connect successfuly.
 | 
        
           |  |  | 2248 |         echo $OUTPUT->notification(get_string('connectingldapsuccess', 'auth_ldap'), \core\output\notification::NOTIFY_SUCCESS);
 | 
        
           |  |  | 2249 |     }
 | 
        
           |  |  | 2250 |   | 
        
           |  |  | 2251 |     /**
 | 
        
           |  |  | 2252 |      * Get the list of profile fields.
 | 
        
           |  |  | 2253 |      *
 | 
        
           |  |  | 2254 |      * @param   bool    $fetchall   Fetch all, not just those for update.
 | 
        
           |  |  | 2255 |      * @return  array
 | 
        
           |  |  | 2256 |      */
 | 
        
           |  |  | 2257 |     protected function get_profile_keys($fetchall = false) {
 | 
        
           |  |  | 2258 |         $keys = array_keys(get_object_vars($this->config));
 | 
        
           |  |  | 2259 |         $updatekeys = [];
 | 
        
           |  |  | 2260 |         foreach ($keys as $key) {
 | 
        
           |  |  | 2261 |             if (preg_match('/^field_updatelocal_(.+)$/', $key, $match)) {
 | 
        
           |  |  | 2262 |                 // If we have a field to update it from and it must be updated 'onlogin' we update it on cron.
 | 
        
           |  |  | 2263 |                 if (!empty($this->config->{'field_map_'.$match[1]})) {
 | 
        
           |  |  | 2264 |                     if ($fetchall || $this->config->{$match[0]} === 'onlogin') {
 | 
        
           |  |  | 2265 |                         array_push($updatekeys, $match[1]); // the actual key name
 | 
        
           |  |  | 2266 |                     }
 | 
        
           |  |  | 2267 |                 }
 | 
        
           |  |  | 2268 |             }
 | 
        
           |  |  | 2269 |         }
 | 
        
           |  |  | 2270 |   | 
        
           |  |  | 2271 |         if ($this->config->suspended_attribute && $this->config->sync_suspended) {
 | 
        
           |  |  | 2272 |             $updatekeys[] = 'suspended';
 | 
        
           |  |  | 2273 |         }
 | 
        
           |  |  | 2274 |   | 
        
           |  |  | 2275 |         return $updatekeys;
 | 
        
           |  |  | 2276 |     }
 | 
        
           |  |  | 2277 | }
 |