sloodle_session.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. <?php
  2. // This file is part of the Sloodle project (www.sloodle.org)
  3. /**
  4. * This file defines the primary API class, SloodleSession.
  5. *
  6. * @package sloodle
  7. * @copyright Copyright (c) 2008 Sloodle (various contributors)
  8. * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
  9. *
  10. * @contributor Peter R. Bloomfield
  11. */
  12. /** General functionality. */
  13. require_once(SLOODLE_LIBROOT.'/general.php');
  14. /** Request and response functionality. */
  15. require_once(SLOODLE_LIBROOT.'/io.php');
  16. /** User functionality. */
  17. require_once(SLOODLE_LIBROOT.'/user.php');
  18. /** Course functionality. */
  19. require_once(SLOODLE_LIBROOT.'/course.php');
  20. /** Sloodle Controller functionality. */
  21. require_once(SLOODLE_LIBROOT.'/controller.php');
  22. /** Module functionality. */
  23. require_once(SLOODLE_LIBROOT.'/modules.php');
  24. /** Plugin management. */
  25. require_once(SLOODLE_LIBROOT.'/plugins.php');
  26. /** Active Objects and their definitions. */
  27. require_once(SLOODLE_LIBROOT.'/object_configs.php');
  28. require_once(SLOODLE_LIBROOT.'/active_object.php');
  29. /**
  30. * The primary API class, which manages all other parts.
  31. * @package sloodle
  32. */
  33. class SloodleSession
  34. {
  35. // DATA //
  36. /**
  37. * Incoming HTTP request.
  38. * @var SloodleRequest
  39. * @access public
  40. */
  41. var $request = null;
  42. /**
  43. * Outgoing response - can be rendered to HTTP or as a string.
  44. * @var SloodleResponse
  45. * @access public
  46. */
  47. var $response = null;
  48. /**
  49. * Current user information.
  50. * @var SloodleUser
  51. * @access public
  52. */
  53. var $user = null;
  54. /**
  55. * The Sloodle course structure for the course this session is accessing
  56. * @var SloodleCourse
  57. * @access public
  58. */
  59. var $course = null;
  60. /**
  61. * The Sloodle module this session relates to, if any.
  62. * Note: this may be the base Sloodle module class, or a derivative.
  63. * @var SloodleModule
  64. * @access public
  65. */
  66. var $module = null;
  67. /**
  68. * A plugin manager to help give access to plugins for various features.
  69. * @var SloodlePluginManager
  70. * @access public
  71. */
  72. var $plugins = null;
  73. /**
  74. * A SloodleActiveObject object representing the in-world object making the request.
  75. * @var $active_object
  76. * @access public
  77. * In case of notecard configuration, this may be null.
  78. * (This should change in the future so that the server knows about all the objects it works with.)
  79. * TODO: The relationship between this and the controller could do with some work -
  80. * ... and some of the functionality in the controller should probably move to the active object.
  81. * ... but we can't fix that until we've made all objects be represented by and active object, even notecard ones.
  82. */
  83. var $active_object = null;
  84. // FUNCTIONS //
  85. /**
  86. * Constructor - initialises members
  87. * @param bool $process If true (default) then basic request data will be processed immediately. Otherwise, it can be done manually by calling $request->process_request_data()
  88. */
  89. function SloodleSession($process = true)
  90. {
  91. // Construct the different parts of the session, as far as possible
  92. $this->user = new SloodleUser($this);
  93. $this->response = new SloodleResponse();
  94. $this->request = new SloodleRequest($this);
  95. $this->course = new SloodleCourse();
  96. $this->plugins = new SloodlePluginManager($this);
  97. // Process the basic request data
  98. if ($process) $this->request->process_request_data();
  99. // Active Object loading is happening right before check_authorization.
  100. // It should probably be happening earlier...
  101. // This whole thing should probably be happening backwards:
  102. // Load up the active object, check it's OK, load it's controller, check it's active, load the course, etc.
  103. }
  104. /**
  105. * Constructs and loads the appropriate module part of the session.
  106. * Note that this function will fail if the current VLE user (in the $user member) does not have permission to access it.
  107. * @param string $type The expected type of module - function fails if type is not correctly matched
  108. * @param bool $db If true then the system will also try to load appropriate data from the database, as specified in the module ID request parameter
  109. * @param bool $require If true, then if something goes wrong, the script will be terminated with an error message
  110. * @param bool $override_access If true, then access can be gained to a module on a separate course from the current controller
  111. * @return bool True if successful, or false otherwise. (Note, if parameter $require was true, then the script will terminate before this function returns if something goes wrong)
  112. */
  113. function load_module($type, $db, $require = true, $override_access = false)
  114. {
  115. // If the database loading is requested, then make sure we have a parameter to load with
  116. $db_id = null;
  117. if ($db) {
  118. $db_id = $this->request->get_module_id($require);
  119. if ($db_id == null) return false;
  120. // Is access being overridden?
  121. if (!$override_access) {
  122. // No
  123. // Make sure we have a controller loaded
  124. if (!$this->course->is_loaded() || !$this->course->controller->is_loaded()) {
  125. if ($require) {
  126. $this->response->quick_output(-714, 'MODULE_INSTANCE', 'Access has not been authenticated through a Controller. Access prohibited.', false);
  127. exit();
  128. }
  129. return false;
  130. }
  131. // Does the specified module instance exist in this course?
  132. if (!sloodle_record_exists('course_modules', 'id', $db_id, 'course', $this->course->get_course_id())) {
  133. if ($require) {
  134. $this->response->quick_output(-714, 'MODULE_INSTANCE', 'Module not found in requested course.', false);
  135. exit();
  136. }
  137. return false;
  138. }
  139. }
  140. }
  141. // Construct the module
  142. $this->module = sloodle_load_module($type, $this, $db_id);
  143. if (!$this->module) {
  144. if ($require) {
  145. $this->response->quick_output(-601, 'MODULE', 'Failed to construct module object', false);
  146. exit();
  147. }
  148. return false;
  149. }
  150. }
  151. /**
  152. * Verifies security for the incoming request (but does not check user access).
  153. * Initially ensures that the request is coming in on a valid and enabled course/controller (rejects it if not).
  154. * The password is then checked, and it can handle prim-passwords and object-specific passwords.
  155. *
  156. * @param bool $require If true, the function will NOT return on authentication failure. Rather, it will terminate the script with an error message.
  157. * @return bool true if successful in authenticating the request, or false if not.
  158. */
  159. function authenticate_request( $require = true )
  160. {
  161. // Make sure that the request data has been processed
  162. if (!$this->request->is_request_data_processed()) {
  163. $this->request->process_request_data();
  164. }
  165. // Make sure the controller ID parameter was specified
  166. if ($this->request->get_controller_id($require) === null) return false;
  167. // Make sure we've got a valid course and controller object
  168. if (!$this->course->controller->is_loaded()) {
  169. if ($require) {
  170. $this->response->quick_output(-514, 'COURSE', 'Course controller could not be accessed.', false);
  171. exit();
  172. }
  173. return false;
  174. }
  175. if (!$this->course->is_loaded()) {
  176. if ($require) {
  177. $this->response->quick_output(-512, 'COURSE', 'Course could not be accessed.', false);
  178. exit();
  179. }
  180. return false;
  181. }
  182. // Make sure the course is available
  183. if (!$this->course->is_available()) {
  184. if ($require) {
  185. $this->response->quick_output(-513, 'COURSE', 'Course not available.', false);
  186. exit();
  187. }
  188. return false;
  189. }
  190. // Make sure the contrller is available
  191. if (!$this->course->controller->is_available()) {
  192. if ($require) {
  193. $this->response->quick_output(-514, 'COURSE', 'Course controller not available.', false);
  194. exit();
  195. }
  196. return false;
  197. }
  198. // Make sure the controller is enabled
  199. if (!$this->course->controller->is_enabled()) {
  200. if ($require) {
  201. $this->response->quick_output(-514, 'COURSE', 'Course controller disabled.', false);
  202. exit();
  203. }
  204. return false;
  205. }
  206. // Get the password parameter
  207. $password = $this->request->get_password($require);
  208. if ($password == null) {
  209. if ($require) {
  210. $this->response->quick_output(-212, 'OBJECT_AUTH', 'Prim Password cannot be empty.', false);
  211. exit();
  212. }
  213. return false;
  214. }
  215. // Does the password contain an object UUID?
  216. $parts = explode('|', $password);
  217. if (count($parts) >= 2) {
  218. $objuuid = $parts[0];
  219. $objpwd = $parts[1];
  220. // Make sure the password was provided
  221. if (empty($objpwd)) {
  222. if ($require) {
  223. $this->response->quick_output(-212, 'OBJECT_AUTH', 'Object-specific password not specified.', false);
  224. exit();
  225. }
  226. return false;
  227. }
  228. // Load up the active object, if there is one.
  229. // TODO: This should probably have happened earlier.
  230. $ao = new SloodleActiveObject();
  231. if ($ao->loadByUUID( $objuuid )) {
  232. $this->active_object = $ao;
  233. }
  234. if ($this->course->controller->check_authorisation($this->active_object, $objpwd)) {
  235. // Passed authorisation - make sure the object is registered as being still active
  236. $this->active_object->recordAccess();
  237. return true;
  238. }
  239. if ($require) {
  240. $this->response->quick_output(-213, 'OBJECT_AUTH', 'Object-specific password was invalid.', false);
  241. exit();
  242. }
  243. return false;
  244. }
  245. // Get the controller password
  246. $controllerpwd = $this->course->controller->get_password();
  247. // Prim Password access is disabled if no password has been specified
  248. if (strlen($controllerpwd) == 0) {
  249. if ($require) {
  250. $this->response->quick_output(-213, 'OBJECT_AUTH', 'Access to this Controller by prim password has been disabled.', false);
  251. exit();
  252. }
  253. return false;
  254. }
  255. // Check that the passwords match
  256. if ($password != $this->course->controller->get_password()) {
  257. if ($require) {
  258. $this->response->quick_output(-213, 'OBJECT_AUTH', 'Prim password was invalid.', false);
  259. exit();
  260. }
  261. return false;
  262. }
  263. return true;
  264. }
  265. /**
  266. * Verifies security for the incoming user-centric request.
  267. * This ensures that the identified object is authorised for user-centric activities with the specified user.
  268. * @param bool $require If TRUE (default) then the script will terminate with an error message on failure. Otherwise, it will return false on failure.
  269. * @return bool TRUE if successful, or FALSE on failure (unless parameter $require was TRUE).
  270. */
  271. function authenticate_user_request( $require = true )
  272. {
  273. // Get the avatar UUID parameter
  274. $avuuid = $this->request->get_avatar_uuid($require);
  275. if ($avuuid == null) {
  276. if ($require) {
  277. $this->response->quick_output(-212, 'OBJECT_AUTH', 'Avatar UUID required for user-centric request authentication.', false);
  278. exit();
  279. }
  280. return false;
  281. }
  282. // Get the password parameter
  283. $password = $this->request->get_password($require);
  284. if ($password == null) {
  285. if ($require) {
  286. $this->response->quick_output(-212, 'OBJECT_AUTH', 'Password cannot be empty.', false);
  287. exit();
  288. }
  289. return false;
  290. }
  291. // Does the password contain an object UUID?
  292. $parts = explode('|', $password);
  293. if (count($parts) < 2) {
  294. if ($require) {
  295. $this->response->quick_output(-212, 'OBJECT_AUTH', 'Expected UUID and password, separated by pipe character.', false);
  296. exit();
  297. }
  298. return false;
  299. }
  300. // Extract the parts
  301. $objuuid = $parts[0];
  302. $objpwd = $parts[1];
  303. // Make sure the password was provided
  304. if (empty($objpwd)) {
  305. if ($require) {
  306. $this->response->quick_output(-212, 'OBJECT_AUTH', 'Object-specific password cannot be empty.', false);
  307. exit();
  308. }
  309. return false;
  310. }
  311. // Attempt to retreive a record matching the avatar and object UUID's
  312. $rec = sloodle_get_record('sloodle_user_object', 'avuuid', $avuuid, 'objuuid', $objuuid);
  313. if (!$rec) {
  314. if ($require) {
  315. $this->response->quick_output(-216, 'OBJECT_AUTH', 'Object not found in database.', false);
  316. exit();
  317. }
  318. return false;
  319. }
  320. // Make sure the object is authorised
  321. if (empty($rec->authorised) || $rec->authorised == "0") {
  322. if ($require) {
  323. $this->response->quick_output(-214, 'OBJECT_AUTH', 'Object is not yet authorised.', false);
  324. exit();
  325. }
  326. return false;
  327. }
  328. // Make sure the passwords match
  329. if ($objpwd != $rec->password) {
  330. if ($require) {
  331. $this->response->quick_output(-213, 'OBJECT_AUTH', 'Object-specific password was invalid.', false);
  332. exit();
  333. }
  334. return false;
  335. }
  336. // Everything looks fine
  337. return true;
  338. }
  339. /**
  340. * Validates the user account and enrolment (ensures there is an avatar linked to a VLE account, and that the VLE account is enrolled in the current course).
  341. * Attempts auto-registration/enrolment if that is allowed and required, and logs-in the user.
  342. * Server access level is checked if it is specified in the request parameters.
  343. * If the request indicates that it relates to an object, then the validation fails.
  344. * Note: if you only require to ensure that an avatar is registered, then use {@link validate_avatar()}.
  345. * @param bool $require If true, the script will be terminated with an error message if validation fails
  346. * @param bool $suppress_autoreg If true, auto-registration will be completely suppressed for this function call
  347. * @param bool $suppress_autoenrol If true, auto-enrolment will be completely suppressed for this function call
  348. * @return bool Returns true if validation and/or autoregistration were successful. Returns false on failure (unless $require was true).
  349. * @see SloodleSession::validate_avatar()
  350. */
  351. function validate_user($require = true, $suppress_autoreg = false, $suppress_autoenrol = false)
  352. {
  353. // Is it an object request?
  354. if ($this->request->is_object_request()) {
  355. if ($require) {
  356. $this->response->quick_output(-301, 'USER_AUTH', 'Cannot validate object as user.', false);
  357. exit();
  358. }
  359. return false;
  360. }
  361. // Was a server access level specified in the request?
  362. $sal = $this->request->get_server_access_level(false);
  363. if ($sal != null) {
  364. // Check what level was specified
  365. $sal = (int)$sal;
  366. $allowed = false;
  367. $reason = 'Unknown.';
  368. switch ($sal) {
  369. case SLOODLE_SERVER_ACCESS_LEVEL_PUBLIC:
  370. // Always allowed
  371. $allowed = true;
  372. break;
  373. case SLOODLE_SERVER_ACCESS_LEVEL_COURSE:
  374. // Is a course already loaded?
  375. if (!$this->course->is_loaded()) {
  376. $reason = 'No course loaded.';
  377. break;
  378. }
  379. // Was a user account already fully loaded?
  380. if ($this->user->is_avatar_linked()) {
  381. // Is the user enrolled on the current course?
  382. if ($this->user->is_enrolled($this->course->get_course_id())) $allowed = true;
  383. else $reason = 'User not enrolled in course.';
  384. } else {
  385. $reason = 'User not registered on site.';
  386. }
  387. break;
  388. case SLOODLE_SERVER_ACCESS_LEVEL_SITE:
  389. // Was a user account already fully loaded?
  390. if ($this->user->is_avatar_linked()) $allowed = true;
  391. else $reason = 'User not registered on site.';
  392. break;
  393. case SLOODLE_SERVER_ACCESS_LEVEL_STAFF:
  394. // Is a course already loaded?
  395. if (!$this->course->is_loaded()) {
  396. $reason = 'No course loaded.';
  397. break;
  398. }
  399. // Was a user account already fully loaded?
  400. if ($this->user->is_avatar_linked()) {
  401. // Is the user staff on the current course?
  402. if ($this->user->is_staff($this->course->get_course_id())) $allowed = true;
  403. else $reason = 'User not staff in course.';
  404. } else {
  405. $reason = 'User not registered on site.';
  406. }
  407. break;
  408. default:
  409. // Unknown access level
  410. $reason = 'Access level not recognised';
  411. break;
  412. }
  413. // Was the user blocked by access level?
  414. if (!$allowed) {
  415. if ($require) {
  416. $this->response->quick_output(-331, 'USER_AUTH', $reason, false);
  417. exit();
  418. }
  419. return false;
  420. }
  421. }
  422. // REGISTRATION //
  423. // Make sure a the course is loaded
  424. if (!$this->course->is_loaded()) {
  425. if ($require) {
  426. $this->response->quick_output(-511, 'COURSE', 'Cannot validate user - no course data loaded.', false);
  427. exit();
  428. }
  429. return false;
  430. }
  431. // Is the user already loaded?
  432. if (!$this->user->is_avatar_linked())
  433. {
  434. // If an avatar is loaded, but the user isn't, then we probably have a deleted Moodle user
  435. if ($this->user->is_avatar_loaded() == true && $this->user->is_user_loaded() == false) {
  436. $this->response->quick_output(-301, 'USER_AUTH', 'Avatar linked to deleted user account', false);
  437. exit();
  438. }
  439. // Make sure avatar details were provided
  440. $uuid = $this->request->get_avatar_uuid(false);
  441. $avname = $this->request->get_avatar_name(false);
  442. // Is validation required?
  443. if ($require) {
  444. // Check the UUID
  445. if (empty($uuid)) {
  446. $this->response->quick_output(-311, 'USER_AUTH', 'User UUID required', false);
  447. exit();
  448. }
  449. // Check the name
  450. if (empty($avname)) {
  451. $this->response->quick_output(-311, 'USER_AUTH', 'Avatar name required', false);
  452. exit();
  453. }
  454. } else if (empty($uuid) || empty($avname)) {
  455. // If there was a problem, just stop
  456. return false;
  457. }
  458. // Ensure autoreg is not suppressed, and that it is permitted on that course and on the site
  459. if ($suppress_autoreg == true || $this->course->check_autoreg() == false) {
  460. if ($require) {
  461. $this->response->quick_output(-321, 'USER_AUTH', 'User not registered, and auto-registration of users was not permitted', false);
  462. exit();
  463. }
  464. return false;
  465. }
  466. // It is important that we also check auto-enrolment here.
  467. // If that is not enabled, but the call here requires it, then there is no point registering the user.
  468. if ($suppress_autoenrol == true || $this->course->check_autoenrol() == false) {
  469. if ($require) {
  470. $this->response->quick_output(-421, 'USER_ENROL', 'User not enrolled, and auto-enrolment of users was not permitted', false);
  471. exit();
  472. }
  473. return false;
  474. }
  475. // Is there an avatar loaded?
  476. if (!$this->user->is_avatar_loaded()) {
  477. // Add the avatar details, linked to imaginary user 0
  478. if (!$this->user->add_linked_avatar(0, $uuid, $avname)) {
  479. if ($require) {
  480. $this->response->quick_output(-322, 'USER_AUTH', 'Failed to add new avatar', false);
  481. exit();
  482. }
  483. return false;
  484. }
  485. }
  486. // If we reached here then we definitely have an avatar
  487. // Create a matching Moodle user
  488. $password = $this->user->autoregister_avatar_user();
  489. if ($password === FALSE) {
  490. if ($require) {
  491. $this->response->quick_output(-322, 'USER_AUTH', 'Failed to register new user account', false);
  492. exit();
  493. }
  494. return false;
  495. }
  496. // Add a side effect code to our response data
  497. $this->response->add_side_effect(322);
  498. // The user needs to be notified of their new username/password
  499. if (isset($_SERVER['HTTP_X_SECONDLIFE_OBJECT_KEY'])) {
  500. sloodle_login_notification($_SERVER['HTTP_X_SECONDLIFE_OBJECT_KEY'], $uuid, $this->user->get_username(), $password);
  501. }
  502. }
  503. // ENROLMENT //
  504. // Is the user already enrolled on the course?
  505. if (!$this->user->is_enrolled($this->course->get_course_id())) {
  506. // Ensure auto-enrolment is not suppressed, and that it is permitted on that course and on the site
  507. if ($suppress_autoenrol == true || $this->course->check_autoenrol() == false) {
  508. if ($require) {
  509. $this->response->quick_output(-421, 'USER_ENROL', 'Auto-enrolment of users was not permitted', false);
  510. exit();
  511. }
  512. return false;
  513. }
  514. // Attempt to enrol the user
  515. if (!$this->user->enrol()) {
  516. if ($require) {
  517. $this->response->quick_output(-422, 'USER_ENROL', 'Auto-enrolment failed', false);
  518. exit();
  519. }
  520. return false;
  521. }
  522. // Add a side effect code to our response data
  523. $this->response->add_side_effect(422);
  524. }
  525. // Make sure the user is logged-in
  526. return ($this->user->login());
  527. }
  528. function validate_requirements($interaction = 'default', $multiplier = 1) {
  529. if (!is_null($this->active_object)) {
  530. $error_messages = $this->active_object->requirement_failures( $interaction, $multiplier, $this->user->get_user_id(), $this->user->get_avatar_uuid());
  531. if(count($error_messages) > 0) {
  532. $this->response->quick_output(-1001, 'AWARDS', join($error_messages,','), false);
  533. exit();
  534. }
  535. }
  536. return true;
  537. }
  538. function process_interaction($interaction = 'default', $multiplier = 1) {
  539. if (!is_null($this->active_object)) {
  540. return $this->active_object->process_interaction( $interaction, $multiplier, $this->user->get_user_id(), $this->user->get_avatar_uuid() );
  541. }
  542. return false;
  543. }
  544. /**
  545. * Validate the avatar specified in the request, to ensure it is registered to a Moodle account.
  546. * (Also ensures that avatar details were in fact provided in the request).
  547. * This is effectively a less strict version of {@link validated_user()}, which also checks enrolment and such like.
  548. * This function will NOT perform auto-registration or auto-enrolment.
  549. * @param bool $require If true, the script will be terminated with an error message if validation fails
  550. * @return bool Returns true if validation was successful. Returns false on failure (unless $require was true).
  551. * @see SloodleSession::validate_user()
  552. */
  553. function validate_avatar( $require = true )
  554. {
  555. // Attempt to fetch avatar details
  556. $sloodleuuid = $this->request->get_avatar_uuid(false);
  557. $sloodleavname = $this->request->get_avatar_name(false);
  558. // We need at least one of the values
  559. if (empty($sloodleuuid) && empty($sloodleavname)) {
  560. if ($require) {
  561. $this->response->quick_output(-311, 'USER_AUTH', 'Require avatar UUID and/or name.', false);
  562. exit();
  563. }
  564. return false;
  565. }
  566. // Attempt to find an avatar matching the given details
  567. $rec = false;
  568. if (!empty($sloodleuuid)) $rec = sloodle_get_record('sloodle_users', 'uuid', $sloodleuuid);
  569. if (!$rec) $rec = sloodle_get_record('sloodle_users', 'avname', $sloodleavname);
  570. // Did we find a matching entry?
  571. if (!$rec) {
  572. // No - avatar is not validated
  573. if ($require) {
  574. $this->response->quick_output(-321, 'USER_AUTH', 'Require avatar UUID and/or name.', false);
  575. exit();
  576. }
  577. return false;
  578. }
  579. return true;
  580. }
  581. //... Add functions for verifying user access to resources?
  582. }
  583. ?>