PageRenderTime 66ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/webservice/classes/privacy/provider.php

https://gitlab.com/unofficial-mirrors/moodle
PHP | 296 lines | 184 code | 29 blank | 83 comment | 21 complexity | 5b2eb143b4c67fd05ae2d640c8841c53 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. * Data provider.
  18. *
  19. * @package core_webservice
  20. * @copyright 2018 Frédéric Massart
  21. * @author Frédéric Massart <fred@branchup.tech>
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. namespace core_webservice\privacy;
  25. defined('MOODLE_INTERNAL') || die();
  26. use context;
  27. use context_user;
  28. use core_privacy\local\metadata\collection;
  29. use core_privacy\local\request\approved_contextlist;
  30. use core_privacy\local\request\transform;
  31. use core_privacy\local\request\writer;
  32. /**
  33. * Data provider class.
  34. *
  35. * @package core_webservice
  36. * @copyright 2018 Frédéric Massart
  37. * @author Frédéric Massart <fred@branchup.tech>
  38. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39. */
  40. class provider implements
  41. \core_privacy\local\metadata\provider,
  42. \core_privacy\local\request\subsystem\provider {
  43. /**
  44. * Returns metadata.
  45. *
  46. * @param collection $collection The initialised collection to add items to.
  47. * @return collection A listing of user data stored through this system.
  48. */
  49. public static function get_metadata(collection $collection) : collection {
  50. $collection->add_database_table('external_tokens', [
  51. 'token' => 'privacy:metadata:tokens:token',
  52. 'privatetoken' => 'privacy:metadata:tokens:privatetoken',
  53. 'tokentype' => 'privacy:metadata:tokens:tokentype',
  54. 'userid' => 'privacy:metadata:tokens:userid',
  55. 'creatorid' => 'privacy:metadata:tokens:creatorid',
  56. 'iprestriction' => 'privacy:metadata:tokens:iprestriction',
  57. 'validuntil' => 'privacy:metadata:tokens:validuntil',
  58. 'timecreated' => 'privacy:metadata:tokens:timecreated',
  59. 'lastaccess' => 'privacy:metadata:tokens:lastaccess',
  60. ], 'privacy:metadata:tokens');
  61. $collection->add_database_table('external_services_users', [
  62. 'userid' => 'privacy:metadata:serviceusers:userid',
  63. 'iprestriction' => 'privacy:metadata:serviceusers:iprestriction',
  64. 'validuntil' => 'privacy:metadata:serviceusers:validuntil',
  65. 'timecreated' => 'privacy:metadata:serviceusers:timecreated',
  66. ], 'privacy:metadata:serviceusers');
  67. return $collection;
  68. }
  69. /**
  70. * Get the list of contexts that contain user information for the specified user.
  71. *
  72. * @param int $userid The user to search.
  73. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
  74. */
  75. public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist {
  76. $contextlist = new \core_privacy\local\request\contextlist();
  77. $sql = "
  78. SELECT ctx.id
  79. FROM {external_tokens} t
  80. JOIN {context} ctx
  81. ON ctx.instanceid = t.userid
  82. AND ctx.contextlevel = :userlevel
  83. WHERE t.userid = :userid1
  84. OR t.creatorid = :userid2";
  85. $contextlist->add_from_sql($sql, ['userlevel' => CONTEXT_USER, 'userid1' => $userid, 'userid2' => $userid]);
  86. $sql = "
  87. SELECT ctx.id
  88. FROM {external_services_users} su
  89. JOIN {context} ctx
  90. ON ctx.instanceid = su.userid
  91. AND ctx.contextlevel = :userlevel
  92. WHERE su.userid = :userid";
  93. $contextlist->add_from_sql($sql, ['userlevel' => CONTEXT_USER, 'userid' => $userid]);
  94. return $contextlist;
  95. }
  96. /**
  97. * Export all user data for the specified user, in the specified contexts.
  98. *
  99. * @param approved_contextlist $contextlist The approved contexts to export information for.
  100. */
  101. public static function export_user_data(approved_contextlist $contextlist) {
  102. global $DB;
  103. $userid = $contextlist->get_user()->id;
  104. $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) use ($userid) {
  105. if ($context->contextlevel == CONTEXT_USER) {
  106. if ($context->instanceid == $userid) {
  107. $carry['has_mine'] = true;
  108. } else {
  109. $carry['others'][] = $context->instanceid;
  110. }
  111. }
  112. return $carry;
  113. }, [
  114. 'has_mine' => false,
  115. 'others' => []
  116. ]);
  117. $path = [get_string('webservices', 'core_webservice')];
  118. // Exporting my stuff.
  119. if ($contexts['has_mine']) {
  120. $data = [];
  121. // Exporting my tokens.
  122. $sql = "
  123. SELECT t.*, s.name as externalservicename
  124. FROM {external_tokens} t
  125. JOIN {external_services} s
  126. ON s.id = t.externalserviceid
  127. WHERE t.userid = :userid
  128. ORDER BY t.id";
  129. $recordset = $DB->get_recordset_sql($sql, ['userid' => $userid]);
  130. foreach ($recordset as $record) {
  131. if (!isset($data['tokens'])) {
  132. $data['tokens'] = [];
  133. }
  134. $data['tokens'][] = static::transform_token($record);
  135. }
  136. $recordset->close();
  137. // Exporting the services I have access to.
  138. $sql = "
  139. SELECT su.*, s.name as externalservicename
  140. FROM {external_services_users} su
  141. JOIN {external_services} s
  142. ON s.id = su.externalserviceid
  143. WHERE su.userid = :userid
  144. ORDER BY su.id";
  145. $recordset = $DB->get_recordset_sql($sql, ['userid' => $userid]);
  146. foreach ($recordset as $record) {
  147. if (!isset($data['services_user'])) {
  148. $data['services_user'] = [];
  149. }
  150. $data['services_user'][] = [
  151. 'external_service' => $record->externalservicename,
  152. 'ip_restriction' => $record->iprestriction,
  153. 'valid_until' => $record->validuntil ? transform::datetime($record->validuntil) : null,
  154. 'created_on' => transform::datetime($record->timecreated),
  155. ];
  156. }
  157. $recordset->close();
  158. if (!empty($data)) {
  159. writer::with_context(context_user::instance($userid))->export_data($path, (object) $data);
  160. };
  161. }
  162. // Exporting the tokens I created.
  163. if (!empty($contexts['others'])) {
  164. list($insql, $inparams) = $DB->get_in_or_equal($contexts['others'], SQL_PARAMS_NAMED);
  165. $sql = "
  166. SELECT t.*, s.name as externalservicename
  167. FROM {external_tokens} t
  168. JOIN {external_services} s
  169. ON s.id = t.externalserviceid
  170. WHERE t.userid $insql
  171. AND t.creatorid = :userid1
  172. AND t.userid <> :userid2
  173. ORDER BY t.userid, t.id";
  174. $params = array_merge($inparams, ['userid1' => $userid, 'userid2' => $userid]);
  175. $recordset = $DB->get_recordset_sql($sql, $params);
  176. static::recordset_loop_and_export($recordset, 'userid', [], function($carry, $record) {
  177. $carry[] = static::transform_token($record);
  178. return $carry;
  179. }, function($userid, $data) use ($path) {
  180. writer::with_context(context_user::instance($userid))->export_related_data($path, 'created_by_you', (object) [
  181. 'tokens' => $data
  182. ]);
  183. });
  184. }
  185. }
  186. /**
  187. * Delete all data for all users in the specified context.
  188. *
  189. * @param context $context The specific context to delete data for.
  190. */
  191. public static function delete_data_for_all_users_in_context(context $context) {
  192. if ($context->contextlevel != CONTEXT_USER) {
  193. return;
  194. }
  195. static::delete_user_data($context->instanceid);
  196. }
  197. /**
  198. * Delete all user data for the specified user, in the specified contexts.
  199. *
  200. * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
  201. */
  202. public static function delete_data_for_user(approved_contextlist $contextlist) {
  203. $userid = $contextlist->get_user()->id;
  204. foreach ($contextlist as $context) {
  205. if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) {
  206. static::delete_user_data($context->instanceid);
  207. break;
  208. }
  209. }
  210. }
  211. /**
  212. * Delete user data.
  213. *
  214. * @param int $userid The user ID.
  215. * @return void
  216. */
  217. protected static function delete_user_data($userid) {
  218. global $DB;
  219. $DB->delete_records('external_tokens', ['userid' => $userid]);
  220. $DB->delete_records('external_services_users', ['userid' => $userid]);
  221. }
  222. /**
  223. * Transform a token entry.
  224. *
  225. * @param object $record The token record.
  226. * @return array
  227. */
  228. protected static function transform_token($record) {
  229. $notexportedstr = get_string('privacy:request:notexportedsecurity', 'core_webservice');
  230. return [
  231. 'external_service' => $record->externalservicename,
  232. 'token' => $notexportedstr,
  233. 'private_token' => $record->privatetoken ? $notexportedstr : null,
  234. 'ip_restriction' => $record->iprestriction,
  235. 'valid_until' => $record->validuntil ? transform::datetime($record->validuntil) : null,
  236. 'created_on' => transform::datetime($record->timecreated),
  237. 'last_access' => $record->lastaccess ? transform::datetime($record->lastaccess) : null,
  238. ];
  239. }
  240. /**
  241. * Loop and export from a recordset.
  242. *
  243. * @param \moodle_recordset $recordset The recordset.
  244. * @param string $splitkey The record key to determine when to export.
  245. * @param mixed $initial The initial data to reduce from.
  246. * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
  247. * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
  248. * @return void
  249. */
  250. protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
  251. callable $reducer, callable $export) {
  252. $data = $initial;
  253. $lastid = null;
  254. foreach ($recordset as $record) {
  255. if ($lastid && $record->{$splitkey} != $lastid) {
  256. $export($lastid, $data);
  257. $data = $initial;
  258. }
  259. $data = $reducer($data, $record);
  260. $lastid = $record->{$splitkey};
  261. }
  262. $recordset->close();
  263. if (!empty($lastid)) {
  264. $export($lastid, $data);
  265. }
  266. }
  267. }