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 28defined('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 */ 39class 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 */ 64class 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 */ 88class 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 */ 119class 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 */ 241class 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 */ 282class 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 */ 288class backup_subplugin_element extends backup_optigroup_element { } 289