From 6480c4091e355e9c55132d46e2d20c060d083e3c Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 19 Apr 2018 12:46:42 +0800 Subject: [PATCH] MDL-62047 core_userkey: Add privacy implementation --- lang/en/userkey.php | 8 + lib/userkey/classes/privacy/provider.php | 132 +++++++++ lib/userkey/tests/privacy_provider.php | 346 +++++++++++++++++++++++ 3 files changed, 486 insertions(+) create mode 100644 lib/userkey/classes/privacy/provider.php create mode 100644 lib/userkey/tests/privacy_provider.php diff --git a/lang/en/userkey.php b/lang/en/userkey.php index 35d772cf54f3a..69614d2d4f7ee 100644 --- a/lang/en/userkey.php +++ b/lang/en/userkey.php @@ -33,6 +33,14 @@ $string['keyvaliduntil_help'] = 'Select an optional date after which the key will no longer be valid (recommended for added security).'; $string['keyvalue'] = 'Key value'; $string['newuserkey'] = 'New user key'; +$string['privacy:metadata:user_private_key:script'] = 'The script which is responsible for the user key.'; +$string['privacy:metadata:user_private_key:value'] = 'The value of the key.'; +$string['privacy:metadata:user_private_key:userid'] = 'The user associated with the key.'; +$string['privacy:metadata:user_private_key:instance'] = 'The instance of the script.'; +$string['privacy:metadata:user_private_key:iprestriction'] = 'The IP address range that this key can be used from.'; +$string['privacy:metadata:user_private_key:validuntil'] = 'The date and time that the private key is valid until.'; +$string['privacy:metadata:user_private_key:timecreated'] = 'The date and time that the key was created.'; +$string['privacy:metadata:user_private_key'] = 'Private keys for the user.'; $string['userkey'] = 'User key'; $string['userkey_help'] = 'Select a saved key that will give users access to the data published by this export plugin, without having to log into Moodle. Select "Create a new user key" to generate a new key when submitting this form.'; $string['userkeys'] = 'User keys'; diff --git a/lib/userkey/classes/privacy/provider.php b/lib/userkey/classes/privacy/provider.php new file mode 100644 index 0000000000000..34c780bdf7741 --- /dev/null +++ b/lib/userkey/classes/privacy/provider.php @@ -0,0 +1,132 @@ +. + +/** + * Privacy class for requesting user data. + * + * @package core_userkey + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_userkey\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use \core_privacy\local\metadata\collection; +use \core_privacy\local\request\transform; +use \core_privacy\local\request\writer; + +/** + * Privacy class for requesting user data. + * + * @package core_userkey + * @copyright 2018 Andrew Nicols + * @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\subsystem\plugin_provider { + + /** + * Returns meta data about this system. + * + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection) : collection { + $collection->add_database_table('user_private_key', [ + 'script' => 'privacy:metadata:user_private_key:script', + 'value' => 'privacy:metadata:user_private_key:value', + 'userid' => 'privacy:metadata:user_private_key:userid', + 'instance' => 'privacy:metadata:user_private_key:instance', + 'iprestriction' => 'privacy:metadata:user_private_key:iprestriction', + 'validuntil' => 'privacy:metadata:user_private_key:validuntil', + 'timecreated' => 'privacy:metadata:user_private_key:timecreated', + ], 'privacy:metadata:user_private_key'); + + return $collection; + } + + /** + * Exports the data relating to user keys for the specified scripts and instance, within the specified + * context/subcontext. + * + * @param \context $context Context owner of the data. + * @param array $subcontext Context owner of the data. + * @param string $script The owner of the data (usually a component name). + * @param int $instance The instance owner of the data. + */ + public static function export_userkeys(\context $context, array $subcontext, $script, $instance = null) { + global $DB, $USER; + + $searchparams = [ + 'script' => $script, + 'userid' => $USER->id, + ]; + + if (null !== $instance) { + $searchparams['instance'] = $instance; + } + + $keys = $DB->get_recordset('user_private_key', $searchparams); + $keydata = []; + foreach ($keys as $key) { + $keydata[] = (object) [ + 'script' => $key->script, + 'instance' => $key->instance, + 'iprestriction' => $key->iprestriction, + 'validuntil' => transform::datetime($key->validuntil), + 'timecreated' => transform::datetime($key->timecreated), + ]; + } + $keys->close(); + + if (!empty($keydata)) { + $data = (object) [ + 'keys' => $keydata, + ]; + + writer::with_context($context)->export_related_data($subcontext, 'userkeys', $data); + } + } + + /** + * Deletes all userkeys for a script. + * + * @param string $script The owner of the data (usually a component name). + * @param int $userid The owner of the data. + * @param int $instance The instance owner of the data. + */ + public static function delete_userkeys($script, $userid = null, $instance = null) { + global $DB; + + $searchparams = [ + 'script' => $script, + ]; + + if (null !== $userid) { + $searchparams['userid'] = $userid; + } + + if (null !== $instance) { + $searchparams['instance'] = $instance; + } + + $DB->delete_records('user_private_key', $searchparams); + } +} diff --git a/lib/userkey/tests/privacy_provider.php b/lib/userkey/tests/privacy_provider.php new file mode 100644 index 0000000000000..57c2760b19a28 --- /dev/null +++ b/lib/userkey/tests/privacy_provider.php @@ -0,0 +1,346 @@ +. + +/** + * Privacy tests for core_userkey. + * + * @package core_userkey + * @category test + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use \core_privacy\tests\provider_testcase; +use \core_privacy\local\request\writer; +use \core_userkey\privacy\provider; + +/** + * Privacy tests for core_userkey. + * + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_userkey_privacy_testcase extends provider_testcase { + /** + * Export for a user with no keys in the specified instance will not have any data exported. + */ + public function test_export_userkeys_no_keys() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $context = \context_system::instance(); + + provider::export_userkeys($context, [], 'core_tests'); + + $this->assertFalse(writer::with_context($context)->has_any_data()); + } + + /** + * Export for a user with a key against a script where no instance is specified. + */ + public function test_export_userkeys_basic_key() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $key = get_user_key('core_tests', $user->id); + + $context = \context_system::instance(); + $subcontext = []; + + provider::export_userkeys($context, $subcontext, 'core_tests'); + + $writer = writer::with_context($context); + + $this->assertTrue($writer->has_any_data()); + $exported = $writer->get_related_data($subcontext, 'userkeys'); + + $this->assertCount(1, $exported->keys); + + $firstkey = reset($exported->keys); + $this->assertEquals('core_tests', $firstkey->script); + $this->assertEquals('', $firstkey->instance); + $this->assertEquals('', $firstkey->iprestriction); + $this->assertNotEmpty($firstkey->validuntil); + $this->assertNotEmpty($firstkey->timecreated); + + provider::delete_userkeys('core_tests', $user->id); + + $this->assertCount(0, $DB->get_records('user_private_key')); + } + + /** + * Export for a user with a key against a script where additional data is specified. + */ + public function test_export_userkeys_complex_key() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $key = get_user_key('core_tests', $user->id, 42, '127.0.0.1', 12345); + + $context = \context_system::instance(); + $subcontext = []; + + // Export all keys in core_tests. + provider::export_userkeys($context, $subcontext, 'core_tests'); + + $writer = writer::with_context($context); + + $this->assertTrue($writer->has_any_data()); + $exported = $writer->get_related_data($subcontext, 'userkeys'); + + $this->assertCount(1, $exported->keys); + + $firstkey = reset($exported->keys); + $this->assertEquals('core_tests', $firstkey->script); + $this->assertEquals(42, $firstkey->instance); + $this->assertEquals('127.0.0.1', $firstkey->iprestriction); + $this->assertNotEmpty($firstkey->validuntil); + $this->assertNotEmpty($firstkey->timecreated); + + provider::delete_userkeys('core_tests', $user->id); + + $this->assertCount(0, $DB->get_records('user_private_key')); + } + + /** + * Export for a user with a key against a script where no instance is specified. + */ + public function test_export_userkeys_basic_key_without_filter() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $key = get_user_key('core_tests', $user->id); + + $context = \context_system::instance(); + $subcontext = []; + + provider::export_userkeys($context, $subcontext, 'core_tests'); + + $writer = writer::with_context($context); + + $this->assertTrue($writer->has_any_data()); + $exported = $writer->get_related_data($subcontext, 'userkeys'); + + $this->assertCount(1, $exported->keys); + + $firstkey = reset($exported->keys); + $this->assertEquals('core_tests', $firstkey->script); + $this->assertEquals('', $firstkey->instance); + $this->assertEquals('', $firstkey->iprestriction); + $this->assertNotEmpty($firstkey->validuntil); + $this->assertNotEmpty($firstkey->timecreated); + + provider::delete_userkeys('core_tests', $user->id); + + $this->assertCount(0, $DB->get_records('user_private_key')); + } + + /** + * Export for a user with a key against a script where additional data is specified. + */ + public function test_export_userkeys_complex_key_with_filter() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $key = get_user_key('core_tests', $user->id, 42, '127.0.0.1', 12345); + + $context = \context_system::instance(); + $subcontext = []; + + // Export all keys in core_tests against instance 43 - no keys. + provider::export_userkeys($context, $subcontext, 'core_tests', 43); + $writer = writer::with_context($context); + $this->assertFalse($writer->has_any_data()); + + // Export all keys in core_tests against instance 42. + provider::export_userkeys($context, $subcontext, 'core_tests', 42); + $writer = writer::with_context($context); + $this->assertTrue($writer->has_any_data()); + $exported = $writer->get_related_data($subcontext, 'userkeys'); + + $this->assertCount(1, $exported->keys); + + $firstkey = reset($exported->keys); + $this->assertEquals('core_tests', $firstkey->script); + $this->assertEquals(42, $firstkey->instance); + $this->assertEquals('127.0.0.1', $firstkey->iprestriction); + $this->assertNotEmpty($firstkey->validuntil); + $this->assertNotEmpty($firstkey->timecreated); + + // Delete for instance 43 (no keys). + provider::delete_userkeys('core_tests', $user->id, 43); + $this->assertCount(1, $DB->get_records('user_private_key')); + + // Delete for instance 42. + provider::delete_userkeys('core_tests', $user->id, 42); + $this->assertCount(0, $DB->get_records('user_private_key')); + } + + /** + * Export for a user with keys against multiple scripts where additional data is specified. + */ + public function test_export_userkeys_multiple_complex_key_with_filter() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $key = get_user_key('core_tests', $user->id, 42, '127.0.0.1', 12345); + $key = get_user_key('core_userkey', $user->id, 99, '240.0.0.1', 54321); + + $context = \context_system::instance(); + $subcontext = []; + + // Export all keys in core_tests against instance 43 - no keys. + provider::export_userkeys($context, $subcontext, 'core_tests', 43); + $writer = writer::with_context($context); + $this->assertFalse($writer->has_any_data()); + + // Export all keys in core_tests against instance 42. + provider::export_userkeys($context, $subcontext, 'core_tests', 42); + $writer = writer::with_context($context); + $this->assertTrue($writer->has_any_data()); + $exported = $writer->get_related_data($subcontext, 'userkeys'); + + $this->assertCount(1, $exported->keys); + + $firstkey = reset($exported->keys); + $this->assertEquals('core_tests', $firstkey->script); + $this->assertEquals(42, $firstkey->instance); + $this->assertEquals('127.0.0.1', $firstkey->iprestriction); + $this->assertNotEmpty($firstkey->validuntil); + $this->assertNotEmpty($firstkey->timecreated); + + // Delete for instance 43 (no keys). + provider::delete_userkeys('core_tests', $user->id, 43); + $this->assertCount(2, $DB->get_records('user_private_key')); + + // Delete for instance 42. + provider::delete_userkeys('core_tests', $user->id, 42); + $this->assertCount(1, $DB->get_records('user_private_key')); + + // Delete for instance 99. + provider::delete_userkeys('core_tests', $user->id, 99); + $this->assertCount(1, $DB->get_records('user_private_key')); + + // Delete for instance 99 of core_userkey too. + provider::delete_userkeys('core_userkey', $user->id, 99); + $this->assertCount(0, $DB->get_records('user_private_key')); + } + + /** + * Export for keys against multiple users. + */ + public function test_export_userkeys_multiple_users() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $otheruser = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $key = get_user_key('core_tests', $user->id, 42, '127.0.0.1', 12345); + $key = get_user_key('core_tests', $otheruser->id, 42, '127.0.0.1', 12345); + + $context = \context_system::instance(); + $subcontext = []; + + // Export all keys in core_tests against instance 43 - no keys. + provider::export_userkeys($context, $subcontext, 'core_tests', 43); + $writer = writer::with_context($context); + $this->assertFalse($writer->has_any_data()); + + // Export all keys in core_tests against instance 42. + provider::export_userkeys($context, $subcontext, 'core_tests', 42); + $writer = writer::with_context($context); + $this->assertTrue($writer->has_any_data()); + $exported = $writer->get_related_data($subcontext, 'userkeys'); + + $this->assertCount(1, $exported->keys); + + $firstkey = reset($exported->keys); + $this->assertEquals('core_tests', $firstkey->script); + $this->assertEquals(42, $firstkey->instance); + $this->assertEquals('127.0.0.1', $firstkey->iprestriction); + $this->assertNotEmpty($firstkey->validuntil); + $this->assertNotEmpty($firstkey->timecreated); + + // Delete for instance 43 (no keys). + provider::delete_userkeys('core_tests', $user->id, 43); + $this->assertCount(2, $DB->get_records('user_private_key')); + + // Delete for instance 42. + provider::delete_userkeys('core_tests', $user->id, 42); + $this->assertCount(1, $DB->get_records('user_private_key')); + + // Delete for instance 99. + provider::delete_userkeys('core_tests', $user->id, 99); + $this->assertCount(1, $DB->get_records('user_private_key')); + } + + /** + * Delete for all users in a script. + */ + public function test_delete_all_userkeys_in_script() { + global $DB; + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $otheruser = $this->getDataGenerator()->create_user(); + + $key = get_user_key('core_tests', $user->id, 42, '127.0.0.1', 12345); + $key = get_user_key('core_tests', $user->id, 43, '127.0.0.1', 12345); + $key = get_user_key('core_userkey', $user->id, 42, '127.0.0.1', 12345); + $key = get_user_key('core_userkey', $user->id, 43, '127.0.0.1', 12345); + $key = get_user_key('core_tests', $otheruser->id, 42, '127.0.0.1', 12345); + $key = get_user_key('core_tests', $otheruser->id, 43, '127.0.0.1', 12345); + $key = get_user_key('core_userkey', $otheruser->id, 42, '127.0.0.1', 12345); + $key = get_user_key('core_userkey', $otheruser->id, 43, '127.0.0.1', 12345); + + $context = \context_system::instance(); + $subcontext = []; + + $this->assertCount(8, $DB->get_records('user_private_key')); + + // Delete for all of core_tests. + provider::delete_userkeys('core_tests'); + $this->assertCount(4, $DB->get_records('user_private_key')); + + // Delete for all of core_userkey where instanceid = 42. + provider::delete_userkeys('core_userkey', null, 42); + $this->assertCount(2, $DB->get_records('user_private_key')); + + provider::delete_userkeys('core_userkey', $otheruser->id); + $this->assertCount(1, $DB->get_records('user_private_key')); + } +}