1: <?php
2: /*****************************************************************************************
3: * X2Engine Open Source Edition is a customer relationship management program developed by
4: * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
5: *
6: * This program is free software; you can redistribute it and/or modify it under
7: * the terms of the GNU Affero General Public License version 3 as published by the
8: * Free Software Foundation with the addition of the following permission added
9: * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10: * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
11: * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12: *
13: * This program is distributed in the hope that it will be useful, but WITHOUT
14: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16: * details.
17: *
18: * You should have received a copy of the GNU Affero General Public License along with
19: * this program; if not, see http://www.gnu.org/licenses or write to the Free
20: * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21: * 02110-1301 USA.
22: *
23: * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
24: * California 95067, USA. or at email address [email protected].
25: *
26: * The interactive user interfaces in modified source and object code versions
27: * of this program must display Appropriate Legal Notices, as required under
28: * Section 5 of the GNU Affero General Public License version 3.
29: *
30: * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31: * these Appropriate Legal Notices must retain the display of the "Powered by
32: * X2Engine" logo. If the display of the logo is not reasonably feasible for
33: * technical reasons, the Appropriate Legal Notices must display the words
34: * "Powered by X2Engine".
35: *****************************************************************************************/
36:
37: /**
38: * Migrate google credentials from admin table to credentials table.
39: */
40:
41: /**
42: * Copy of Encrypt util with an irrelevant method removed
43: */
44: class EncryptUtilTmp {
45:
46: public static $generatedValues = array('IV','key');
47:
48: private $_IV;
49: /**
50: * Encryption key
51: * @var mixed
52: */
53: private $_key;
54:
55: /**
56: * Whether all the necessary dependencies are installed to use encryption.
57: * @var bool
58: */
59: public $canEncrypt;
60:
61: /**
62: * File for storing IV length (for encoding purposes)
63: * @var type
64: */
65: public $IVFile;
66:
67: /**
68: * A file for storing an encryption key
69: * @var string
70: */
71: public $keyFile;
72:
73: /**
74: * Checks dependencies.
75: * @param type $throw Throw an exception if this is set to true and dependencies are missing.
76: * @throws Exception
77: */
78: public static function dependencyCheck($throw) {
79: $hasDeps = extension_loaded('openssl') && extension_loaded('mcrypt');
80: if(!$hasDeps && $throw)
81: throw new Exception('The "openssl" and "mcrypt" extensions are not loaded. The EncryptUtil class cannot function properly.');
82: return $hasDeps;
83: }
84:
85: /**
86: * Generates a new encryption key
87: *
88: * @param integer $length
89: * @return string|bool
90: */
91: public static function genKey($length = 32){
92: $key = openssl_random_pseudo_bytes($length, $strong);
93: return ($strong ? $key : false);
94: }
95:
96: public static function genIV() {
97: return mcrypt_create_iv(
98: mcrypt_get_iv_size(
99: MCRYPT_RIJNDAEL_256,
100: MCRYPT_MODE_ECB
101: ),
102: MCRYPT_RAND
103: );
104: }
105:
106: public function __construct($keyFile=null,$IVFile=null,$throw=true) {
107: $this->canEncrypt = self::dependencyCheck($throw);
108: foreach(array('keyFile','IVFile') as $arg) {
109: $this->$arg = ${$arg};
110: }
111: }
112:
113: /**
114: * Magic getter that obtains a value for an attribute from a file, or by
115: * generating new values.
116: *
117: * The assumption is made: if no storage files are specified, the instance
118: * creates new keys for a single usage without complaining, and does not
119: * store them. Otherwise, if files are specified but do not exist, a new
120: * encryption key is generated (to be stored when {@link saveNew()} is called).
121: *
122: * @return string
123: * @throws Exception
124: */
125: public function __get($name){
126: if(in_array($name,self::$generatedValues)) {
127: $pp = "_$name"; // Private storage property
128: $sf = $name.'File'; // File for storing the property
129: $gf = 'gen'.ucfirst($name); // Function for generating the property
130: if(!isset($this->$pp)){
131: $set = false;
132: if(isset($this->$sf)){
133: $file = realpath($this->$sf);
134: if($file){
135: $this->$pp = file_get_contents($file);
136: $set = true;
137: }
138: }
139: // Must use "$set" because the file may in some cases be empty.
140: if(!(isset($this->$pp)||$set))
141: $this->$pp = call_user_func("self::$gf");
142: }
143: return $this->$pp;
144: } else
145: return $this->$name;
146: }
147:
148: public function __set($name, $value){
149: if(in_array($name,self::$generatedValues)) {
150: $pp = "_$name";
151: return $this->$pp = $value;
152: } else
153: return $this->$name = $value;
154: }
155:
156: /**
157: * Encrypts data.
158: */
159: public function encrypt($data){
160: if($this->key)
161: return base64_encode(rtrim(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->key, $data, MCRYPT_MODE_ECB, $this->IV),"\0"));
162: else
163: return $data;
164: }
165:
166: /**
167: * Decrypts data.
168: */
169: public function decrypt($data){
170: if($this->key)
171: return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256,$this->key, base64_decode($data), MCRYPT_MODE_ECB, $this->IV),"\0");
172: else
173: return $data;
174: }
175:
176: /**
177: * Generates and saves an encryption key/IV length in files specified by
178: * {@link _keyFile} and {@link _IVFile}. Throws an exception if the key
179: * couldn't be made securely.
180: *
181: * @param type $safe
182: * @return type
183: * @throws Exception
184: */
185: public function saveNew($safe=true) {
186: foreach(array('key', 'IV') as $attr){
187: $sf = $attr.'File';
188: if(!isset($this->$sf))
189: throw new Exception("Cannot save $attr; path to $sf not set.");
190: $dir = dirname($this->$sf);
191: if(!realpath($dir))
192: throw new Exception(ucfirst($attr)." file's containing directory at $dir not found.");
193: file_put_contents($this->$sf, $this->$attr);
194: }
195: if($safe && !$this->key)
196: throw new Exception('Strength of the encryption key could not be verified.');
197: return $this->key;
198: }
199:
200: }
201:
202: $migrateGoogleCredentials = function () {
203:
204: // retrieve existing Google credentials
205: $clientId = null;
206: $clientSecret = null;
207: $admin = Yii::app()->db->createCommand ("
208: select * from x2_admin where id=1;
209: ")->queryRow ();
210:
211: if (isset ($admin['googleClientId'])) {
212: $clientId = $admin['googleClientId'];
213: Yii::app()->db->createCommand ("
214: alter table x2_admin
215: drop column googleClientId;
216: ")->execute ();
217: }
218: if (isset ($admin['googleClientSecret'])) {
219: $clientSecret = $admin['googleClientSecret'];
220: Yii::app()->db->createCommand ("
221: alter table x2_admin
222: drop column googleClientSecret;
223: ")->execute ();
224: }
225: if (isset ($admin['googleAPIKey'])) {
226: Yii::app()->db->createCommand ("
227: alter table x2_admin
228: drop column googleAPIKey;
229: ")->execute ();
230: }
231:
232: // check if it's possible to encrypt the credentials
233: $key = implode(DIRECTORY_SEPARATOR,array(Yii::app()->basePath,'config','encryption.key'));
234: $iv = implode(DIRECTORY_SEPARATOR,array(Yii::app()->basePath,'config','encryption.iv'));
235: $encryption = new EncryptUtilTmp ($key, $iv, false);
236: if (!$encryption->canEncrypt) {
237: // server doesn't meet requirements. There's nothing that can be done. Credentials will
238: // be lost and will need to be re-entered by admin user.
239: return;
240: }
241: if (!file_exists ($key) || !file_exists ($iv)) {
242: try {
243: $encryption->saveNew();
244: } catch (Exception $e) {
245: // Encryption failed. There's nothing that can be done. Credentials will
246: // be lost and will need to be re-entered by admin user.
247: return;
248: }
249: }
250:
251: // manually insert encrypted credentials into credentials table
252: $attributes = CJSON::encode (array (
253: 'clientId' => $clientId,
254: 'clientSecret' => $clientSecret,
255: ));
256: $encryptedAttributes = $encryption->encrypt ($attributes);
257: $googleProject = array (
258: 'name' => 'Google project',
259: 'userId' => -1,
260: 'private' => 1,
261: 'isEncrypted' => 1,
262: 'modelClass' => 'GoogleProject',
263: 'createDate' => time (),
264: 'lastUpdated' => time (),
265: 'auth' => $encryptedAttributes,
266: );
267: if (Yii::app()->db->createCommand ()->insert ("x2_credentials", $googleProject)) {
268: // update admin table foreign key
269: $credId = Yii::app()->db->createCommand ("
270: select id
271: from x2_credentials
272: where name='Google project'
273: ")->queryScalar ();
274: if ($credId !== false) {
275: Yii::app()->db->createCommand ()->update ("x2_admin", array (
276: 'googleCredentialsId' => $credId,
277: ), 'id=1');
278: }
279: }
280: };
281:
282: $migrateGoogleCredentials ();
283:
284:
285: ?>
286: