Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core\oauth2\service;
  18  
  19  use core\http_client;
  20  use core\oauth2\discovery\auth_server_config_reader;
  21  use core\oauth2\endpoint;
  22  use core\oauth2\issuer;
  23  use GuzzleHttp\Psr7\Request;
  24  
  25  /**
  26   * MoodleNet OAuth 2 configuration.
  27   *
  28   * @package    core
  29   * @copyright  2023 Jake Dallimore <jrhdallimore@gmail.com>
  30   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  class moodlenet implements issuer_interface {
  33  
  34      /**
  35       * Get the issuer template to display in the form.
  36       *
  37       * @return issuer the issuer.
  38       */
  39      public static function init(): ?issuer {
  40          $record = (object) [
  41              'name' => 'MoodleNet',
  42              'image' => 'https://moodle.net/favicon.ico',
  43              'baseurl' => 'https://moodle.net',
  44              'loginscopes' => '',
  45              'loginscopesoffline' => '',
  46              'loginparamsoffline' => '',
  47              'showonloginpage' => issuer::SERVICEONLY,
  48              'servicetype' => 'moodlenet',
  49          ];
  50          $issuer = new issuer(0, $record);
  51  
  52          return $issuer;
  53      }
  54  
  55      /**
  56       * Create the endpoints for the issuer.
  57       *
  58       * @param issuer $issuer the issuer instance.
  59       * @return issuer the issuer instance.
  60       */
  61      public static function create_endpoints(issuer $issuer): issuer {
  62          self::discover_endpoints($issuer);
  63          return $issuer;
  64      }
  65  
  66      /**
  67       * Read the OAuth 2 Auth Server Metadata.
  68       *
  69       * @param issuer $issuer the issuer instance.
  70       * @return int the number of endpoints created.
  71       */
  72      public static function discover_endpoints($issuer): int {
  73          $baseurl = $issuer->get('baseurl');
  74          if (empty($baseurl)) {
  75              return 0;
  76          }
  77  
  78          $endpointscreated = 0;
  79          $config = [];
  80          if (defined('BEHAT_SITE_RUNNING')) {
  81              $config['verify'] = false;
  82          }
  83          $configreader = new auth_server_config_reader(new http_client($config));
  84          try {
  85              $config = $configreader->read_configuration(new \moodle_url($baseurl));
  86  
  87              foreach ($config as $key => $value) {
  88                  if (substr_compare($key, '_endpoint', -strlen('_endpoint')) === 0) {
  89                      $record = new \stdClass();
  90                      $record->issuerid = $issuer->get('id');
  91                      $record->name = $key;
  92                      $record->url = $value;
  93  
  94                      $endpoint = new endpoint(0, $record);
  95                      $endpoint->create();
  96                      $endpointscreated++;
  97                  }
  98  
  99                  if ($key == 'scopes_supported') {
 100                      $issuer->set('scopessupported', implode(' ', $value));
 101                      $issuer->update();
 102                  }
 103              }
 104          } catch (\Exception $e) {
 105              throw new \moodle_exception('Could not read service configuration for issuer: ' . $issuer->get('name'));
 106          }
 107  
 108          try {
 109              self::client_registration($issuer);
 110          } catch (\Exception $e) {
 111              throw new \moodle_exception('Could not register client for issuer: ' . $issuer->get('name'));
 112          }
 113  
 114          return $endpointscreated;
 115      }
 116  
 117      /**
 118       * Perform (open) OAuth 2 Dynamic Client Registration with the MoodleNet application.
 119       *
 120       * @param issuer $issuer the issuer instance containing the service baseurl.
 121       * @return void
 122       */
 123      protected static function client_registration(issuer $issuer): void {
 124          global $CFG, $SITE;
 125  
 126          $clientid = $issuer->get('clientid');
 127          $clientsecret = $issuer->get('clientsecret');
 128  
 129          if (empty($clientid) && empty($clientsecret)) {
 130              $url = $issuer->get_endpoint_url('registration');
 131              if ($url) {
 132                  $scopes = str_replace("\r", " ", $issuer->get('scopessupported'));
 133                  $hosturl = $CFG->wwwroot;
 134  
 135                  $request = [
 136                      'client_name' => $SITE->fullname,
 137                      'client_uri' => $hosturl,
 138                      'logo_uri' => $hosturl . '/pix/f/moodle-256.png',
 139                      'tos_uri' => $hosturl,
 140                      'policy_uri' => $hosturl,
 141                      'software_id' => 'moodle',
 142                      'software_version' => $CFG->version,
 143                      'redirect_uris' => [
 144                          $hosturl . '/admin/oauth2callback.php'
 145                      ],
 146                      'token_endpoint_auth_method' => 'client_secret_basic',
 147                      'grant_types' => [
 148                          'authorization_code',
 149                          'refresh_token'
 150                      ],
 151                      'response_types' => [
 152                          'code'
 153                      ],
 154                      'scope' => $scopes
 155                  ];
 156  
 157                  $config = [];
 158                  if (defined('BEHAT_SITE_RUNNING')) {
 159                      $config['verify'] = false;
 160                  }
 161                  $client = new http_client($config);
 162                  $request = new Request(
 163                      'POST',
 164                      $url,
 165                      [
 166                          'Content-type' => 'application/json',
 167                          'Accept' => 'application/json',
 168                      ],
 169                      json_encode($request)
 170                  );
 171  
 172                  try {
 173                      $response = $client->send($request);
 174                      $responsebody = $response->getBody()->getContents();
 175                      $decodedbody = json_decode($responsebody, true);
 176                      if (is_null($decodedbody)) {
 177                          throw new \moodle_exception('Error: ' . __METHOD__ . ': Failed to decode response body. Invalid JSON.');
 178                      }
 179                      $issuer->set('clientid', $decodedbody['client_id']);
 180                      $issuer->set('clientsecret', $decodedbody['client_secret']);
 181                      $issuer->update();
 182                  } catch (\Exception $e) {
 183                      $msg = "Could not self-register {$issuer->get('name')}. Wrong URL or JSON data [URL: $url]";
 184                      throw new \moodle_exception($msg);
 185                  }
 186              }
 187          }
 188      }
 189  }