ilm_security.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. /**
  3. * This file is used to allow a more secure access to the iLM file contents.
  4. * The iLM must get the parameter "iLM_PARAM_Assignment" to load the iLM content,
  5. * see: https://www.matematica.br/ihanoi/ima.html
  6. * After iAssign received the requisition to the iLM content, it is erased (avoiding
  7. * the learner coud direclty access it).
  8. * Under this security process, iLM can even hide in the file content the template answer,
  9. * as iGeom Java does.
  10. *
  11. * ATTENTION: DO NOT USE any 'print' (or 'echo') other then the one that print the iLM file content.
  12. * Otherwise the iLM will receive this printed message as its file (probably nothing will be shown).
  13. *
  14. * How:
  15. * The principle is to allow a single access to the file content, providing a "token" that is erased on the first use.
  16. * Every access to the content, by any iLM, must be provided by this vehicle.
  17. *
  18. * Why:
  19. * The iLM must requires the file content by a GET connection. But if the file content is opened,
  20. * this means that the user (usually the learner) can get access to it by copying the URL directly.
  21. * In this case, if the iLM is based on "model answer" (like iGeom), the learner can open a local version of iLM
  22. * with this "model answer" (iGeom provides a special format to exercises to avoid this).
  23. *
  24. * Table 'iassign_security': id iassign_statementid userid file timecreated view
  25. *
  26. * TODO : the insertion in 'iassign_security' table must be provided by functions inside this code (not in './mod/iassign/locallib.php'
  27. *
  28. * @author Leo^nidas de Oliveira Branda~o - Computer Science Dep. of IME-USP (Brazil)
  29. * @author Patricia Alves Rodrigues
  30. * @version v 1.1 2017/11/02: fixed error in '$stringDebugAuxFile = "";' (it was with ".=")
  31. * @version v 1.0 2010/12/10
  32. * @package mod_iassign_ilm
  33. * @since 2012/03/10
  34. * iMath/iMatica : www.matematica.br
  35. * LInE : www.usp.br/line ; line.ime.usp.br
  36. *
  37. * <b>License</b>
  38. * - http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39. */
  40. require_once("../../config.php");
  41. global $DB;
  42. //Debug: debug iLM security scheme
  43. //Debug: ATTENTION, this requests the directory './mod/iassign/ilm_debug/' with write permition to www-data !!!!
  44. $DEBUG = 0; //Debug: help to debug, register data in file 'MOODLE/mod/iassign/ilm_debug/YYYY_mm_dd_m_s_int'
  45. class ilm_security {
  46. // Table 'iassign_security' : id iassign_statementid userid file timecreated view
  47. /// Since we are using {iassign_security}.file (to store the iLM text content) with as 'utf8mb4_unicode_ci'
  48. /// to avoid error "Error writing to database"!"Incorrect string value" lets ensure to use UTF-8
  49. // Considering only 'ISO-8859-1'
  50. static function convert2utf8 ($strcontent) {
  51. // $current_encoding = mb_detect_encoding($strcontent, ['ASCII', 'UTF-8', 'ISO-8859-1']); // 'ASCII', 'UTF-8' and 'ISO-8859-1'
  52. $current_encoding = mb_detect_encoding($strcontent, 'ISO-8859-1'); // 'ASCII', 'UTF-8' and 'ISO-8859-1'
  53. $converted_text_to_utf8 = iconv($current_encoding, 'UTF-8', $strcontent); // converted to UTF-8
  54. //D echo "ilm_security.php:<br/>current_encoding=" . $current_encoding . ", new_encoding=" . $new_encoding . "<br/>";
  55. //D echo "strcontent[0,200]=" . substr($strcontent, 0, 200) . "<br/>";
  56. //D echo "converted_text_to_utf8[0,200]=" . substr($converted_text_to_utf8, 0, 200) . "<br/>---<br/>";
  57. return $converted_text_to_utf8;
  58. }
  59. /// Warning message
  60. static function warning_message_iassign ($strcode) { // errado no 'locallib.php' sempre com constante 'error_view_without_actiontype'!!!
  61. return "<div classs='warning' style='display:inline; font-weight: bold; color:#a00'>" . get_string($strcode, 'iassign') . "</div>\n";
  62. }
  63. // @calledby here : after print $strFileContent;
  64. static function remove_records ($userid) { //, $iassign_statementid
  65. if (!isset($userid) || !isset($iassign_statementid)) {
  66. // self::warning_message_iassign('???');
  67. print self::warning_message_iassign('error_security_no_userid'); // 'Internal error: must be informed the user identification. Inform the Administrator.'
  68. return null;
  69. }
  70. if (!isset($iassign_statementid) || $iassign_statementid<1)
  71. $array_param = array("userid" => $userid); // erase all entries of this user
  72. else
  73. $array_param = array("userid" => $userid, "iassign_statementid" => $iassign_statementid);
  74. $DB->delete_records("iassign_security", $array_param); // erase only for this iAssign activity
  75. }
  76. // @calledby locallib.php : class ilm : function view_iLM($iassign_statement_activity_item, $student_answer, $enderecoPOST, $view)
  77. static function remove_old_iLM_security_entries ($userid) { // substituir 'locallib' de mesmo nome!
  78. global $DB;
  79. // This is an additional security: erase eventually old entries in 'iassign_security' table (do not remove '$iassign_statementid' since it is going to be used "now")
  80. $result = $DB->delete_records_select("iassign_security", "userid=" . $userid . " AND view>1", null);
  81. }
  82. /// Function to give a single access to an iLM content avoi (after used, 'view()', after 'view_iLM(...)', will erase the entry)
  83. // @calledby locallib.php : class ilm_manager : function preview_ilm($courseid, $iassign_ilm): $id_iLM_security = ilm_security::write_iLM_security($USER->id, $timecreated, -1, $content_or_id_from_ilm_security);
  84. // @param int $iassign_statement_activity_itemid Id of iassign statement, when from iLM 'preview' (there is none activity), -1
  85. // @param Object $file File in use in activity
  86. // @return int Return the id of log
  87. static function write_iLM_security ($userid, $timecreated, $iassign_statementid = -1, $content_or_id_from_ilm_security = "") {
  88. global $DB;
  89. $newentry = new stdClass();
  90. $newentry->iassign_statementid = $iassign_statementid; // when came from iLM previw => there is none activity, use -1
  91. $newentry->userid = $userid;
  92. //_ $newentry->file = $content_or_id_from_ilm_security;
  93. if (!$content_or_id_from_ilm_security)
  94. return null; // avoid error trying to load empty content
  95. $newentry->file = ilm_security::convert2utf8($content_or_id_from_ilm_security); // must be utf8mb4_unicode_ci
  96. $newentry->timecreated = $timecreated; // who calls will generate: $timecreated = time(); $token == md5($timecreated);
  97. $newentry->view = 1;
  98. $id_iLM_security = $DB->insert_record("iassign_security", $newentry); // insert into {iassign_security}
  99. if (!$id_iLM_security) {
  100. print_error('error_security', 'iassign'); // ./lib/setuplib.php: moodle_exception thrown
  101. }
  102. return $id_iLM_security;
  103. }
  104. } // class ilm_security
  105. $view = optional_param('view', NULL, PARAM_TEXT); //$view = $_GET['view'];
  106. $token = optional_param('token', NULL, PARAM_TEXT); //$token = $_GET['token'];
  107. $id = optional_param('id', NULL, PARAM_TEXT); //$id = $_GET['id']; //id of the table iassign_security
  108. $stringDebugAux = "";
  109. $strFileContent = "";
  110. //DEBUG
  111. if ($DEBUG) {
  112. $file_name = "ilm_debug/" . date('Y') . "_" . date('m') . "_" . date('d') . "_" . date('H_i') . "_" . $id;
  113. $file_debug = "id=" . $id . "<br/>\nview=" . $view . "<br/>\ntoken=" . $token;
  114. $stringDebugAux = "user.id=" . $USER->id . ", user.name=" . $USER->firstname . " " . $USER->lastname . "\n";
  115. }
  116. if ($view == -1) { // view free
  117. $fs = get_file_storage();
  118. $file = $fs->get_file_by_id($id);
  119. $strFileContent .= $file->get_content();
  120. $stringDebugAux .= "1: file content:" . $strFileContent; //DEBUG
  121. print $strFileContent;
  122. ilm_security::remove_old_iLM_security_entries($USER->id); // for security reason erase the used entry in 'iassign_security' (and others for this user/activity)
  123. }
  124. else {
  125. // Get data from table 'iassign_security'
  126. $iassign_security = $DB->get_record("iassign_security", array("id" => $id)); // id iassign_statementid userid file timecreated view
  127. if ($DEBUG) { //DEBUG
  128. $strAux = "iassign_security = { id=" . $iassign_security->id . ", " . $iassign_security->iassign_statementid . ", | " . $iassign_security->file . " |, " . $iassign_security->view . " }";
  129. $stringDebugAux .= $strAux;
  130. }
  131. if ($iassign_security) {
  132. $fileid = $iassign_security->file;
  133. if ($iassign_security) { //TODO must be 'if ($fileid)'?
  134. $update = new stdClass();
  135. $update->id = $iassign_security->id;
  136. $update->view = $iassign_security->view + 1;
  137. $DB->update_record("iassign_security", $update);
  138. if ($DEBUG) $stringDebugAux .= " view++ = " . $iassign_security->view . "\n";
  139. if ($update->view >= 2 && $token == md5($iassign_security->timecreated)) { //
  140. // Security iLM: remove the entry
  141. if ($view) {
  142. // If it is view of the exercise, then get it on the Moodle data (usually '/var/moodledata') => file is a number = '*_files.id'
  143. // If it is learner answer get it in data base => file is the iLM file content
  144. // $stringDebugAuxFile = ""; //Debug
  145. $fs = get_file_storage();
  146. $file_moodledata = $fs->get_file_by_id($fileid);
  147. $strFileContent = $file_moodledata->get_content();
  148. $stringDebugAuxFile = $file_moodledata->get_filename() . "/"; //Debug
  149. if ($DEBUG) { //DEBUG
  150. $stringDebugAux .= "view>=2: view=$view: update->view=" . $update->view . "\n" . $token . "=" . md5($iassign_security->timecreated) . "?\n";
  151. }
  152. } // if ($update->view == 2 && $token == md5($iassign_security->timecreated))
  153. else { // not view - get the student content answer
  154. // *_iassign_security : id iassign_statementid userid file timecreated view (where 'file' is longtext utf8_unicode_ci)
  155. $strFileContent = $iassign_security->file; //ERROR: use some filter, remove '.', '/' and other special characters
  156. //$strFileContent = $contextid; - this also do NOT work!!
  157. if ($DEBUG) { //DEBUG
  158. $stringDebugAux .= "view>=2: else view=$view: update->view=" . $update->view . "\n" . $token . "=" . md5($iassign_security->timecreated) . "?\n";
  159. $stringDebugAux .= " " . $iassign_security->id . ", " . $iassign_security->timecreated . "\n";
  160. }
  161. }
  162. // Here is the print to the iLM request the content
  163. print $strFileContent; // Important: to iAssign sent to the iLM its content
  164. // Effectively erase the content just read (for security reason)
  165. ilm_security::remove_old_iLM_security_entries($USER->id); // for security reason erase the used entry in 'iassign_security' (and others for this user/activity)
  166. } // if ($update->view == 2 && $token == md5($iassign_security->timecreated))
  167. else {
  168. if ($DEBUG) { //DEBUG
  169. $countF = 0;
  170. foreach ($files as $thefile) {
  171. $strFileName = $thefile->get_filename(); //Debug
  172. $stringDebugAux .= " " . ($countF++) . ": " . $strFileName . "\n";
  173. $stringDebugAuxFile = $strFileName . "/"; //Debug
  174. if ($strFileName != '.') {
  175. $strFileContent = $thefile->get_content();
  176. }
  177. }
  178. $stringDebugAux .= "view<=2: NOT update->view=" . $update->view . "\n" . $token . "=" . md5($iassign_security->timecreated) . "?\nstrFileContent=" . $strFileContent . "\n";
  179. }
  180. }
  181. } // if ($iassign_security)
  182. } // if ($iassign_security)
  183. }
  184. // Attention, do not use 'print' nor 'echo' here, since this could mixed up the iLM reading its content!
  185. if ($DEBUG) { //DEBUG
  186. $fpointer = fopen($file_name, "w");
  187. $file_debug .= "\nAuxiliary information: " . $stringDebugAux . "";
  188. $file_debug .= "\nContent iLM file: |" . $strFileContent . "|";
  189. fwrite($fpointer, "From: ./mod/iassign/ilm_security.php<br/>\n" . $file_debug);
  190. fclose($fpointer);
  191. }