sso.TCC.class.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use ChamiloSession as Session;
  4. /**
  5. * This file contains the necessary elements to implement a Single Sign On.
  6. *
  7. INSERT INTO `settings_current` (`variable`, `type`, `category`, `selected_value`, `title`, `comment`, `access_url`, access_url_changeable)
  8. VALUES ('sso_authentication_subclass', 'textfield', 'Security', 'TCC', 'SSOSubclass', 'SSOSubclassComment', 1, 0);
  9. *
  10. * @package chamilo.auth.sso
  11. */
  12. /**
  13. * The SSO class allows for management of remote Single Sign On resources.
  14. */
  15. class ssoTCC
  16. {
  17. public $protocol; // 'http://',
  18. public $domain; // 'localhost/project/drupal',
  19. public $auth_uri; // '/?q=user',
  20. public $deauth_uri; // '/?q=logout',
  21. public $referer; // http://my.chamilo.com/main/auth/profile.php
  22. /**
  23. * Instanciates the object, initializing all relevant URL strings.
  24. */
  25. public function __construct()
  26. {
  27. $this->protocol = api_get_setting('sso_authentication_protocol');
  28. // There can be multiple domains, so make sure to take only the first
  29. // This might be later extended with a decision process
  30. $domains = explode('/,/', api_get_setting('sso_authentication_domain'));
  31. $this->domain = trim($domains[0]);
  32. $this->auth_uri = api_get_setting('sso_authentication_auth_uri');
  33. $this->deauth_uri = api_get_setting('sso_authentication_unauth_uri');
  34. //cut the string to avoid recursive URL construction in case of failure
  35. $this->referer = $this->protocol.$_SERVER['HTTP_HOST'].substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], 'sso'));
  36. $this->deauth_url = $this->protocol.$this->domain.$this->deauth_uri;
  37. $this->master_url = $this->protocol.$this->domain.$this->auth_uri;
  38. $this->referrer_uri = base64_encode($_SERVER['REQUEST_URI']);
  39. $this->target = api_get_path(WEB_PATH);
  40. }
  41. /**
  42. * Unlogs the user from the remote server.
  43. */
  44. public function logout()
  45. {
  46. $forceSsoRedirect = api_get_setting('sso_force_redirect');
  47. if ($forceSsoRedirect === 'true') {
  48. // no_redirect means Drupal sent the signal to logout. When redirecting to Drupal, the $_GET['stop'] param is
  49. // set to 1, to allow Drupal to know that this is it, the logout is already done in Chamilo and there's no
  50. // need to do it again
  51. if (empty($_GET['no_redirect'])) {
  52. header('Location: '.$this->deauth_url.'&stop=1');
  53. } else {
  54. header('Location: '.$this->protocol.$this->domain);
  55. }
  56. exit;
  57. }
  58. }
  59. /**
  60. * Sends the user to the master URL for a check of active connection.
  61. */
  62. public function ask_master()
  63. {
  64. // Generate a single usage token that must be encoded by the master
  65. $_SESSION['sso_challenge'] = api_generate_password(48);
  66. // Redirect browser to the master URL
  67. $params = '';
  68. if (empty($_GET['no_redirect'])) {
  69. $params = 'sso_referer='.urlencode($this->referer).
  70. '&sso_target='.urlencode($this->target).
  71. '&sso_challenge='.urlencode($_SESSION['sso_challenge']).
  72. '&sso_ruri='.urlencode($this->referrer_uri);
  73. if (strpos($this->master_url, "?") === false) {
  74. $params = "?{$params}";
  75. } else {
  76. $params = "&{$params}";
  77. }
  78. }
  79. header('Location: '.$this->master_url.$params);
  80. exit;
  81. }
  82. /**
  83. * Validates the received active connection data with the database.
  84. *
  85. * @return false|null Return the loginFailed variable value to local.inc.php
  86. */
  87. public function check_user()
  88. {
  89. global $_user;
  90. $loginFailed = false;
  91. //change the way we recover the cookie depending on how it is formed
  92. $sso = $this->decode_cookie($_REQUEST['sso_cookie']);
  93. $value = explode(';;', $sso);
  94. $ssoSecret = substr($value[1], 0, 5);
  95. $value = $value[0];
  96. $userExtraFieldValue = new ExtraFieldValue('user');
  97. $userData = $userExtraFieldValue->get_item_id_from_field_variable_and_field_value(
  98. 'tcc_user_id',
  99. $value
  100. );
  101. if ($userData) {
  102. $userId = $userData['item_id'];
  103. } else {
  104. return false;
  105. }
  106. //get token that should have been used and delete it
  107. //from session since it can only be used once
  108. $sso_challenge = '';
  109. if (isset($_SESSION['sso_challenge'])) {
  110. $sso_challenge = $_SESSION['sso_challenge'];
  111. unset($_SESSION['sso_challenge']);
  112. }
  113. // lookup the user in the main database
  114. $user_table = Database::get_main_table(TABLE_MAIN_USER);
  115. $sql = "SELECT id, username, password, auth_source, active, expiration_date, status
  116. FROM $user_table
  117. WHERE id = '".$userId."'";
  118. $result = Database::query($sql);
  119. if (Database::num_rows($result) > 0) {
  120. $uData = Database::fetch_array($result);
  121. //Check the user's password
  122. if ($uData['auth_source'] == PLATFORM_AUTH_SOURCE) {
  123. $secret = substr(api_get_security_key(), 0, 5);
  124. // Check if secret sent in sso is correct
  125. if ((string) $ssoSecret == (string) $secret) {
  126. //Check if the account is active (not locked)
  127. if ($uData['active'] == '1') {
  128. // check if the expiration date has not been reached
  129. if (empty($uData['expiration_date']) ||
  130. $uData['expiration_date'] > date('Y-m-d H:i:s') ||
  131. $uData['expiration_date'] == '0000-00-00 00:00:00'
  132. ) {
  133. //If Multiple URL is enabled
  134. if (api_get_multiple_access_url()) {
  135. //Check the access_url configuration setting if the user is registered in the access_url_rel_user table
  136. //Getting the current access_url_id of the platform
  137. $current_access_url_id = api_get_current_access_url_id();
  138. // my user is subscribed in these
  139. //sites: $my_url_list
  140. $my_url_list = api_get_access_url_from_user($uData['id']);
  141. } else {
  142. $current_access_url_id = 1;
  143. $my_url_list = [1];
  144. }
  145. $my_user_is_admin = UserManager::is_admin($uData['id']);
  146. if ($my_user_is_admin === false) {
  147. if (is_array($my_url_list) && count($my_url_list) > 0) {
  148. if (in_array($current_access_url_id, $my_url_list)) {
  149. // the user has permission to enter at this site
  150. $_user['user_id'] = $uData['id'];
  151. $_user = api_get_user_info($_user['user_id']);
  152. $_user['uidReset'] = true;
  153. Session::write('_user', $_user);
  154. Event::eventLogin($_user['user_id']);
  155. // Redirect to homepage
  156. $sso_target = '';
  157. if (!empty($sso['ruri'])) {
  158. //The referrer URI is *only* used if
  159. // the user credentials are OK, which
  160. // should be protection enough
  161. // against evil URL spoofing...
  162. $sso_target = api_get_path(WEB_PATH).base64_decode($sso['ruri']);
  163. } else {
  164. $sso_target = isset($sso['target']) ? $sso['target'] : api_get_path(WEB_PATH).'index.php';
  165. }
  166. header('Location: '.$sso_target);
  167. exit;
  168. } else {
  169. // user does not have permission for this site
  170. $loginFailed = true;
  171. Session::erase('_uid');
  172. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=access_url_inactive');
  173. exit;
  174. }
  175. } else {
  176. // there is no URL in the multiple
  177. // urls list for this user
  178. $loginFailed = true;
  179. Session::erase('_uid');
  180. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=access_url_inactive');
  181. exit;
  182. }
  183. } else {
  184. //Only admins of the "main" (first) Chamilo
  185. // portal can login wherever they want
  186. if (in_array(1, $my_url_list)) {
  187. //Check if this admin is admin on the
  188. // principal portal
  189. $_user['user_id'] = $uData['id'];
  190. $_user = api_get_user_info($_user['user_id']);
  191. $is_platformAdmin = $uData['status'] == COURSEMANAGER;
  192. Session::write('is_platformAdmin', $is_platformAdmin);
  193. Session::write('_user', $_user);
  194. Event::eventLogin($_user['user_id']);
  195. } else {
  196. //Secondary URL admin wants to login
  197. // so we check as a normal user
  198. if (in_array($current_access_url_id, $my_url_list)) {
  199. $_user['user_id'] = $uData['user_id'];
  200. $_user = api_get_user_info($_user['user_id']);
  201. Session::write('_user', $_user);
  202. Event::eventLogin($_user['user_id']);
  203. } else {
  204. $loginFailed = true;
  205. Session::erase('_uid');
  206. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=access_url_inactive');
  207. exit;
  208. }
  209. }
  210. }
  211. } else {
  212. // user account expired
  213. $loginFailed = true;
  214. Session::erase('_uid');
  215. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=account_expired');
  216. exit;
  217. }
  218. } else {
  219. //User not active
  220. $loginFailed = true;
  221. Session::erase('_uid');
  222. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=account_inactive');
  223. exit;
  224. }
  225. } else {
  226. //Secret sent through SSO is incorrect
  227. $loginFailed = true;
  228. Session::erase('_uid');
  229. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=wrong_password');
  230. exit;
  231. }
  232. } else {
  233. //Auth_source is wrong
  234. $loginFailed = true;
  235. Session::erase('_uid');
  236. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=wrong_authentication_source');
  237. exit;
  238. }
  239. } else {
  240. //No user by that login
  241. $loginFailed = true;
  242. Session::erase('_uid');
  243. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=user_not_found');
  244. exit;
  245. }
  246. return $loginFailed;
  247. }
  248. /**
  249. * Generate the URL for profile editing for a any user or the current user.
  250. *
  251. * @param int $userId Optional. The user id
  252. * @param bool $asAdmin Optional. Whether get the URL for the platform admin
  253. *
  254. * @return string If the URL is obtained return the drupal_user_id. Otherwise return false
  255. */
  256. public function generateProfileEditingURL($userId = 0, $asAdmin = false)
  257. {
  258. $userId = intval($userId);
  259. if (empty($userId)) {
  260. $userId = api_get_user_id();
  261. }
  262. $userExtraFieldValue = new ExtraFieldValue('user');
  263. $drupalUserIdData = $userExtraFieldValue->get_values_by_handler_and_field_variable(
  264. $userId,
  265. 'tcc_user_id'
  266. );
  267. // If this is an administrator, allow him to make some changes in
  268. // the Chamilo profile
  269. if ($asAdmin && api_is_platform_admin(true)) {
  270. return api_get_path(WEB_CODE_PATH)."admin/user_edit.php?user_id=$userId";
  271. }
  272. // If the user doesn't match a Drupal user, give the normal profile
  273. // link
  274. /* if ($drupalUserIdData === false) {
  275. return api_get_path(WEB_CODE_PATH) . 'auth/profile.php';
  276. }
  277. // In all other cases, generate a link to the Drupal profile edition
  278. $drupalUserId = $drupalUserIdData['value'];
  279. $url = "{$this->protocol}{$this->domain}/user/{$drupalUserId}/edit";
  280. return $url;
  281. */
  282. return api_get_path(WEB_CODE_PATH).'auth/profile.php';
  283. }
  284. /**
  285. * Decode the cookie (this function may vary depending on the
  286. * Single Sign On implementation.
  287. *
  288. * @param string $value
  289. *
  290. * @return array Parsed and unencoded cookie
  291. */
  292. private function decode_cookie($value)
  293. {
  294. $key = substr(api_get_security_key(), 0, 16);
  295. $ivsize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
  296. $iv = mcrypt_create_iv($ivsize, MCRYPT_RAND);
  297. $valuedecode = base64_decode($value);
  298. return mcrypt_decrypt(
  299. MCRYPT_RIJNDAEL_128,
  300. $key,
  301. $valuedecode,
  302. MCRYPT_MODE_ECB,
  303. $iv
  304. );
  305. }
  306. }