Example #1
0
 /**
  * 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';
 }
Example #2
0
 /**
  * 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;
 }
Example #3
0
 /**
  * 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;
 }
Example #5
0
 /**
  * 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;
     }
 }
Example #6
0
 /**
  * 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')];
     }
 }
Example #9
0
 /**
  * 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;
     }
 }
Example #10
0
 /**
  * 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;
     }
 }
Example #13
0
 /**
  * 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);
             }
         }
     }
 }
Example #15
0
 /**
  * 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;
 }
Example #16
0
 /**
  * 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;
 }
Example #17
0
 /**
  * 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();
 }