From d20af220f8c4b1a56ea79a5216d4c6fd275726e2 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Mon, 26 Jun 2023 11:20:18 +0800 Subject: [PATCH 1/2] MDL-79520 core: update Clever integration to use OpenId Connect Instead of creating the endpoints manually, and fetching the user info from several endpoints, which also necessitates the use of a custom client subclass, use the OpenId configuration endpoint to auto-configure endpoints, and then use the userinfo endpoint to fetch user info. --- lib/classes/oauth2/client/clever.php | 64 --------------------------- lib/classes/oauth2/service/clever.php | 51 ++++++--------------- 2 files changed, 13 insertions(+), 102 deletions(-) delete mode 100644 lib/classes/oauth2/client/clever.php diff --git a/lib/classes/oauth2/client/clever.php b/lib/classes/oauth2/client/clever.php deleted file mode 100644 index 1b3aa2b164b..00000000000 --- a/lib/classes/oauth2/client/clever.php +++ /dev/null @@ -1,64 +0,0 @@ -. - -namespace core\oauth2\client; - -use core\oauth2\client; - -/** - * Class clever - Custom client handler to fetch data from Clever - * - * @package core - * @copyright 2022 OpenStax - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class clever extends client { - /** - * Fetch the user id from the userinfo endpoint and then query userdata - * - * @return array|false - */ - public function get_userinfo() { - $userinfo = parent::get_userinfo(); - $userid = $userinfo['idnumber']; - - return $this->get_userdata($userid); - } - - /** - * Obtain user name and email data via the userdata endpoint - * - * @param string $userid User ID value - * @return array|false - */ - private function get_userdata($userid) { - $url = $this->get_issuer()->get_endpoint_url('userdata'); - $url .= '/' . $userid; - - $response = $this->get($url); - if (!$response) { - return false; - } - - $userinfo = json_decode($response); - if (json_last_error() != JSON_ERROR_NONE) { - debugging('Error encountered while decoding user information: ' . json_last_error_msg()); - return false; - } - - return $this->map_userinfo_to_fields($userinfo); - } -} diff --git a/lib/classes/oauth2/service/clever.php b/lib/classes/oauth2/service/clever.php index 2d4c8ed4bab..8926bda50b5 100644 --- a/lib/classes/oauth2/service/clever.php +++ b/lib/classes/oauth2/service/clever.php @@ -18,7 +18,6 @@ namespace core\oauth2\service; use core\oauth2\issuer; use core\oauth2\discovery\openidconnect; -use core\oauth2\endpoint; use core\oauth2\user_field_mapping; /** @@ -39,7 +38,7 @@ class clever extends openidconnect implements issuer_interface { 'name' => 'Clever', 'image' => 'https://apps.clever.com/favicon.ico', 'basicauth' => 1, - 'baseurl' => '', + 'baseurl' => 'https://clever.com', 'showonloginpage' => issuer::LOGINONLY, 'servicetype' => 'clever', ]; @@ -48,45 +47,21 @@ class clever extends openidconnect implements issuer_interface { } /** - * Create endpoints for this issuer. + * Create field mappings for this issuer. * - * @param issuer $issuer Issuer the endpoints should be created for. - * @return issuer + * @param issuer $issuer Issuer the field mappings should be created for. */ - public static function create_endpoints(issuer $issuer): issuer { - $endpoints = [ - 'authorization_endpoint' => 'https://clever.com/oauth/authorize', - 'token_endpoint' => 'https://clever.com/oauth/tokens', - 'userinfo_endpoint' => 'https://api.clever.com/v3.0/me', - 'userdata_endpoint' => 'https://api.clever.com/v3.0/users' - ]; - foreach ($endpoints as $name => $url) { - $record = (object) [ - 'issuerid' => $issuer->get('id'), - 'name' => $name, - 'url' => $url - ]; - $endpoint = new endpoint(0, $record); - $endpoint->create(); - } + public static function create_field_mappings(issuer $issuer): void { + // Perform OIDC default field mapping. + parent::create_field_mappings($issuer); - // Create the field mappings. - $mapping = [ - 'data-id' => 'idnumber', - 'data-name-first' => 'firstname', - 'data-name-last' => 'lastname', - 'data-email' => 'email' + // Create the additional 'sub' field mapping. + $record = (object) [ + 'issuerid' => $issuer->get('id'), + 'externalfield' => 'sub', + 'internalfield' => 'idnumber', ]; - foreach ($mapping as $external => $internal) { - $record = (object) [ - 'issuerid' => $issuer->get('id'), - 'externalfield' => $external, - 'internalfield' => $internal - ]; - $userfieldmapping = new user_field_mapping(0, $record); - $userfieldmapping->create(); - } - - return $issuer; + $userfieldmapping = new user_field_mapping(0, $record); + $userfieldmapping->create(); } } From b2e184f9ad1dde315e85c49f48c01a7f83eec7b8 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 28 Sep 2023 13:09:22 +0800 Subject: [PATCH 2/2] MDL-79520 core: upgrade step to update existing Clever issuers to OIDC This change is an upgrade step which: - Updates endpoints to the new endpoints - Updates user field mappings so they map the new OIDC userinfo fields - Updates the issuer's baseurl --- lib/db/upgrade.php | 82 ++++++++++++++++++++++++++++++++++++++++++++++ version.php | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 541e042d9e6..5ff65523021 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1004,5 +1004,87 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2024020200.02); } + if ($oldversion < 2024021500.01) { + // A [name => url] map of new OIDC endpoints to be updated/created. + $endpointuris = [ + 'authorization_endpoint' => 'https://clever.com/oauth/authorize', + 'token_endpoint' => 'https://clever.com/oauth/tokens', + 'userinfo_endpoint' => 'https://api.clever.com/userinfo', + 'jwks_uri' => 'https://clever.com/oauth/certs', + ]; + + // A [internalfield => externalfield] map of new OIDC-based user field mappings to be updated/created. + $userfieldmappings = [ + 'idnumber' => 'sub', + 'firstname' => 'given_name', + 'lastname' => 'family_name', + 'email' => 'email', + ]; + + $admin = get_admin(); + $adminid = $admin ? $admin->id : '0'; + + $cleverservices = $DB->get_records('oauth2_issuer', ['servicetype' => 'clever']); + foreach ($cleverservices as $cleverservice) { + $time = time(); + + // Insert/update the new endpoints. + foreach ($endpointuris as $endpointname => $endpointuri) { + $endpoint = ['issuerid' => $cleverservice->id, 'name' => $endpointname]; + $endpointid = $DB->get_field('oauth2_endpoint', 'id', $endpoint); + + if ($endpointid) { + $endpoint = array_merge($endpoint, [ + 'id' => $endpointid, + 'url' => $endpointuri, + 'timemodified' => $time, + 'usermodified' => $adminid, + ]); + $DB->update_record('oauth2_endpoint', $endpoint); + } else { + $endpoint = array_merge($endpoint, [ + 'url' => $endpointuri, + 'timecreated' => $time, + 'timemodified' => $time, + 'usermodified' => $adminid, + ]); + $DB->insert_record('oauth2_endpoint', $endpoint); + } + } + + // Insert/update new user field mappings. + foreach ($userfieldmappings as $internalfieldname => $externalfieldname) { + $fieldmap = ['issuerid' => $cleverservice->id, 'internalfield' => $internalfieldname]; + $fieldmapid = $DB->get_field('oauth2_user_field_mapping', 'id', $fieldmap); + + if ($fieldmapid) { + $fieldmap = array_merge($fieldmap, [ + 'id' => $fieldmapid, + 'externalfield' => $externalfieldname, + 'timemodified' => $time, + 'usermodified' => $adminid, + ]); + $DB->update_record('oauth2_user_field_mapping', $fieldmap); + } else { + $fieldmap = array_merge($fieldmap, [ + 'externalfield' => $externalfieldname, + 'timecreated' => $time, + 'timemodified' => $time, + 'usermodified' => $adminid, + ]); + $DB->insert_record('oauth2_user_field_mapping', $fieldmap); + } + } + + // Update the baseurl for the issuer. + $cleverservice->baseurl = 'https://clever.com'; + $cleverservice->timemodified = $time; + $cleverservice->usermodified = $adminid; + $DB->update_record('oauth2_issuer', $cleverservice); + } + + upgrade_main_savepoint(true, 2024021500.01); + } + return true; } diff --git a/version.php b/version.php index 2cf1b5955b7..aa8fa402aed 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024021500.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024021500.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.4dev (Build: 20240215)'; // Human-friendly version name