예제 #1
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;
 }
예제 #2
0
 /**
  * Get a unified api 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 unified api token object.
  */
 protected function get_unified_token($system = false, $userid = null)
 {
     global $USER;
     $resource = \local_o365\rest\unified::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);
     }
 }
예제 #3
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;
     }
 }
 /**
  * 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;
 }
예제 #5
0
 /**
  * Get a unified api token.
  *
  * @return \local_o365\oauth2\token A unified api token object.
  */
 protected function get_unified_token()
 {
     global $USER;
     $resource = \local_o365\rest\unified::get_resource();
     return \local_o365\oauth2\token::instance($USER->id, $resource, $this->clientdata, $this->httpclient);
 }
예제 #6
0
 /**
  * Check setup in Azure.
  */
 public function mode_checksetup()
 {
     $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');
     }
     // Legacy API.
     $legacyapi = new \stdClass();
     $aadapiclient = new \local_o365\rest\azuread($token, $httpclient);
     list($missingperms, $haswrite) = $aadapiclient->check_permissions();
     $legacyapi->missingperms = $missingperms;
     $legacyapi->haswrite = $haswrite;
     // Unified API.
     $unifiedapi = new \stdClass();
     $unifiedapi->active = false;
     $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;
     }
     $data->legacyapi = $legacyapi;
     $data->unifiedapi = $unifiedapi;
     set_config('azuresetupresult', serialize($data), 'local_o365');
     set_config('unifiedapiactive', (int) $unifiedapi->active, 'local_o365');
     $success = true;
     echo $this->ajax_response($data, $success);
 }
