PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/auth/shibboleth/auth.php

https://gitlab.com/JrLucena/moodle
PHP | 459 lines | 226 code | 73 blank | 160 comment | 59 complexity | 3fcc2b1d7969c3670fecaae02bdafe5a MD5 | raw file
  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. * Authentication Plugin: Shibboleth Authentication
  18. * Authentication using Shibboleth.
  19. *
  20. * Distributed under GPL (c)Markus Hagman 2004-2006
  21. *
  22. * @package auth_shibboleth
  23. * @author Martin Dougiamas
  24. * @author Lukas Haemmerle
  25. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  26. */
  27. defined('MOODLE_INTERNAL') || die();
  28. require_once($CFG->libdir.'/authlib.php');
  29. /**
  30. * Shibboleth authentication plugin.
  31. */
  32. class auth_plugin_shibboleth extends auth_plugin_base {
  33. /**
  34. * Constructor.
  35. */
  36. public function __construct() {
  37. $this->authtype = 'shibboleth';
  38. $this->config = get_config('auth/shibboleth');
  39. }
  40. /**
  41. * Old syntax of class constructor. Deprecated in PHP7.
  42. *
  43. * @deprecated since Moodle 3.1
  44. */
  45. public function auth_plugin_shibboleth() {
  46. debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
  47. self::__construct();
  48. }
  49. /**
  50. * Returns true if the username and password work and false if they are
  51. * wrong or don't exist.
  52. *
  53. * @param string $username The username (with system magic quotes)
  54. * @param string $password The password (with system magic quotes)
  55. * @return bool Authentication success or failure.
  56. */
  57. function user_login($username, $password) {
  58. global $SESSION;
  59. // If we are in the shibboleth directory then we trust the server var
  60. if (!empty($_SERVER[$this->config->user_attribute])) {
  61. // Associate Shibboleth session with user for SLO preparation
  62. $sessionkey = '';
  63. if (isset($_SERVER['Shib-Session-ID'])){
  64. // This is only available for Shibboleth 2.x SPs
  65. $sessionkey = $_SERVER['Shib-Session-ID'];
  66. } else {
  67. // Try to find out using the user's cookie
  68. foreach ($_COOKIE as $name => $value){
  69. if (preg_match('/_shibsession_/i', $name)){
  70. $sessionkey = $value;
  71. }
  72. }
  73. }
  74. // Set shibboleth session ID for logout
  75. $SESSION->shibboleth_session_id = $sessionkey;
  76. return (strtolower($_SERVER[$this->config->user_attribute]) == strtolower($username));
  77. } else {
  78. // If we are not, the user has used the manual login and the login name is
  79. // unknown, so we return false.
  80. return false;
  81. }
  82. }
  83. /**
  84. * Returns the user information for 'external' users. In this case the
  85. * attributes provided by Shibboleth
  86. *
  87. * @return array $result Associative array of user data
  88. */
  89. function get_userinfo($username) {
  90. // reads user information from shibboleth attributes and return it in array()
  91. global $CFG;
  92. // Check whether we have got all the essential attributes
  93. if ( empty($_SERVER[$this->config->user_attribute]) ) {
  94. print_error( 'shib_not_all_attributes_error', 'auth_shibboleth' , '', "'".$this->config->user_attribute."' ('".$_SERVER[$this->config->user_attribute]."'), '".$this->config->field_map_firstname."' ('".$_SERVER[$this->config->field_map_firstname]."'), '".$this->config->field_map_lastname."' ('".$_SERVER[$this->config->field_map_lastname]."') and '".$this->config->field_map_email."' ('".$_SERVER[$this->config->field_map_email]."')");
  95. }
  96. $attrmap = $this->get_attributes();
  97. $result = array();
  98. $search_attribs = array();
  99. foreach ($attrmap as $key=>$value) {
  100. // Check if attribute is present
  101. if (!isset($_SERVER[$value])){
  102. $result[$key] = '';
  103. continue;
  104. }
  105. // Make usename lowercase
  106. if ($key == 'username'){
  107. $result[$key] = strtolower($this->get_first_string($_SERVER[$value]));
  108. } else {
  109. $result[$key] = $this->get_first_string($_SERVER[$value]);
  110. }
  111. }
  112. // Provide an API to modify the information to fit the Moodle internal
  113. // data representation
  114. if (
  115. $this->config->convert_data
  116. && $this->config->convert_data != ''
  117. && is_readable($this->config->convert_data)
  118. ) {
  119. // Include a custom file outside the Moodle dir to
  120. // modify the variable $moodleattributes
  121. include($this->config->convert_data);
  122. }
  123. return $result;
  124. }
  125. /**
  126. * Returns array containg attribute mappings between Moodle and Shibboleth.
  127. *
  128. * @return array
  129. */
  130. function get_attributes() {
  131. $configarray = (array) $this->config;
  132. $moodleattributes = array();
  133. $userfields = array_merge($this->userfields, $this->get_custom_user_profile_fields());
  134. foreach ($userfields as $field) {
  135. if (isset($configarray["field_map_$field"])) {
  136. $moodleattributes[$field] = $configarray["field_map_$field"];
  137. }
  138. }
  139. $moodleattributes['username'] = $configarray["user_attribute"];
  140. return $moodleattributes;
  141. }
  142. function prevent_local_passwords() {
  143. return true;
  144. }
  145. /**
  146. * Returns true if this authentication plugin is 'internal'.
  147. *
  148. * @return bool
  149. */
  150. function is_internal() {
  151. return false;
  152. }
  153. /**
  154. * Returns true if this authentication plugin can change the user's
  155. * password.
  156. *
  157. * @return bool
  158. */
  159. function can_change_password() {
  160. return false;
  161. }
  162. /**
  163. * Hook for login page
  164. *
  165. */
  166. function loginpage_hook() {
  167. global $SESSION, $CFG;
  168. // Prevent username from being shown on login page after logout
  169. $CFG->nolastloggedin = true;
  170. return;
  171. }
  172. /**
  173. * Hook for logout page
  174. *
  175. */
  176. function logoutpage_hook() {
  177. global $SESSION, $redirect;
  178. // Only do this if logout handler is defined, and if the user is actually logged in via Shibboleth
  179. $logouthandlervalid = isset($this->config->logout_handler) && !empty($this->config->logout_handler);
  180. if (isset($SESSION->shibboleth_session_id) && $logouthandlervalid ) {
  181. // Check if there is an alternative logout return url defined
  182. if (isset($this->config->logout_return_url) && !empty($this->config->logout_return_url)) {
  183. // Set temp_redirect to alternative return url
  184. $temp_redirect = $this->config->logout_return_url;
  185. } else {
  186. // Backup old redirect url
  187. $temp_redirect = $redirect;
  188. }
  189. // Overwrite redirect in order to send user to Shibboleth logout page and let him return back
  190. $redirecturl = new moodle_url($this->config->logout_handler, array('return' => $temp_redirect));
  191. $redirect = $redirecturl->out();
  192. }
  193. }
  194. /**
  195. * Prints a form for configuring this authentication plugin.
  196. *
  197. * This function is called from admin/auth.php, and outputs a full page with
  198. * a form for configuring this plugin.
  199. *
  200. * @param array $page An object containing all the data for this page.
  201. */
  202. function config_form($config, $err, $user_fields) {
  203. include "config.html";
  204. }
  205. /**
  206. * Processes and stores configuration data for this authentication plugin.
  207. *
  208. *
  209. * @param object $config Configuration object
  210. */
  211. function process_config($config) {
  212. global $CFG;
  213. // set to defaults if undefined
  214. if (!isset($config->auth_instructions) or empty($config->user_attribute)) {
  215. $config->auth_instructions = get_string('auth_shib_instructions', 'auth_shibboleth', $CFG->wwwroot.'/auth/shibboleth/index.php');
  216. }
  217. if (!isset ($config->user_attribute)) {
  218. $config->user_attribute = '';
  219. }
  220. if (!isset ($config->convert_data)) {
  221. $config->convert_data = '';
  222. }
  223. if (!isset($config->changepasswordurl)) {
  224. $config->changepasswordurl = '';
  225. }
  226. if (!isset($config->login_name)) {
  227. $config->login_name = 'Shibboleth Login';
  228. }
  229. // Clean idp list
  230. if (isset($config->organization_selection) && !empty($config->organization_selection) && isset($config->alt_login) && $config->alt_login == 'on') {
  231. $idp_list = get_idp_list($config->organization_selection);
  232. if (count($idp_list) < 1){
  233. return false;
  234. }
  235. $config->organization_selection = '';
  236. foreach ($idp_list as $idp => $value){
  237. $config->organization_selection .= $idp.', '.$value[0].', '.$value[1]."\n";
  238. }
  239. }
  240. // save settings
  241. set_config('user_attribute', $config->user_attribute, 'auth/shibboleth');
  242. if (isset($config->organization_selection) && !empty($config->organization_selection)) {
  243. set_config('organization_selection', $config->organization_selection, 'auth/shibboleth');
  244. }
  245. set_config('logout_handler', $config->logout_handler, 'auth/shibboleth');
  246. set_config('logout_return_url', $config->logout_return_url, 'auth/shibboleth');
  247. set_config('login_name', $config->login_name, 'auth/shibboleth');
  248. set_config('convert_data', $config->convert_data, 'auth/shibboleth');
  249. set_config('auth_instructions', $config->auth_instructions, 'auth/shibboleth');
  250. set_config('changepasswordurl', $config->changepasswordurl, 'auth/shibboleth');
  251. // Overwrite alternative login URL if integrated WAYF is used
  252. if (isset($config->alt_login) && $config->alt_login == 'on'){
  253. set_config('alt_login', $config->alt_login, 'auth/shibboleth');
  254. set_config('alternateloginurl', $CFG->wwwroot.'/auth/shibboleth/login.php');
  255. } else {
  256. // Check if integrated WAYF was enabled and is now turned off
  257. // If it was and only then, reset the Moodle alternate URL
  258. if (isset($this->config->alt_login) and $this->config->alt_login == 'on'){
  259. set_config('alt_login', 'off', 'auth/shibboleth');
  260. set_config('alternateloginurl', '');
  261. }
  262. $config->alt_login = 'off';
  263. }
  264. // Check values and return false if something is wrong
  265. // Patch Anyware Technologies (14/05/07)
  266. if (($config->convert_data != '')&&(!file_exists($config->convert_data) || !is_readable($config->convert_data))){
  267. return false;
  268. }
  269. // Check if there is at least one entry in the IdP list
  270. if (isset($config->organization_selection) && empty($config->organization_selection) && isset($config->alt_login) && $config->alt_login == 'on'){
  271. return false;
  272. }
  273. return true;
  274. }
  275. /**
  276. * Cleans and returns first of potential many values (multi-valued attributes)
  277. *
  278. * @param string $string Possibly multi-valued attribute from Shibboleth
  279. */
  280. function get_first_string($string) {
  281. $list = explode( ';', $string);
  282. $clean_string = rtrim($list[0]);
  283. return $clean_string;
  284. }
  285. }
  286. /**
  287. * Sets the standard SAML domain cookie that is also used to preselect
  288. * the right entry on the local wayf
  289. *
  290. * @param IdP identifiere
  291. */
  292. function set_saml_cookie($selectedIDP) {
  293. if (isset($_COOKIE['_saml_idp']))
  294. {
  295. $IDPArray = generate_cookie_array($_COOKIE['_saml_idp']);
  296. }
  297. else
  298. {
  299. $IDPArray = array();
  300. }
  301. $IDPArray = appendCookieValue($selectedIDP, $IDPArray);
  302. setcookie ('_saml_idp', generate_cookie_value($IDPArray), time() + (100*24*3600));
  303. }
  304. /**
  305. * Prints the option elements for the select element of the drop down list
  306. *
  307. */
  308. function print_idp_list(){
  309. $config = get_config('auth/shibboleth');
  310. $IdPs = get_idp_list($config->organization_selection);
  311. if (isset($_COOKIE['_saml_idp'])){
  312. $idp_cookie = generate_cookie_array($_COOKIE['_saml_idp']);
  313. do {
  314. $selectedIdP = array_pop($idp_cookie);
  315. } while (!isset($IdPs[$selectedIdP]) && count($idp_cookie) > 0);
  316. } else {
  317. $selectedIdP = '-';
  318. }
  319. foreach($IdPs as $IdP => $data){
  320. if ($IdP == $selectedIdP){
  321. echo '<option value="'.$IdP.'" selected="selected">'.$data[0].'</option>';
  322. } else {
  323. echo '<option value="'.$IdP.'">'.$data[0].'</option>';
  324. }
  325. }
  326. }
  327. /**
  328. * Generate array of IdPs from Moodle Shibboleth settings
  329. *
  330. * @param string Text containing tuble/triple of IdP entityId, name and (optionally) session initiator
  331. * @return array Identifier of IdPs and their name/session initiator
  332. */
  333. function get_idp_list($organization_selection) {
  334. $idp_list = array();
  335. $idp_raw_list = explode("\n", $organization_selection);
  336. foreach ($idp_raw_list as $idp_line){
  337. $idp_data = explode(',', $idp_line);
  338. if (isset($idp_data[2]))
  339. {
  340. $idp_list[trim($idp_data[0])] = array(trim($idp_data[1]),trim($idp_data[2]));
  341. }
  342. elseif(isset($idp_data[1]))
  343. {
  344. $idp_list[trim($idp_data[0])] = array(trim($idp_data[1]));
  345. }
  346. }
  347. return $idp_list;
  348. }
  349. /**
  350. * Generates an array of IDPs using the cookie value
  351. *
  352. * @param string Value of SAML domain cookie
  353. * @return array Identifiers of IdPs
  354. */
  355. function generate_cookie_array($value) {
  356. // Decodes and splits cookie value
  357. $CookieArray = explode(' ', $value);
  358. $CookieArray = array_map('base64_decode', $CookieArray);
  359. return $CookieArray;
  360. }
  361. /**
  362. * Generate the value that is stored in the cookie using the list of IDPs
  363. *
  364. * @param array IdP identifiers
  365. * @return string SAML domain cookie value
  366. */
  367. function generate_cookie_value($CookieArray) {
  368. // Merges cookie content and encodes it
  369. $CookieArray = array_map('base64_encode', $CookieArray);
  370. $value = implode(' ', $CookieArray);
  371. return $value;
  372. }
  373. /**
  374. * Append a value to the array of IDPs
  375. *
  376. * @param string IdP identifier
  377. * @param array IdP identifiers
  378. * @return array IdP identifiers with appended IdP
  379. */
  380. function appendCookieValue($value, $CookieArray) {
  381. array_push($CookieArray, $value);
  382. $CookieArray = array_reverse($CookieArray);
  383. $CookieArray = array_unique($CookieArray);
  384. $CookieArray = array_reverse($CookieArray);
  385. return $CookieArray;
  386. }