diff --git a/mnet/service/enrol/classes/privacy/provider.php b/mnet/service/enrol/classes/privacy/provider.php new file mode 100644 index 00000000000..7e891bb71c1 --- /dev/null +++ b/mnet/service/enrol/classes/privacy/provider.php @@ -0,0 +1,176 @@ +. +/** + * Privacy Subsystem implementation for mnetservice_enrol. + * + * @package mnetservice_enrol + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mnetservice_enrol\privacy; +defined('MOODLE_INTERNAL') || die(); +use core_privacy\local\metadata\collection; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\context; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +/** + * Privacy Subsystem for mnetservice_enrol implementing metadata and plugin providers. + * + * @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\subsystem\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( + 'mnetservice_enrol_enrolments', + [ + 'hostid' => 'privacy:metadata:mnetservice_enrol_enrolments:hostid', + 'userid' => 'privacy:metadata:mnetservice_enrol_enrolments:userid', + 'remotecourseid' => 'privacy:metadata:mnetservice_enrol_enrolments:remotecourseid', + 'rolename' => 'privacy:metadata:mnetservice_enrol_enrolments:rolename', + 'enroltime' => 'privacy:metadata:mnetservice_enrol_enrolments:enroltime', + 'enroltype' => 'privacy:metadata:mnetservice_enrol_enrolments:enroltype' + ], + 'privacy:metadata:mnetservice_enrol_enrolments:tableexplanation' + ); + + 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 c.id + FROM {context} c + JOIN {mnetservice_enrol_enrolments} me + ON me.userid = c.instanceid + AND c.contextlevel = :contextlevel + WHERE me.userid = :userid"; + $params = [ + 'contextlevel' => CONTEXT_USER, + 'userid' => $userid + ]; + $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) { + global $DB; + if (empty($contextlist->count())) { + return; + } + $userid = $contextlist->get_user()->id; + $contextuser = \context_user::instance($userid); + list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); + $params = [ + 'userid' => $userid, + 'contextlevel' => CONTEXT_USER + ]; + $params += $inparams; + $sql = "SELECT me.id, + me.rolename, + me.enroltime, + me.enroltype, + mh.name as hostname, + mc.fullname + FROM {mnetservice_enrol_enrolments} me + JOIN {context} ctx + ON ctx.instanceid = me.userid + AND ctx.contextlevel = :contextlevel + JOIN {mnet_host} mh + ON mh.id = me.hostid + JOIN {mnetservice_enrol_courses} mc + ON mc.remoteid = me.remotecourseid + WHERE me.userid = :userid + AND ctx.id {$insql}"; + $mnetenrolments = $DB->get_records_sql($sql, $params); + foreach ($mnetenrolments as $mnetenrolment) { + // The core_enrol data export is organised in: + // {User Context}/User enrolments/data.json. + $data[] = (object) [ + 'host' => $mnetenrolment->hostname, + 'remotecourseid' => $mnetenrolment->fullname, + 'rolename' => $mnetenrolment->rolename, + 'enroltime' => transform::datetime($mnetenrolment->enroltime), + 'enroltype' => $mnetenrolment->enroltype + ]; + } + writer::with_context($contextuser)->export_data( + [get_string('privacy:metadata:mnetservice_enrol_enrolments', 'mnetservice_enrol')], + (object)$data + ); + } + /** + * Delete all data for all users in the specified context. + * + * @param context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + if (empty($context)) { + return; + } + // Sanity check that context is at the User context level. + if ($context->contextlevel == CONTEXT_USER) { + static::delete_user_data($context->instanceid); + } + } + /** + * 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 (empty($contextlist->count())) { + return; + } + $user = $contextlist->get_user(); + foreach ($contextlist->get_contexts() as $context) { + // Verify the context is a user context and that the instanceid matches the userid of the contextlist. + if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $user->id) { + // Get the data and write it. + static::delete_user_data($user->id); + } + } + } + /** + * This does the deletion of user data for the auth_oauth2. + * + * @param int $userid The user ID + */ + protected static function delete_user_data(int $userid) { + global $DB; + // Because we only use user contexts the instance ID is the user ID. + $DB->delete_records('mnetservice_enrol_enrolments', ['userid' => $userid]); + } +} \ No newline at end of file diff --git a/mnet/service/enrol/lang/en/mnetservice_enrol.php b/mnet/service/enrol/lang/en/mnetservice_enrol.php index c39d3c68531..10e54bf3557 100644 --- a/mnet/service/enrol/lang/en/mnetservice_enrol.php +++ b/mnet/service/enrol/lang/en/mnetservice_enrol.php @@ -37,3 +37,11 @@ $string['noroamingusers'] = 'Users require the capability \'{$a}\' in the system $string['otherenrolledusers'] = 'Other enrolled users'; $string['pluginname'] = 'Remote enrolment service'; $string['refetch'] = 'Re-fetch up to date state from remote hosts'; +$string['privacy:metadata:mnetservice_enrol_enrolments'] = 'Remote enrolment service'; +$string['privacy:metadata:mnetservice_enrol_enrolments:enroltime'] = 'The date/time of when the enrolment was modified.'; +$string['privacy:metadata:mnetservice_enrol_enrolments:enroltype'] = 'The name of the enrol plugin at the remote server that was used to enrol our student into their course.'; +$string['privacy:metadata:mnetservice_enrol_enrolments:hostid'] = 'The Id of the remote MNet host.'; +$string['privacy:metadata:mnetservice_enrol_enrolments:remotecourseid'] = 'ID of the course at the remote server.'; +$string['privacy:metadata:mnetservice_enrol_enrolments:rolename'] = 'The name of the role at the remote server.'; +$string['privacy:metadata:mnetservice_enrol_enrolments:tableexplanation'] = 'This table stores the information about enrolments of our local users in courses on remote hosts.'; +$string['privacy:metadata:mnetservice_enrol_enrolments:userid'] = 'The Id of our local user on this server.'; diff --git a/mnet/service/enrol/tests/privacy_test.php b/mnet/service/enrol/tests/privacy_test.php new file mode 100644 index 00000000000..6623c601b70 --- /dev/null +++ b/mnet/service/enrol/tests/privacy_test.php @@ -0,0 +1,231 @@ +. +/** + * Privacy test for the mnetservice_enrol implementation of the privacy API. + * + * @package mnetservice_enrol + * @category test + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); +use mnetservice_enrol\privacy\provider; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\writer; +use core_privacy\local\request\transform; +use core_privacy\tests\provider_testcase; +/** + * Privacy test for the mnetservice_enrol. + * + * @copyright 2018 Carlos Escobedo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mnetservice_enrol_privacy_testcase extends provider_testcase { + /** @var stdClass the mnet host we are using to test. */ + protected $mnethost; + /** @var stdClass the mnet service enrolment to test. */ + protected $enrolment; + + /** + * Test set up. + * + * This is executed before running any test in this file. + */ + public function setUp() { + global $DB; + + // Add a mnet host. + $this->mnethost = new stdClass(); + $this->mnethost->name = 'A mnet host'; + $this->mnethost->public_key = 'A random public key!'; + $this->mnethost->id = $DB->insert_record('mnet_host', $this->mnethost); + } + /** + * Check that a user context is returned if there is any user data for this user. + */ + public function test_get_contexts_for_userid() { + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->assertEmpty(provider::get_contexts_for_userid($user->id)); + + // Create a test MNet service enrol enrolments. + $remotecourseid = 101; + $this->insert_mnetservice_enrol_courses($remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user->id, $remotecourseid); + + $contextlist = provider::get_contexts_for_userid($user->id); + // Check that we only get back two context. + $this->assertCount(1, $contextlist); + // Check that the contexts are returned are the expected. + $usercontext = \context_user::instance($user->id); + $this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]); + } + /** + * Test that user data is exported correctly. + */ + public function test_export_user_data() { + global $DB; + + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $this->assertEmpty(provider::get_contexts_for_userid($user->id)); + + // Create a test MNet service enrol enrolments. + $remotecourseid = 101; + $this->insert_mnetservice_enrol_courses($remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user->id, $remotecourseid); + + $subcontexts = [ + get_string('privacy:metadata:mnetservice_enrol_enrolments', 'mnetservice_enrol') + ]; + $usercontext = \context_user::instance($user->id); + $writer = writer::with_context($usercontext); + $this->assertFalse($writer->has_any_data()); + $approvedlist = new approved_contextlist($user, 'mnetservice_enrol', [$usercontext->id]); + provider::export_user_data($approvedlist); + $data = $writer->get_data($subcontexts); + $this->assertCount(1, (array)$data); + $this->assertEquals($this->mnethost->name, reset($data)->host); + $remotecoursename = $DB->get_field('mnetservice_enrol_courses', 'fullname', + array('remoteid' => $this->enrolment->remotecourseid)); + $this->assertEquals($remotecoursename, reset($data)->remotecourseid); + $this->assertEquals($this->enrolment->rolename, reset($data)->rolename); + $this->assertEquals($this->enrolment->enroltype, reset($data)->enroltype); + $this->assertEquals(transform::datetime($this->enrolment->enroltime), reset($data)->enroltime); + } + + /** + * Test deleting all user data for a specific context. + */ + public function test_delete_data_for_all_users_in_context() { + global $DB; + + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $this->assertEmpty(provider::get_contexts_for_userid($user->id)); + + // Create a test MNet service enrol enrolments. + $remotecourseid = 101; + $this->insert_mnetservice_enrol_courses($remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user->id, $remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user2->id, $remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user3->id, $remotecourseid); + $usercontext = \context_user::instance($user->id); + // Get all user enrolments. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', array()); + $this->assertCount(3, $userenrolments); + // Get all user enrolments match with user. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', array('userid' => $user->id)); + $this->assertCount(1, $userenrolments); + // Delete everything for the first user context. + provider::delete_data_for_all_users_in_context($usercontext); + // Get all user enrolments match with user. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', ['userid' => $user->id]); + $this->assertCount(0, $userenrolments); + // Get all user enrolments. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', array()); + $this->assertCount(2, $userenrolments); + } + /** + * This should work identical to the above test. + */ + public function test_delete_data_for_user() { + global $DB; + + $this->resetAfterTest(); + $user = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $this->assertEmpty(provider::get_contexts_for_userid($user->id)); + + $remotecourseid = 101; + $this->insert_mnetservice_enrol_courses($remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user->id, $remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user2->id, $remotecourseid); + $this->insert_mnetservice_enrol_enrolments($user3->id, $remotecourseid); + $remotecourseid2 = 102; + $this->insert_mnetservice_enrol_courses($remotecourseid2); + $this->insert_mnetservice_enrol_enrolments($user->id, $remotecourseid2); + + $usercontext = \context_user::instance($user->id); + // Get all user enrolments. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', array()); + $this->assertCount(4, $userenrolments); + // Get all user enrolments match with user. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', array('userid' => $user->id)); + $this->assertCount(2, $userenrolments); + // Delete everything for the first user. + $approvedlist = new approved_contextlist($user, 'mnetservice_enrol', [$usercontext->id]); + provider::delete_data_for_user($approvedlist); + // Get all user enrolments match with user. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', ['userid' => $user->id]); + $this->assertCount(0, $userenrolments); + // Get all user enrolments accounts. + $userenrolments = $DB->get_records('mnetservice_enrol_enrolments', array()); + $this->assertCount(2, $userenrolments); + } + + /** + * Help function to create a simulation of MNet service enrol. + * Create a Dummy Enrol into mnetservice_enrol_enrolments. + * + * @param int $userid Userid. + * @param int $remotecourseid Remotecourseid. + */ + protected function insert_mnetservice_enrol_enrolments($userid, $remotecourseid) { + global $DB; + + // Create a test MNet service enrol enrolments. + $this->enrolment = new stdclass(); + $this->enrolment->hostid = $this->mnethost->id; + $this->enrolment->userid = $userid; + $this->enrolment->remotecourseid = $remotecourseid; + $this->enrolment->rolename = 'student'; + $this->enrolment->enroltime = time(); + $this->enrolment->enroltype = 'mnet'; + $DB->insert_record('mnetservice_enrol_enrolments', $this->enrolment); + } + + /** + * Help function to create a simualtion of MNet service enrol. + * Create a Dummy Course into mnetservice_enrol_courses. + * Important: The real course is on the host. + * + * @param int $remoteid Remote courseid. + */ + protected function insert_mnetservice_enrol_courses($remoteid) { + global $DB; + + // Create a Dummy Remote Course to test. + $course = new stdclass(); + $course->hostid = $this->mnethost->id; + $course->remoteid = $remoteid; + $course->categoryid = 1; + $course->categoryname = 'Miscellaneous'; + $course->sortorder = 10001; + $course->fullname = 'Test Remote Course '.$remoteid; + $course->shortname = 'testremotecourse '.$remoteid; + $course->idnumber = 'IdnumberRemote '.$remoteid; + $course->summary = 'TestSummaryRemote '.$remoteid; + $course->summaryformat = FORMAT_MOODLE; + $course->startdate = time(); + $course->roleid = 5; + $course->rolename = 'student'; + $DB->insert_record('mnetservice_enrol_courses', $course); + } +} \ No newline at end of file