예제 #7
0
 /**
  * Sync Azure AD Moodle users with the configured Azure AD directory.
  *
  * @param array $aadusers Array of Azure AD users from $this->get_users().
  * @return bool Success/Failure
  */
 public function sync_users(array $aadusers = array())
 {
     global $DB, $CFG;
     $aadsync = get_config('local_o365', 'aadsync');
     $aadsync = array_flip(explode(',', $aadsync));
     $usernames = [];
     foreach ($aadusers as $i => $user) {
         $upnlower = \core_text::strtolower($user['userPrincipalName']);
         $aadusers[$i]['upnlower'] = $upnlower;
         $usernames[] = $upnlower;
         $upnsplit = explode('@', $upnlower);
         if (!empty($upnsplit[0])) {
             $aadusers[$i]['upnsplit0'] = $upnsplit[0];
             $usernames[] = $upnsplit[0];
         }
     }
     // Retrieve object id for app.
     if (!PHPUNIT_TEST) {
         $appinfo = $this->get_application_serviceprincipal_info();
     }
     $objectid = null;
     if (!empty($appinfo)) {
         if (\local_o365\rest\unified::is_configured()) {
             $objectid = $appinfo['value'][0]['id'];
         } else {
             $objectid = $appinfo['value'][0]['objectId'];
         }
     }
     list($usernamesql, $usernameparams) = $DB->get_in_or_equal($usernames);
     $sql = 'SELECT u.username,
                    u.id as muserid,
                    u.auth,
                    tok.id as tokid,
                    conn.id as existingconnectionid,
                    assign.assigned assigned
               FROM {user} u
          LEFT JOIN {auth_oidc_token} tok ON tok.username = u.username
          LEFT JOIN {local_o365_connections} conn ON conn.muserid = u.id
          LEFT JOIN {local_o365_appassign} assign ON assign.muserid = u.id
              WHERE u.username ' . $usernamesql . ' AND u.mnethostid = ? AND u.deleted = ? ';
     $params = array_merge($usernameparams, [$CFG->mnet_localhost_id, '0']);
     $existingusers = $DB->get_records_sql($sql, $params);
     foreach ($aadusers as $user) {
         $this->mtrace(' ');
         $this->mtrace('Syncing user ' . $user['upnlower']);
         if (isset($user['aad.isDeleted']) && $user['aad.isDeleted'] == '1') {
             $this->mtrace('User is deleted. Skipping.');
             continue;
         }
         if (\local_o365\rest\unified::is_configured()) {
             $userobjectid = $user['id'];
         } else {
             $userobjectid = $user['objectId'];
         }
         if (!isset($existingusers[$user['upnlower']]) && !isset($existingusers[$user['upnsplit0']])) {
             $this->mtrace('User doesn\'t exist in Moodle');
             if (!isset($aadsync['create'])) {
                 $this->mtrace('Not creating a Moodle user because that sync option is disabled.');
                 continue;
             }
             try {
                 // Create moodle account, if enabled.
                 $newmuser = $this->create_user_from_aaddata($user);
                 if (!empty($newmuser)) {
                     $this->mtrace('Created user #' . $newmuser->id);
                 }
             } catch (\Exception $e) {
                 $this->mtrace('Could not create user "' . $user['userPrincipalName'] . '" Reason: ' . $e->getMessage());
             }
             try {
                 if (!PHPUNIT_TEST) {
                     if (!empty($newmuser) && !empty($userobjectid) && !empty($objectid) && isset($aadsync['appassign'])) {
                         $this->assign_user($newmuser->id, $userobjectid, $objectid);
                     }
                 }
             } catch (\Exception $e) {
                 $this->mtrace('Could not assign user "' . $user['userPrincipalName'] . '" Reason: ' . $e->getMessage());
             }
         } else {
             $existinguser = null;
             if (isset($existingusers[$user['upnlower']])) {
                 $existinguser = $existingusers[$user['upnlower']];
             } else {
                 if (isset($existingusers[$user['upnsplit0']])) {
                     $existinguser = $existingusers[$user['upnsplit0']];
                 }
             }
             // Assign user to app if not already assigned.
             if (empty($existinguser->assigned)) {
                 try {
                     if (!PHPUNIT_TEST) {
                         if (!empty($existinguser->muserid) && !empty($userobjectid) && !empty($objectid) && isset($aadsync['appassign'])) {
                             $this->assign_user($existinguser->muserid, $userobjectid, $objectid);
                         }
                     }
                 } catch (\Exception $e) {
                     $this->mtrace('Could not assign user "' . $user['userPrincipalName'] . '" Reason: ' . $e->getMessage());
                 }
             }
             if ($existinguser->auth !== 'oidc' && empty($existinguser->tok)) {
                 $this->mtrace('Found a user in Azure AD that seems to match a user in Moodle');
                 $this->mtrace(sprintf('moodle username: %s, aad upn: %s', $existinguser->username, $user['upnlower']));
                 if (!isset($aadsync['match'])) {
                     $this->mtrace('Not matching user because that sync option is disabled.');
                     continue;
                 }
                 if (!empty($existinguser->existingconnectionid)) {
                     $this->mtrace('User is already matched.');
                     continue;
                 }
                 // Match to o365 account, if enabled.
                 $matchrec = ['muserid' => $existinguser->muserid, 'aadupn' => $user['upnlower'], 'uselogin' => isset($aadsync['matchswitchauth']) ? 1 : 0];
                 $DB->insert_record('local_o365_connections', $matchrec);
                 $this->mtrace('Matched user.');
             } else {
                 // User already connected.
                 $this->mtrace('User is already synced.');
             }
         }
     }
     return true;
 }
예제 #8
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();
 }
