sso.TCC.class.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. * @return null|false Return the loginFailed variable value to local.inc.php
  85. */
  86. public function check_user()
  87. {
  88. global $_user;
  89. $loginFailed = false;
  90. //change the way we recover the cookie depending on how it is formed
  91. $sso = $this->decode_cookie($_REQUEST['sso_cookie']);
  92. $value = explode(';;', $sso);
  93. $ssoSecret = substr($value[1], 0, 5);
  94. $value = $value[0];
  95. $userExtraFieldValue = new ExtraFieldValue('user');
  96. $userData = $userExtraFieldValue->get_item_id_from_field_variable_and_field_value(
  97. 'tcc_user_id',
  98. $value
  99. );
  100. if ($userData) {
  101. $userId = $userData['item_id'];
  102. } else {
  103. return false;
  104. }
  105. //get token that should have been used and delete it
  106. //from session since it can only be used once
  107. $sso_challenge = '';
  108. if (isset($_SESSION['sso_challenge'])) {
  109. $sso_challenge = $_SESSION['sso_challenge'];
  110. unset($_SESSION['sso_challenge']);
  111. }
  112. // lookup the user in the main database
  113. $user_table = Database::get_main_table(TABLE_MAIN_USER);
  114. $sql = "SELECT id, username, password, auth_source, active, expiration_date, status
  115. FROM $user_table
  116. WHERE id = '".$userId."'";
  117. $result = Database::query($sql);
  118. if (Database::num_rows($result) > 0) {
  119. $uData = Database::fetch_array($result);
  120. //Check the user's password
  121. if ($uData['auth_source'] == PLATFORM_AUTH_SOURCE) {
  122. $secret = substr(api_get_security_key(), 0, 5);
  123. // Check if secret sent in sso is correct
  124. if ((string) $ssoSecret == (string) $secret) {
  125. //Check if the account is active (not locked)
  126. if ($uData['active']=='1') {
  127. // check if the expiration date has not been reached
  128. if (empty($uData['expiration_date']) ||
  129. $uData['expiration_date'] > date('Y-m-d H:i:s') ||
  130. $uData['expiration_date']=='0000-00-00 00:00:00'
  131. ) {
  132. //If Multiple URL is enabled
  133. if (api_get_multiple_access_url()) {
  134. //Check the access_url configuration setting if the user is registered in the access_url_rel_user table
  135. //Getting the current access_url_id of the platform
  136. $current_access_url_id = api_get_current_access_url_id();
  137. // my user is subscribed in these
  138. //sites: $my_url_list
  139. $my_url_list = api_get_access_url_from_user($uData['id']);
  140. } else {
  141. $current_access_url_id = 1;
  142. $my_url_list = array(1);
  143. }
  144. $my_user_is_admin = UserManager::is_admin($uData['id']);
  145. if ($my_user_is_admin === false) {
  146. if (is_array($my_url_list) && count($my_url_list) > 0) {
  147. if (in_array($current_access_url_id, $my_url_list)) {
  148. // the user has permission to enter at this site
  149. $_user['user_id'] = $uData['id'];
  150. $_user = api_get_user_info($_user['user_id']);
  151. $_user['uidReset'] = true;
  152. Session::write('_user', $_user);
  153. Event::event_login($_user['user_id']);
  154. // Redirect to homepage
  155. $sso_target = '';
  156. if (!empty($sso['ruri'])) {
  157. //The referrer URI is *only* used if
  158. // the user credentials are OK, which
  159. // should be protection enough
  160. // against evil URL spoofing...
  161. $sso_target = api_get_path(WEB_PATH) . base64_decode($sso['ruri']);
  162. } else {
  163. $sso_target = isset($sso['target']) ? $sso['target'] : api_get_path(WEB_PATH) . 'index.php';
  164. }
  165. header('Location: '. $sso_target);
  166. exit;
  167. } else {
  168. // user does not have permission for this site
  169. $loginFailed = true;
  170. Session::erase('_uid');
  171. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=access_url_inactive');
  172. exit;
  173. }
  174. } else {
  175. // there is no URL in the multiple
  176. // urls list for this user
  177. $loginFailed = true;
  178. Session::erase('_uid');
  179. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=access_url_inactive');
  180. exit;
  181. }
  182. } else {
  183. //Only admins of the "main" (first) Chamilo
  184. // portal can login wherever they want
  185. if (in_array(1, $my_url_list)) {
  186. //Check if this admin is admin on the
  187. // principal portal
  188. $_user['user_id'] = $uData['id'];
  189. $_user = api_get_user_info($_user['user_id']);
  190. $is_platformAdmin = $uData['status'] == COURSEMANAGER;
  191. Session::write('is_platformAdmin', $is_platformAdmin);
  192. Session::write('_user', $_user);
  193. Event::event_login($_user['user_id']);
  194. } else {
  195. //Secondary URL admin wants to login
  196. // so we check as a normal user
  197. if (in_array($current_access_url_id, $my_url_list)) {
  198. $_user['user_id'] = $uData['user_id'];
  199. $_user = api_get_user_info($_user['user_id']);
  200. Session::write('_user', $_user);
  201. Event::event_login($_user['user_id']);
  202. } else {
  203. $loginFailed = true;
  204. Session::erase('_uid');
  205. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=access_url_inactive');
  206. exit;
  207. }
  208. }
  209. }
  210. } else {
  211. // user account expired
  212. $loginFailed = true;
  213. Session::erase('_uid');
  214. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=account_expired');
  215. exit;
  216. }
  217. } else {
  218. //User not active
  219. $loginFailed = true;
  220. Session::erase('_uid');
  221. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=account_inactive');
  222. exit;
  223. }
  224. } else {
  225. //Secret sent through SSO is incorrect
  226. $loginFailed = true;
  227. Session::erase('_uid');
  228. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=wrong_password');
  229. exit;
  230. }
  231. } else {
  232. //Auth_source is wrong
  233. $loginFailed = true;
  234. Session::erase('_uid');
  235. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=wrong_authentication_source');
  236. exit;
  237. }
  238. } else {
  239. //No user by that login
  240. $loginFailed = true;
  241. Session::erase('_uid');
  242. header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=user_not_found');
  243. exit;
  244. }
  245. return $loginFailed;
  246. }
  247. /**
  248. * Decode the cookie (this function may vary depending on the
  249. * Single Sign On implementation
  250. * @param string $value
  251. *
  252. * @return array Parsed and unencoded cookie
  253. */
  254. private function decode_cookie($value)
  255. {
  256. $key = substr(api_get_security_key(), 0, 16);
  257. $ivsize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
  258. $iv = mcrypt_create_iv($ivsize, MCRYPT_RAND);
  259. $valuedecode = base64_decode($value);
  260. return mcrypt_decrypt(
  261. MCRYPT_RIJNDAEL_128,
  262. $key,
  263. $valuedecode,
  264. MCRYPT_MODE_ECB,
  265. $iv
  266. );
  267. }
  268. /**
  269. * Generate the URL for profile editing for a any user or the current user
  270. * @param int $userId Optional. The user id
  271. * @param boolean $asAdmin Optional. Whether get the URL for the platform admin
  272. * @return string If the URL is obtained return the drupal_user_id. Otherwise return false
  273. */
  274. public function generateProfileEditingURL($userId = 0, $asAdmin = false)
  275. {
  276. $userId = intval($userId);
  277. if (empty($userId)) {
  278. $userId = api_get_user_id();
  279. }
  280. $userExtraFieldValue = new ExtraFieldValue('user');
  281. $drupalUserIdData = $userExtraFieldValue->get_values_by_handler_and_field_variable(
  282. $userId,
  283. 'tcc_user_id'
  284. );
  285. // If this is an administrator, allow him to make some changes in
  286. // the Chamilo profile
  287. if ($asAdmin && api_is_platform_admin(true)) {
  288. return api_get_path(WEB_CODE_PATH) . "admin/user_edit.php?user_id=$userId";
  289. }
  290. // If the user doesn't match a Drupal user, give the normal profile
  291. // link
  292. /* if ($drupalUserIdData === false) {
  293. return api_get_path(WEB_CODE_PATH) . 'auth/profile.php';
  294. }
  295. // In all other cases, generate a link to the Drupal profile edition
  296. $drupalUserId = $drupalUserIdData['value'];
  297. $url = "{$this->protocol}{$this->domain}/user/{$drupalUserId}/edit";
  298. return $url;
  299. */
  300. return api_get_path(WEB_CODE_PATH) . 'auth/profile.php';
  301. }
  302. }