mirrored from git://git.moodle.org/moodle.git
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'MDL-75650-master-simple' of https://github.com/snake/mo…
- Loading branch information
Showing
6 changed files
with
973 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
lib/classes/oauth2/discovery/auth_server_config_reader.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace core\oauth2\discovery; | ||
|
||
use core\http_client; | ||
use GuzzleHttp\Exception\ClientException; | ||
|
||
/** | ||
* Simple reader class, allowing OAuth 2 Authorization Server Metadata to be read from an auth server's well-known. | ||
* | ||
* {@link https://www.rfc-editor.org/rfc/rfc8414} | ||
* | ||
* @package core | ||
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class auth_server_config_reader { | ||
|
||
/** @var \stdClass the config object read from the discovery document. */ | ||
protected \stdClass $metadata; | ||
|
||
/** @var \moodle_url the base URL for the auth server which was last used during a read.*/ | ||
protected \moodle_url $issuerurl; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param http_client $httpclient an http client instance. | ||
* @param string $wellknownsuffix the well-known suffix, defaulting to 'oauth-authorization-server'. | ||
*/ | ||
public function __construct(protected http_client $httpclient, | ||
protected string $wellknownsuffix = 'oauth-authorization-server') { | ||
} | ||
|
||
/** | ||
* Read the metadata from the remote host. | ||
* | ||
* @param \moodle_url $issuerurl the auth server issuer URL. | ||
* @return \stdClass the configuration data object. | ||
* @throws ClientException|\GuzzleHttp\Exception\GuzzleException if the http client experiences any problems. | ||
*/ | ||
public function read_configuration(\moodle_url $issuerurl): \stdClass { | ||
$this->issuerurl = $issuerurl; | ||
$this->validate_uri(); | ||
|
||
$url = $this->get_configuration_url()->out(false); | ||
$response = $this->httpclient->request('GET', $url); | ||
$this->metadata = json_decode($response->getBody()); | ||
return $this->metadata; | ||
} | ||
|
||
/** | ||
* Make sure the base URI is suitable for use in discovery. | ||
* | ||
* @return void | ||
* @throws \moodle_exception if the URI fails validation. | ||
*/ | ||
protected function validate_uri() { | ||
if (!empty($this->issuerurl->get_query_string())) { | ||
throw new \moodle_exception('Error: '.__METHOD__.': Auth server base URL cannot contain a query component.'); | ||
} | ||
if (strtolower($this->issuerurl->get_scheme()) !== 'https') { | ||
throw new \moodle_exception('Error: '.__METHOD__.': Auth server base URL must use HTTPS scheme.'); | ||
} | ||
// This catches URL fragments. Since a query string is ruled out above, out_omit_querystring(false) returns only fragments. | ||
if ($this->issuerurl->out_omit_querystring() != $this->issuerurl->out(false)) { | ||
throw new \moodle_exception('Error: '.__METHOD__.': Auth server base URL must not contain fragments.'); | ||
} | ||
} | ||
|
||
/** | ||
* Get the Auth server metadata URL. | ||
* | ||
* Per {@link https://www.rfc-editor.org/rfc/rfc8414#section-3}, if the issuer URL contains a path component, | ||
* the well known suffix is added between the host and path components. | ||
* | ||
* @return \moodle_url the full URL to the auth server metadata. | ||
*/ | ||
protected function get_configuration_url(): \moodle_url { | ||
$path = $this->issuerurl->get_path(); | ||
if (!empty($path) && $path !== '/') { | ||
// Insert the well known suffix between the host and path components. | ||
$port = $this->issuerurl->get_port() ? ':'.$this->issuerurl->get_port() : ''; | ||
$uri = $this->issuerurl->get_scheme() . "://" . $this->issuerurl->get_host() . $port ."/". | ||
".well-known/" . $this->wellknownsuffix . $path; | ||
} else { | ||
// No path, just append the well known suffix. | ||
$uri = $this->issuerurl->out(false); | ||
$uri .= (substr($uri, -1) == '/' ? '' : '/'); | ||
$uri .= ".well-known/$this->wellknownsuffix"; | ||
} | ||
|
||
return new \moodle_url($uri); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace core\oauth2\service; | ||
|
||
use core\http_client; | ||
use core\oauth2\discovery\auth_server_config_reader; | ||
use core\oauth2\endpoint; | ||
use core\oauth2\issuer; | ||
use GuzzleHttp\Psr7\Request; | ||
|
||
/** | ||
* MoodleNet OAuth 2 configuration. | ||
* | ||
* @package core | ||
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class moodlenet implements issuer_interface { | ||
|
||
/** | ||
* Get the issuer template to display in the form. | ||
* | ||
* @return issuer the issuer. | ||
*/ | ||
public static function init(): ?issuer { | ||
$record = (object) [ | ||
'name' => 'MoodleNet', | ||
'image' => 'https://moodle.net/favicon.ico', | ||
'baseurl' => 'https://moodle.net', | ||
'loginscopes' => '', | ||
'loginscopesoffline' => '', | ||
'loginparamsoffline' => '', | ||
'showonloginpage' => issuer::SERVICEONLY, | ||
'servicetype' => 'moodlenet', | ||
]; | ||
$issuer = new issuer(0, $record); | ||
|
||
return $issuer; | ||
} | ||
|
||
/** | ||
* Create the endpoints for the issuer. | ||
* | ||
* @param issuer $issuer the issuer instance. | ||
* @return issuer the issuer instance. | ||
*/ | ||
public static function create_endpoints(issuer $issuer): issuer { | ||
self::discover_endpoints($issuer); | ||
return $issuer; | ||
} | ||
|
||
/** | ||
* Read the OAuth 2 Auth Server Metadata. | ||
* | ||
* @param issuer $issuer the issuer instance. | ||
* @return int the number of endpoints created. | ||
*/ | ||
public static function discover_endpoints($issuer): int { | ||
$baseurl = $issuer->get('baseurl'); | ||
if (empty($baseurl)) { | ||
return 0; | ||
} | ||
|
||
$endpointscreated = 0; | ||
$configreader = new auth_server_config_reader(new http_client()); | ||
try { | ||
$config = $configreader->read_configuration(new \moodle_url($baseurl)); | ||
|
||
foreach ($config as $key => $value) { | ||
if (substr_compare($key, '_endpoint', -strlen('_endpoint')) === 0) { | ||
$record = new \stdClass(); | ||
$record->issuerid = $issuer->get('id'); | ||
$record->name = $key; | ||
$record->url = $value; | ||
|
||
$endpoint = new endpoint(0, $record); | ||
$endpoint->create(); | ||
$endpointscreated++; | ||
} | ||
|
||
if ($key == 'scopes_supported') { | ||
$issuer->set('scopessupported', implode(' ', $value)); | ||
$issuer->update(); | ||
} | ||
} | ||
} catch (\Exception $e) { | ||
throw new \moodle_exception('Could not read service configuration for issuer: ' . $issuer->get('name')); | ||
} | ||
|
||
try { | ||
self::client_registration($issuer); | ||
} catch (\Exception $e) { | ||
throw new \moodle_exception('Could not register client for issuer: ' . $issuer->get('name')); | ||
} | ||
|
||
return $endpointscreated; | ||
} | ||
|
||
/** | ||
* Perform (open) OAuth 2 Dynamic Client Registration with the MoodleNet application. | ||
* | ||
* @param issuer $issuer the issuer instance containing the service baseurl. | ||
* @return void | ||
*/ | ||
protected static function client_registration(issuer $issuer): void { | ||
global $CFG, $SITE; | ||
|
||
$clientid = $issuer->get('clientid'); | ||
$clientsecret = $issuer->get('clientsecret'); | ||
|
||
if (empty($clientid) && empty($clientsecret)) { | ||
$url = $issuer->get_endpoint_url('registration'); | ||
if ($url) { | ||
$scopes = str_replace("\r", " ", $issuer->get('scopessupported')); | ||
$hosturl = $CFG->wwwroot; | ||
|
||
$request = [ | ||
'client_name' => $SITE->fullname, | ||
'client_uri' => $hosturl, | ||
'logo_uri' => $hosturl . '/pix/f/moodle-256.png', | ||
'tos_uri' => $hosturl, | ||
'policy_uri' => $hosturl, | ||
'software_id' => 'moodle', | ||
'software_version' => $CFG->version, | ||
'redirect_uris' => [ | ||
$hosturl . '/admin/oauth2callback.php' | ||
], | ||
'token_endpoint_auth_method' => 'client_secret_basic', | ||
'grant_types' => [ | ||
'authorization_code', | ||
'refresh_token' | ||
], | ||
'response_types' => [ | ||
'code' | ||
], | ||
'scope' => $scopes | ||
]; | ||
|
||
$client = new http_client(); | ||
$request = new Request( | ||
'POST', | ||
$url, | ||
[ | ||
'Content-type' => 'application/json', | ||
'Accept' => 'application/json', | ||
], | ||
json_encode($request) | ||
); | ||
|
||
try { | ||
$response = $client->send($request); | ||
$responsebody = $response->getBody()->getContents(); | ||
$decodedbody = json_decode($responsebody, true); | ||
if (is_null($decodedbody)) { | ||
throw new \moodle_exception('Error: ' . __METHOD__ . ': Failed to decode response body. Invalid JSON.'); | ||
} | ||
$issuer->set('clientid', $decodedbody['client_id']); | ||
$issuer->set('clientsecret', $decodedbody['client_secret']); | ||
$issuer->update(); | ||
} catch (\Exception $e) { | ||
$msg = "Could not self-register {$issuer->get('name')}. Wrong URL or JSON data [URL: $url]"; | ||
throw new \moodle_exception($msg); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.