/** * Validate that a given SharePoint url is accessible with the given client data. * * @param string $uncleanurl Uncleaned, unvalidated URL to check. * @param \local_o365\oauth2\clientdata $clientdata oAuth2 Credentials * @param \local_o365\httpclientinterface $httpclient An HttpClient to use for transport. * @return string One of: * "invalid" : The URL is not a usable SharePoint url. * "notempty" : The URL is a usable SharePoint url, and the SharePoint site exists. * "valid" : The URL is a usable SharePoint url, and the SharePoint site doesn't exist. */ public static function validate_site($uncleanurl, \local_o365\oauth2\clientdata $clientdata, \local_o365\httpclientinterface $httpclient) { $siteinfo = static::parse_site_url($uncleanurl); if (empty($siteinfo)) { return 'invalid'; } $token = \local_o365\oauth2\systemtoken::get_for_new_resource(null, $siteinfo['resource'], $clientdata, $httpclient); if (empty($token)) { return 'invalid'; } $sharepoint = new \local_o365\rest\sharepoint($token, $httpclient); $sharepoint->override_resource($siteinfo['resource']); // Try to get the / site's info to validate we can communicate with this parent Sharepoint site. try { $mainsiteinfo = $sharepoint->get_site(); } catch (\Exception $e) { return 'invalid'; } if ($siteinfo['subsiteurl'] === '/') { // We just successfully got the / site's info, so if we're going to use that, it's obviously not empty. return 'notempty'; } $subsiteexists = $sharepoint->site_exists($siteinfo['subsiteurl']); return $subsiteexists === true ? 'notempty' : 'valid'; }
/** * 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; }
/** * Validate that a given url is a valid OneDrive for Business SharePoint URL. * * @param string $resource Uncleaned, unvalidated URL to check. * @param \local_o365\oauth2\clientdata $clientdata oAuth2 Credentials * @param \local_o365\httpclientinterface $httpclient An HttpClient to use for transport. * @return bool Whether the received resource is valid or not. */ public static function validate_resource($resource, \local_o365\oauth2\clientdata $clientdata, \local_o365\httpclientinterface $httpclient) { $cleanresource = clean_param($resource, PARAM_URL); if ($cleanresource !== $resource) { return false; } $fullcleanresource = 'https://' . $cleanresource; $token = \local_o365\oauth2\systemtoken::get_for_new_resource(null, $fullcleanresource, $clientdata, $httpclient); return !empty($token) ? true : false; }
/** * Do the job. */ public function execute() { // Attempt token refresh. $oidcconfig = get_config('auth_oidc'); if (!empty($oidcconfig)) { $httpclient = new \local_o365\httpclient(); $clientdata = new \local_o365\oauth2\clientdata($oidcconfig->clientid, $oidcconfig->clientsecret, $oidcconfig->authendpoint, $oidcconfig->tokenendpoint); $graphresource = 'https://graph.windows.net'; $systemtoken = \local_o365\oauth2\systemtoken::get_for_new_resource(null, $graphresource, $clientdata, $httpclient); } return true; }
/** * Get a Unified API instance. * * @param string $caller The calling function, used for logging. * @return \local_o365\rest\unified A Unified API instance. */ public static function get_unified_api($caller = 'get_unified_api') { $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $httpclient = new \local_o365\httpclient(); $resource = \local_o365\rest\unified::get_resource(); $token = \local_o365\oauth2\systemtoken::instance(null, $resource, $clientdata, $httpclient); if (!empty($token)) { return new \local_o365\rest\unified($token, $httpclient); } else { $msg = 'Couldn\'t construct unified 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; } }
/** * Do the job. */ public function execute() { global $DB; $oidcconfig = get_config('auth_oidc'); if (empty($oidcconfig)) { throw new \moodle_exception('erroracpauthoidcnotconfig', 'local_o365'); } $spresource = \local_o365\rest\sharepoint::get_resource(); if (empty($spresource)) { throw new \moodle_exception('erroracplocalo365notconfig', 'local_o365'); } $httpclient = new \local_o365\httpclient(); $clientdata = new \local_o365\oauth2\clientdata($oidcconfig->clientid, $oidcconfig->clientsecret, $oidcconfig->authendpoint, $oidcconfig->tokenendpoint); $sptoken = \local_o365\oauth2\systemtoken::instance(null, $spresource, $clientdata, $httpclient); if (empty($sptoken)) { throw new \moodle_exception('erroracpnosptoken', 'local_o365'); } $sharepoint = new \local_o365\rest\sharepoint($sptoken, $httpclient); $sharepoint->set_site(''); $moodlesiteuri = $sharepoint->get_moodle_parent_site_uri(); if ($sharepoint->site_exists($moodlesiteuri) === false) { $moodlesitename = get_string('acp_parentsite_name', 'local_o365'); $moodlesitedesc = get_string('acp_parentsite_desc', 'local_o365'); $frontpagerec = $DB->get_record('course', ['id' => SITEID], 'id,shortname'); if (!empty($frontpagerec) && !empty($frontpagerec->shortname)) { $moodlesitename = $frontpagerec->shortname; } $result = $sharepoint->create_site($moodlesitename, $moodlesiteuri, $moodlesitedesc); mtrace('Created parent site'); } $courses = $DB->get_recordset('course'); $successes = []; $failures = []; foreach ($courses as $course) { if ($course->id == SITEID) { continue; } try { $sharepoint->create_course_site($course); $successes[] = $course->id; mtrace('Created course subsite for course ' . $course->id); } catch (\Exception $e) { $failures[$course->id] = $e->getMessage(); } } set_config('sharepoint_initialized', '1', 'local_o365'); }
/** * 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; }
/** * Run the health check. * * @return array Array of result data. Must include: * bool result Whether the health check passed or not. * int severity If the health check failed, how bad a problem is it? This is one of the SEVERITY_* constants. * string message A message to show the user. * string fixlink If the healthcheck failed, a link to help resolve the problem. */ public function run() { // Check that the system API user has a graph resource. $tokens = get_config('local_o365', 'systemtokens'); $tokens = unserialize($tokens); $graphresource = 'https://graph.windows.net'; if (!isset($tokens[$graphresource])) { return ['result' => false, 'severity' => static::SEVERITY_WARNING, 'message' => get_string('healthcheck_systemtoken_result_notoken', 'local_o365'), 'fixlink' => new \moodle_url('/local/o365/acp.php', ['mode' => 'setsystemuser'])]; } // Try to refresh the token as an indicator for successful communication. $oidcconfig = get_config('auth_oidc'); if (empty($oidcconfig)) { return ['result' => false, 'severity' => static::SEVERITY_FATAL, 'message' => get_string('healthcheck_systemtoken_result_noclientcreds', 'local_o365'), 'fixlink' => new \moodle_url('/admin/auth_config.php', ['auth' => 'oidc'])]; } $httpclient = new \local_o365\httpclient(); $clientdata = new \local_o365\oauth2\clientdata($oidcconfig->clientid, $oidcconfig->clientsecret, $oidcconfig->authendpoint, $oidcconfig->tokenendpoint); $systemtoken = \local_o365\oauth2\systemtoken::get_for_new_resource(null, 'https://graph.windows.net', $clientdata, $httpclient); if (empty($systemtoken)) { return ['result' => false, 'severity' => static::SEVERITY_WARNING, 'message' => get_string('healthcheck_systemtoken_result_badtoken', 'local_o365'), 'fixlink' => new \moodle_url('/local/o365/acp.php', ['mode' => 'setsystemuser'])]; } else { return ['result' => true, 'severity' => static::SEVERITY_OK, 'message' => get_string('healthcheck_systemtoken_result_passed', 'local_o365')]; } }
/** * Get the Azure AD UPN of a connected Moodle user. * * @param \stdClass $user The Moodle user. * @return string|bool The user's Azure AD UPN, or false if failure. */ public static function get_muser_upn($user) { global $DB; $now = time(); if (is_numeric($user)) { $user = $DB->get_record('user', ['id' => $user]); if (empty($user)) { \local_o365\utils::debug('User not found', 'rest\\azuread\\get_muser_upn', $user); return false; } } // Get user UPN. $userobjectdata = $DB->get_record('local_o365_objects', ['type' => 'user', 'moodleid' => $user->id]); if (!empty($userobjectdata)) { return $userobjectdata->o365name; } else { // Get user data. $authoidcuserdata = $DB->get_record('auth_oidc_token', ['username' => $user->username]); if (empty($authoidcuserdata)) { // No data for the user in the OIDC token table. Can't proceed. \local_o365\utils::debug('No oidc token found for user.', 'rest\\azuread\\get_muser_upn', $user->username); return false; } $httpclient = new \local_o365\httpclient(); try { $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); } catch (\Exception $e) { \local_o365\utils::debug($e->getMessage()); return false; } $resource = static::get_resource(); $token = \local_o365\oauth2\systemtoken::instance(null, $resource, $clientdata, $httpclient); $aadapiclient = new \local_o365\rest\azuread($token, $httpclient); $aaduserdata = $aadapiclient->get_user($authoidcuserdata->oidcuniqid); $userobjectdata = (object) ['type' => 'user', 'subtype' => '', 'objectid' => $aaduserdata['objectId'], 'o365name' => $aaduserdata['userPrincipalName'], 'moodleid' => $user->id, 'timecreated' => $now, 'timemodified' => $now]; $userobjectdata->id = $DB->insert_record('local_o365_objects', $userobjectdata); return $userobjectdata->o365name; } }
/** * 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); }
/** * Looks for links pointing to Office 365 Video content and processes them. * * @param $link HTML tag containing a link * @return string HTML content after processing. */ function filter_oembed_o365videocallback($link) { if (empty($link[3])) { return $link[0]; } $link[3] = preg_replace("/&/", "&", $link[3]); $values = array(); parse_str($link[3], $values); if (empty($values['chid']) || empty($values['vid'])) { return $link[0]; } if (!\local_o365\rest\sharepoint::is_configured()) { \local_o365\utils::debug('filter_oembed share point is not configured', 'filter_oembed_o365videocallback'); return $link[0]; } try { $spresource = \local_o365\rest\sharepoint::get_resource(); if (!empty($spresource)) { $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $sptoken = \local_o365\oauth2\systemtoken::instance(null, $spresource, $clientdata, $httpclient); if (!empty($sptoken)) { $sharepoint = new \local_o365\rest\sharepoint($sptoken, $httpclient); // Retrieve api url for video service. $url = $sharepoint->videoservice_discover(); if (!empty($url)) { $sharepoint->override_resource($url); $width = 640; if (!empty($values['width'])) { $width = $values['width']; } $height = 360; if (!empty($values['height'])) { $height = $values['height']; } // Retrieve embed code. return $sharepoint->get_video_embed_code($values['chid'], $values['vid'], $width, $height); } } } } catch (\Exception $e) { \local_o365\utils::debug('filter_oembed share point execption: ' . $e->getMessage(), 'filter_oembed_o365videocallback'); } return $link[0]; }
/** * Do the job. */ public function execute() { global $DB; // API Setup. try { $spresource = \local_o365\rest\sharepoint::get_resource(); if (empty($spresource)) { throw new \moodle_exception('erroracplocalo365notconfig', 'local_o365'); } $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $sptoken = \local_o365\oauth2\systemtoken::instance(null, $spresource, $clientdata, $httpclient); if (empty($sptoken)) { throw new \moodle_exception('erroracpnosptoken', 'local_o365'); } $sharepoint = new \local_o365\rest\sharepoint($sptoken, $httpclient); } catch (\Exception $e) { $errmsg = 'ERROR: Problem initializing SharePoint API. Reason: ' . $e->getMessage(); mtrace($errmsg); \local_o365\utils::debug($errmsg, 'local_o365\\task\\sharepointinit::execute'); set_config('sharepoint_initialized', 'error', 'local_o365'); return false; } // Create parent site(s). try { mtrace('Creating parent site for Moodle...'); $moodlesiteuri = $sharepoint->get_moodle_parent_site_uri(); $sitelevels = explode('/', $moodlesiteuri); $currentparentsite = ''; foreach ($sitelevels as $partialurl) { $sharepoint->set_site($currentparentsite); if ($sharepoint->site_exists($currentparentsite . '/' . $partialurl) === false) { $moodlesitename = get_string('acp_parentsite_name', 'local_o365'); $moodlesitedesc = get_string('acp_parentsite_desc', 'local_o365'); $frontpagerec = $DB->get_record('course', ['id' => SITEID], 'id,shortname'); if (!empty($frontpagerec) && !empty($frontpagerec->shortname)) { $moodlesitename = $frontpagerec->shortname; } mtrace('Setting parent site to "' . $currentparentsite . '", creating subsite "' . $partialurl . '"'); $result = $sharepoint->create_site($moodlesitename, $partialurl, $moodlesitedesc); $currentparentsite .= '/' . $partialurl; mtrace('Created parent site "' . $currentparentsite . '"'); } else { $currentparentsite .= '/' . $partialurl; mtrace('Parent site "' . $currentparentsite . '" already exists.'); } } mtrace('Finished creating Moodle parent site.'); } catch (\Exception $e) { $errmsg = 'ERROR: Problem creating parent site. Reason: ' . $e->getMessage(); mtrace($errmsg); \local_o365\utils::debug($errmsg, 'local_o365\\task\\sharepointinit::execute'); set_config('sharepoint_initialized', 'error', 'local_o365'); return false; } // Create course sites. mtrace('Creating course subsites in "' . $moodlesiteuri . '"'); $sharepoint->set_site($moodlesiteuri); $courses = $DB->get_recordset('course'); $successes = []; $failures = []; foreach ($courses as $course) { if ($course->id == SITEID) { continue; } try { $sharepoint->create_course_site($course); $successes[] = $course->id; mtrace('Created course subsite for course ' . $course->id); } catch (\Exception $e) { mtrace('Encountered error creating course subsite for course ' . $course->id); $failures[$course->id] = $e->getMessage(); } } if (!empty($failures)) { $errmsg = 'ERROR: Encountered problems creating course sites.'; mtrace($errmsg . ' See logs.'); \local_o365\utils::debug($errmsg, 'local_o365\\task\\sharepointinit::execute', $failures); set_config('sharepoint_initialized', 'error', 'local_o365'); } else { set_config('sharepoint_initialized', '1', 'local_o365'); mtrace('SharePoint successfully initialized.'); return true; } }
/** * Get a SharePoint token. * * @param bool $system If true, get a system API ser token instead of the user's token. * @param int|null $userid The userid to get a token for. If null, the current user will be used. * @return \local_o365\oauth2\token A SharePoint token object. */ protected function get_sharepoint_token($system = false, $userid = null) { global $USER; $resource = \local_o365\rest\sharepoint::get_resource(); if ($system === true) { return \local_o365\oauth2\systemtoken::instance(null, $resource, $this->clientdata, $this->httpclient); } else { $userid = !empty($userid) ? $userid : $USER->id; return \local_o365\oauth2\token::instance($userid, $resource, $this->clientdata, $this->httpclient); } }
/** * Do the job. */ public function execute() { $reqcap = \local_o365\rest\sharepoint::get_course_site_required_capability(); $oidcconfig = get_config('auth_oidc'); if (!empty($oidcconfig)) { $spresource = \local_o365\rest\sharepoint::get_resource(); if (!empty($spresource)) { $httpclient = new \local_o365\httpclient(); $clientdata = new \local_o365\oauth2\clientdata($oidcconfig->clientid, $oidcconfig->clientsecret, $oidcconfig->authendpoint, $oidcconfig->tokenendpoint); $sptoken = \local_o365\oauth2\systemtoken::instance(null, $spresource, $clientdata, $httpclient); if (!empty($sptoken)) { $sharepoint = new \local_o365\rest\sharepoint($sptoken, $httpclient); } } } if (empty($sharepoint)) { throw new \moodle_exception('errorcreatingsharepointclient', 'local_o365'); } $opdata = $this->get_custom_data(); if ($opdata->userid !== '*' && $opdata->roleid !== '*' && !empty($opdata->contextid)) { // Single user role assign/unassign. $this->do_role_assignmentchange($opdata->roleid, $opdata->userid, $opdata->contextid, $reqcap, $sharepoint); } else { if ($opdata->userid === '*' && $opdata->roleid !== '*') { // Capability update. $this->do_role_capabilitychange($opdata->roleid, $reqcap, $sharepoint); } else { if ($opdata->roleid === '*' && $opdata->userid === '*') { // Role deleted. $this->do_role_delete($reqcap, $sharepoint); } } } }
/** * Construct a sharepoint API client using the system API user. * * @return \local_o365\rest\sharepoint|bool A constructed sharepoint API client, or false if error. */ public static function construct_sharepoint_api_with_system_user() { $oidcconfig = get_config('auth_oidc'); if (!empty($oidcconfig)) { $spresource = \local_o365\rest\sharepoint::get_resource(); if (!empty($spresource)) { $httpclient = new \local_o365\httpclient(); $clientdata = new \local_o365\oauth2\clientdata($oidcconfig->clientid, $oidcconfig->clientsecret, $oidcconfig->authendpoint, $oidcconfig->tokenendpoint); $sptoken = \local_o365\oauth2\systemtoken::instance(null, $spresource, $clientdata, $httpclient); if (!empty($sptoken)) { $sharepoint = new \local_o365\rest\sharepoint($sptoken, $httpclient); return $sharepoint; } } } return false; }
/** * Construct a sharepoint API client using the system API user. * * @return \local_o365\rest\sharepoint|bool A constructed sharepoint API client, or false if error. */ public static function construct_sharepoint_api_with_system_user() { try { $spresource = \local_o365\rest\sharepoint::get_resource(); if (!empty($spresource)) { $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $sptoken = \local_o365\oauth2\systemtoken::instance(null, $spresource, $clientdata, $httpclient); if (!empty($sptoken)) { $sharepoint = new \local_o365\rest\sharepoint($sptoken, $httpclient); return $sharepoint; } } } catch (\Exception $e) { \local_o365\utils::debug($e->getMessage(), get_called_class()); } return false; }
/** * 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(); }