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.
   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * @package moodlecore
  20   * @subpackage backup-plan
  21   * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  /**
  26   * Abstract class defining the needed stuf for one restore step
  27   *
  28   * TODO: Finish phpdocs
  29   */
  30  abstract class restore_step extends base_step {
  31  
  32      /**
  33       * Constructor - instantiates one object of this class
  34       */
  35      public function __construct($name, $task = null) {
  36          if (!is_null($task) && !($task instanceof restore_task)) {
  37              throw new restore_step_exception('wrong_restore_task_specified');
  38          }
  39          parent::__construct($name, $task);
  40      }
  41  
  42      protected function get_restoreid() {
  43          if (is_null($this->task)) {
  44              throw new restore_step_exception('not_specified_restore_task');
  45          }
  46          return $this->task->get_restoreid();
  47      }
  48  
  49      /**
  50       * Apply course startdate offset based in original course startdate and course_offset_startdate setting
  51       * Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
  52       * executions in the same request
  53       *
  54       * Note: The policy is to roll date only for configurations and not for user data. see MDL-9367.
  55       *
  56       * @param int $value Time value (seconds since epoch), or empty for nothing
  57       * @return int Time value after applying the date offset, or empty for nothing
  58       */
  59      public function apply_date_offset($value) {
  60  
  61          // Empties don't offset - zeros (int and string), false and nulls return original value.
  62          if (empty($value)) {
  63              return $value;
  64          }
  65  
  66          static $cache = array();
  67          // Lookup cache.
  68          if (isset($cache[$this->get_restoreid()])) {
  69              return $value + $cache[$this->get_restoreid()];
  70          }
  71          // No cache, let's calculate the offset.
  72          $original = $this->task->get_info()->original_course_startdate;
  73          $setting = 0;
  74          if ($this->setting_exists('course_startdate')) { // Seting may not exist (MDL-25019).
  75              $settingobject = $this->task->get_setting('course_startdate');
  76              if (method_exists($settingobject, 'get_normalized_value')) {
  77                  $setting = $settingobject->get_normalized_value();
  78              } else {
  79                  $setting = $settingobject->get_value();
  80              }
  81          }
  82  
  83          if (empty($original) || empty($setting)) {
  84              // Original course has not startdate or setting doesn't exist, offset = 0.
  85              $cache[$this->get_restoreid()] = 0;
  86  
  87          } else {
  88              // Arrived here, let's calculate the real offset.
  89              $cache[$this->get_restoreid()] = $setting - $original;
  90          }
  91  
  92          // Return the passed value with cached offset applied.
  93          return $value + $cache[$this->get_restoreid()];
  94      }
  95  
  96      /**
  97       * Returns symmetric-key AES-256 decryption of base64 encoded contents.
  98       *
  99       * This method is used in restore operations to decrypt contents encrypted with
 100       * {@link encrypted_final_element} automatically decoding (base64) and decrypting
 101       * contents using the key stored in backup_encryptkey config.
 102       *
 103       * Requires openssl, cipher availability, and key existence (backup
 104       * automatically sets it if missing). Integrity is provided via HMAC.
 105       *
 106       * @param string $value {@link encrypted_final_element} value to decode and decrypt.
 107       * @return string|null decoded and decrypted value or null if the operation can not be performed.
 108       */
 109      public function decrypt($value) {
 110  
 111          // No openssl available, skip this field completely.
 112          if (!function_exists('openssl_encrypt')) {
 113              return null;
 114          }
 115  
 116          // No hash available, skip this field completely.
 117          if (!function_exists('hash_hmac')) {
 118              return null;
 119          }
 120  
 121          // Cypher not available, skip this field completely.
 122          if (!in_array(backup::CIPHER, openssl_get_cipher_methods())) {
 123              return null;
 124          }
 125  
 126          // Get the decrypt key. Skip if missing.
 127          $key = get_config('backup', 'backup_encryptkey');
 128          if ($key === false) {
 129              return null;
 130          }
 131  
 132          // And decode it.
 133          $key = base64_decode($key);
 134  
 135          // Arrived here, let's proceed with authentication (provides integrity).
 136          $hmaclen = 32; // SHA256 is 32 bytes.
 137          $ivlen = openssl_cipher_iv_length(backup::CIPHER);
 138          list($hmac, $iv, $text) = array_values(unpack("a{$hmaclen}hmac/a{$ivlen}iv/a*text", base64_decode($value)));
 139  
 140          // Verify HMAC matches expectations, skip if not (integrity failed).
 141          if (!hash_equals($hmac, hash_hmac('sha256', $iv . $text, $key, true))) {
 142              return null;
 143          }
 144  
 145          // Arrived here, integrity is ok, let's decrypt.
 146          $result = openssl_decrypt($text, backup::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
 147  
 148          // For some reason decrypt failed (strange, HMAC check should have deteted it), skip this field completely.
 149          if ($result === false) {
 150              return null;
 151          }
 152  
 153          return $result;
 154      }
 155  }
 156  
 157  /*
 158   * Exception class used by all the @restore_step stuff
 159   */
 160  class restore_step_exception extends base_step_exception {
 161  
 162      public function __construct($errorcode, $a=NULL, $debuginfo=null) {
 163          parent::__construct($errorcode, $a, $debuginfo);
 164      }
 165  }