diff --git a/lib/userkey/classes/privacy/provider.php b/lib/userkey/classes/privacy/provider.php index 39dfa4567bf..5b671489c0d 100644 --- a/lib/userkey/classes/privacy/provider.php +++ b/lib/userkey/classes/privacy/provider.php @@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\transform; use \core_privacy\local\request\writer; +use core_privacy\local\request\userlist; /** * Privacy class for requesting user data. @@ -62,6 +63,41 @@ class provider implements return $collection; } + /** + * Get the list of users within a specific context for this system. + * + * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. + * @param context $context The context. + * @param string $script The unique target identifier. + * @param int $instance The instance ID. + */ + public static function get_user_contexts_with_script(userlist $userlist, \context $context, string $script, + int $instance = null) { + if (!$context instanceof \context_user) { + return; + } + + $params = [ + 'userid' => $context->instanceid, + 'script' => $script + ]; + + $whereinstance = ''; + + if (!empty($instance)) { + $params['instance'] = $instance; + $whereinstance = ' AND k.instance = :instance'; + } + + $sql = "SELECT k.userid + FROM {user_private_key} k + WHERE k.script = :script + AND k.userid = :userid + {$whereinstance}"; + + $userlist->add_from_sql('userid', $sql, $params); + } + /** * Exports the data relating to user keys for the specified scripts and instance, within the specified * context/subcontext. diff --git a/rss/classes/privacy/provider.php b/rss/classes/privacy/provider.php index 48150766c0e..9828743a9da 100644 --- a/rss/classes/privacy/provider.php +++ b/rss/classes/privacy/provider.php @@ -31,6 +31,8 @@ use \core_privacy\local\request\contextlist; use \core_privacy\local\request\approved_contextlist; use \core_privacy\local\request\transform; use \core_privacy\local\request\writer; +use core_privacy\local\request\userlist; +use \core_privacy\local\request\approved_userlist; /** * Privacy class for requesting user data. @@ -40,6 +42,7 @@ use \core_privacy\local\request\writer; */ class provider implements \core_privacy\local\metadata\provider, + \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\subsystem\provider { /** @@ -74,6 +77,21 @@ class provider implements return $contextlist; } + /** + * Get the list of users within a specific context. + * + * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. + */ + public static function get_users_in_context(userlist $userlist) { + $context = $userlist->get_context(); + + if (!$context instanceof \context_user) { + return; + } + + \core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'rss'); + } + /** * Export all user data for the specified user, in the specified contexts. * @@ -113,6 +131,20 @@ class provider implements } + /** + * Delete multiple users within a single context. + * + * @param approved_userlist $userlist The approved context and user information to delete information for. + */ + public static function delete_data_for_users(approved_userlist $userlist) { + + $context = $userlist->get_context(); + + if ($context instanceof \context_user) { + \core_userkey\privacy\provider::delete_userkeys('rss', $context->instanceid); + } + } + /** * Delete all user data for the specified user, in the specified contexts. * diff --git a/rss/tests/privacy_test.php b/rss/tests/privacy_test.php index 1fc46d4fded..791d0c5e67b 100644 --- a/rss/tests/privacy_test.php +++ b/rss/tests/privacy_test.php @@ -27,6 +27,7 @@ use \core_privacy\tests\provider_testcase; use \core_rss\privacy\provider; use \core_privacy\local\request\writer; use \core_privacy\local\request\approved_contextlist; +use \core_privacy\local\request\approved_userlist; /** * Unit tests for rss\classes\privacy\provider.php @@ -127,4 +128,90 @@ class core_rss_testcase extends provider_testcase { $count = $DB->count_records('user_private_key', ['script' => 'rss']); $this->assertEquals(0, $count); } + + /** + * Test that only users with a user context are fetched. + */ + public function test_get_users_in_context() { + $component = 'core_rss'; + // Create a user. + $user = $this->getDataGenerator()->create_user(); + + $usercontext = \context_user::instance($user->id); + $userlist = new \core_privacy\local\request\userlist($usercontext, $component); + // The list of users should not return anything yet (related data still haven't been created). + provider::get_users_in_context($userlist); + $this->assertCount(0, $userlist); + // Create private access key for user. + get_user_key('rss', $user->id); + + // The list of users for user context should return the user. + provider::get_users_in_context($userlist); + $this->assertCount(1, $userlist); + $expected = [$user->id]; + $actual = $userlist->get_userids(); + $this->assertEquals($expected, $actual); + + // The list of users for system context should not return any users. + $systemcontext = context_system::instance(); + $userlist = new \core_privacy\local\request\userlist($systemcontext, $component); + provider::get_users_in_context($userlist); + $this->assertCount(0, $userlist); + } + + /** + * Test that data for users in approved userlist is deleted. + */ + public function test_delete_data_for_users() { + $component = 'core_rss'; + // Create a user1. + $user1 = $this->getDataGenerator()->create_user(); + $usercontext1 = \context_user::instance($user1->id); + // Create list of users with a related user data in usercontext1. + $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); + + // Create a user1. + $user2 = $this->getDataGenerator()->create_user(); + $usercontext2 = \context_user::instance($user2->id); + // Create list of users with a related user data in usercontext2. + $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); + + // Create private access key for user1. + get_user_key('rss', $user1->id); + // Create private access key for user2. + get_user_key('rss', $user2->id); + + // Ensure the user list for usercontext1 contains user1. + provider::get_users_in_context($userlist1); + $this->assertCount(1, $userlist1); + // Ensure the user list for usercontext2 contains user2. + provider::get_users_in_context($userlist2); + $this->assertCount(1, $userlist2); + + // Convert $userlist1 into an approved_contextlist. + $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids()); + + // Delete using delete_data_for_user. + provider::delete_data_for_users($approvedlist); + + // Re-fetch users in usercontext1 - The user list should now be empty. + $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(0, $userlist1); + // Re-fetch users in usercontext2 - The user list should not be empty (user2). + $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist2); + $this->assertCount(1, $userlist2); + + // User data should be only removed in the user context. + $systemcontext = context_system::instance(); + // Add userlist2 to the approved user list in the system context. + $approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids()); + // Delete user1 data using delete_data_for_user. + provider::delete_data_for_users($approvedlist); + // Re-fetch users in usercontext2 - The user list should not be empty (user2). + $userlist1 = new \core_privacy\local\request\userlist($usercontext2, $component); + provider::get_users_in_context($userlist1); + $this->assertCount(1, $userlist1); + } }