Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
   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\discovery;
  18  
  19  use curl;
  20  use stdClass;
  21  use moodle_exception;
  22  use core\oauth2\issuer;
  23  use core\oauth2\endpoint;
  24  
  25  /**
  26   * Class for provider discovery definition, to allow services easily discover and process information.
  27   * This abstract class is called from core\oauth2\api when discovery points need to be updated.
  28   *
  29   * @package    core
  30   * @since      Moodle 3.11
  31   * @copyright  2021 Sara Arjona (sara@moodle.com)
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  abstract class base_definition {
  35  
  36      /**
  37       * Get the URL for the discovery manifest.
  38       *
  39       * @param issuer $issuer The OAuth issuer the endpoints should be discovered for.
  40       * @return string The URL of the discovery file, containing the endpoints.
  41       */
  42      public abstract static function get_discovery_endpoint_url(issuer $issuer): string;
  43  
  44      /**
  45       * Process the discovery information and create endpoints defined with the expected format.
  46       *
  47       * @param issuer $issuer The OAuth issuer the endpoints should be discovered for.
  48       * @param stdClass $info The discovery information, with the endpoints to process and create.
  49       * @return void
  50       */
  51      protected abstract static function process_configuration_json(issuer $issuer, stdClass $info): void;
  52  
  53      /**
  54       * Process how to map user field information.
  55       *
  56       * @param issuer $issuer The OAuth issuer the endpoints should be discovered for.
  57       * @return void
  58       */
  59      protected abstract static function create_field_mappings(issuer $issuer): void;
  60  
  61      /**
  62       * Self-register the issuer if the 'registration' endpoint exists and client id and secret aren't defined.
  63       *
  64       * @param issuer $issuer The OAuth issuer to register.
  65       * @return void
  66       */
  67      protected abstract static function register(issuer $issuer): void;
  68  
  69      /**
  70       * Create endpoints for this issuer.
  71       *
  72       * @param issuer $issuer Issuer the endpoints should be created for.
  73       * @return issuer
  74       */
  75      public static function create_endpoints(issuer $issuer): issuer {
  76          static::discover_endpoints($issuer);
  77  
  78          return $issuer;
  79      }
  80  
  81      /**
  82       * If the discovery endpoint exists for this issuer, try and determine the list of valid endpoints.
  83       *
  84       * @param issuer $issuer
  85       * @return int The number of discovered services.
  86       */
  87      public static function discover_endpoints($issuer): int {
  88          // Early return if baseurl is empty.
  89          if (empty($issuer->get('baseurl'))) {
  90              return 0;
  91          }
  92  
  93          // Get the discovery URL and check if it has changed.
  94          $creatediscoveryendpoint = false;
  95          $url = $issuer->get_endpoint_url('discovery');
  96          $providerurl = static::get_discovery_endpoint_url($issuer);
  97          if (!$url || $url != $providerurl) {
  98              $url = $providerurl;
  99              $creatediscoveryendpoint = true;
 100          }
 101  
 102          // Remove the existing endpoints before starting discovery.
 103          foreach (endpoint::get_records(['issuerid' => $issuer->get('id')]) as $endpoint) {
 104              // Discovery endpoint will be removed only if it will be created later, once we confirm it's working as expected.
 105              if ($creatediscoveryendpoint || $endpoint->get('name') != 'discovery_endpoint') {
 106                  $endpoint->delete();
 107              }
 108          }
 109  
 110          // Early return if discovery URL is empty.
 111          if (empty($url)) {
 112              return 0;
 113          }
 114  
 115          $curl = new curl();
 116          if (!$json = $curl->get($url)) {
 117              $msg = 'Could not discover end points for identity issuer: ' . $issuer->get('name') . " [URL: $url]";
 118              throw new moodle_exception($msg);
 119          }
 120  
 121          if ($msg = $curl->error) {
 122              throw new moodle_exception('Could not discover service endpoints: ' . $msg);
 123          }
 124  
 125          $info = json_decode($json);
 126          if (empty($info)) {
 127              $msg = 'Could not discover end points for identity issuer: ' . $issuer->get('name') . " [URL: $url]";
 128              throw new moodle_exception($msg);
 129          }
 130  
 131          if ($creatediscoveryendpoint) {
 132              // Create the discovery endpoint (because it didn't exist and the URL exists and is returning some valid JSON content).
 133              static::create_discovery_endpoint($issuer, $url);
 134          }
 135  
 136          static::process_configuration_json($issuer, $info);
 137          static::create_field_mappings($issuer);
 138          static::register($issuer);
 139  
 140          return endpoint::count_records(['issuerid' => $issuer->get('id')]);
 141      }
 142  
 143      /**
 144       * Helper method to create discovery endpoint.
 145       *
 146       * @param issuer $issuer Issuer the endpoints should be created for.
 147       * @param string $url Discovery endpoint URL.
 148       * @return endpoint The endpoint created.
 149       *
 150       * @throws \core\invalid_persistent_exception
 151       */
 152      protected static function create_discovery_endpoint(issuer $issuer, string $url): endpoint {
 153          $record = (object) [
 154              'issuerid' => $issuer->get('id'),
 155              'name' => 'discovery_endpoint',
 156              'url' => $url,
 157          ];
 158          $endpoint = new endpoint(0, $record);
 159          $endpoint->create();
 160  
 161          return $endpoint;
 162      }
 163  
 164  }