/** * Get the content of the block. * * @return stdObject */ public function get_content() { global $USER, $DB; if (!isloggedin()) { return null; } if ($this->content !== null) { return $this->content; } $this->content = new \stdClass(); $this->content->text = ''; $this->content->footer = ''; try { $o365connected = \local_o365\utils::is_o365_connected($USER->id); if ($o365connected === true) { $this->content->text .= $this->get_content_connected(); } else { $connection = $DB->get_record('local_o365_connections', ['muserid' => $USER->id]); if (!empty($connection)) { $uselogin = !empty($connection->uselogin) ? true : false; $this->content->text .= $this->get_content_matched($connection->aadupn, $uselogin); } else { $this->content->text .= $this->get_content_notconnected(); } } } catch (\Exception $e) { $this->content->text = $e->getMessage(); } return $this->content; }
/** * Run before the main page mode - determines connection status. * * @return bool Success/Failure. */ public function header() { global $USER, $DB; $this->o365loginconnected = $USER->auth === 'oidc' ? true : false; $this->o365connected = \local_o365\utils::is_o365_connected($USER->id); return true; }
/** * Do the job. */ public function execute() { if (\local_o365\utils::is_configured() !== true) { return false; } $aadsyncenabled = get_config('local_o365', 'aadsync'); if (empty($aadsyncenabled) || $aadsyncenabled === 'photosynconlogin') { mtrace('Azure AD cron sync disabled. Nothing to do.'); return true; } $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $usersync = new \local_o365\feature\usersync\main($clientdata, $httpclient); $skiptoken = get_config('local_o365', 'task_usersync_lastskiptoken'); if (empty($skiptoken)) { $skiptoken = ''; } for ($i = 0; $i < 5; $i++) { $users = $usersync->get_users('default', $skiptoken); if (!empty($users) && is_array($users) && !empty($users['value']) && is_array($users['value'])) { $usersync->sync_users($users['value']); } else { // No users returned, we're likely past the last page of results. Erase deltalink state and exit loop. mtrace('No more users to sync.'); set_config('task_usersync_lastskiptoken', '', 'local_o365'); break; } $nextlink = ''; if (isset($users['odata.nextLink'])) { $nextlink = $users['odata.nextLink']; } else { if (isset($users['@odata.nextLink'])) { $nextlink = $users['@odata.nextLink']; } } // If we have an odata.nextLink, extract deltalink value and store in $deltalink for the next loop. Otherwise break. if (!empty($nextlink)) { $skiptoken = $this->extract_skiptoken($nextlink); if (empty($skiptoken)) { $skiptoken = ''; mtrace('Bad odata.nextLink received.'); break; } } else { $skiptoken = ''; mtrace('No odata.nextLink received.'); break; } } if (!empty($skiptoken)) { mtrace('Partial user sync completed. Saving place for next run.'); } else { mtrace('Full user sync completed. Resetting saved state for new run.'); } set_config('task_usersync_lastskiptoken', $skiptoken, 'local_o365'); return true; }
/** * Setup repistory form. * * @param moodleform $mform Moodle form (passed by reference) * @param string $classname repository class name */ public static function type_config_form($mform, $classname = 'repository') { global $CFG; $a = new stdClass(); if (\local_o365\utils::is_configured() !== true) { $mform->addElement('static', null, '', get_string('notconfigured', 'repository_onenote', $CFG->wwwroot)); } parent::type_config_form($mform); }
/** * Create a OneNote page inside the given section using the postdata containing the content of the page. * @param string $sectionid Id of OneNote section which the page will be created in. * @param string $postdata String containing the postdata containing the contents of the page. * @param string $boundary Boundary string to be used during the POST request. * @return mixed|null|string The HTTP response object from the POST request. */ protected function create_page_from_postdata($sectionid, $postdata, $boundary) { try { $url = static::API . '/sections/' . $sectionid . '/pages'; $token = $this->get_token(); if (empty($token)) { \local_o365\utils::debug('Could not get user token', 'create_page_from_postdata'); return null; } $ch = curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $headers = ['Content-Type: multipart/form-data; boundary=' . $boundary, 'Authorization: Bearer ' . rawurlencode($token)]; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata); $rawresponse = curl_exec($ch); // Check if curl call fails. if ($rawresponse === false) { $errorno = curl_errno($ch); curl_close($ch); // If curl call fails and reason is net connectivity return it or return null. return in_array($errorno, ['6', '7', '28']) ? 'connection_error' : null; } $info = curl_getinfo($ch); curl_close($ch); if ($info['http_code'] == 201) { $responsewithoutheader = substr($rawresponse, $info['header_size']); $response = json_decode($responsewithoutheader); return $response; } } catch (\Exception $e) { \local_o365\utils::debug($e->getMessage()); } return null; }
/** * 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; } }
/** * 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; $sql = 'SELECT mq.*, u.id as muserid, muserconn.id as muserexistingconnectionid, officeconn.id as officeuserexistingconnectionid, oidctok.id as officeuserexistingoidctoken FROM {local_o365_matchqueue} mq LEFT JOIN {user} u ON mq.musername = u.username LEFT JOIN {local_o365_connections} muserconn ON muserconn.muserid = u.id LEFT JOIN {local_o365_connections} officeconn ON officeconn.aadupn = mq.o365username LEFT JOIN {auth_oidc_token} oidctok ON oidctok.oidcusername = mq.o365username WHERE mq.completed = ? AND mq.errormessage = "" ORDER BY mq.id ASC LIMIT 0, 100'; $params = ['0']; $matchqueue = $DB->get_recordset_sql($sql, $params); $apiclient = $this->get_api(); foreach ($matchqueue as $matchrec) { mtrace('Processing ' . $matchrec->musername . '/' . $matchrec->o365username); try { // Check for matching Moodle user. if (empty($matchrec->muserid)) { $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = get_string('task_processmatchqueue_err_nomuser', 'local_o365'); $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($updatedrec->errormessage); continue; } // Check whether Moodle user is already o365 connected. if (\local_o365\utils::is_o365_connected($matchrec->muserid)) { $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = get_string('task_processmatchqueue_err_museralreadyo365', 'local_o365'); $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($updatedrec->errormessage); continue; } // Check existing matches for Moodle user. if (!empty($matchrec->muserexistingconnectionid)) { $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = get_string('task_processmatchqueue_err_museralreadymatched', 'local_o365'); $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($updatedrec->errormessage); continue; } // Check existing matches for Office user. if (!empty($matchrec->officeuserexistingconnectionid)) { $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = get_string('task_processmatchqueue_err_o365useralreadymatched', 'local_o365'); $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($updatedrec->errormessage); continue; } // Check existing tokens for Office 365 user (indicates o365 user is already connected to someone). if (!empty($matchrec->officeuserexistingoidctoken)) { $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = get_string('task_processmatchqueue_err_o365useralreadyconnected', 'local_o365'); $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($updatedrec->errormessage); continue; } // Check o365 username. $userfound = false; try { $o365user = $apiclient->get_user_by_upn($matchrec->o365username); $userfound = true; } catch (\Exception $e) { $userfound = false; } if ($userfound !== true) { $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = get_string('task_processmatchqueue_err_noo365user', 'local_o365'); $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($updatedrec->errormessage); continue; } // Match validated. $connectionrec = new \stdClass(); $connectionrec->muserid = $matchrec->muserid; $connectionrec->aadupn = \core_text::strtolower($o365user['userPrincipalName']); $connectionrec->uselogin = 0; $DB->insert_record('local_o365_connections', $connectionrec); $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace('Match record created for userid #' . $matchrec->muserid . ' and o365 user ' . $connectionrec->aadupn); } catch (\Exception $e) { $exceptionstring = $e->getMessage() . ': ' . $e->debuginfo; $updatedrec = new \stdClass(); $updatedrec->id = $matchrec->id; $updatedrec->errormessage = $exceptionstring; $updatedrec->completed = 1; $DB->update_record('local_o365_matchqueue', $updatedrec); mtrace($exceptionstring); } } $matchqueue->close(); return true; }
/** * Get a token instance for a new resource. * * @param string $resource The new resource. * @param \local_o365\oauth2\clientdata $clientdata Client information. * @param \local_o365\httpclientinterface $httpclient An HTTP client. * @return \local_o365\oauth2\token|bool A constructed token for the new resource, or false if failure. */ public static function get_for_new_resource($userid, $resource, \local_o365\oauth2\clientdata $clientdata, $httpclient) { $aadgraphtoken = static::instance($userid, 'https://graph.windows.net', $clientdata, $httpclient); if (!empty($aadgraphtoken)) { $params = ['client_id' => $clientdata->get_clientid(), 'client_secret' => $clientdata->get_clientsecret(), 'grant_type' => 'refresh_token', 'refresh_token' => $aadgraphtoken->get_refreshtoken(), 'resource' => $resource]; $params = http_build_query($params, '', '&'); $tokenendpoint = $clientdata->get_tokenendpoint(); $header = ['Content-Type: application/x-www-form-urlencoded', 'Content-Length: ' . strlen($params)]; $httpclient->resetHeader(); $httpclient->setHeader($header); $tokenresult = $httpclient->post($tokenendpoint, $params); $tokenresult = @json_decode($tokenresult, true); if (!empty($tokenresult) && isset($tokenresult['token_type']) && $tokenresult['token_type'] === 'Bearer') { static::store_new_token($userid, $tokenresult['access_token'], $tokenresult['expires_on'], $tokenresult['refresh_token'], $tokenresult['scope'], $tokenresult['resource']); $token = static::instance($userid, $resource, $clientdata, $httpclient); return $token; } else { $errmsg = 'Problem encountered getting a new token.'; if (isset($tokenresult['access_token'])) { $tokenresult['access_token'] = '---'; } if (isset($tokenresult['refresh_token'])) { $tokenresult['refresh_token'] = '---'; } $debuginfo = ['tokenresult' => $tokenresult, 'resource' => $resource]; \local_o365\utils::debug($errmsg, 'local_o365\\oauth2\\token::get_for_new_resource', $debuginfo); } } return false; }
/** * 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; } }
/** * Handle role_deleted event * * Does the following: * - Unfortunately the role has already been deleted when we hear about it here, and have no way to determine the affected * users. Therefore, we have to do a global sync. * * @param \core\event\role_deleted $event The triggered event. * @return bool Success/Failure. */ public static function handle_role_deleted(\core\event\role_deleted $event) { if (\local_o365\utils::is_configured() !== true) { return false; } $roleid = $event->objectid; // Role deletions can be heavy - run in cron. $spaccesssync = new \local_o365\task\sharepointaccesssync(); $spaccesssync->set_custom_data(['roleid' => '*', 'userid' => '*', 'contextid' => null]); \core\task\manager::queue_adhoc_task($spaccesssync); return true; }
/** * Handle calendar_unsubscribed event - queue calendar sync jobs for cron. * * @param \local_o365\event\calendar_unsubscribed $event The triggered event. * @return bool Success/Failure. */ public static function handle_calendar_unsubscribed(\local_o365\event\calendar_unsubscribed $event) { if (\local_o365\utils::is_configured() !== true) { return false; } $eventdata = $event->get_data(); $calunsubscribe = new \local_o365\feature\calsync\task\syncoldevents(); $calunsubscribe->set_custom_data(['caltype' => $eventdata['other']['caltype'], 'caltypeid' => isset($eventdata['other']['caltypeid']) ? $eventdata['other']['caltypeid'] : 0, 'userid' => $eventdata['userid'], 'timecreated' => time()]); \core\task\manager::queue_adhoc_task($calunsubscribe); return true; }
/** * Processes API responses. * * @param string $response The raw response from an API call. * @param array $expectedstructure A structure to validate. * @return array|null Array if successful, null if not. */ public function process_apicall_response($response, array $expectedstructure = array()) { $backtrace = debug_backtrace(0); $callingclass = isset($backtrace[1]['class']) ? $backtrace[1]['class'] : '?'; $callingfunc = isset($backtrace[1]['function']) ? $backtrace[1]['function'] : '?'; $callingline = isset($backtrace[0]['line']) ? $backtrace[0]['line'] : '?'; $caller = $callingclass . '::' . $callingfunc . ':' . $callingline; $result = @json_decode($response, true); if (empty($result) || !is_array($result)) { \local_o365\utils::debug('Bad response received', $caller, $response); throw new \moodle_exception('erroro365apibadcall', 'local_o365'); } if (isset($result['odata.error'])) { $errmsg = 'Error response received.'; \local_o365\utils::debug($errmsg, $caller, $result['odata.error']); if (isset($result['odata.error']['message']) && isset($result['odata.error']['message']['value'])) { $apierrormessage = $result['odata.error']['message']['value']; throw new \moodle_exception('erroro365apibadcall_message', 'local_o365', '', htmlentities($apierrormessage)); } else { throw new \moodle_exception('erroro365apibadcall', 'local_o365'); } } if (isset($result['error'])) { $errmsg = 'Error response received.'; \local_o365\utils::debug($errmsg, $caller, $result['error']); if (isset($result['error']['message'])) { $apierrormessage = 'Unknown error, check logs for more information.'; if (is_string($result['error']['message'])) { $apierrormessage = $result['error']['message']; } else { if (is_array($result['error']['message']) && isset($result['error']['message']['value'])) { $apierrormessage = $result['error']['message']['value']; } } throw new \moodle_exception('erroro365apibadcall_message', 'local_o365', '', htmlentities($apierrormessage)); } else { throw new \moodle_exception('erroro365apibadcall', 'local_o365'); } } foreach ($expectedstructure as $key => $val) { if (!isset($result[$key])) { $errmsg = 'Invalid structure received. No "' . $key . '"'; \local_o365\utils::debug($errmsg, $caller, $result); throw new \moodle_exception('erroro365apibadcall_message', 'local_o365', '', $errmsg); } if ($val !== null && $result[$key] !== $val) { $strreceivedval = \local_o365\utils::tostring($result[$key]); $strval = \local_o365\utils::tostring($val); $errmsg = 'Invalid structure received. Invalid "' . $key . '". Received "' . $strreceivedval . '", expected "' . $strval . '"'; \local_o365\utils::debug($errmsg, $caller, $result); throw new \moodle_exception('erroro365apibadcall_message', 'local_o365', '', $errmsg); } } return $result; }
/** * Gets the instance of the correct api class. Use this method to get an instance of the api class. * * @return \local_onenote\api\base An implementation of the OneNote API. */ public static function getinstance() { global $USER, $SESSION, $CFG; $msaccountclass = '\\local_onenote\\api\\msaccount'; $o365class = '\\local_onenote\\api\\o365'; $iso365user = \local_o365\utils::is_o365_connected($USER->id) === true && class_exists('\\local_o365\\rest\\onenote') ? true : false; if ($iso365user === true) { require_once $CFG->dirroot . '/local/msaccount/msaccount_client.php'; $sesskey = 'msaccount_client-' . md5(\msaccount_client::SCOPE); $disableo365onenote = get_user_preferences('local_o365_disableo365onenote', 0); $iso365user = !empty($SESSION->{$sesskey}) || !empty($disableo365onenote) ? false : $iso365user; if ($iso365user === true) { try { $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $onenoteresource = \local_o365\rest\onenote::get_resource(); $token = \local_o365\oauth2\token::instance($USER->id, $onenoteresource, $clientdata, $httpclient); if (empty($token)) { $iso365user = false; } } catch (\Exception $e) { $iso365user = false; } } $class = $iso365user === true ? $o365class : $msaccountclass; } else { $class = $msaccountclass; } if (empty(self::$instance)) { self::$instance = new $class(); } return self::$instance; }
/** * Do the job. */ public function execute() { $opdata = $this->get_custom_data(); $timecreated = isset($opdata->timecreated) ? $opdata->timecreated : time(); if (\local_o365\utils::is_configured() !== true) { \local_o365\utils::debug(get_string('erroracpauthoidcnotconfig', 'local_o365'), get_called_class()); return false; } // Sync site events. if ($opdata->caltype === 'site') { $this->sync_siteevents($timecreated); } else { if ($opdata->caltype === 'course') { $this->sync_courseevents($opdata->caltypeid, $timecreated); } else { if ($opdata->caltype === 'user') { $this->sync_userevents($opdata->userid, $timecreated); } } } }
/** * 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); } else { $errmsg = 'Could not get system API user token for SharePoint'; \local_o365\utils::debug($errmsg, 'local_o365\\task\\sharepointaccesssync::execute'); } } } 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); } } } }
/** * Do the job. */ public function execute() { global $DB; if (\local_o365\utils::is_configured() !== true) { return false; } $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['id'])) { mtrace('Could not create group for course #' . $course->id); var_dump($response); continue; } mtrace('Created group ' . $response['id'] . ' for course #' . $course->id); $objectrec = ['type' => 'group', 'subtype' => 'course', 'objectid' => $response['id'], '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(); }
/** * Render OneNote section of the block. * * @return string HTML for the rendered OneNote section of the block. */ protected function render_onenote() { global $USER, $PAGE; $action = optional_param('action', '', PARAM_TEXT); try { $onenoteapi = \local_onenote\api\base::getinstance(); $output = ''; if ($onenoteapi->is_logged_in()) { // Add the "save to onenote" button if we are on an assignment page. $onassignpage = $PAGE->cm && $PAGE->cm->modname == 'assign' && $action == 'editsubmission' ? true : false; if ($onassignpage === true && $onenoteapi->is_student($PAGE->cm->id, $USER->id)) { $workstr = get_string('workonthis', 'block_microsoft'); $output .= $onenoteapi->render_action_button($workstr, $PAGE->cm->id) . '<br /><br />'; } // Find moodle notebook, create if not found. $moodlenotebook = null; $cache = \cache::make('block_microsoft', 'onenotenotebook'); $moodlenotebook = $cache->get($USER->id); if (empty($moodlenotebook)) { $moodlenotebook = $this->get_onenote_notebook($onenoteapi); $result = $cache->set($USER->id, $moodlenotebook); } if (!empty($moodlenotebook)) { $url = new \moodle_url($moodlenotebook['url']); $stropennotebook = get_string('linkonenote', 'block_microsoft'); $linkattrs = ['onclick' => 'window.open(this.href,\'_blank\'); return false;', 'class' => 'servicelink block_microsoft_onenote']; $output .= \html_writer::link($url->out(false), $stropennotebook, $linkattrs); } else { $output .= get_string('error_nomoodlenotebook', 'block_microsoft'); } } else { $output .= $this->render_signin_widget($onenoteapi->get_login_url()); } return $output; } catch (\Exception $e) { if (class_exists('\\local_o365\\utils')) { $debuginfo = !empty($e->debuginfo) ? $e->debuginfo : null; \local_o365\utils::debug($e->getMessage(), 'block_microsoft', $debuginfo); } return '<span class="block_microsoft_onenote servicelink">' . get_string('linkonenote_unavailable', 'block_microsoft') . '<br /><small>' . get_string('contactadmin', 'block_microsoft') . '</small></span>'; } }
/** * Delete a course site and it's associated groups. * @param int $courseid The ID of the course to delete the site for. * @return bool Success/Failure. */ public function delete_course_site($courseid) { global $DB; $spsite = $DB->get_record('local_o365_coursespsite', ['courseid' => $courseid]); if (empty($spsite)) { // No site created (that we know about). $errmsg = 'Did not delete course SharePoint site because no record of a SharePoint site for that course was found.'; \local_o365\utils::debug($errmsg, 'rest\\sharepoint\\delete_course_site', $courseid); return false; } $this->set_site($spsite->siteurl); $spgroupsql = 'SELECT spgroup.* FROM {local_o365_spgroupdata} spgroup JOIN {local_o365_coursespsite} spsite ON spgroup.coursespsiteid = spsite.id WHERE spsite.courseid = ?'; $spgroups = $DB->get_records_sql($sqlgroupsql, [$courseid]); foreach ($spgroups as $spgroup) { try { $this->delete_group_by_id($spgroup->groupid); $DB->delete_records('local_o365_spgroupdata', ['id' => $spgroup->id]); } catch (\Exception $e) { // If the API call failed we can still continue. // Error is logged in API call function if failed. } } $this->delete_site(); $DB->delete_records('local_o365_coursespsite', ['courseid' => $courseid]); return true; }
/** * Do the job. */ public function execute() { global $DB, $CFG; require_once $CFG->dirroot . '/calendar/lib.php'; if (\local_o365\utils::is_configured() !== true) { return false; } // Get calendars set to sync in. $starttime = time(); \local_o365\feature\calsync\observers::set_event_import(true); // Using a direct query here so we don't run into static cache issues. $laststarttime = $DB->get_record('config_plugins', ['plugin' => 'local_o365', 'name' => 'calsyncinlastrun']); $laststarttime = !empty($laststarttime) && !empty($laststarttime->value) ? $laststarttime->value : 0; $httpclient = new \local_o365\httpclient(); $clientdata = \local_o365\oauth2\clientdata::instance_from_oidc(); $calsubs = $DB->get_recordset_select('local_o365_calsub', 'syncbehav = ? OR syncbehav = ?', ['in', 'both']); $calsync = new \local_o365\feature\calsync\main($clientdata, $httpclient); foreach ($calsubs as $i => $calsub) { try { mtrace('Syncing events for user #' . $calsub->user_id); $events = $calsync->get_events($calsub->user_id, $calsub->o365calid, $laststarttime); if (!empty($events) && is_array($events) && isset($events['value']) && is_array($events['value'])) { if (!empty($events['value'])) { foreach ($events['value'] as $i => $event) { if (!isset($event['Id'])) { $errmsg = 'Skipped an event because of malformed data.'; \local_o365\utils::debug($errmsg, 'importfromoutlook', $event); mtrace($errmsg); continue; } $idmapexists = $DB->record_exists('local_o365_calidmap', ['outlookeventid' => $event['Id']]); if ($idmapexists === false) { // Create Moodle event. $eventparams = ['name' => $event['Subject'], 'description' => $event['Body']['Content'], 'eventtype' => $calsub->caltype, 'repeatid' => 0, 'modulename' => 0, 'instance' => 0, 'timestart' => strtotime($event['Start']), 'visible' => 1, 'uuid' => '', 'sequence' => 1]; $end = strtotime($event['End']); $eventparams['timeduration'] = $end - $eventparams['timestart']; if ($calsub->caltype === 'user') { $eventparams['userid'] = $calsub->caltypeid; } if ($calsub->caltype === 'course') { $eventparams['courseid'] = $calsub->caltypeid; } $moodleevent = \calendar_event::create($eventparams, false); if (!empty($moodleevent) && !empty($moodleevent->id)) { $idmaprec = ['eventid' => $moodleevent->id, 'outlookeventid' => $event['Id'], 'origin' => 'o365', 'userid' => $calsub->user_id]; $DB->insert_record('local_o365_calidmap', (object) $idmaprec); mtrace('Successfully imported event #' . $moodleevent->id); } } } } else { mtrace('No new events to sync in.'); } } else { $errmsg = 'Bad response received when fetching events.'; \local_o365\utils::debug($errmsg, 'importfromoutlook', $events); mtrace($errmsg); } } catch (\Exception $e) { \local_o365\utils::debug('Error syncing events', 'importfromoutlook', $e->getMessage()); mtrace('Error: ' . $e->getMessage()); } } $calsubs->close(); \local_o365\feature\calsync\observers::set_event_import(false); set_config('calsyncinlastrun', $starttime, 'local_o365'); return true; }
/** * Handle group_member_removed event to remove a user from an o365 group. * * @param \core\event\group_member_removed $event The triggered event. * @return bool Success/Failure. */ public static function handle_group_member_removed(\core\event\group_member_removed $event) { global $DB; $unifiedapiclient = static::get_unified_api('handle_group_member_removed'); if (empty($unifiedapiclient)) { return false; } $azureadapiclient = static::get_azuread_api('handle_group_member_removed'); if (empty($azureadapiclient)) { return false; } $newmemberid = $event->relateduserid; $usergroupid = $event->objectid; // Look up group. $groupobjectrec = static::get_stored_groupdata($usergroupid, 'handle_group_member_removed'); if (empty($groupobjectrec)) { return false; } // Look up user. $userobjectdata = $DB->get_record('local_o365_objects', ['type' => 'user', 'moodleid' => $newmemberid]); if (empty($userobjectdata)) { $msg = 'Not removing user "' . $newmemberid . '" from group "' . $usergroupid . '". No Azure AD data for user found.'; $caller = '\\local_o365\\feature\\usergroups\\observers::handle_group_member_removed'; \local_o365\utils::debug($msg, $caller); return false; } $result = $azureadapiclient->remove_member_from_group($groupobjectrec->objectid, $userobjectdata->objectid); if ($result !== true) { $msg = 'Couldn\'t remove user from group.'; $caller = '\\local_o365\\feature\\usergroups\\observers::handle_group_member_removed'; \local_o365\utils::debug($msg, $caller, $result); return false; } return true; }
/** * Initiate an OIDC authorization request. * * @param bool $uselogin Whether to switch the user's Moodle login method to OpenID Connect upon successful authorization. */ protected function doauthrequest($uselogin) { global $CFG, $SESSION, $DB, $USER; require_once $CFG->dirroot . '/auth/oidc/auth.php'; $stateparams = ['redirect' => '/local/o365/ucp.php']; $extraparams = []; $promptlogin = false; $o365connected = \local_o365\utils::is_o365_connected($USER->id); if ($o365connected === true) { // User is already connected. redirect('/local/o365/ucp.php'); } $connection = $DB->get_record('local_o365_connections', ['muserid' => $USER->id]); if (!empty($connection)) { // Matched user. $extraparams['login_hint'] = $connection->aadupn; $promptlogin = true; } $auth = new \auth_oidc\loginflow\authcode(); $auth->set_httpclient(new \auth_oidc\httpclient()); if ($uselogin !== true) { $SESSION->auth_oidc_connectiononly = true; $stateparams['connectiononly'] = true; } $auth->initiateauthrequest($promptlogin, $stateparams, $extraparams); }
/** * Determines whether the given Azure AD UPN is already matched to a Moodle user (and has not been completed). * * @return false|stdClass Either the matched Moodle user record, or false if not matched. */ protected function check_for_matched($aadupn) { global $DB; $dbman = $DB->get_manager(); if ($dbman->table_exists('local_o365_connections')) { $match = $DB->get_record('local_o365_connections', ['aadupn' => $aadupn]); if (!empty($match) && \local_o365\utils::is_o365_connected($match->muserid) !== true) { return $DB->get_record('user', ['id' => $match->muserid]); } } return false; }
/** * Check setup in Azure. */ public function mode_checksetup() { $data = new \stdClass(); $success = false; $enableunifiedapi = optional_param('enableunifiedapi', 0, PARAM_INT); set_config('enableunifiedapi', $enableunifiedapi, 'local_o365'); $chineseapi = optional_param('chineseapi', 0, PARAM_INT); set_config('chineseapi', $chineseapi, 'local_o365'); $aadtenant = required_param('aadtenant', PARAM_TEXT); set_config('aadtenant', $aadtenant, 'local_o365'); $odburl = required_param('odburl', PARAM_TEXT); set_config('odburl', $odburl, 'local_o365'); $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'); } // Legacy API. $legacyapi = new \stdClass(); try { $aadapiclient = new \local_o365\rest\azuread($token, $httpclient); list($missingperms, $haswrite) = $aadapiclient->check_permissions(); $legacyapi->missingperms = $missingperms; $legacyapi->haswrite = $haswrite; } catch (\Exception $e) { \local_o365\utils::debug($e->getMessage(), 'mode_checksetup:legacy'); $legacyapi->error = $e->getMessage(); } $data->legacyapi = $legacyapi; // Unified API. $unifiedapi = new \stdClass(); $unifiedapi->active = false; if (\local_o365\rest\unified::is_enabled() === true) { try { $httpclient = new \local_o365\httpclient(); $unifiedresource = \local_o365\rest\unified::get_resource(); $token = \local_o365\oauth2\systemtoken::instance(null, $unifiedresource, $clientdata, $httpclient); if (empty($token)) { throw new \moodle_exception('errorchecksystemapiuser', 'local_o365'); } $unifiedapiclient = new \local_o365\rest\unified($token, $httpclient); $unifiedpermsresult = $unifiedapiclient->check_permissions(); if ($unifiedpermsresult === null) { $unifiedapi->active = false; } else { $unifiedapi->active = true; $unifiedapi->missingperms = $unifiedpermsresult; } } catch (\Exception $e) { $unifiedapi->active = false; \local_o365\utils::debug($e->getMessage(), 'mode_checksetup:unified'); $unifiedapi->error = $e->getMessage(); } } $data->unifiedapi = $unifiedapi; set_config('unifiedapiactive', (int) $unifiedapi->active, 'local_o365'); set_config('azuresetupresult', serialize($data), 'local_o365'); $success = true; echo $this->ajax_response($data, $success); }