Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   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 tool_brickfield;
  18  
  19  /**
  20   * Class registration contains the functions to manage registration validation.
  21   *
  22   * @package     tool_brickfield
  23   * @author      2021 Onwards Mike Churchward <mike@brickfieldlabs.ie>
  24   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL
  25   */
  26  class registration {
  27  
  28      /** @var int Registration information has not been entered. */
  29      const NOT_ENTERED = 0;
  30  
  31      /** @var int Registration information has been entered but not externally validated. */
  32      const PENDING = 1;
  33  
  34      /** @var int Registration information was entered but was not validated within the defined grace periods. */
  35      const INVALID = 2;
  36  
  37      /** @var int Registration information has been externally validated. */
  38      const VALIDATED = 3;
  39  
  40      /** @var int Registration information has expired and needs to be revalidated. */
  41      const EXPIRED = 4;
  42  
  43      /** @var int Registration validation attempted, but failed. */
  44      const ERROR = 5;
  45  
  46      /** @var string Name of variable storing the registration status. */
  47      const STATUS = 'bfregstatus';
  48  
  49      /** @var string Name of variable storing the last time the registration information was checked. */
  50      const VALIDATION_CHECK_TIME = 'bfregvalidationchecktime';
  51  
  52      /** @var string Name of variable storing the time the registration information was validated. */
  53      const VALIDATION_TIME = 'bfregvalidationtime';
  54  
  55      /** @var string Name of variable storing the time the summary data was last sent. */
  56      const SUMMARY_TIME = 'bfsummarytime';
  57  
  58      /** @var string Name of variable storing the registration API key. */
  59      const API_KEY = 'key';
  60  
  61      /** @var string Name of variable storing the registration API key. */
  62      const SECRET_KEY = 'hash';
  63  
  64      /** @var string Name of the variable storing the site id. */
  65      const SITEID = 'id';
  66  
  67      /** @var int The current validation status. */
  68      protected $validation;
  69  
  70      /** @var int The last time the validation was checked. */
  71      protected $checktime;
  72  
  73      /** @var int The last time the validation time was confirmed. */
  74      protected $validationtime;
  75  
  76      /** @var int The last time the summary data was sent. */
  77      protected $summarytime;
  78  
  79      /** @var string The API key required for registration. */
  80      protected $apikey;
  81  
  82      /** @var string The secret key required for registration. */
  83      protected $secretkey;
  84  
  85      /** @var string The registered site id. */
  86      protected $siteid;
  87  
  88      /** @var string The URL to register at. */
  89      private static $regurl = 'https://account.mybrickfield.ie/register';
  90  
  91      /** @var string The URL to view terms at. */
  92      private static $termsurl = 'https://account.mybrickfield.ie/terms';
  93  
  94      /**
  95       * Object registration constructor.
  96       * @throws \dml_exception
  97       */
  98      public function __construct() {
  99          $this->validation = $this->get_status();
 100          $this->checktime = $this->get_check_time();
 101          $this->validationtime = $this->get_validation_time();
 102          $this->summarytime = $this->get_summary_time();
 103          $this->apikey = $this->get_api_key();
 104          $this->secretkey = $this->get_secret_key();
 105          $this->siteid = $this->get_siteid();
 106      }
 107  
 108      /**
 109       * System can be used when it has been validated, or when its still awaiting validation.
 110       * @return bool
 111       */
 112      public function toolkit_is_active(): bool {
 113          return $this->status_is_validated() || $this->validation_pending();
 114      }
 115  
 116      /**
 117       * The "not validated" state also needs the grace period to still be in effect.
 118       * @return bool
 119       */
 120      public function validation_pending(): bool {
 121          return ($this->status_is_pending() || $this->status_is_error()) && $this->grace_period_valid();
 122      }
 123  
 124      /**
 125       * Return true if there was a validation error.
 126       * @return bool
 127       */
 128      public function validation_error(): bool {
 129          return $this->status_is_error();
 130      }
 131  
 132      /**
 133       * Perform all necessary steps when new keys are added. Also check that they actually look like keys.
 134       * @param string $apikey
 135       * @param string $secretkey
 136       * @return bool
 137       */
 138      public function set_keys_for_registration(string $apikey, string $secretkey): bool {
 139          if ($this->keys_are_valid($apikey, $secretkey)) {
 140              $this->set_api_key($apikey);
 141              $this->set_secret_key($secretkey);
 142              $this->set_not_validated();
 143              if ($this->summarytime <= 0) {
 144                  $this->set_summary_time();
 145              }
 146              return true;
 147          } else {
 148              $this->set_api_key('');
 149              $this->set_secret_key('');
 150              $this->set_not_entered();
 151              return false;
 152          }
 153      }
 154  
 155      /**
 156       * If the registration is not already valid, validate it. This may connect to the registration site.
 157       * @return bool
 158       * @throws \dml_exception
 159       */
 160      public function validate(): bool {
 161          // If this is currently valid, return true, unless its time to check again.
 162          if ($this->status_is_validated()) {
 163              // If the summary data has not been sent in over a week, invalidate the registration.
 164              if ($this->summarydata_grace_period_expired()) {
 165                  $this->set_invalid();
 166                  return false;
 167              }
 168              // Confirm registration after the grace period has expired.
 169              if ($this->grace_period_valid()) {
 170                  return true;
 171              } else {
 172                  // Recheck the registration.
 173                  return $this->revalidate();
 174              }
 175          }
 176  
 177          // Check for valid keys, and possibly move status to validation stage.
 178          if (!$this->keys_are_valid()) {
 179              // The current stored keys are not valid format, set the status to "not entered".
 180              $this->set_not_entered();
 181              return false;
 182          } else if ($this->status_is_not_entered()) {
 183              // If no keys have previously been seen, move to validation stage.
 184              $this->set_not_validated();
 185          }
 186  
 187          // If no validation has been confirmed, check the registration site.
 188          if ($this->validation_pending()) {
 189              $brickfieldconnect = $this->get_registration_connection();
 190              $this->set_check_time();
 191              if ($brickfieldconnect->is_registered() || $brickfieldconnect->update_registration($this->apikey, $this->secretkey)) {
 192                  // Keys are present and have been validated.
 193                  $this->set_valid();
 194                  return true;
 195              } else {
 196                  // Keys are present but were not validated.
 197                  $this->set_error();
 198              }
 199          }
 200  
 201          // If any of the grace periods have passed without a validation, invalidate the registration.
 202          if (!$this->grace_period_valid() || $this->summarydata_grace_period_expired()) {
 203              $this->set_invalid();
 204              return false;
 205          } else {
 206              return true;
 207          }
 208      }
 209  
 210      /**
 211       * Even if the regisration is currently valid, validate it again.
 212       * @return bool
 213       * @throws \dml_exception
 214       */
 215      public function revalidate(): bool {
 216          if ($this->status_is_validated()) {
 217              $this->set_not_validated();
 218          }
 219          return $this->validate();
 220      }
 221  
 222      /**
 223       * Get api key.
 224       * @return string
 225       * @throws \dml_exception
 226       */
 227      public function get_api_key(): string {
 228          $key = get_config(manager::PLUGINNAME, self::API_KEY);
 229          if ($key === false) {
 230              // Not set in config yet, so default it to "".
 231              $key = '';
 232              $this->set_api_key($key);
 233          }
 234          return $key;
 235      }
 236  
 237      /**
 238       * Get secret key.
 239       * @return string
 240       * @throws \dml_exception
 241       */
 242      public function get_secret_key(): string {
 243          $key = get_config(manager::PLUGINNAME, self::SECRET_KEY);
 244          if ($key === false) {
 245              // Not set in config yet, so default it to "".
 246              $key = '';
 247              $this->set_secret_key($key);
 248          }
 249          return $key;
 250      }
 251  
 252      /**
 253       * Get the registration URL.
 254       * @return string
 255       */
 256      public function get_regurl(): string {
 257          return self::$regurl;
 258      }
 259  
 260      /**
 261       * Get the terms and conditions URL.
 262       * @return string
 263       */
 264      public function get_termsurl(): string {
 265          return self::$termsurl;
 266      }
 267  
 268      /**
 269       * Perform all actions needed to note that the summary data was sent.
 270       */
 271      public function mark_summary_data_sent() {
 272          $this->set_summary_time();
 273      }
 274  
 275      /**
 276       * Set the registered site id.
 277       * @param int $id
 278       * @return bool
 279       */
 280      public function set_siteid(int $id): bool {
 281          $this->siteid = $id;
 282          return set_config(self::SITEID, $id, manager::PLUGINNAME);
 283      }
 284  
 285      /**
 286       * Return the registered site id.
 287       * @return int
 288       * @throws \dml_exception
 289       */
 290      public function get_siteid(): int {
 291          $siteid = get_config(manager::PLUGINNAME, self::SITEID);
 292          if ($siteid === false) {
 293              // Not set in config yet, so default it to 0.
 294              $siteid = 0;
 295              $this->set_siteid($siteid);
 296          }
 297          return (int)$siteid;
 298      }
 299  
 300      /**
 301       * Set the status as keys "not entered".
 302       * @return bool
 303       */
 304      protected function set_not_entered(): bool {
 305          return $this->set_status(self::NOT_ENTERED);
 306      }
 307  
 308      /**
 309       * "Not validated" means we have keys, but have not confirmed them yet. Set the validation time to start the grace period.
 310       * @return bool
 311       */
 312      protected function set_not_validated(): bool {
 313          $this->set_validation_time();
 314          return $this->set_status(self::PENDING);
 315      }
 316  
 317      /**
 318       * Set the registration as invalid.
 319       * @return bool
 320       */
 321      protected function set_invalid(): bool {
 322          $this->set_api_key('');
 323          $this->set_secret_key('');
 324          $this->set_siteid(0);
 325          return $this->set_status(self::INVALID);
 326      }
 327  
 328      /**
 329       * Set the registration as valid.
 330       * @return bool
 331       */
 332      protected function set_valid(): bool {
 333          $this->set_validation_time();
 334          $this->set_summary_time();
 335          return $this->set_status(self::VALIDATED);
 336      }
 337  
 338      /**
 339       * Set the status to "expired".
 340       * @return bool
 341       */
 342      protected function set_expired(): bool {
 343          return $this->set_status(self::EXPIRED);
 344      }
 345  
 346      /**
 347       * Set the status to "error".
 348       * @return bool
 349       */
 350      protected function set_error(): bool {
 351          return $this->set_status(self::ERROR);
 352      }
 353  
 354      /**
 355       * Set the configured api key value.
 356       * @param string $keyvalue
 357       * @return bool
 358       */
 359      protected function set_api_key(string $keyvalue): bool {
 360          $this->apikey = $keyvalue;
 361          return set_config(self::API_KEY, $keyvalue, manager::PLUGINNAME);
 362      }
 363  
 364      /**
 365       * Set the configured secret key value.
 366       * @param string $keyvalue
 367       * @return bool
 368       */
 369      protected function set_secret_key(string $keyvalue): bool {
 370          $this->secretkey = $keyvalue;
 371          return set_config(self::SECRET_KEY, $keyvalue, manager::PLUGINNAME);
 372      }
 373  
 374      /**
 375       * Return true if the logic says that the registration is valid.
 376       * @return bool
 377       */
 378      protected function status_is_validated(): bool {
 379          return $this->validation == self::VALIDATED;
 380      }
 381  
 382      /**
 383       * Return true if the current status is "not entered".
 384       * @return bool
 385       */
 386      protected function status_is_not_entered(): bool {
 387          return $this->validation == self::NOT_ENTERED;
 388      }
 389  
 390      /**
 391       * Return true if the current status is "pending".
 392       * @return bool
 393       */
 394      protected function status_is_pending(): bool {
 395          return $this->validation == self::PENDING;
 396      }
 397  
 398      /**
 399       * Return true if the current status is "expired".
 400       * @return bool
 401       */
 402      protected function status_is_expired(): bool {
 403          return $this->validation == self::EXPIRED;
 404      }
 405  
 406      /**
 407       * Return true if the current status is "invalid".
 408       * @return bool
 409       */
 410      protected function status_is_invalid(): bool {
 411          return $this->validation == self::INVALID;
 412      }
 413  
 414      /**
 415       * Return true if the current status is "error".
 416       * @return bool
 417       */
 418      protected function status_is_error() {
 419          return $this->validation == self::ERROR;
 420      }
 421  
 422      /**
 423       * Set the current registration status.
 424       * @param int $status
 425       * @return bool
 426       */
 427      protected function set_status(int $status): bool {
 428          $this->validation = $status;
 429          return set_config(self::STATUS, $status, manager::PLUGINNAME);
 430      }
 431  
 432      /**
 433       * Return the current registration status.
 434       * @return int
 435       * @throws \dml_exception
 436       */
 437      protected function get_status(): int {
 438          $status = get_config(manager::PLUGINNAME, self::STATUS);
 439          if ($status === false) {
 440              // Not set in config yet, so default it to "NOT_ENTERED".
 441              $status = self::NOT_ENTERED;
 442              $this->set_status($status);
 443          }
 444          return (int)$status;
 445      }
 446  
 447      /**
 448       * Set the time of the last registration check.
 449       * @param int $time
 450       * @return bool
 451       */
 452      protected function set_check_time(int $time = 0): bool {
 453          $time = ($time == 0) ? time() : $time;
 454          $this->checktime = $time;
 455          return set_config(self::VALIDATION_CHECK_TIME, $time, manager::PLUGINNAME);
 456      }
 457  
 458      /**
 459       * Get the time of the last registration check.
 460       * @return int
 461       * @throws \dml_exception
 462       */
 463      protected function get_check_time(): int {
 464          $time = get_config(manager::PLUGINNAME, self::VALIDATION_CHECK_TIME);
 465          if ($time === false) {
 466              // Not set in config yet, so default it to 0.
 467              $time = 0;
 468              $this->set_check_time($time);
 469          }
 470          return (int)$time;
 471      }
 472  
 473      /**
 474       * Set the registration validation time.
 475       * @param int $time
 476       * @return bool
 477       */
 478      protected function set_validation_time(int $time = 0): bool {
 479          $time = ($time == 0) ? time() : $time;
 480          $this->validationtime = $time;
 481          return set_config(self::VALIDATION_TIME, $time, manager::PLUGINNAME);
 482      }
 483  
 484      /**
 485       * Return the time of the registration validation.
 486       * @return int
 487       * @throws \dml_exception
 488       */
 489      protected function get_validation_time(): int {
 490          $time = get_config(manager::PLUGINNAME, self::VALIDATION_TIME);
 491          if ($time === false) {
 492              // Not set in config yet, so default it to 0.
 493              $time = 0;
 494              $this->set_validation_time($time);
 495          }
 496          return (int)$time;
 497      }
 498  
 499      /**
 500       * Set the time of the summary update.
 501       * @param int $time
 502       * @return bool
 503       */
 504      protected function set_summary_time(int $time = 0): bool {
 505          $time = ($time == 0) ? time() : $time;
 506          $this->summarytime = $time;
 507          return set_config(self::SUMMARY_TIME, $time, manager::PLUGINNAME);
 508      }
 509  
 510      /**
 511       * Return the time of the last summary update.
 512       * @return int
 513       * @throws \dml_exception
 514       */
 515      protected function get_summary_time(): int {
 516          $time = get_config(manager::PLUGINNAME, self::SUMMARY_TIME);
 517          if ($time === false) {
 518              // Not set in config yet, so default it to 0.
 519              $time = 0;
 520              $this->set_summary_time($time);
 521          }
 522          return (int)$time;
 523      }
 524  
 525      /**
 526       * Return true if all keys have valid format.
 527       * @param string|null $apikey
 528       * @param string|null $secretkey
 529       * @return bool
 530       */
 531      protected function keys_are_valid(?string $apikey = null, ?string $secretkey = null): bool {
 532          $apikey = $apikey ?? $this->apikey;
 533          $secretkey = $secretkey ?? $this->secretkey;
 534          return $this->apikey_is_valid($apikey) && $this->secretkey_is_valid($secretkey);
 535      }
 536  
 537      /**
 538       * Validates that the entered API key is in the expected format.
 539       * @param string $apikey
 540       * @return bool
 541       */
 542      protected function apikey_is_valid(string $apikey): bool {
 543          return $this->valid_key_format($apikey);
 544      }
 545  
 546      /**
 547       * Validates that the entered Secret key is in the expected format.
 548       * @param string $secretkey
 549       * @return bool
 550       */
 551      protected function secretkey_is_valid(string $secretkey): bool {
 552          return $this->valid_key_format($secretkey);
 553      }
 554  
 555      /**
 556       * Validates that the passed in key looks like an MD5 hash.
 557       * @param string $key
 558       * @return bool
 559       */
 560      protected function valid_key_format(string $key): bool {
 561          return !empty($key) && (preg_match('/^[a-f0-9]{32}$/', $key) === 1);
 562      }
 563  
 564      /**
 565       * Get the registration grace period.
 566       * @return int
 567       */
 568      protected function get_grace_period(): int {
 569          return WEEKSECS;
 570      }
 571  
 572      /**
 573       * Check if the unvalidated time is still within the grace period.
 574       * @return bool
 575       */
 576      protected function grace_period_valid(): bool {
 577          return (time() - $this->validationtime) < $this->get_grace_period();
 578      }
 579  
 580      /**
 581       * Check if the last time the summary data was sent is within the grace period.
 582       * @return bool
 583       */
 584      protected function summarydata_grace_period_expired(): bool {
 585          return (time() - $this->summarytime) > $this->get_grace_period();
 586      }
 587  
 588      /**
 589       * Return an instance of the connection class.
 590       * @return brickfieldconnect
 591       */
 592      protected function get_registration_connection(): brickfieldconnect {
 593          return new brickfieldconnect();
 594      }
 595  }