Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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   * Defines various element classes used in specific areas
  20   *
  21   * @package     core_backup
  22   * @subpackage  moodle2
  23   * @category    backup
  24   * @copyright   2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  25   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * Implementation of backup_final_element that provides one interceptor for anonymization of data
  32   *
  33   * This class overwrites the standard set_value() method, in order to get (by name)
  34   * functions from backup_anonymizer_helper executed, producing anonymization of information
  35   * to happen in a clean way
  36   *
  37   * TODO: Finish phpdocs
  38   */
  39  class anonymizer_final_element extends backup_final_element {
  40  
  41      public function set_value($value) {
  42          // Get parent name
  43          $pname = $this->get_parent()->get_name();
  44          // Get my name
  45          $myname = $this->get_name();
  46          // Define class and function name
  47          $classname = 'backup_anonymizer_helper';
  48          $methodname= 'process_' . $pname . '_' . $myname;
  49          // Invoke the interception method
  50          $result = call_user_func(array($classname, $methodname), $value);
  51          // Finally set it
  52          parent::set_value($result);
  53      }
  54  }
  55  
  56  /**
  57   * Implementation of backup_final_element that provides special handling of mnethosturl
  58   *
  59   * This class overwrites the standard set_value() method, in order to decide,
  60   * based on various config options, what to do with the field.
  61   *
  62   * TODO: Finish phpdocs
  63   */
  64  class mnethosturl_final_element extends backup_final_element {
  65  
  66      public function set_value($value) {
  67          global $CFG;
  68  
  69          $localhostwwwroot = backup_plan_dbops::get_mnet_localhost_wwwroot();
  70  
  71          // If user wwwroot matches mnet local host one or if
  72          // there isn't associated wwwroot, skip sending it to file
  73          if ($localhostwwwroot == $value || empty($value)) {
  74              // Do nothing
  75          } else {
  76              parent::set_value($value);
  77          }
  78      }
  79  }
  80  
  81  /**
  82   * Implementation of {@link backup_final_element} that provides base64 encoding.
  83   *
  84   * This final element transparently encodes with base64_encode() contents that
  85   * normally are not safe for being stored in utf-8 xml files (binaries, serialized
  86   * data...).
  87   */
  88  class base64_encode_final_element extends backup_final_element {
  89  
  90      /**
  91       * Set the value for the final element, encoding it as utf-8/xml safe base64.
  92       *
  93       * @param string $value Original value coming from backup step source, usually db.
  94       */
  95      public function set_value($value) {
  96          parent::set_value(base64_encode($value));
  97      }
  98  }
  99  
 100  /**
 101   * Implementation of {@link backup_final_element} that provides symmetric-key AES-256 encryption of contents.
 102   *
 103   * This final element transparently encrypts, for secure storage and transport, any content
 104   * that shouldn't be shown normally in plain text. Usually, passwords or keys that cannot use
 105   * hashing algorithms, although potentially can encrypt any content. All information is encoded
 106   * using base64.
 107   *
 108   * Features:
 109   *   - requires openssl extension to work. Without it contents are completely omitted.
 110   *   - automatically creates an appropriate default key for the site and stores it into backup_encryptkey config (bas64 encoded).
 111   *   - uses a different appropriate init vector for every operation, which is transmited with the encrypted contents.
 112   *   - all generated data is base64 encoded for safe transmission.
 113   *   - automatically adds "encrypted" attribute for easier detection.
 114   *   - implements HMAC for providing integrity.
 115   *
 116   * @copyright 2017 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
 117   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 118   */
 119  class encrypted_final_element extends backup_final_element {
 120  
 121      /** @var string cypher appropiate raw key for backups in the site. Defaults to backup_encryptkey config. */
 122      protected $key = null;
 123  
 124      /**
 125       * Constructor - instantiates a encrypted_final_element, specifying its basic info.
 126       *
 127       * Overridden to automatically add the 'encrypted' attribute if missing.
 128       *
 129       * @param string $name name of the element
 130       * @param array  $attributes attributes this element will handle (optional, defaults to null)
 131       */
 132      public function __construct($name, $attributes = null) {
 133          parent::__construct($name, $attributes);
 134          if (! $this->get_attribute('encrypted')) {
 135              $this->add_attributes('encrypted');
 136          }
 137      }
 138  
 139      /**
 140       * Set the encryption key manually, overriding default backup_encryptkey config.
 141       *
 142       * @param string $key key to be used for encrypting. Required to be 256-bit key.
 143       *               Use a safe generation technique. See self::generate_encryption_random_key() below.
 144       */
 145      protected function set_key($key) {
 146          $bytes = strlen($key); // Get key length in bytes.
 147  
 148          // Only accept keys with the expected (backup::CIPHERKEYLEN) key length. There are a number of hashing,
 149          // random generators to achieve this esasily, like the one shown below to create the default
 150          // site encryption key and ivs.
 151          if ($bytes !== backup::CIPHERKEYLEN) {
 152              $info = (object)array('expected' => backup::CIPHERKEYLEN, 'found' => $bytes);
 153              throw new base_element_struct_exception('encrypted_final_element incorrect key length', $info);
 154          }
 155          // Everything went ok, store the key.
 156          $this->key = $key;
 157      }
 158  
 159      /**
 160       * Set the value of the field.
 161       *
 162       * This method sets the value of the element, encrypted using the specified key for it,
 163       * defaulting to (and generating) backup_encryptkey config. HMAC is used for integrity.
 164       *
 165       * @param string $value plain-text content the will be stored encrypted and encoded.
 166       */
 167      public function set_value($value) {
 168  
 169          // No openssl available, skip this field completely.
 170          if (!function_exists('openssl_encrypt')) {
 171              return;
 172          }
 173  
 174          // No hmac available, skip this field completely.
 175          if (!function_exists('hash_hmac')) {
 176              return;
 177          }
 178  
 179          // Cypher not available, skip this field completely.
 180          if (!in_array(backup::CIPHER, openssl_get_cipher_methods())) {
 181              return;
 182          }
 183  
 184          // Ensure we have a good key, manual or default.
 185          if (empty($this->key)) {
 186              // The key has not been set manually, look for it at config (base64 encoded there).
 187              $enckey = get_config('backup', 'backup_encryptkey');
 188              if ($enckey === false) {
 189                  // Has not been set, calculate and save an appropiate random key automatically.
 190                  $enckey = base64_encode(self::generate_encryption_random_key(backup::CIPHERKEYLEN));
 191                  set_config('backup_encryptkey', $enckey, 'backup');
 192              }
 193              $this->set_key(base64_decode($enckey));
 194          }
 195  
 196          // Now we need an iv for this operation.
 197          $iv = self::generate_encryption_random_key(openssl_cipher_iv_length(backup::CIPHER));
 198  
 199          // Everything is ready, let's encrypt and prepend the 1-shot iv.
 200          $value = $iv . openssl_encrypt($value, backup::CIPHER, $this->key, OPENSSL_RAW_DATA, $iv);
 201  
 202          // Calculate the hmac of the value (iv + encrypted) and prepend it.
 203          $hmac = hash_hmac('sha256', $value, $this->key, true);
 204          $value = $hmac . $value;
 205  
 206          // Ready, set the encoded value.
 207          parent::set_value(base64_encode($value));
 208  
 209          // Finally, if the field has an "encrypted" attribute, set it to true.
 210          if ($att = $this->get_attribute('encrypted')) {
 211              $att->set_value('true');
 212          }
 213      }
 214  
 215      /**
 216       * Generate an appropiate random key to be used for encrypting backup information.
 217       *
 218       * Normally used as site default encryption key (backup_encryptkey config) and also
 219       * for calculating the init vectors.
 220       *
 221       * Note that until PHP 5.6.12 openssl_random_pseudo_bytes() did NOT
 222       * use a "cryptographically strong algorithm" {@link https://bugs.php.net/bug.php?id=70014}
 223       * But it's beyond my crypto-knowledge when it's worth finding a *real* better alternative.
 224       *
 225       * @param int $bytes Number of bytes to determine the key length expected.
 226       */
 227      protected static function generate_encryption_random_key($bytes) {
 228          return openssl_random_pseudo_bytes($bytes);
 229      }
 230  }
 231  
 232  /**
 233   * Implementation of backup_nested_element that provides special handling of files
 234   *
 235   * This class overwrites the standard fill_values() method, so it gets intercepted
 236   * for each file record being set to xml, in order to copy, at the same file, the
 237   * physical file from moodle file storage to backup file storage
 238   *
 239   * TODO: Finish phpdocs
 240   */
 241  class file_nested_element extends backup_nested_element {
 242  
 243      protected $backupid;
 244  
 245      public function process($processor) {
 246          // Get current backupid from processor, we'll need later
 247          if (is_null($this->backupid)) {
 248              $this->backupid = $processor->get_var(backup::VAR_BACKUPID);
 249          }
 250          return parent::process($processor);
 251      }
 252  
 253      public function fill_values($values) {
 254          // Fill values
 255          parent::fill_values($values);
 256          // Do our own tasks (copy file from moodle to backup)
 257          try {
 258              backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
 259          } catch (file_exception $e) {
 260              $this->add_result(array('missing_files_in_pool' => true));
 261  
 262              // Build helpful log message with all information necessary to identify
 263              // file location.
 264              $context = context::instance_by_id($values->contextid, IGNORE_MISSING);
 265              $contextname = '';
 266              if ($context) {
 267                  $contextname = ' \'' . $context->get_context_name() . '\'';
 268              }
 269              $message = 'Missing file in pool: ' . $values->filepath  . $values->filename .
 270                      ' (context ' . $values->contextid . $contextname . ', component ' .
 271                      $values->component . ', filearea ' . $values->filearea . ', itemid ' .
 272                      $values->itemid . ') [' . $e->debuginfo . ']';
 273              $this->add_log($message, backup::LOG_WARNING);
 274          }
 275      }
 276  }
 277  
 278  /**
 279   * Implementation of backup_optigroup_element to be used by plugins stuff.
 280   * Split just for better separation and future specialisation
 281   */
 282  class backup_plugin_element extends backup_optigroup_element { }
 283  
 284  /**
 285   * Implementation of backup_optigroup_element to be used by subplugins stuff.
 286   * Split just for better separation and future specialisation
 287   */
 288  class backup_subplugin_element extends backup_optigroup_element { }