Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   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  /**
  18   * Class for loading/storing issuers from the DB.
  19   *
  20   * @package    core
  21   * @copyright  2017 Damyon Wiese
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace core\oauth2;
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  use core\persistent;
  29  use lang_string;
  30  
  31  /**
  32   * Class for loading/storing issuer from the DB
  33   *
  34   * @copyright  2017 Damyon Wiese
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class issuer extends persistent {
  38  
  39      /** @var int Issuer is displayed on both login page and in the services lists */
  40      const EVERYWHERE = 1;
  41      /** @var int Issuer is displayed on the login page only */
  42      const LOGINONLY = 2;
  43      /** @var int Issuer is displayed only in the services lists and can not be used for login */
  44      const SERVICEONLY = 0;
  45  
  46      const TABLE = 'oauth2_issuer';
  47  
  48      /**
  49       * Return the definition of the properties of this model.
  50       *
  51       * @return array
  52       */
  53      protected static function define_properties() {
  54          return array(
  55              'name' => array(
  56                  'type' => PARAM_TEXT
  57              ),
  58              'image' => array(
  59                  'type' => PARAM_URL,
  60                  'null' => NULL_ALLOWED,
  61                  'default' => null
  62              ),
  63              'clientid' => array(
  64                  'type' => PARAM_RAW_TRIMMED,
  65                  'default' => ''
  66              ),
  67              'clientsecret' => array(
  68                  'type' => PARAM_RAW_TRIMMED,
  69                  'default' => ''
  70              ),
  71              'baseurl' => array(
  72                  'type' => PARAM_URL,
  73                  'default' => ''
  74              ),
  75              'enabled' => array(
  76                  'type' => PARAM_BOOL,
  77                  'default' => true
  78              ),
  79              'showonloginpage' => array(
  80                  'type' => PARAM_INT,
  81                  'default' => self::SERVICEONLY,
  82              ),
  83              'basicauth' => array(
  84                  'type' => PARAM_BOOL,
  85                  'default' => false
  86              ),
  87              'scopessupported' => array(
  88                  'type' => PARAM_RAW,
  89                  'null' => NULL_ALLOWED,
  90                  'default' => null
  91              ),
  92              'loginscopes' => array(
  93                  'type' => PARAM_RAW,
  94                  'default' => 'openid profile email'
  95              ),
  96              'loginscopesoffline' => array(
  97                  'type' => PARAM_RAW,
  98                  'default' => 'openid profile email'
  99              ),
 100              'loginparams' => array(
 101                  'type' => PARAM_RAW,
 102                  'default' => ''
 103              ),
 104              'loginparamsoffline' => array(
 105                  'type' => PARAM_RAW,
 106                  'default' => ''
 107              ),
 108              'alloweddomains' => array(
 109                  'type' => PARAM_RAW,
 110                  'default' => ''
 111              ),
 112              'sortorder' => array(
 113                  'type' => PARAM_INT,
 114                  'default' => 0,
 115              ),
 116              'requireconfirmation' => array(
 117                  'type' => PARAM_BOOL,
 118                  'default' => true
 119              ),
 120              'servicetype' => array(
 121                  'type' => PARAM_ALPHANUM,
 122                  'null' => NULL_ALLOWED,
 123                  'default' => null,
 124              ),
 125              'loginpagename' => array(
 126                  'type' => PARAM_TEXT,
 127                  'null' => NULL_ALLOWED,
 128                  'default' => null,
 129              ),
 130          );
 131      }
 132  
 133      /**
 134       * Hook to execute before validate.
 135       *
 136       * @return void
 137       */
 138      protected function before_validate() {
 139          if (($this->get('id') && $this->get('sortorder') === null) || !$this->get('id')) {
 140              $this->set('sortorder', $this->count_records());
 141          }
 142      }
 143  
 144      /**
 145       * Helper the get a named service endpoint.
 146       * @param string $type
 147       * @return string|false
 148       */
 149      public function get_endpoint_url($type) {
 150          $endpoint = endpoint::get_record([
 151              'issuerid' => $this->get('id'),
 152              'name' => $type . '_endpoint'
 153          ]);
 154  
 155          if ($endpoint) {
 156              return $endpoint->get('url');
 157          }
 158          return false;
 159      }
 160  
 161      /**
 162       * Perform matching against the list of allowed login domains for this issuer.
 163       *
 164       * @param string $email The email to check.
 165       * @return boolean
 166       */
 167      public function is_valid_login_domain($email) {
 168          if (empty($this->get('alloweddomains'))) {
 169              return true;
 170          }
 171  
 172          $validdomains = explode(',', $this->get('alloweddomains'));
 173  
 174          $parts = explode('@', $email, 2);
 175          $emaildomain = '';
 176          if (count($parts) > 1) {
 177              $emaildomain = $parts[1];
 178          }
 179  
 180          return \core\ip_utils::is_domain_in_allowed_list($emaildomain, $validdomains);
 181      }
 182  
 183      /**
 184       * Does this OAuth service support user authentication?
 185       * @return boolean
 186       */
 187      public function is_authentication_supported() {
 188          debugging('Method is_authentication_supported() is deprecated, please use is_available_for_login()',
 189              DEBUG_DEVELOPER);
 190          return (!empty($this->get_endpoint_url('userinfo')));
 191      }
 192  
 193      /**
 194       * Is this issue fully configured and enabled and can be used for login/signup
 195       *
 196       * @return bool
 197       * @throws \coding_exception
 198       */
 199      public function is_available_for_login(): bool {
 200          return $this->get('id') &&
 201              $this->is_configured() &&
 202              $this->get('showonloginpage') != self::SERVICEONLY &&
 203              $this->get('enabled') &&
 204              !empty($this->get_endpoint_url('userinfo'));
 205      }
 206  
 207      /**
 208       * Return true if this issuer looks like it has been configured.
 209       *
 210       * @return boolean
 211       */
 212      public function is_configured() {
 213          return (!empty($this->get('clientid')) && !empty($this->get('clientsecret')));
 214      }
 215  
 216      /**
 217       * Do we have a refresh token for a system account?
 218       * @return boolean
 219       */
 220      public function is_system_account_connected() {
 221          if (!$this->is_configured()) {
 222              return false;
 223          }
 224          $sys = system_account::get_record(['issuerid' => $this->get('id')]);
 225          if (empty($sys) || empty($sys->get('refreshtoken'))) {
 226              return false;
 227          }
 228  
 229          $scopes = api::get_system_scopes_for_issuer($this);
 230  
 231          $grantedscopes = $sys->get('grantedscopes');
 232  
 233          $scopes = explode(' ', $scopes);
 234  
 235          foreach ($scopes as $scope) {
 236              if (!empty($scope)) {
 237                  if (strpos(' ' . $grantedscopes . ' ', ' ' . $scope . ' ') === false) {
 238                      // We have not been granted all the scopes that are required.
 239                      return false;
 240                  }
 241              }
 242          }
 243  
 244          return true;
 245      }
 246  
 247      /**
 248       * Custom validator for end point URLs.
 249       * Because we send Bearer tokens we must ensure SSL.
 250       *
 251       * @param string $value The value to check.
 252       * @return lang_string|boolean
 253       */
 254      protected function validate_baseurl($value) {
 255          global $CFG;
 256          include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
 257          if (!empty($value) && !validateUrlSyntax($value, 'S+')) {
 258              return new lang_string('sslonlyaccess', 'error');
 259          }
 260          return true;
 261      }
 262  
 263      /**
 264       * Display name for the issuers used on the login page
 265       *
 266       * @return string
 267       */
 268      public function get_display_name(): string {
 269          return $this->get('loginpagename') ? $this->get('loginpagename') : $this->get('name');
 270      }
 271  }