diff --git a/admin/tool/mobile/classes/privacy/provider.php b/admin/tool/mobile/classes/privacy/provider.php new file mode 100644 index 00000000000..c29c0a1c8bb --- /dev/null +++ b/admin/tool/mobile/classes/privacy/provider.php @@ -0,0 +1,142 @@ +. +/** + * Privacy Subsystem implementation for tool_mobile. + * + * @package tool_mobile + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace tool_mobile\privacy; +defined('MOODLE_INTERNAL') || die(); +use \core_privacy\local\request\writer; +use \core_privacy\local\metadata\collection; +use \core_privacy\local\request\contextlist; +use \core_privacy\local\request\approved_contextlist; +use \core_privacy\local\request\transform; + +/** + * Privacy provider for tool_mobile. + * + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\user_preference_provider, + \core_privacy\local\request\plugin\provider { + /** + * Returns meta data about this system. + * + * @param collection $collection The initialised item collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection) : collection { + // There is a one user preference. + $collection->add_user_preference('tool_mobile_autologin_request_last', + 'privacy:metadata:preference:tool_mobile_autologin_request_last'); + $collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:core_userkey'); + + return $collection; + } + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + $sql = "SELECT ctx.id + FROM {user_private_key} k + JOIN {user} u ON k.userid = u.id + JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel + WHERE k.userid = :userid AND k.script = 'tool_mobile'"; + $params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER]; + $contextlist = new contextlist(); + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + // If the user has data, then only the CONTEXT_USER should be present so get the first context. + $contexts = $contextlist->get_contexts(); + if (count($contexts) == 0) { + return; + } + $context = reset($contexts); + // Sanity check that context is at the user context level, then get the userid. + if ($context->contextlevel !== CONTEXT_USER) { + return; + } + // Export associated userkeys. + \core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile'); + } + /** + * Export all user preferences for the plugin. + * + * @param int $userid The userid of the user whose data is to be exported. + */ + public static function export_user_preferences(int $userid) { + $autologinrequestlast = get_user_preferences('tool_mobile_autologin_request_last', null, $userid); + if ($autologinrequestlast !== null) { + $time = transform::datetime($autologinrequestlast); + writer::export_user_preference('tool_mobile', + 'tool_mobile_autologin_request_last', + $time, + get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile') + ); + } + } + /** + * Delete all use data which matches the specified deletion_criteria. + * + * @param context $context A user context. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + // Sanity check that context is at the user context level, then get the userid. + if ($context->contextlevel !== CONTEXT_USER) { + return; + } + $userid = $context->instanceid; + // Delete all the userkeys. + \core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid); + } + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + // If the user has data, then only the user context should be present so get the first context. + $contexts = $contextlist->get_contexts(); + if (count($contexts) == 0) { + return; + } + $context = reset($contexts); + // Sanity check that context is at the user context level, then get the userid. + if ($context->contextlevel !== CONTEXT_USER) { + return; + } + $userid = $context->instanceid; + // Delete all the userkeys. + \core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid); + } +} \ No newline at end of file diff --git a/admin/tool/mobile/lang/en/tool_mobile.php b/admin/tool/mobile/lang/en/tool_mobile.php index 25b89f98ec9..7ac7ef4b3c1 100644 --- a/admin/tool/mobile/lang/en/tool_mobile.php +++ b/admin/tool/mobile/lang/en/tool_mobile.php @@ -92,3 +92,5 @@ $string['smartappbanners'] = 'App Banners'; $string['typeoflogin'] = 'Type of login'; $string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.'; $string['getmoodleonyourmobile'] = 'Get the mobile app'; +$string['privacy:metadata:preference:tool_mobile_autologin_request_last'] = 'The date of the last auto-login key request. Between each request 6 minutes are required.'; +$string['privacy:metadata:core_userkey'] = 'User\'s keys used to create auto-login key for the current user.'; diff --git a/admin/tool/mobile/tests/privacy_provider_test.php b/admin/tool/mobile/tests/privacy_provider_test.php new file mode 100644 index 00000000000..d553a105bc3 --- /dev/null +++ b/admin/tool/mobile/tests/privacy_provider_test.php @@ -0,0 +1,132 @@ +. +/** + * Base class for unit tests for tool_mobile. + * + * @package tool_mobile + * @category test + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); + +use \core_privacy\local\request\writer; +use \core_privacy\local\request\transform; +use \core_privacy\local\request\approved_contextlist; +use \tool_mobile\privacy\provider; + +/** + * Unit tests for the tool_mobile implementation of the privacy API. + * + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class tool_mobile_privacy_testcase extends \core_privacy\tests\provider_testcase { + + /** + * Basic setup for these tests. + */ + public function setUp() { + $this->resetAfterTest(true); + } + + /** + * Test to check export_user_preferences. + * returns user preferences data. + */ + public function test_export_user_preferences() { + $user = $this->getDataGenerator()->create_user(); + $expectedtime = time(); + set_user_preference('tool_mobile_autologin_request_last', time(), $user); + provider::export_user_preferences($user->id); + $writer = writer::with_context(\context_system::instance()); + $prefs = $writer->get_user_preferences('tool_mobile'); + $time = transform::datetime($expectedtime); + $this->assertEquals($time, $prefs->tool_mobile_autologin_request_last->value); + $this->assertEquals(get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile'), + $prefs->tool_mobile_autologin_request_last->description); + } + /** + * Test getting the context for the user ID related to this plugin. + */ + public function test_get_contexts_for_userid() { + // Create user and Mobile user keys. + $user = $this->getDataGenerator()->create_user(); + $context = \context_user::instance($user->id); + $key = get_user_key('tool_mobile', $user->id); + $contextlist = provider::get_contexts_for_userid($user->id); + $this->assertEquals($context->id, $contextlist->current()->id); + } + /** + * Test that data is exported correctly for this plugin. + */ + public function test_export_user_data() { + global $DB; + // Create user and Mobile user keys. + $user = $this->getDataGenerator()->create_user(); + $context = \context_user::instance($user->id); + $keyvalue = get_user_key('tool_mobile', $user->id); + $key = $DB->get_record('user_private_key', ['value' => $keyvalue]); + // Validate exported data. + $this->setUser($user); + $writer = writer::with_context($context); + $this->assertFalse($writer->has_any_data()); + $this->export_context_data_for_user($user->id, $context, 'tool_mobile'); + $userkeydata = $writer->get_related_data([], 'userkeys'); + $this->assertCount(1, $userkeydata->keys); + $this->assertEquals($key->script, reset($userkeydata->keys)->script); + } + /** + * Test for provider::delete_data_for_all_users_in_context(). + */ + public function test_delete_data_for_all_users_in_context() { + global $DB; + // Create user and Mobile user keys. + $user = $this->getDataGenerator()->create_user(); + $context = \context_user::instance($user->id); + $keyvalue = get_user_key('tool_mobile', $user->id); + $key = $DB->get_record('user_private_key', ['value' => $keyvalue]); + // Before deletion, we should have 1 user_private_key. + $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']); + $this->assertEquals(1, $count); + // Delete data. + provider::delete_data_for_all_users_in_context($context); + // After deletion, the user_private_key entries should have been deleted. + $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']); + $this->assertEquals(0, $count); + } + /** + * Test for provider::delete_data_for_user(). + */ + public function test_delete_data_for_user() { + global $DB; + // Create user and Mobile user keys. + $user = $this->getDataGenerator()->create_user(); + $context = \context_user::instance($user->id); + $keyvalue = get_user_key('tool_mobile', $user->id); + $key = $DB->get_record('user_private_key', ['value' => $keyvalue]); + // Before deletion, we should have 1 user_private_key. + $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']); + $this->assertEquals(1, $count); + // Delete data. + $contextlist = provider::get_contexts_for_userid($user->id); + $approvedcontextlist = new approved_contextlist($user, 'tool_mobile', $contextlist->get_contextids()); + provider::delete_data_for_user($approvedcontextlist); + // After deletion, the user_private_key entries should have been deleted. + $count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']); + $this->assertEquals(0, $count); + } +} \ No newline at end of file diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 7cde09c3be1..dfe2ad6b2ea 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -505,7 +505,7 @@ class core_component_testcase extends advanced_testcase { $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile')); // Without namespace it returns classes/ classes. - $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_mobile', '')); + $this->assertCount(3, core_component::get_component_classes_in_namespace('tool_mobile', '')); $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes')); }