PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/auth/cas/auth.php

https://bitbucket.org/ceu/moodle_demo
PHP | 1193 lines | 849 code | 26 blank | 318 comment | 186 complexity | 0b33b0f9e32dba0bbf655a0219fc8d24 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1

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

  1. <?php
  2. /**
  3. * @author Martin Dougiamas
  4. * @authro Jerome GUTIERREZ
  5. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  6. * @package moodle multiauth
  7. *
  8. * Authentication Plugin: CAS Authentication
  9. *
  10. * Authentication using CAS (Central Authentication Server).
  11. *
  12. * 2006-08-28 File created.
  13. */
  14. if (!defined('MOODLE_INTERNAL')) {
  15. die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
  16. }
  17. require_once($CFG->libdir.'/authlib.php');
  18. require_once($CFG->dirroot.'/auth/cas/CAS/CAS.php');
  19. /**
  20. * CAS authentication plugin.
  21. */
  22. class auth_plugin_cas extends auth_plugin_base {
  23. /**
  24. * Constructor.
  25. */
  26. function auth_plugin_cas() {
  27. $this->authtype = 'cas';
  28. $this->config = get_config('auth/cas');
  29. if (empty($this->config->ldapencoding)) {
  30. $this->config->ldapencoding = 'utf-8';
  31. }
  32. if (empty($this->config->user_type)) {
  33. $this->config->user_type = 'default';
  34. }
  35. $default = $this->ldap_getdefaults();
  36. //use defaults if values not given
  37. foreach ($default as $key => $value) {
  38. // watch out - 0, false are correct values too
  39. if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
  40. $this->config->{$key} = $value[$this->config->user_type];
  41. }
  42. }
  43. //hack prefix to objectclass
  44. if (empty($this->config->objectclass)) { // Can't send empty filter
  45. $this->config->objectclass='objectClass=*';
  46. } else if (strpos($this->config->objectclass, 'objectClass=') !== 0) {
  47. $this->config->objectclass = 'objectClass='.$this->config->objectclass;
  48. }
  49. }
  50. /**
  51. * Authenticates user againt CAS
  52. * Returns true if the username and password work and false if they are
  53. * wrong or don't exist.
  54. *
  55. * @param string $username The username
  56. * @param string $password The password
  57. * @return bool Authentication success or failure.
  58. */
  59. function user_login ($username, $password) {
  60. $this->connectCAS();
  61. return phpCAS::isAuthenticated() && (trim(moodle_strtolower(phpCAS::getUser())) == $username);
  62. }
  63. function prevent_local_passwords() {
  64. return true;
  65. }
  66. /**
  67. * Returns true if this authentication plugin is 'internal'.
  68. *
  69. * @return bool
  70. */
  71. function is_internal() {
  72. return false;
  73. }
  74. /**
  75. * Returns true if this authentication plugin can change the user's
  76. * password.
  77. *
  78. * @return bool
  79. */
  80. function can_change_password() {
  81. return false;
  82. }
  83. /**
  84. * authentication choice (CAS or other)
  85. * redirection to the CAS form or to login/index.php
  86. * for other authentication
  87. */
  88. function loginpage_hook() {
  89. global $frm;
  90. global $CFG;
  91. global $SESSION;
  92. $site = get_site();
  93. $CASform = get_string("CASform","auth");
  94. $username = optional_param("username");
  95. if (!empty($username)) {
  96. if (isset($SESSION->wantsurl) && (strstr($SESSION->wantsurl, 'ticket') ||
  97. strstr($SESSION->wantsurl, 'NOCAS'))) {
  98. unset($SESSION->wantsurl);
  99. }
  100. return;
  101. }
  102. // Test si cas activ� et param�tres non remplis
  103. if (empty($this->config->hostname)) {
  104. return;
  105. }
  106. // Connection to CAS server
  107. $this->connectCAS();
  108. if($this->config->certificate_check && $this->config->certificate_path){
  109. phpCAS::setCasServerCACert($this->config->certificate_path);
  110. }else{
  111. // Don't try to validate the server SSL credentials
  112. phpCAS::setNoCasServerValidation();
  113. }
  114. // Gestion de la connection CAS si acc�s direct d'un ent ou autre
  115. if (phpCAS::checkAuthentication()) {
  116. $frm->username=phpCAS::getUser();
  117. // if (phpCAS::getUser()=='esup9992')
  118. // $frm->username='erhar0062';
  119. $frm->password="passwdCas";
  120. return;
  121. }
  122. if (isset($_GET["loginguest"]) && ($_GET["loginguest"]== true)) {
  123. $frm->username="guest";
  124. $frm->password="guest";
  125. return;
  126. }
  127. if ($this->config->multiauth) {
  128. $authCAS = optional_param("authCAS");
  129. if ($authCAS=="NOCAS")
  130. return;
  131. // choice authentication form for multi-authentication
  132. // test pgtIou parameter for proxy mode (https connection
  133. // in background from CAS server to the php server)
  134. if ($authCAS!="CAS" && !isset($_GET["pgtIou"])) {
  135. $navlinks = array();
  136. $navlinks[] = array('name' => $CASform, 'link' => null, 'type' => 'misc');
  137. $navigation = build_navigation($navlinks);
  138. print_header("$site->fullname: $CASform", $site->fullname, $navigation);
  139. include($CFG->dirroot."/auth/cas/cas_form.html");
  140. print_footer();
  141. exit();
  142. }
  143. }
  144. // CAS authentication
  145. if (!phpCAS::isAuthenticated())
  146. {phpCAS::forceAuthentication();}
  147. }
  148. /**
  149. * logout from the cas
  150. *
  151. * This function is called from admin/auth.php
  152. *
  153. */
  154. function prelogout_hook() {
  155. global $CFG;
  156. if ($this->config->logoutcas ) {
  157. $backurl = $CFG->wwwroot;
  158. $this->connectCAS();
  159. phpCAS::logoutWithURL($backurl);
  160. }
  161. }
  162. /**
  163. * Connect to the cas (clientcas connection or proxycas connection
  164. *
  165. * This function is called from admin/auth.php
  166. *
  167. */
  168. function connectCAS() {
  169. global $PHPCAS_CLIENT;
  170. // mode proxy CAS
  171. if ( !is_object($PHPCAS_CLIENT) ) {
  172. // Make sure phpCAS doesn't try to start a new PHP session when connecting to the CAS server.
  173. if ($this->config->proxycas) {
  174. phpCAS::proxy($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri, false);
  175. }
  176. // mode client CAS
  177. else {
  178. phpCAS::client($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri, false);
  179. }
  180. }
  181. }
  182. /**
  183. * Prints a form for configuring this authentication plugin.
  184. *
  185. * This function is called from admin/auth.php, and outputs a full page with
  186. * a form for configuring this plugin.
  187. *
  188. * @param array $page An object containing all the data for this page.
  189. */
  190. function config_form($config, $err, $user_fields) {
  191. include 'config.html';
  192. }
  193. /**
  194. * A chance to validate form data, and last chance to
  195. * do stuff before it is inserted in config_plugin
  196. * @param object object with submitted configuration settings (without system magic quotes)
  197. * @param array $err array of error messages
  198. */
  199. function validate_form(&$form, &$err) {
  200. $certificate_path = trim($form->certificate_path);
  201. if ($form->certificate_check && empty($certificate_path)) {
  202. $err['certificate_path'] = get_string('auth_cas_certificate_path_empty', 'auth');
  203. }
  204. }
  205. /**
  206. * Returns the URL for changing the user's pw, or empty if the default can
  207. * be used.
  208. *
  209. * @return string
  210. */
  211. function change_password_url() {
  212. return "";
  213. }
  214. /**
  215. * returns predefined usertypes
  216. *
  217. * @return array of predefined usertypes
  218. */
  219. function ldap_suppported_usertypes() {
  220. $types = array();
  221. $types['edir']='Novell Edirectory';
  222. $types['rfc2307']='posixAccount (rfc2307)';
  223. $types['rfc2307bis']='posixAccount (rfc2307bis)';
  224. $types['samba']='sambaSamAccount (v.3.0.7)';
  225. $types['ad']='MS ActiveDirectory';
  226. $types['default']=get_string('default');
  227. return $types;
  228. }
  229. /**
  230. * Processes and stores configuration data for this authentication plugin.
  231. */
  232. function process_config($config) {
  233. // set to defaults if undefined
  234. // CAS settings
  235. if (!isset ($config->hostname))
  236. $config->hostname = '';
  237. if (!isset ($config->port))
  238. $config->port = '';
  239. if (!isset ($config->casversion))
  240. $config->casversion = '';
  241. if (!isset ($config->baseuri))
  242. $config->baseuri = '';
  243. if (!isset ($config->language))
  244. $config->language = '';
  245. if (!isset ($config->proxycas))
  246. $config->proxycas = '';
  247. if (!isset ($config->logoutcas))
  248. $config->logoutcas = '';
  249. if (!isset ($config->multiauth))
  250. $config->multiauth = '';
  251. if (!isset ($config->certificate_check))
  252. $config->certificate_check = '';
  253. if (!isset ($config->certificate_path))
  254. $config->certificate_path = '';
  255. // LDAP settings
  256. if (!isset($config->host_url))
  257. { $config->host_url = ''; }
  258. if (empty($config->ldapencoding))
  259. { $config->ldapencoding = 'utf-8'; }
  260. if (!isset($config->contexts))
  261. { $config->contexts = ''; }
  262. if (!isset($config->user_type))
  263. { $config->user_type = 'default'; }
  264. if (!isset($config->user_attribute))
  265. { $config->user_attribute = ''; }
  266. if (!isset($config->search_sub))
  267. { $config->search_sub = ''; }
  268. if (!isset($config->opt_deref))
  269. { $config->opt_deref = ''; }
  270. if (!isset($config->bind_dn))
  271. {$config->bind_dn = ''; }
  272. if (!isset($config->bind_pw))
  273. {$config->bind_pw = ''; }
  274. if (!isset($config->version))
  275. {$config->version = '2'; }
  276. if (!isset($config->objectclass))
  277. {$config->objectclass = ''; }
  278. if (!isset($config->memberattribute))
  279. {$config->memberattribute = ''; }
  280. if (!isset($config->memberattribute_isdn))
  281. {$config->memberattribute_isdn = ''; }
  282. if (!isset($config->attrcreators))
  283. {$config->attrcreators = ''; }
  284. if (!isset($config->groupecreators))
  285. {$config->groupecreators = ''; }
  286. if (!isset($config->removeuser))
  287. {$config->removeuser = 0; }
  288. // save CAS settings
  289. set_config('hostname', $config->hostname, 'auth/cas');
  290. set_config('port', $config->port, 'auth/cas');
  291. set_config('casversion', $config->casversion, 'auth/cas');
  292. set_config('baseuri', $config->baseuri, 'auth/cas');
  293. set_config('language', $config->language, 'auth/cas');
  294. set_config('proxycas', $config->proxycas, 'auth/cas');
  295. set_config('logoutcas', $config->logoutcas, 'auth/cas');
  296. set_config('multiauth', $config->multiauth, 'auth/cas');
  297. set_config('certificate_check', $config->certificate_check, 'auth/cas');
  298. set_config('certificate_path', $config->certificate_path, 'auth/cas');
  299. // save LDAP settings
  300. set_config('host_url', $config->host_url, 'auth/cas');
  301. set_config('ldapencoding', $config->ldapencoding, 'auth/cas');
  302. set_config('host_url', $config->host_url, 'auth/cas');
  303. set_config('contexts', $config->contexts, 'auth/cas');
  304. set_config('user_type', $config->user_type, 'auth/cas');
  305. set_config('user_attribute', $config->user_attribute, 'auth/cas');
  306. set_config('search_sub', $config->search_sub, 'auth/cas');
  307. set_config('opt_deref', $config->opt_deref, 'auth/cas');
  308. set_config('bind_dn', $config->bind_dn, 'auth/cas');
  309. set_config('bind_pw', $config->bind_pw, 'auth/cas');
  310. set_config('version', $config->version, 'auth/cas');
  311. set_config('objectclass', $config->objectclass, 'auth/cas');
  312. set_config('memberattribute', $config->memberattribute, 'auth/cas');
  313. set_config('memberattribute_isdn', $config->memberattribute_isdn, 'auth/cas');
  314. set_config('attrcreators', $config->attrcreators, 'auth/cas');
  315. set_config('groupecreators', $config->groupecreators, 'auth/cas');
  316. set_config('removeuser', $config->removeuser, 'auth/cas');
  317. return true;
  318. }
  319. /**
  320. * Initializes needed ldap variables for cas-module
  321. *
  322. * Uses names defined in ldap_supported_usertypes.
  323. * $default is first defined as:
  324. * $default['pseudoname'] = array(
  325. * 'typename1' => 'value',
  326. * 'typename2' => 'value'
  327. * ....
  328. * );
  329. *
  330. * @return array of default values
  331. */
  332. function ldap_getdefaults() {
  333. $default['objectclass'] = array(
  334. 'edir' => 'User',
  335. 'rfc2307' => 'posixAccount',
  336. 'rfc2307bis' => 'posixAccount',
  337. 'samba' => 'sambaSamAccount',
  338. 'ad' => 'user',
  339. 'default' => '*'
  340. );
  341. $default['user_attribute'] = array(
  342. 'edir' => 'cn',
  343. 'rfc2307' => 'uid',
  344. 'rfc2307bis' => 'uid',
  345. 'samba' => 'uid',
  346. 'ad' => 'cn',
  347. 'default' => 'cn'
  348. );
  349. $default['memberattribute'] = array(
  350. 'edir' => 'member',
  351. 'rfc2307' => 'member',
  352. 'rfc2307bis' => 'member',
  353. 'samba' => 'member',
  354. 'ad' => 'member',
  355. 'default' => 'member'
  356. );
  357. $default['memberattribute_isdn'] = array(
  358. 'edir' => '1',
  359. 'rfc2307' => '0',
  360. 'rfc2307bis' => '1',
  361. 'samba' => '0', //is this right?
  362. 'ad' => '1',
  363. 'default' => '0'
  364. );
  365. return $default;
  366. }
  367. /**
  368. * reads userinformation from ldap and return it in array()
  369. *
  370. * Read user information from external database and returns it as array().
  371. * Function should return all information available. If you are saving
  372. * this information to moodle user-table you should honor syncronization flags
  373. *
  374. * @param string $username username (with system magic quotes)
  375. *
  376. * @return mixed array with no magic quotes or false on error
  377. */
  378. function get_userinfo($username) {
  379. // No LDAP servers configured, so user info has to be provided
  380. // via other methods (CSV file, manually, etc.). Return empty
  381. // array so existing user info is not lost.
  382. if (empty($this->config->host_url)) {
  383. return array();
  384. }
  385. $textlib = textlib_get_instance();
  386. $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
  387. $ldapconnection = $this->ldap_connect();
  388. $attrmap = $this->ldap_attributes();
  389. $result = array();
  390. $search_attribs = array();
  391. foreach ($attrmap as $key=>$values) {
  392. if (!is_array($values)) {
  393. $values = array($values);
  394. }
  395. foreach ($values as $value) {
  396. if (!in_array($value, $search_attribs)) {
  397. array_push($search_attribs, $value);
  398. }
  399. }
  400. }
  401. $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
  402. if (!$user_info_result = ldap_read($ldapconnection, $user_dn, $this->config->objectclass, $search_attribs)) {
  403. return false; // error!
  404. }
  405. $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
  406. if (empty($user_entry)) {
  407. return false; // entry not found
  408. }
  409. foreach ($attrmap as $key=>$values) {
  410. if (!is_array($values)) {
  411. $values = array($values);
  412. }
  413. $ldapval = NULL;
  414. foreach ($values as $value) {
  415. if ($value == 'dn') {
  416. $result[$key] = $user_dn;
  417. }
  418. if (!array_key_exists(strtolower($value), $user_entry[0])) {
  419. continue; // wrong data mapping!
  420. }
  421. if (is_array($user_entry[0][strtolower($value)])) {
  422. $newval = $textlib->convert($user_entry[0][strtolower($value)][0], $this->config->ldapencoding, 'utf-8');
  423. } else {
  424. $newval = $textlib->convert($user_entry[0][strtolower($value)], $this->config->ldapencoding, 'utf-8');
  425. }
  426. if (!empty($newval)) { // favour ldap entries that are set
  427. $ldapval = $newval;
  428. }
  429. }
  430. if (!is_null($ldapval)) {
  431. $result[$key] = $ldapval;
  432. }
  433. }
  434. $this->ldap_close($ldapconnection);
  435. return $result;
  436. }
  437. /**
  438. * reads userinformation from ldap and return it in an object
  439. *
  440. * @param string $username username (with system magic quotes)
  441. * @return mixed object or false on error
  442. */
  443. function get_userinfo_asobj($username) {
  444. $user_array = $this->get_userinfo($username);
  445. if ($user_array == false) {
  446. return false; //error or not found
  447. }
  448. $user_array = truncate_userinfo($user_array);
  449. $user = new object();
  450. foreach ($user_array as $key=>$value) {
  451. $user->{$key} = $value;
  452. }
  453. return $user;
  454. }
  455. /**
  456. * connects to ldap server
  457. *
  458. * Tries connect to specified ldap servers.
  459. * Returns connection result or error.
  460. *
  461. * @return connection result
  462. */
  463. function ldap_connect($binddn='',$bindpwd='') {
  464. // Cache ldap connections (they are expensive to set up
  465. // and can drain the TCP/IP ressources on the server if we
  466. // are syncing a lot of users (as we try to open a new connection
  467. // to get the user details). This is the least invasive way
  468. // to reuse existing connections without greater code surgery.
  469. if(!empty($this->ldapconnection)) {
  470. $this->ldapconns++;
  471. return $this->ldapconnection;
  472. }
  473. //Select bind password, With empty values use
  474. //ldap_bind_* variables or anonymous bind if ldap_bind_* are empty
  475. if ($binddn == '' and $bindpwd == '') {
  476. if (!empty($this->config->bind_dn)) {
  477. $binddn = $this->config->bind_dn;
  478. }
  479. if (!empty($this->config->bind_pw)) {
  480. $bindpwd = $this->config->bind_pw;
  481. }
  482. }
  483. $urls = explode(";",$this->config->host_url);
  484. foreach ($urls as $server) {
  485. $server = trim($server);
  486. if (empty($server)) {
  487. continue;
  488. }
  489. $connresult = ldap_connect($server);
  490. //ldap_connect returns ALWAYS true
  491. if (!empty($this->config->version)) {
  492. ldap_set_option($connresult, LDAP_OPT_PROTOCOL_VERSION, $this->config->version);
  493. }
  494. if ($this->config->user_type == 'ad') {
  495. ldap_set_option($connresult, LDAP_OPT_REFERRALS, 0);
  496. }
  497. if (!empty($binddn)) {
  498. //bind with search-user
  499. //$debuginfo .= 'Using bind user'.$binddn.'and password:'.$bindpwd;
  500. $bindresult=ldap_bind($connresult, $binddn,$bindpwd);
  501. }
  502. else {
  503. //bind anonymously
  504. $bindresult=@ldap_bind($connresult);
  505. }
  506. if (!empty($this->config->opt_deref)) {
  507. ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref);
  508. }
  509. if ($bindresult) {
  510. // Set the connection counter so we can call PHP's ldap_close()
  511. // when we call $this->ldap_close() for the last 'open' connection.
  512. $this->ldapconns = 1;
  513. $this->ldapconnection = $connresult;
  514. return $connresult;
  515. }
  516. $debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>";
  517. }
  518. //If any of servers are alive we have already returned connection
  519. print_error('auth_ldap_noconnect_all','auth',$this->config->user_type);
  520. return false;
  521. }
  522. /**
  523. * disconnects from a ldap server
  524. *
  525. */
  526. function ldap_close() {
  527. $this->ldapconns--;
  528. if($this->ldapconns == 0) {
  529. @ldap_close($this->ldapconnection);
  530. unset($this->ldapconnection);
  531. }
  532. }
  533. /**
  534. * retuns user attribute mappings between moodle and ldap
  535. *
  536. * @return array
  537. */
  538. function ldap_attributes () {
  539. $moodleattributes = array();
  540. foreach ($this->userfields as $field) {
  541. if (!empty($this->config->{"field_map_$field"})) {
  542. $moodleattributes[$field] = $this->config->{"field_map_$field"};
  543. if (preg_match('/,/',$moodleattributes[$field])) {
  544. $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ?
  545. }
  546. }
  547. }
  548. $moodleattributes['username'] = $this->config->user_attribute;
  549. return $moodleattributes;
  550. }
  551. /**
  552. * retuns dn of username
  553. *
  554. * Search specified contexts for username and return user dn
  555. * like: cn=username,ou=suborg,o=org
  556. *
  557. * @param mixed $ldapconnection $ldapconnection result
  558. * @param mixed $username username (external encoding no slashes)
  559. *
  560. */
  561. function ldap_find_userdn ($ldapconnection, $extusername) {
  562. //default return value
  563. $ldap_user_dn = FALSE;
  564. //get all contexts and look for first matching user
  565. $ldap_contexts = explode(";",$this->config->contexts);
  566. if (!empty($this->config->create_context)) {
  567. array_push($ldap_contexts, $this->config->create_context);
  568. }
  569. foreach ($ldap_contexts as $context) {
  570. $context = trim($context);
  571. if (empty($context)) {
  572. continue;
  573. }
  574. if ($this->config->search_sub) {
  575. //use ldap_search to find first user from subtree
  576. $ldap_result = ldap_search($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute));
  577. }
  578. else {
  579. //search only in this context
  580. $ldap_result = ldap_list($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute));
  581. }
  582. $entry = ldap_first_entry($ldapconnection,$ldap_result);
  583. if ($entry) {
  584. $ldap_user_dn = ldap_get_dn($ldapconnection, $entry);
  585. break ;
  586. }
  587. }
  588. return $ldap_user_dn;
  589. }
  590. /**
  591. * Quote control characters in quoted "texts" used in ldap
  592. *
  593. * @param string
  594. */
  595. function ldap_addslashes($text) {
  596. $text = str_replace('\\', '\\\\', $text);
  597. $text = str_replace(array('"', "\0"),
  598. array('\\"', '\\00'), $text);
  599. return $text;
  600. }
  601. /**
  602. * returns all usernames from external database
  603. *
  604. * get_userlist returns all usernames from external database
  605. *
  606. * @return array
  607. */
  608. function get_userlist() {
  609. return $this->ldap_get_userlist("({$this->config->user_attribute}=*)");
  610. }
  611. /**
  612. * checks if user exists on external db
  613. *
  614. * @param string $username (with system magic quotes)
  615. */
  616. function user_exists($username) {
  617. $textlib = textlib_get_instance();
  618. $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->ldapencoding);
  619. //returns true if given username exist on ldap
  620. $users = $this->ldap_get_userlist("({$this->config->user_attribute}=".$this->filter_addslashes($extusername).")");
  621. return count($users);
  622. }
  623. /**
  624. * syncronizes user fron external db to moodle user table
  625. *
  626. * Sync is now using username attribute.
  627. *
  628. * Syncing users removes or suspends users that dont exists anymore in external db.
  629. * Creates new users and updates coursecreator status of users.
  630. *
  631. * @param int $bulk_insert_records will insert $bulkinsert_records per insert statement
  632. * valid only with $unsafe. increase to a couple thousand for
  633. * blinding fast inserts -- but test it: you may hit mysqld's
  634. * max_allowed_packet limit.
  635. * @param bool $do_updates will do pull in data updates from ldap if relevant
  636. */
  637. function sync_users ($bulk_insert_records = 1000, $do_updates = true) {
  638. global $CFG;
  639. if(empty($this->config->host_url)) {
  640. echo "No LDAP server configured for CAS! Syncing disabled.\n";
  641. return;
  642. }
  643. $textlib = textlib_get_instance();
  644. $droptablesql = array(); /// sql commands to drop the table (because session scope could be a problem for
  645. /// some persistent drivers like ODBTP (mssql) or if this function is invoked
  646. /// from within a PHP application using persistent connections
  647. // configure a temp table
  648. print "Configuring temp table\n";
  649. switch (strtolower($CFG->dbfamily)) {
  650. case 'mysql':
  651. $temptable = $CFG->prefix . 'extuser';
  652. $droptablesql[] = 'DROP TEMPORARY TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
  653. execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
  654. echo "Creating temp table $temptable\n";
  655. execute_sql('CREATE TEMPORARY TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username)) ENGINE=MyISAM', false);
  656. break;
  657. case 'postgres':
  658. $temptable = $CFG->prefix . 'extuser';
  659. $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
  660. execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
  661. echo "Creating temp table $temptable\n";
  662. $bulk_insert_records = 1; // no support for multiple sets of values
  663. execute_sql('CREATE TEMPORARY TABLE '. $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))', false);
  664. break;
  665. case 'mssql':
  666. $temptable = '#'.$CFG->prefix . 'extuser'; /// MSSQL temp tables begin with #
  667. $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
  668. execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
  669. echo "Creating temp table $temptable\n";
  670. $bulk_insert_records = 1; // no support for multiple sets of values
  671. execute_sql('CREATE TABLE ' . $temptable . ' (username VARCHAR(64), PRIMARY KEY (username))', false);
  672. break;
  673. case 'oracle':
  674. $temptable = $CFG->prefix . 'extuser';
  675. $droptablesql[] = 'TRUNCATE TABLE ' . $temptable; // oracle requires truncate before being able to drop a temp table
  676. $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
  677. execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
  678. echo "Creating temp table $temptable\n";
  679. $bulk_insert_records = 1; // no support for multiple sets of values
  680. execute_sql('CREATE GLOBAL TEMPORARY TABLE '.$temptable.' (username VARCHAR(64), PRIMARY KEY (username)) ON COMMIT PRESERVE ROWS', false);
  681. break;
  682. }
  683. print "Connecting to ldap...\n";
  684. $ldapconnection = $this->ldap_connect();
  685. if (!$ldapconnection) {
  686. $this->ldap_close($ldapconnection);
  687. print get_string('auth_ldap_noconnect','auth',$this->config->host_url);
  688. exit;
  689. }
  690. ////
  691. //// get user's list from ldap to sql in a scalable fashion
  692. ////
  693. // prepare some data we'll need
  694. $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
  695. $contexts = explode(";",$this->config->contexts);
  696. if (!empty($this->config->create_context)) {
  697. array_push($contexts, $this->config->create_context);
  698. }
  699. $fresult = array();
  700. foreach ($contexts as $context) {
  701. $context = trim($context);
  702. if (empty($context)) {
  703. continue;
  704. }
  705. begin_sql();
  706. if ($this->config->search_sub) {
  707. //use ldap_search to find first user from subtree
  708. $ldap_result = ldap_search($ldapconnection, $context,
  709. $filter,
  710. array($this->config->user_attribute));
  711. } else {
  712. //search only in this context
  713. $ldap_result = ldap_list($ldapconnection, $context,
  714. $filter,
  715. array($this->config->user_attribute));
  716. }
  717. if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) {
  718. do {
  719. $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute);
  720. $value = $textlib->convert($value[0], $this->config->ldapencoding, 'utf-8');
  721. array_push($fresult, $value);
  722. if (count($fresult) >= $bulk_insert_records) {
  723. $this->ldap_bulk_insert($fresult, $temptable);
  724. $fresult = array();
  725. }
  726. } while ($entry = ldap_next_entry($ldapconnection, $entry));
  727. }
  728. unset($ldap_result); // free mem
  729. // insert any remaining users and release mem
  730. if (count($fresult)) {
  731. $this->ldap_bulk_insert($fresult, $temptable);
  732. $fresult = array();
  733. }
  734. commit_sql();
  735. }
  736. /// preserve our user database
  737. /// if the temp table is empty, it probably means that something went wrong, exit
  738. /// so as to avoid mass deletion of users; which is hard to undo
  739. $count = get_record_sql('SELECT COUNT(username) AS count, 1 FROM ' . $temptable);
  740. $count = $count->{'count'};
  741. if ($count < 1) {
  742. print "Did not get any users from LDAP -- error? -- exiting\n";
  743. exit;
  744. } else {
  745. print "Got $count records from LDAP\n\n";
  746. }
  747. /// User removal
  748. // find users in DB that aren't in ldap -- to be removed!
  749. // this is still not as scalable (but how often do we mass delete?)
  750. if (!empty($this->config->removeuser)) {
  751. $sql = "SELECT u.id, u.username, u.email, u.auth
  752. FROM {$CFG->prefix}user u
  753. LEFT JOIN $temptable e ON u.username = e.username
  754. WHERE u.auth='cas'
  755. AND u.deleted=0
  756. AND e.username IS NULL";
  757. $remove_users = get_records_sql($sql);
  758. if (!empty($remove_users)) {
  759. print "User entries to remove: ". count($remove_users) . "\n";
  760. foreach ($remove_users as $user) {
  761. if ($this->config->removeuser == 2) {
  762. if (delete_user($user)) {
  763. echo "\t"; print_string('auth_dbdeleteuser', 'auth', array($user->username, $user->id)); echo "\n";
  764. } else {
  765. echo "\t"; print_string('auth_dbdeleteusererror', 'auth', $user->username); echo "\n";
  766. }
  767. } else if ($this->config->removeuser == 1) {
  768. $updateuser = new object();
  769. $updateuser->id = $user->id;
  770. $updateuser->auth = 'nologin';
  771. if (update_record('user', $updateuser)) {
  772. echo "\t"; print_string('auth_dbsuspenduser', 'auth', array($user->username, $user->id)); echo "\n";
  773. } else {
  774. echo "\t"; print_string('auth_dbsuspendusererror', 'auth', $user->username); echo "\n";
  775. }
  776. }
  777. }
  778. } else {
  779. print "No user entries to be removed\n";
  780. }
  781. unset($remove_users); // free mem!
  782. }
  783. /// Revive suspended users
  784. if (!empty($this->config->removeuser) and $this->config->removeuser == 1) {
  785. $sql = "SELECT u.id, u.username
  786. FROM $temptable e, {$CFG->prefix}user u
  787. WHERE e.username=u.username
  788. AND u.auth='nologin'";
  789. $revive_users = get_records_sql($sql);
  790. if (!empty($revive_users)) {
  791. print "User entries to be revived: ". count($revive_users) . "\n";
  792. begin_sql();
  793. foreach ($revive_users as $user) {
  794. $updateuser = new object();
  795. $updateuser->id = $user->id;
  796. $updateuser->auth = 'cas';
  797. if (update_record('user', $updateuser)) {
  798. echo "\t"; print_string('auth_dbreviveduser', 'auth', array($user->username, $user->id)); echo "\n";
  799. } else {
  800. echo "\t"; print_string('auth_dbrevivedusererror', 'auth', $user->username); echo "\n";
  801. }
  802. }
  803. commit_sql();
  804. } else {
  805. print "No user entries to be revived\n";
  806. }
  807. unset($revive_users);
  808. }
  809. /// User Updates - time-consuming (optional)
  810. if ($do_updates) {
  811. // narrow down what fields we need to update
  812. $all_keys = array_keys(get_object_vars($this->config));
  813. $updatekeys = array();
  814. foreach ($all_keys as $key) {
  815. if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
  816. // if we have a field to update it from
  817. // and it must be updated 'onlogin' we
  818. // update it on cron
  819. if ( !empty($this->config->{'field_map_'.$match[1]})
  820. and $this->config->{$match[0]} === 'onlogin') {
  821. array_push($updatekeys, $match[1]); // the actual key name
  822. }
  823. }
  824. }
  825. // print_r($all_keys); print_r($updatekeys);
  826. unset($all_keys); unset($key);
  827. } else {
  828. print "No updates to be done\n";
  829. }
  830. if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant
  831. $users = get_records_sql("SELECT u.username, u.id
  832. FROM {$CFG->prefix}user u
  833. WHERE u.deleted=0 AND u.auth='cas'");
  834. if (!empty($users)) {
  835. print "User entries to update: ". count($users). "\n";
  836. $sitecontext = get_context_instance(CONTEXT_SYSTEM);
  837. if (!empty($this->config->creators) and !empty($this->config->memberattribute)
  838. and $roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
  839. $creatorrole = array_shift($roles); // We can only use one, let's use the first one
  840. } else {
  841. $creatorrole = false;
  842. }
  843. begin_sql();
  844. $xcount = 0;
  845. $maxxcount = 100;
  846. foreach ($users as $user) {
  847. echo "\t"; print_string('auth_dbupdatinguser', 'auth', array($user->username, $user->id));
  848. if (!$this->update_user_record(addslashes($user->username), $updatekeys)) {
  849. echo " - ".get_string('skipped');
  850. }
  851. echo "\n";
  852. $xcount++;
  853. // update course creators if needed
  854. if ($creatorrole !== false) {
  855. if ($this->iscreator($user->username)) {
  856. role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
  857. } else {
  858. role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'cas');
  859. }
  860. }
  861. if ($xcount++ > $maxxcount) {
  862. commit_sql();
  863. begin_sql();
  864. $xcount = 0;
  865. }
  866. }
  867. commit_sql();
  868. unset($users); // free mem
  869. }
  870. } else { // end do updates
  871. print "No updates to be done\n";
  872. }
  873. /// User Additions
  874. // find users missing in DB that are in LDAP
  875. // note that get_records_sql wants at least 2 fields returned,
  876. // and gives me a nifty object I don't want.
  877. // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin
  878. $sql = "SELECT e.username, e.username
  879. FROM $temptable e LEFT JOIN {$CFG->prefix}user u ON e.username = u.username
  880. WHERE u.id IS NULL";
  881. $add_users = get_records_sql($sql); // get rid of the fat
  882. if (!empty($add_users)) {
  883. print "User entries to add: ". count($add_users). "\n";
  884. $sitecontext = get_context_instance(CONTEXT_SYSTEM);
  885. if (!empty($this->config->creators) and !empty($this->config->memberattribute)
  886. and $roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
  887. $creatorrole = array_shift($roles); // We can only use one, let's use the first one
  888. } else {
  889. $creatorrole = false;
  890. }
  891. begin_sql();
  892. foreach ($add_users as $user) {
  893. $user = $this->get_userinfo_asobj(addslashes($user->username));
  894. // prep a few params
  895. $user->modified = time();
  896. $user->confirmed = 1;
  897. $user->auth = 'cas';
  898. $user->mnethostid = $CFG->mnet_localhost_id;
  899. if (empty($user->lang)) {
  900. $user->lang = $CFG->lang;
  901. }
  902. $user = addslashes_recursive($user);
  903. if ($id = insert_record('user',$user)) {
  904. echo "\t"; print_string('auth_dbinsertuser', 'auth', array(stripslashes($user->username), $id)); echo "\n";
  905. $userobj = $this->update_user_record($user->username);
  906. if (!empty($this->config->forcechangepassword)) {
  907. set_user_preference('auth_forcepasswordchange', 1, $userobj->id);
  908. }
  909. } else {
  910. echo "\t"; print_string('auth_dbinsertusererror', 'auth', $user->username); echo "\n";
  911. }
  912. // add course creators if needed
  913. if ($creatorrole !== false and $this->iscreator(stripslashes($user->username))) {
  914. role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
  915. }
  916. }
  917. commit_sql();
  918. unset($add_users); // free mem
  919. } else {
  920. print "No users to be added\n";
  921. }
  922. $this->ldap_close();
  923. return true;
  924. }
  925. /**
  926. * Update a local user record from an external source.
  927. * This is a lighter version of the one in moodlelib -- won't do
  928. * expensive ops such as enrolment.
  929. *
  930. * If you don't pass $updatekeys, there is a performance hit and
  931. * values removed from LDAP won't be removed from moodle.
  932. *
  933. * @param string $username username (with system magic quotes)
  934. */
  935. function update_user_record($username, $updatekeys = false) {
  936. global $CFG;
  937. //just in case check text case
  938. $username = trim(moodle_strtolower($username));
  939. // get the current user record
  940. $user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id);
  941. if (empty($user)) { // trouble
  942. error_log("Cannot update non-existent user: ".stripslashes($username));
  943. print_error('auth_dbusernotexist','auth',$username);
  944. die;
  945. }
  946. // Protect the userid from being overwritten
  947. $userid = $user->id;
  948. if ($newinfo = $this->get_userinfo($username)) {
  949. $newinfo = truncate_userinfo($newinfo);
  950. if (empty($updatekeys)) { // all keys? this does not support removing values
  951. $updatekeys = array_keys($newinfo);
  952. }
  953. foreach ($updatekeys as $key) {
  954. if (isset($newinfo[$key])) {
  955. $value = $newinfo[$key];
  956. } else {
  957. $value = '';
  958. }
  959. if (!empty($this->config->{'field_updatelocal_' . $key})) {
  960. if ($user->{$key} != $value) { // only update if it's changed
  961. set_field('user', $key, addslashes($value), 'id', $userid);
  962. }
  963. }
  964. }
  965. } else {
  966. return false;
  967. }
  968. return get_record_select('user', "id = $userid AND deleted = 0");
  969. }
  970. /**
  971. * Bulk insert in SQL's temp table
  972. * @param array $users is an array of usernames
  973. */
  974. function ldap_bulk_insert($users, $temptable) {
  975. // bulk insert -- superfast with $bulk_insert_records
  976. $sql = 'INSERT INTO ' . $temptable . ' (username) VALUES ';
  977. // make those values safe
  978. $users = addslashes_recursive($users);
  979. // join and quote the whole lot
  980. $sql = $sql . "('" . implode("'),('", $users) . "')";
  981. print "\t+ " . count($users) . " users\n";
  982. execute_sql($sql, false);
  983. }
  984. /**
  985. * Returns true if user should be coursecreator.
  986. *
  987. * @param mixed $username username (without system magic quotes)
  988. * @return boolean result
  989. */
  990. function iscreator($username) {
  991. if (empty($this->config->host_url) or (empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) {
  992. return null;
  993. }
  994. $textlib = textlib_get_instance();
  995. $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
  996. //test for groupe creator
  997. if (!empty($this->config->groupecreators))
  998. if ((boolean)$this->ldap_isgroupmember($extusername, $this->config->groupecreators))
  999. return true;
  1000. //build filter for attrcreator
  1001. if (!empty($this->config->attrcreators)) {
  1002. $attrs = explode(";",$this->config->attrcreators);
  1003. $filter = "(& (".$this->config->user_attribute."=$username)(|";
  1004. foreach ($attrs as $attr){
  1005. if(strpos($attr, "="))
  1006. $filter .= "($attr)";
  1007. else
  1008. $filter .= "(".$this->config->memberattribute."=$attr)";
  1009. }
  1010. $filter .= "))";
  1011. //search
  1012. $result = $this->ldap_get_userlist($filter);
  1013. if (count($result)!=0)
  1014. return true;
  1015. }
  1016. return false;
  1017. }
  1018. /**
  1019. * checks if user belong to specific group(s)
  1020. *
  1021. * Returns true if user belongs group in grupdns string.
  1022. *
  1023. * @param mixed $username username
  1024. * @param mixed $groupdns string of group dn separated by ;
  1025. *
  1026. */
  1027. function ldap_isgroupmember($extusername='', $groupdns='') {
  1028. // Takes username and groupdn(s) , separated by ;
  1029. // Returns true if user is member of any given groups
  1030. $ldapconnection = $this->ldap_connect();
  1031. if (empty($extusername) or empty($groupdns)) {
  1032. return false;
  1033. }
  1034. if ($this->config->memberattribute_isdn) {
  1035. $memberuser = $this->ldap_find_userdn($ldapconnection, $extusername);
  1036. } else {
  1037. $memberuser = $extusername;
  1038. }
  1039. if (empty($memberuser)) {
  1040. return false;
  1041. }
  1042. $groups = explode(";",$groupdns);
  1043. $result = false;
  1044. foreach ($groups as $group) {
  1045. $group = trim($group);
  1046. if (empty($group)) {
  1047. continue;
  1048. }
  1049. //echo "Checking group $group for member $username\n";
  1050. $search = ldap_read($ldapconnection, $group, '('.$this->config->memberattribute.'='.$this->filter_addslashes($memberuser).')', array($this->config->memberattribute));
  1051. if (!empty($search) and ldap_count_entries($ldapconnection, $search)) {
  1052. $info = $this->ldap_get_entries($ldapconnection, $search);
  1053. if (count($info) > 0 ) {
  1054. // user is member of group
  1055. $result = true;
  1056. break;
  1057. }
  1058. }
  1059. }
  1060. $this->ldap_close();
  1061. return $result;
  1062. }
  1063. /**
  1064. * return all usernames from ldap
  1065. *
  1066. * @return array
  1067. */
  1068. function ldap_get_userlist($filter="*") {
  1069. /// returns all users from ldap servers
  1070. $fresult = array();
  1071. $ldapconnection = $this->ldap_connect();
  1072. if ($filter=="*") {
  1073. $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
  1074. }
  1075. $contexts = explode(";",$this->config->contexts);
  1076. if (!empty($this->config->create_context)) {
  1077. array_push($contexts, $this->config->create_context);
  1078. }
  1079. foreach ($contexts as $context) {
  1080. $context = trim($context);
  1081. if (empty($context)) {
  1082. continue;
  1083. }
  1084. if ($this->config->search_sub) {
  1085. //use ldap_search to find first user from subtree
  1086. $ldap_result = ldap_search($ldapconnection, $context,$filter,array($this->config->user_attribute));
  1087. }
  1088. else {
  1089. //search only in this context
  1090. $ldap_result = ldap_list($ldapconnection, $context,
  1091. $filter,
  1092. array($this->config->user_attribute));
  1093. }
  1094. $users = $this->ldap_get_entries($ldapconnection, $ldap_result);
  1095. //add found users to list
  1096. for ($i=0;$i<count($users);$i++) {
  1097. array_push($fresult, ($users[$i][$this->config->user_attribute][0]) );
  1098. }
  1099. }
  1100. $this->ldap_close();
  1101. return $fresult;
  1102. }
  1103. /**
  1104. * return entries from ldap
  1105. *
  1106. * Returns values like ldap_get_entries but is
  1107. * binary compatible and return all attributes as array
  1108. *
  1109. * @return array ldap-entries
  1110. */
  1111. function ldap_get_entries($conn, $searchresult) {
  1112. //Returns values like ldap_get_entries but is
  1113. //binary compatible
  1114. $i=0;
  1115. $fresult=array();
  1116. $entry = ldap_first_entry($conn, $searchresult);
  1117. do {
  1118. $attributes = @ldap_get_attributes($conn, $entry);
  1119. for ($j=0; $j<$attributes['count']; $j++) {
  1120. $values = ldap_get_values_len($conn, $entry,$attributes[$j]);
  1121. if (is_array($values)) {
  1122. $fresult[$i][strtolower($attributes[$j])] = $values;
  1123. }
  1124. else {
  1125. $fresult[$i][strtolower($attributes[$j])] = array($values);
  1126. }
  1127. }
  1128. $i++;
  1129. }
  1130. while ($entry = @ldap_next_entry($conn, $entry));
  1131. //were done
  1132. return ($fresult);
  1133. }
  1134. /**
  1135. * Sync roles for this user
  1136. *
  1137. * @param $user object user object (without system magic quotes)
  1138. */
  1139. function sync_roles($user) {
  1140. $iscreator = $this->iscreator($user->username);
  1141. if ($iscreator === null) {
  1142. return; //nothing to sync - creators not configured
  1143. }
  1144. if ($roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
  1145. $creatorrole = arr

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