/** * Construct a user API client, accounting for unified api presence, and fall back to system api user if desired. * * @param int $muserid The userid to get the outlook token for. If you want to force a system API user client, use an empty * value here and set $systemfallback to true. * @return \local_o365\rest\o365api|bool A constructed user API client (unified or legacy), or false if error. */ public function construct_user_api($muserid = null, $systemfallback = true) { $unifiedconfigured = \local_o365\rest\unified::is_configured(); if ($unifiedconfigured === true) { $resource = \local_o365\rest\unified::get_resource(); } else { $resource = \local_o365\rest\azuread::get_resource(); } $token = null; if (!empty($muserid)) { $token = \local_o365\oauth2\token::instance($muserid, $resource, $this->clientdata, $this->httpclient); } if (empty($token) && $systemfallback === true) { $token = \local_o365\oauth2\systemtoken::instance(null, $resource, $this->clientdata, $this->httpclient); } if (empty($token)) { throw new \Exception('No token available for user #' . $muserid); } if ($unifiedconfigured === true) { $apiclient = new \local_o365\rest\unified($token, $this->httpclient); } else { $apiclient = new \local_o365\rest\azuread($token, $this->httpclient); } return $apiclient; }
/** * Sync sharepoint access for a list of courses and users. * * @param array $courses The courses to sync. * @param array $users The users to sync. * @param string $requiredcap The required capability. * @param \local\o365\rest\sharepoint $sharepoint Constructed sharepoint API client. * @return bool Success/Failure. */ protected function sync_spsiteaccess_for_courses_and_users(array $courses, array $users, $requiredcap, \local_o365\rest\sharepoint $sharepoint) { global $DB; foreach ($courses as $course) { $courseid = is_numeric($course) ? $course : $course->id; $context = \context_course::instance($courseid); $spgroupsql = 'SELECT * FROM {local_o365_coursespsite} site JOIN {local_o365_spgroupdata} grp ON grp.coursespsiteid = site.id WHERE site.courseid = ? AND grp.permtype = ?'; $spgrouprec = $DB->get_record_sql($spgroupsql, [$courseid, 'contribute']); foreach ($users as $user) { $userid = is_numeric($user) ? $user : $user->id; $userupn = \local_o365\rest\azuread::get_muser_upn($user); $hascap = has_capability($requiredcap, $context, $user); if ($hascap === true) { // Add to group. $sharepoint->add_user_to_group($userupn, $spgrouprec->groupid, $userid); } else { // Remove from group. $sharepoint->remove_user_from_group($userupn, $spgrouprec->groupid, $userid); } } } return true; }
/** * Get an Azure AD API instance. * * @param string $caller The calling function, used for logging. * @return \local_o365\rest\azuread An Azure AD API instance. */ public static function get_azuread_api($caller = 'get_azuread_api') { $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $httpclient = new \local_o365\httpclient(); $resource = \local_o365\rest\azuread::get_resource(); $token = \local_o365\oauth2\systemtoken::instance(null, $resource, $clientdata, $httpclient); if (!empty($token)) { return new \local_o365\rest\azuread($token, $httpclient); } else { $msg = 'Couldn\'t construct azuread api client because we didn\'t have a system API user token.'; $caller = '\\local_o365\\feature\\usergroups\\observers::' . $caller; \local_o365\utils::debug($msg, $caller); return false; } }
/** * Sync sharepoint access for a list of courses and users. * * @param array $courses The courses to sync. * @param array $users The users to sync. * @param string $requiredcap The required capability. * @param \local\o365\rest\sharepoint $sharepoint Constructed sharepoint API client. * @return bool Success/Failure. */ protected function sync_spsiteaccess_for_courses_and_users(array $courses, array $users, $requiredcap, \local_o365\rest\sharepoint $sharepoint) { global $DB; foreach ($courses as $course) { $courseid = is_numeric($course) ? $course : $course->id; $context = \context_course::instance($courseid); $spgroupsql = 'SELECT * FROM {local_o365_coursespsite} site JOIN {local_o365_spgroupdata} grp ON grp.coursespsiteid = site.id WHERE site.courseid = ? AND grp.permtype = ?'; $spgrouprec = $DB->get_record_sql($spgroupsql, [$courseid, 'contribute']); if (!empty($spgrouprec)) { foreach ($users as $user) { $userid = is_numeric($user) ? $user : $user->id; if (!\local_o365\utils::is_o365_connected($userid)) { continue; } $userupn = \local_o365\rest\azuread::get_muser_upn($user); $hascap = has_capability($requiredcap, $context, $user); if ($hascap === true) { // Add to group. try { mtrace('Adding user #' . $userid . ' to group id ' . $spgrouprec->groupid . '...'); $sharepoint->add_user_to_group($userupn, $spgrouprec->groupid, $userid); } catch (\Exception $e) { mtrace('Error: ' . $e->getMessage()); } } else { // Remove from group. try { mtrace('Removing user #' . $userid . ' from group id ' . $spgrouprec->groupid . '...'); $sharepoint->remove_user_from_group($userupn, $spgrouprec->groupid, $userid); } catch (\Exception $e) { mtrace('Error: ' . $e->getMessage()); } } } } } return true; }
/** * Construct an API client. * * @return \local_o365\rest\o365api|bool A constructed user API client (unified or legacy), or false if error. */ public function get_api() { $unifiedconfigured = \local_o365\rest\unified::is_configured(); if ($unifiedconfigured === true) { $resource = \local_o365\rest\unified::get_resource(); } else { $resource = \local_o365\rest\azuread::get_resource(); } $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $httpclient = new \local_o365\httpclient(); $token = \local_o365\oauth2\systemtoken::instance(null, $resource, $clientdata, $httpclient); if (empty($token)) { throw new \Exception('No token available for system user. Please run local_o365 health check.'); } if ($unifiedconfigured === true) { $apiclient = new \local_o365\rest\unified($token, $httpclient); } else { $apiclient = new \local_o365\rest\azuread($token, $httpclient); } return $apiclient; }
/** * Sync Sharepoint course site access when a role was assigned or unassigned for a user. * * @param int $roleid The ID of the role that was assigned/unassigned. * @param int $userid The ID of the user that it was assigned to or unassigned from. * @param int $contextid The ID of the context the role was assigned/unassigned in. * @return bool Success/Failure. */ public static function sync_spsite_access_for_roleassign_change($roleid, $userid, $contextid) { global $DB; $requiredcap = \local_o365\rest\sharepoint::get_course_site_required_capability(); // Check if the role affected the required capability. $rolecapsql = "SELECT *\n FROM {role_capabilities}\n WHERE roleid = ? AND capability = ?"; $capassignrec = $DB->get_record_sql($rolecapsql, [$roleid, $requiredcap]); if (empty($capassignrec) || $capassignrec->permission == CAP_INHERIT) { // Role doesn't affect required capability. Doesn't concern us. return false; } $context = \context::instance_by_id($contextid, IGNORE_MISSING); if (empty($context)) { // Invalid context, stop here. return false; } if ($context->contextlevel == CONTEXT_COURSE) { $courseid = $context->instanceid; $user = $DB->get_record('user', ['id' => $userid]); if (empty($user)) { // Bad userid. return false; } $userupn = \local_o365\rest\azuread::get_muser_upn($user); if (empty($userupn)) { // No user UPN, can't continue. return false; } $spgroupsql = 'SELECT * FROM {local_o365_coursespsite} site JOIN {local_o365_spgroupdata} grp ON grp.coursespsiteid = site.id WHERE site.courseid = ? AND grp.permtype = ?'; $spgrouprec = $DB->get_record_sql($spgroupsql, [$courseid, 'contribute']); if (empty($spgrouprec)) { // No sharepoint group, can't fix that here. return false; } // If the context is a course context we can change SP access now. $sharepoint = static::construct_sharepoint_api_with_system_user(); if (empty($sharepoint)) { // O365 not configured. return false; } $hascap = has_capability($requiredcap, $context, $user); if ($hascap === true) { // Add to group. $sharepoint->add_user_to_group($userupn, $spgrouprec->groupid, $user->id); } else { // Remove from group. $sharepoint->remove_user_from_group($userupn, $spgrouprec->groupid, $user->id); } return true; } else { if ($context->get_course_context(false) == false) { // If the context is higher than a course, we have to run a sync in cron. $spaccesssync = new \local_o365\task\sharepointaccesssync(); $spaccesssync->set_custom_data(['roleid' => $roleid, 'userid' => $userid, 'contextid' => $contextid]); \core\task\manager::queue_adhoc_task($spaccesssync); return true; } } }
/** * Attempt to fix application permissions. */ public function mode_fixappperms() { $data = new \stdClass(); $success = false; $resource = \local_o365\rest\azuread::get_resource(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $httpclient = new \local_o365\httpclient(); $token = \local_o365\oauth2\systemtoken::instance(null, $resource, $clientdata, $httpclient); if (empty($token)) { throw new \moodle_exception('errorchecksystemapiuser', 'local_o365'); } $apiclient = new \local_o365\rest\azuread($token, $httpclient); $success = $apiclient->push_permissions(); $data->success = $success; if ($success === true) { set_config('detectperms', 1, 'local_o365'); } echo $this->ajax_response($data, $success); }
/** * Transform the full request URL. * * @param string $requesturi The full request URI, includes the API uri and called endpoint. * @return string The transformed full request URI. */ public function transform_full_request_uri($requesturi) { return parent::transform_full_request_uri($requesturi); }
/** * Test sync_users method. */ public function test_sync_users() { global $CFG, $DB; for ($i = 1; $i <= 2; $i++) { $muser = ['auth' => 'oidc', 'deleted' => '0', 'mnethostid' => $CFG->mnet_localhost_id, 'username' => '00000000-0000-0000-0000-00000000000' . $i, 'firstname' => 'Test', 'lastname' => 'User' . $i, 'email' => 'testuser' . $i . '@example.onmicrosoft.com', 'lang' => 'en']; $DB->insert_record('user', (object) $muser); $token = ['oidcuniqid' => '00000000-0000-0000-0000-00000000000' . $i, 'authcode' => '000', 'username' => 'testuser' . $i . '@example.onmicrosoft.com', 'scope' => 'test', 'resource' => \local_o365\rest\azuread::get_resource(), 'token' => '000', 'expiry' => '9999999999', 'refreshtoken' => 'fsdfsdf' . $i, 'idtoken' => 'sdfsdfsdf' . $i]; $DB->insert_record('auth_oidc_token', (object) $token); } $response = ['value' => [$this->get_aad_userinfo(1), $this->get_aad_userinfo(3)]]; $response = json_encode($response); $httpclient = new \local_o365\tests\mockhttpclient(); $httpclient->set_response($response); $apiclient = new \local_o365\rest\azuread($this->get_mock_token(), $httpclient); $aadusers = $apiclient->get_users(); $apiclient->sync_users($aadusers['value']); $existinguser = ['auth' => 'oidc', 'username' => '*****@*****.**']; $this->assertTrue($DB->record_exists('user', $existinguser)); $createduser = ['auth' => 'oidc', 'username' => '*****@*****.**']; $this->assertTrue($DB->record_exists('user', $createduser)); $createduser = $DB->get_record('user', $createduser); $this->assertEquals('Test', $createduser->firstname); $this->assertEquals('User3', $createduser->lastname); $this->assertEquals('*****@*****.**', $createduser->email); $this->assertEquals('Toronto', $createduser->city); $this->assertEquals('CA', $createduser->country); $this->assertEquals('Dev', $createduser->department); $this->assertEquals('en', $createduser->lang); }
/** * Do the job. */ public function execute() { global $DB; $configsetting = get_config('local_o365', 'creategroups'); if (empty($configsetting)) { mtrace('Groups not enabled, skipping...'); return true; } $now = time(); $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $unifiedresource = \local_o365\rest\unified::get_resource(); $unifiedtoken = \local_o365\oauth2\systemtoken::instance(null, $unifiedresource, $clientdata, $httpclient); if (empty($unifiedtoken)) { mtrace('Could not get unified API token.'); return true; } $unifiedclient = new \local_o365\rest\unified($unifiedtoken, $httpclient); $aadresource = \local_o365\rest\azuread::get_resource(); $aadtoken = \local_o365\oauth2\systemtoken::instance(null, $aadresource, $clientdata, $httpclient); if (empty($aadtoken)) { mtrace('Could not get Azure AD token.'); return true; } $aadclient = new \local_o365\rest\azuread($aadtoken, $httpclient); $siterec = $DB->get_record('course', ['id' => SITEID]); $siteshortname = strtolower(preg_replace('/[^a-z0-9]+/iu', '', $siterec->shortname)); $sql = 'SELECT crs.* FROM {course} crs LEFT JOIN {local_o365_objects} obj ON obj.type = ? AND obj.subtype = ? AND obj.moodleid = crs.id WHERE obj.id IS NULL AND crs.id != ? LIMIT 0, 5'; $params = ['group', 'course', SITEID]; $courses = $DB->get_recordset_sql($sql, $params); foreach ($courses as $course) { // Create group. $groupname = $siterec->shortname . ': ' . $course->fullname; $groupshortname = $siteshortname . '_' . $course->shortname; $response = $unifiedclient->create_group($groupname, $groupshortname); if (empty($response) || !is_array($response) || empty($response['objectId'])) { mtrace('Could not create group for course #' . $course->id); var_dump($response); continue; } mtrace('Created group ' . $response['objectId'] . ' for course #' . $course->id); $objectrec = ['type' => 'group', 'subtype' => 'course', 'objectid' => $response['objectId'], 'moodleid' => $course->id, 'o365name' => $groupname, 'timecreated' => $now, 'timemodified' => $now]; $objectrec['id'] = $DB->insert_record('local_o365_objects', (object) $objectrec); mtrace('Recorded group object (' . $objectrec['objectid'] . ') into object table with record id ' . $objectrec['id']); // It takes a little while for the group object to register. mtrace('Waiting 10 seconds for group to register...'); sleep(10); // Add enrolled users to group. mtrace('Adding users to group (' . $objectrec['objectid'] . ')'); $coursecontext = \context_course::instance($course->id); list($esql, $params) = get_enrolled_sql($coursecontext); $sql = "SELECT u.*,\n tok.oidcuniqid as userobjectid\n FROM {user} u\n JOIN ({$esql}) je ON je.id = u.id\n JOIN {auth_oidc_token} tok ON tok.username = u.username AND tok.resource = :tokresource\n WHERE u.deleted = 0"; $params['tokresource'] = 'https://graph.windows.net'; $enrolled = $DB->get_recordset_sql($sql, $params); foreach ($enrolled as $user) { $response = $aadclient->add_member_to_group($objectrec['objectid'], $user->userobjectid); if ($response === true) { mtrace('Added user #' . $user->id . ' (' . $user->userobjectid . ')'); } else { mtrace('Could not add user #' . $user->id . ' (' . $user->userobjectid . ')'); mtrace('Received: ' . $response); } } $enrolled->close(); } $courses->close(); }
/** * Add users with a given capability in a given context to a Sharepoint group. * * @param \context $context The context to check for the capability. * @param string $capability The capability to check for. * @param int $spgroupid The sharepoint group ID to add users to. */ public function add_users_with_capability_to_group($context, $capability, $spgroupid) { $now = time(); $users = get_users_by_capability($context, $capability); $results = []; // Assign users to group. foreach ($users as $user) { // Only Azure AD users can be added to sharepoint. if (\local_o365\utils::is_o365_connected($user->id) !== true) { continue; } try { $userupn = \local_o365\rest\azuread::get_muser_upn($user); } catch (\Exception $e) { continue; } if (!empty($userupn)) { $results[$user->id] = $this->add_user_to_group($userupn, $spgroupid, $user->id); } } return $results; }