diff --git a/admin/tool/oauth2/tests/behat/basic_settings.feature b/admin/tool/oauth2/tests/behat/basic_settings.feature index f45a2c21019..d71f00edb9d 100644 --- a/admin/tool/oauth2/tests/behat/basic_settings.feature +++ b/admin/tool/oauth2/tests/behat/basic_settings.feature @@ -54,13 +54,17 @@ Feature: Basic OAuth2 functionality And I should see "Testing service" And "Allow login" "icon" should exist in the "Testing service" "table_row" And "Allow services" "icon" should exist in the "Testing service" "table_row" - And I should see "-" in the "Testing service" "table_row" + And "Service discovery successful" "icon" should exist in the "Testing service" "table_row" And I click on "Configure endpoints" "link" in the "Testing service" "table_row" And I should see "authorization_endpoint" - And I should not see "discovery_endpoint" + And I should see "discovery_endpoint" + And I should see "device_authorization_endpoint" And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Testing service" "table_row" - And I should see "firstname" in the "givenName" "table_row" + And I should see "firstname" in the "givenname" "table_row" + And I should see "idnumber" in the "sub" "table_row" + And I should see "email" in the "email" "table_row" + And I should see "lang" in the "locale" "table_row" And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Testing service" "table_row" And I set the following fields to these values: diff --git a/lib/classes/oauth2/service/microsoft.php b/lib/classes/oauth2/service/microsoft.php index 3a891a3c1c9..f31ee7cecf4 100644 --- a/lib/classes/oauth2/service/microsoft.php +++ b/lib/classes/oauth2/service/microsoft.php @@ -17,7 +17,6 @@ namespace core\oauth2\service; use core\oauth2\issuer; -use core\oauth2\endpoint; use core\oauth2\user_field_mapping; use core\oauth2\discovery\openidconnect; @@ -39,7 +38,7 @@ class microsoft extends openidconnect implements issuer_interface { $record = (object) [ 'name' => 'Microsoft', 'image' => 'https://www.microsoft.com/favicon.ico', - 'baseurl' => '', + 'baseurl' => 'https://login.microsoftonline.com/common/v2.0', 'loginscopes' => 'openid profile email user.read', 'loginscopesoffline' => 'openid profile email user.read offline_access', 'showonloginpage' => issuer::EVERYWHERE, @@ -50,49 +49,33 @@ class microsoft extends openidconnect implements issuer_interface { return $issuer; } - /** - * Create endpoints for this issuer. - * - * @param issuer $issuer Issuer the endpoints should be created for. - * @return issuer - */ - public static function create_endpoints(issuer $issuer): issuer { - $endpoints = [ - 'authorization_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', - 'token_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token', - 'userinfo_endpoint' => 'https://graph.microsoft.com/v1.0/me/', - 'userpicture_endpoint' => 'https://graph.microsoft.com/v1.0/me/photo/$value', - ]; - foreach ($endpoints as $name => $url) { - $record = (object) [ - 'issuerid' => $issuer->get('id'), - 'name' => $name, - 'url' => $url - ]; - $endpoint = new endpoint(0, $record); - $endpoint->create(); + #[\Override] + protected static function create_field_mappings(issuer $issuer): void { + // Remove existing user field mapping. + foreach (user_field_mapping::get_records(['issuerid' => $issuer->get('id')]) as $userfieldmapping) { + $userfieldmapping->delete(); } // Create the field mappings. $mapping = [ - 'givenName' => 'firstname', - 'surname' => 'lastname', - 'userPrincipalName' => 'email', + 'sub' => 'idnumber', + 'givenname' => 'firstname', + 'familyname' => 'lastname', + 'email' => 'email', 'displayName' => 'alternatename', 'officeLocation' => 'address', 'mobilePhone' => 'phone1', - 'preferredLanguage' => 'lang' + 'locale' => 'lang', ]; + foreach ($mapping as $external => $internal) { $record = (object) [ 'issuerid' => $issuer->get('id'), 'externalfield' => $external, - 'internalfield' => $internal + 'internalfield' => $internal, ]; $userfieldmapping = new user_field_mapping(0, $record); $userfieldmapping->create(); } - - return $issuer; } } diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index b9ce6a08005..9e785736a4c 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1501,5 +1501,103 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2024100704.07); } + if ($oldversion < 2024100704.09) { + // A [name => url] map of new OIDC endpoints to be updated/created. + $endpointuris = [ + 'discovery_endpoint' => 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration', + 'token_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + 'userinfo_endpoint' => 'https://graph.microsoft.com/oidc/userinfo', + 'authorization_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + 'device_authorization_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/devicecode', + 'end_session_endpoint' => 'https://login.microsoftonline.com/common/oauth2/v2.0/logout', + 'kerberos_endpoint' => 'https://login.microsoftonline.com/common/kerberos', + ]; + + // A [name] map of endpoints to be deleted. + $deletedendpointuris = [ + 'userpicture_endpoint', + ]; + + // A [internalfield => externalfield] map of new OIDC-based user field mappings to be updated/created. + $userfieldmappings = [ + 'idnumber' => 'sub', + 'firstname' => 'givenname', + 'lastname' => 'familyname', + 'email' => 'email', + 'lang' => 'locale', + ]; + + $admin = get_admin(); + $adminid = $admin ? $admin->id : '0'; + + $microsoftservices = $DB->get_records('oauth2_issuer', ['servicetype' => 'microsoft']); + foreach ($microsoftservices as $microsoftservice) { + $time = time(); + + // Insert/update the new endpoints. + foreach ($endpointuris as $endpointname => $endpointuri) { + $endpoint = ['issuerid' => $microsoftservice->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); + } + } + + // Delete the old endpoints. + foreach ($deletedendpointuris as $endpointname) { + $endpoint = ['issuerid' => $microsoftservice->id, 'name' => $endpointname]; + $DB->delete_records('oauth2_endpoint', $endpoint); + } + + // Insert/update new user field mappings. + foreach ($userfieldmappings as $internalfieldname => $externalfieldname) { + $fieldmap = ['issuerid' => $microsoftservice->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. + $microsoftservice->baseurl = 'https://login.microsoftonline.com/common/v2.0'; + $microsoftservice->timemodified = $time; + $microsoftservice->usermodified = $adminid; + $DB->update_record('oauth2_issuer', $microsoftservice); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2024100704.09); + } + return true; } diff --git a/lib/tests/oauth2_test.php b/lib/tests/oauth2_test.php index b95c74975b0..5292b7338e3 100644 --- a/lib/tests/oauth2_test.php +++ b/lib/tests/oauth2_test.php @@ -337,6 +337,7 @@ final class oauth2_test extends \advanced_testcase { ], 'Microsoft' => [ 'type' => 'microsoft', + 'discoveryurl' => '.well-known/openid-configuration', ], 'Facebook' => [ 'type' => 'facebook', diff --git a/version.php b/version.php index ee6a872331e..cc2807c5806 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024100704.08; // 20241007 = branching date YYYYMMDD - do not modify! +$version = 2024100704.09; // 20241007 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.5.4+ (Build: 20250530)'; // Human-friendly version name