| 1 | efrain | 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 |         // Avoid null being passed to base64_encode.
 | 
        
           |  |  | 97 |         $value = $value ?? '';
 | 
        
           |  |  | 98 |         parent::set_value(base64_encode($value));
 | 
        
           |  |  | 99 |     }
 | 
        
           |  |  | 100 | }
 | 
        
           |  |  | 101 |   | 
        
           |  |  | 102 | /**
 | 
        
           |  |  | 103 |  * Implementation of {@link backup_final_element} that provides symmetric-key AES-256 encryption of contents.
 | 
        
           |  |  | 104 |  *
 | 
        
           |  |  | 105 |  * This final element transparently encrypts, for secure storage and transport, any content
 | 
        
           |  |  | 106 |  * that shouldn't be shown normally in plain text. Usually, passwords or keys that cannot use
 | 
        
           |  |  | 107 |  * hashing algorithms, although potentially can encrypt any content. All information is encoded
 | 
        
           |  |  | 108 |  * using base64.
 | 
        
           |  |  | 109 |  *
 | 
        
           |  |  | 110 |  * Features:
 | 
        
           |  |  | 111 |  *   - requires openssl extension to work. Without it contents are completely omitted.
 | 
        
           |  |  | 112 |  *   - automatically creates an appropriate default key for the site and stores it into backup_encryptkey config (bas64 encoded).
 | 
        
           |  |  | 113 |  *   - uses a different appropriate init vector for every operation, which is transmited with the encrypted contents.
 | 
        
           |  |  | 114 |  *   - all generated data is base64 encoded for safe transmission.
 | 
        
           |  |  | 115 |  *   - automatically adds "encrypted" attribute for easier detection.
 | 
        
           |  |  | 116 |  *   - implements HMAC for providing integrity.
 | 
        
           |  |  | 117 |  *
 | 
        
           |  |  | 118 |  * @copyright 2017 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
 | 
        
           |  |  | 119 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 120 |  */
 | 
        
           |  |  | 121 | class encrypted_final_element extends backup_final_element {
 | 
        
           |  |  | 122 |   | 
        
           |  |  | 123 |     /** @var string cypher appropiate raw key for backups in the site. Defaults to backup_encryptkey config. */
 | 
        
           |  |  | 124 |     protected $key = null;
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |     /**
 | 
        
           |  |  | 127 |      * Constructor - instantiates a encrypted_final_element, specifying its basic info.
 | 
        
           |  |  | 128 |      *
 | 
        
           |  |  | 129 |      * Overridden to automatically add the 'encrypted' attribute if missing.
 | 
        
           |  |  | 130 |      *
 | 
        
           |  |  | 131 |      * @param string $name name of the element
 | 
        
           |  |  | 132 |      * @param array  $attributes attributes this element will handle (optional, defaults to null)
 | 
        
           |  |  | 133 |      */
 | 
        
           |  |  | 134 |     public function __construct($name, $attributes = null) {
 | 
        
           |  |  | 135 |         parent::__construct($name, $attributes);
 | 
        
           |  |  | 136 |         if (! $this->get_attribute('encrypted')) {
 | 
        
           |  |  | 137 |             $this->add_attributes('encrypted');
 | 
        
           |  |  | 138 |         }
 | 
        
           |  |  | 139 |     }
 | 
        
           |  |  | 140 |   | 
        
           |  |  | 141 |     /**
 | 
        
           |  |  | 142 |      * Set the encryption key manually, overriding default backup_encryptkey config.
 | 
        
           |  |  | 143 |      *
 | 
        
           |  |  | 144 |      * @param string $key key to be used for encrypting. Required to be 256-bit key.
 | 
        
           |  |  | 145 |      *               Use a safe generation technique. See self::generate_encryption_random_key() below.
 | 
        
           |  |  | 146 |      */
 | 
        
           |  |  | 147 |     protected function set_key($key) {
 | 
        
           |  |  | 148 |         $bytes = strlen($key); // Get key length in bytes.
 | 
        
           |  |  | 149 |   | 
        
           |  |  | 150 |         // Only accept keys with the expected (backup::CIPHERKEYLEN) key length. There are a number of hashing,
 | 
        
           |  |  | 151 |         // random generators to achieve this esasily, like the one shown below to create the default
 | 
        
           |  |  | 152 |         // site encryption key and ivs.
 | 
        
           |  |  | 153 |         if ($bytes !== backup::CIPHERKEYLEN) {
 | 
        
           |  |  | 154 |             $info = (object)array('expected' => backup::CIPHERKEYLEN, 'found' => $bytes);
 | 
        
           |  |  | 155 |             throw new base_element_struct_exception('encrypted_final_element incorrect key length', $info);
 | 
        
           |  |  | 156 |         }
 | 
        
           |  |  | 157 |         // Everything went ok, store the key.
 | 
        
           |  |  | 158 |         $this->key = $key;
 | 
        
           |  |  | 159 |     }
 | 
        
           |  |  | 160 |   | 
        
           |  |  | 161 |     /**
 | 
        
           |  |  | 162 |      * Set the value of the field.
 | 
        
           |  |  | 163 |      *
 | 
        
           |  |  | 164 |      * This method sets the value of the element, encrypted using the specified key for it,
 | 
        
           |  |  | 165 |      * defaulting to (and generating) backup_encryptkey config. HMAC is used for integrity.
 | 
        
           |  |  | 166 |      *
 | 
        
           |  |  | 167 |      * @param string $value plain-text content the will be stored encrypted and encoded.
 | 
        
           |  |  | 168 |      */
 | 
        
           |  |  | 169 |     public function set_value($value) {
 | 
        
           |  |  | 170 |   | 
        
           |  |  | 171 |         // No openssl available, skip this field completely.
 | 
        
           |  |  | 172 |         if (!function_exists('openssl_encrypt')) {
 | 
        
           |  |  | 173 |             return;
 | 
        
           |  |  | 174 |         }
 | 
        
           |  |  | 175 |   | 
        
           |  |  | 176 |         // No hmac available, skip this field completely.
 | 
        
           |  |  | 177 |         if (!function_exists('hash_hmac')) {
 | 
        
           |  |  | 178 |             return;
 | 
        
           |  |  | 179 |         }
 | 
        
           |  |  | 180 |   | 
        
           |  |  | 181 |         // Cypher not available, skip this field completely.
 | 
        
           |  |  | 182 |         if (!in_array(backup::CIPHER, openssl_get_cipher_methods())) {
 | 
        
           |  |  | 183 |             return;
 | 
        
           |  |  | 184 |         }
 | 
        
           |  |  | 185 |   | 
        
           |  |  | 186 |         // Ensure we have a good key, manual or default.
 | 
        
           |  |  | 187 |         if (empty($this->key)) {
 | 
        
           |  |  | 188 |             // The key has not been set manually, look for it at config (base64 encoded there).
 | 
        
           |  |  | 189 |             $enckey = get_config('backup', 'backup_encryptkey');
 | 
        
           |  |  | 190 |             if ($enckey === false) {
 | 
        
           |  |  | 191 |                 // Has not been set, calculate and save an appropiate random key automatically.
 | 
        
           |  |  | 192 |                 $enckey = base64_encode(self::generate_encryption_random_key(backup::CIPHERKEYLEN));
 | 
        
           |  |  | 193 |                 set_config('backup_encryptkey', $enckey, 'backup');
 | 
        
           |  |  | 194 |             }
 | 
        
           |  |  | 195 |             $this->set_key(base64_decode($enckey));
 | 
        
           |  |  | 196 |         }
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |         // Now we need an iv for this operation.
 | 
        
           |  |  | 199 |         $iv = self::generate_encryption_random_key(openssl_cipher_iv_length(backup::CIPHER));
 | 
        
           |  |  | 200 |   | 
        
           |  |  | 201 |         // Everything is ready, let's encrypt and prepend the 1-shot iv.
 | 
        
           |  |  | 202 |         $value = $iv . openssl_encrypt($value ?? '', backup::CIPHER, $this->key, OPENSSL_RAW_DATA, $iv);
 | 
        
           |  |  | 203 |   | 
        
           |  |  | 204 |         // Calculate the hmac of the value (iv + encrypted) and prepend it.
 | 
        
           |  |  | 205 |         $hmac = hash_hmac('sha256', $value, $this->key, true);
 | 
        
           |  |  | 206 |         $value = $hmac . $value;
 | 
        
           |  |  | 207 |   | 
        
           |  |  | 208 |         // Ready, set the encoded value.
 | 
        
           |  |  | 209 |         parent::set_value(base64_encode($value));
 | 
        
           |  |  | 210 |   | 
        
           |  |  | 211 |         // Finally, if the field has an "encrypted" attribute, set it to true.
 | 
        
           |  |  | 212 |         if ($att = $this->get_attribute('encrypted')) {
 | 
        
           |  |  | 213 |             $att->set_value('true');
 | 
        
           |  |  | 214 |         }
 | 
        
           |  |  | 215 |     }
 | 
        
           |  |  | 216 |   | 
        
           |  |  | 217 |     /**
 | 
        
           |  |  | 218 |      * Generate an appropiate random key to be used for encrypting backup information.
 | 
        
           |  |  | 219 |      *
 | 
        
           |  |  | 220 |      * Normally used as site default encryption key (backup_encryptkey config) and also
 | 
        
           |  |  | 221 |      * for calculating the init vectors.
 | 
        
           |  |  | 222 |      *
 | 
        
           |  |  | 223 |      * Note that until PHP 5.6.12 openssl_random_pseudo_bytes() did NOT
 | 
        
           |  |  | 224 |      * use a "cryptographically strong algorithm" {@link https://bugs.php.net/bug.php?id=70014}
 | 
        
           |  |  | 225 |      * But it's beyond my crypto-knowledge when it's worth finding a *real* better alternative.
 | 
        
           |  |  | 226 |      *
 | 
        
           |  |  | 227 |      * @param int $bytes Number of bytes to determine the key length expected.
 | 
        
           |  |  | 228 |      */
 | 
        
           |  |  | 229 |     protected static function generate_encryption_random_key($bytes) {
 | 
        
           |  |  | 230 |         return openssl_random_pseudo_bytes($bytes);
 | 
        
           |  |  | 231 |     }
 | 
        
           |  |  | 232 | }
 | 
        
           |  |  | 233 |   | 
        
           |  |  | 234 | /**
 | 
        
           |  |  | 235 |  * Implementation of backup_nested_element that provides special handling of files
 | 
        
           |  |  | 236 |  *
 | 
        
           |  |  | 237 |  * This class overwrites the standard fill_values() method, so it gets intercepted
 | 
        
           |  |  | 238 |  * for each file record being set to xml, in order to copy, at the same file, the
 | 
        
           |  |  | 239 |  * physical file from moodle file storage to backup file storage
 | 
        
           |  |  | 240 |  *
 | 
        
           |  |  | 241 |  * TODO: Finish phpdocs
 | 
        
           |  |  | 242 |  */
 | 
        
           |  |  | 243 | class file_nested_element extends backup_nested_element {
 | 
        
           |  |  | 244 |   | 
        
           |  |  | 245 |     protected $backupid;
 | 
        
           |  |  | 246 |   | 
        
           |  |  | 247 |     public function process($processor) {
 | 
        
           |  |  | 248 |         // Get current backupid from processor, we'll need later
 | 
        
           |  |  | 249 |         if (is_null($this->backupid)) {
 | 
        
           |  |  | 250 |             $this->backupid = $processor->get_var(backup::VAR_BACKUPID);
 | 
        
           |  |  | 251 |         }
 | 
        
           |  |  | 252 |         return parent::process($processor);
 | 
        
           |  |  | 253 |     }
 | 
        
           |  |  | 254 |   | 
        
           |  |  | 255 |     public function fill_values($values) {
 | 
        
           |  |  | 256 |         // Fill values
 | 
        
           |  |  | 257 |         parent::fill_values($values);
 | 
        
           |  |  | 258 |         // Do our own tasks (copy file from moodle to backup)
 | 
        
           |  |  | 259 |         try {
 | 
        
           |  |  | 260 |             backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
 | 
        
           |  |  | 261 |         } catch (file_exception $e) {
 | 
        
           |  |  | 262 |             $this->add_result(array('missing_files_in_pool' => true));
 | 
        
           |  |  | 263 |   | 
        
           |  |  | 264 |             // Build helpful log message with all information necessary to identify
 | 
        
           |  |  | 265 |             // file location.
 | 
        
           |  |  | 266 |             $context = context::instance_by_id($values->contextid, IGNORE_MISSING);
 | 
        
           |  |  | 267 |             $contextname = '';
 | 
        
           |  |  | 268 |             if ($context) {
 | 
        
           |  |  | 269 |                 $contextname = ' \'' . $context->get_context_name() . '\'';
 | 
        
           |  |  | 270 |             }
 | 
        
           |  |  | 271 |             $message = 'Missing file in pool: ' . $values->filepath  . $values->filename .
 | 
        
           |  |  | 272 |                     ' (context ' . $values->contextid . $contextname . ', component ' .
 | 
        
           |  |  | 273 |                     $values->component . ', filearea ' . $values->filearea . ', itemid ' .
 | 
        
           |  |  | 274 |                     $values->itemid . ') [' . $e->debuginfo . ']';
 | 
        
           |  |  | 275 |             $this->add_log($message, backup::LOG_WARNING);
 | 
        
           |  |  | 276 |         }
 | 
        
           |  |  | 277 |     }
 | 
        
           |  |  | 278 | }
 | 
        
           |  |  | 279 |   | 
        
           |  |  | 280 | /**
 | 
        
           |  |  | 281 |  * Implementation of backup_optigroup_element to be used by plugins stuff.
 | 
        
           |  |  | 282 |  * Split just for better separation and future specialisation
 | 
        
           |  |  | 283 |  */
 | 
        
           |  |  | 284 | class backup_plugin_element extends backup_optigroup_element { }
 | 
        
           |  |  | 285 |   | 
        
           |  |  | 286 | /**
 | 
        
           |  |  | 287 |  * Implementation of backup_optigroup_element to be used by subplugins stuff.
 | 
        
           |  |  | 288 |  * Split just for better separation and future specialisation
 | 
        
           |  |  | 289 |  */
 | 
        
           |  |  | 290 | class backup_subplugin_element extends backup_optigroup_element { }
 |