예제 #9
0
    /**
     * Return an XHTML string for the setting
     * @return string Returns an XHTML string
     */
    public function output_html($data, $query = '')
    {
        global $OUTPUT;
        $button = \html_writer::tag('button', get_string('settings_detectperms_update', 'local_o365'), ['class' => 'refreshperms']);
        $results = \html_writer::tag('div', '', ['class' => 'results']);
        $settinghtml = $button . $results;
        if (\local_o365\adminsetting\detectoidc::setup_step_complete() === true) {
            $existingsetting = $this->config_read($this->name);
            if (!empty($existingsetting)) {
                $messageattrs = ['class' => 'permmessage'];
                $message = \html_writer::tag('span', get_string('settings_detectperms_valid', 'local_o365'), $messageattrs);
            } else {
                $messageattrs = ['class' => 'permmessage'];
                $message = \html_writer::tag('span', get_string('settings_detectperms_invalid', 'local_o365'), $messageattrs);
            }
        } else {
            $icon = $OUTPUT->pix_icon('i/warning', 'prerequisite not complete', 'moodle');
            $message = \html_writer::tag('span', get_string('settings_detectperms_nocreds', 'local_o365'));
            $settinghtml .= \html_writer::tag('div', $icon . $message, ['class' => 'alert-info local_o365_statusmessage']);
        }
        // Using a <script> tag here instead of $PAGE->requires->js() because using $PAGE object loads file too late.
        $scripturl = new \moodle_url('/local/o365/classes/adminsetting/azuresetup.js');
        $settinghtml .= '<script src="' . $scripturl->out() . '"></script>';
        $lastresults = get_config('local_o365', 'azuresetupresult');
        if (!empty($lastresults)) {
            $lastresults = @unserialize($lastresults);
            $lastresults = !empty($lastresults) && is_object($lastresults) ? $lastresults : false;
            $lastresults = json_encode(['success' => true, 'data' => $lastresults]);
        } else {
            $lastresults = json_encode(false);
        }
        $unifiedenabled = \local_o365\rest\unified::is_enabled() === true ? 'true' : 'false';
        $ajaxurl = new \moodle_url('/local/o365/ajax.php');
        $settinghtml .= '<script>
                            $(function() {
                                var opts = {
                                    url: "' . $ajaxurl->out() . '",
                                    lastresults: ' . $lastresults . ',
                                    iconsuccess: "' . addslashes($OUTPUT->pix_icon('t/check', 'success', 'moodle')) . '",
                                    iconinfo: "' . addslashes($OUTPUT->pix_icon('i/warning', 'information', 'moodle')) . '",
                                    iconerror: "' . addslashes($OUTPUT->pix_icon('t/delete', 'error', 'moodle')) . '",

                                    strupdate: "' . addslashes(get_string('settings_azuresetup_update', 'local_o365')) . '",
                                    strchecking: "' . addslashes(get_string('settings_azuresetup_checking', 'local_o365')) . '",
                                    strmissingperms: "' . addslashes(get_string('settings_azuresetup_missingperms', 'local_o365')) . '",
                                    strpermscorrect: "' . addslashes(get_string('settings_azuresetup_permscorrect', 'local_o365')) . '",
                                    strfixperms: "' . addslashes(get_string('settings_detectperms_fixperms', 'local_o365')) . '",
                                    strfixprereq: "' . addslashes(get_string('settings_detectperms_fixprereq', 'local_o365')) . '",
                                    strerrorfix: "' . addslashes(get_string('settings_detectperms_errorfix', 'local_o365')) . '",
                                    strerrorcheck: "' . addslashes(get_string('settings_azuresetup_errorcheck', 'local_o365')) . '",
                                    strnoinfo: "' . addslashes(get_string('settings_azuresetup_noinfo', 'local_o365')) . '",

                                    showunified: ' . $unifiedenabled . ',
                                    strunifiedheader: "' . addslashes(get_string('settings_azuresetup_unifiedheader', 'local_o365')) . '",
                                    strunifieddesc: "' . addslashes(get_string('settings_azuresetup_unifieddesc', 'local_o365')) . '",
                                    strunifiederror: "' . addslashes(get_string('settings_azuresetup_unifiederror', 'local_o365')) . '",
                                    strunifiedpermerror: "' . addslashes(get_string('settings_azuresetup_strunifiedpermerror', 'local_o365')) . '",
                                    strunifiedmissing: "' . addslashes(get_string('settings_azuresetup_unifiedmissing', 'local_o365')) . '",
                                    strunifiedactive: "' . addslashes(get_string('settings_azuresetup_unifiedactive', 'local_o365')) . '",

                                    strlegacyheader: "' . addslashes(get_string('settings_azuresetup_legacyheader', 'local_o365')) . '",
                                    strlegacydesc: "' . addslashes(get_string('settings_azuresetup_legacydesc', 'local_o365')) . '",
                                    strlegacyerror: "' . addslashes(get_string('settings_azuresetup_legacyerror', 'local_o365')) . '",

                                    strtenanterror: "' . addslashes(get_string('settings_azuresetup_strtenanterror', 'local_o365')) . '"
                                };
                                $("#admin-' . $this->name . '").azuresetup(opts);
                            });
                        </script>';
        return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description, true, '', null, $query);
    }
예제 #10
0
 /**
  * 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);
 }