PageRenderTime 39ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/backupbuddy2/destinations/_s3lib/aws-sdk/extensions/dynamodbsessionhandler.class.php

https://gitlab.com/mattswann/launch-housing
PHP | 518 lines | 252 code | 63 blank | 203 comment | 17 complexity | c4b8284459c1dedc345a9f5fc23b5bd4 MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License").
  6. * You may not use this file except in compliance with the License.
  7. * A copy of the License is located at
  8. *
  9. * http://aws.amazon.com/apache2.0
  10. *
  11. * or in the "license" file accompanying this file. This file is distributed
  12. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  13. * express or implied. See the License for the specific language governing
  14. * permissions and limitations under the License.
  15. */
  16. /**
  17. * Provides an interface for using Amazon DynamoDB as a session store by hooking into
  18. * PHP's session handler hooks. This class is not auto-loaded, and must be included
  19. * manually or via the <code>AmazonDynamoDB::register_session_hander()</code> method.
  20. *
  21. * Once registered, You may use the native <code>$_SESSION</code> superglobal and
  22. * session functions, and the sessions will be stored automatically in DynamoDB.
  23. */
  24. class DynamoDBSessionHandler
  25. {
  26. /**
  27. * @var AmazonDynamoDB The DyanamoDB client.
  28. */
  29. protected $_dynamodb = null;
  30. /**
  31. * @var string The session save path (see <php:session_save_path()>).
  32. */
  33. protected $_save_path = null;
  34. /**
  35. * @var string The session name (see <php:session_name()>).
  36. */
  37. protected $_session_name = null;
  38. /**
  39. * @var boolean Keeps track of if the session is open.
  40. */
  41. protected $_open_session = null;
  42. /**
  43. * @var boolean Keeps track of whether the session is open.
  44. */
  45. protected $_session_written = false;
  46. /**
  47. * @var string The name of the DynamoDB table in which to store sessions.
  48. */
  49. protected $_table_name = 'sessions';
  50. /**
  51. * @var string The name of the hash key in the DynamoDB sessions table.
  52. */
  53. protected $_hash_key = 'id';
  54. /**
  55. * @var integer The lifetime of an inactive session before it should be garbage collected.
  56. */
  57. protected $_session_lifetime = 0;
  58. /**
  59. * @var boolean Whether or not the session handler should do consistent reads from DynamoDB.
  60. */
  61. protected $_consistent_reads = true;
  62. /**
  63. * @var boolean Whether or not the session handler should do session locking.
  64. */
  65. protected $_session_locking = true;
  66. /**
  67. * @var integer Maximum time, in seconds, that the session handler should take to acquire a lock.
  68. */
  69. protected $_max_lock_wait_time = 30;
  70. /**
  71. * @var integer Minimum time, in microseconds, that the session handler should wait to retry acquiring a lock.
  72. */
  73. protected $_min_lock_retry_utime = 10000;
  74. /**
  75. * @var integer Maximum time, in microseconds, that the session handler should wait to retry acquiring a lock.
  76. */
  77. protected $_max_lock_retry_utime = 100000;
  78. /**
  79. * @var array Type for casting the configuration options.
  80. */
  81. protected static $_option_types = array(
  82. 'table_name' => 'string',
  83. 'hash_key' => 'string',
  84. 'session_lifetime' => 'integer',
  85. 'consistent_reads' => 'boolean',
  86. 'session_locking' => 'boolean',
  87. 'max_lock_wait_time' => 'integer',
  88. 'min_lock_retry_utime' => 'integer',
  89. 'max_lock_retry_utime' => 'integer',
  90. );
  91. /**
  92. * Initializes the session handler and prepares the configuration options.
  93. *
  94. * @param AmazonDynamoDB $dynamodb (Required) An instance of the DynamoDB client.
  95. * @param array $options (Optional) Configuration options.
  96. */
  97. public function __construct(AmazonDynamoDB $dynamodb, array $options = array())
  98. {
  99. // Store the AmazonDynamoDB client for use in the session handler
  100. $this->_dynamodb = $dynamodb;
  101. // Do type conversions on options and store the values
  102. foreach ($options as $key => $value)
  103. {
  104. if (isset(self::$_option_types[$key]))
  105. {
  106. settype($value, self::$_option_types[$key]);
  107. $this->{'_' . $key} = $value;
  108. }
  109. }
  110. // Make sure the lifetime is positive. Use the gc_maxlifetime otherwise
  111. if ($this->_session_lifetime <= 0)
  112. {
  113. $this->_session_lifetime = (integer) ini_get('session.gc_maxlifetime');
  114. }
  115. }
  116. /**
  117. * Destruct the session handler and make sure the session gets written.
  118. *
  119. * NOTE: It is usually better practice to call <code>session_write_close()</code>
  120. * manually in your application as soon as session modifications are complete. This
  121. * is especially true if session locking is enabled (which it is by default).
  122. *
  123. * @see http://php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes
  124. */
  125. public function __destruct()
  126. {
  127. session_write_close();
  128. }
  129. /**
  130. * Register DynamoDB as a session handler.
  131. *
  132. * Uses the PHP-provided method to register this class as a session handler.
  133. *
  134. * @return DynamoDBSessionHandler Chainable.
  135. */
  136. public function register()
  137. {
  138. session_set_save_handler(
  139. array($this, 'open'),
  140. array($this, 'close'),
  141. array($this, 'read'),
  142. array($this, 'write'),
  143. array($this, 'destroy'),
  144. array($this, 'garbage_collect')
  145. );
  146. return $this;
  147. }
  148. /**
  149. * Checks if the session is open and writable.
  150. *
  151. * @return boolean Whether or not the session is still open for writing.
  152. */
  153. public function is_session_open()
  154. {
  155. return (boolean) $this->_open_session;
  156. }
  157. /**
  158. * Delegates to <code>session_start()</code>
  159. *
  160. * @return DynamoDBSessionHandler Chainable.
  161. */
  162. public function open_session()
  163. {
  164. session_start();
  165. return $this;
  166. }
  167. /**
  168. * Delegates to <code>session_commit()</code>
  169. *
  170. * @return DynamoDBSessionHandler Chainable.
  171. */
  172. public function close_session()
  173. {
  174. session_commit();
  175. return $this;
  176. }
  177. /**
  178. * Delegates to <code>session_destroy()</code>
  179. *
  180. * @return DynamoDBSessionHandler Chainable.
  181. */
  182. public function destroy_session()
  183. {
  184. session_destroy();
  185. return $this;
  186. }
  187. /**
  188. * Open a session for writing. Triggered by <php:session_start()>.
  189. *
  190. * Part of the standard PHP session handler interface.
  191. *
  192. * @param string $save_path (Required) The session save path (see <php:session_save_path()>).
  193. * @param string $session_name (Required) The session name (see <php:session_name()>).
  194. * @return boolean Whether or not the operation succeeded.
  195. */
  196. public function open($save_path, $session_name)
  197. {
  198. $this->_save_path = $save_path;
  199. $this->_session_name = $session_name;
  200. $this->_open_session = session_id();
  201. return true;
  202. }
  203. /**
  204. * Close a session from writing
  205. *
  206. * Part of the standard PHP session handler interface
  207. *
  208. * @return boolean Success
  209. */
  210. public function close()
  211. {
  212. if (!$this->_session_written)
  213. {
  214. // Ensure that the session is unlocked even if the write did not happen
  215. $id = $this->_open_session;
  216. $response = $this->_dynamodb->update_item(array(
  217. 'TableName' => $this->_table_name,
  218. 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($this->_id($id))),
  219. 'AttributeUpdates' => array(
  220. 'expires' => $this->_dynamodb->attribute(time() + $this->_session_lifetime, 'update'),
  221. 'lock' => array('Action' => AmazonDynamoDB::ACTION_DELETE)
  222. ),
  223. ));
  224. $this->_session_written = $response->isOK();
  225. }
  226. $this->_open_session = null;
  227. return $this->_session_written;
  228. }
  229. /**
  230. * Read a session stored in DynamoDB
  231. *
  232. * Part of the standard PHP session handler interface
  233. *
  234. * @param string $id (Required) The session ID.
  235. * @return string The session data.
  236. */
  237. public function read($id)
  238. {
  239. $result = '';
  240. // Get the session data from DynamoDB (acquire a lock if locking is enabled)
  241. if ($this->_session_locking)
  242. {
  243. $response = $this->_lock_and_read($id);
  244. $node_name = 'Attributes';
  245. }
  246. else
  247. {
  248. $response = $this->_dynamodb->get_item(array(
  249. 'TableName' => $this->_table_name,
  250. 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($this->_id($id))),
  251. 'ConsistentRead' => $this->_consistent_reads,
  252. ));
  253. $node_name = 'Item';
  254. }
  255. if ($response && $response->isOK())
  256. {
  257. $item = array();
  258. // Get the data from the DynamoDB response
  259. if ($response->body->{$node_name})
  260. {
  261. foreach ($response->body->{$node_name}->children() as $key => $value)
  262. {
  263. $type = $value->children()->getName();
  264. $item[$key] = $value->{$type}->to_string();
  265. }
  266. }
  267. if (isset($item['expires']) && isset($item['data']))
  268. {
  269. // Check the expiration date before using
  270. if ($item['expires'] > time())
  271. {
  272. $result = $item['data'];
  273. }
  274. else
  275. {
  276. $this->destroy($id);
  277. }
  278. }
  279. }
  280. return $result;
  281. }
  282. /**
  283. * Write a session to DynamoDB.
  284. *
  285. * Part of the standard PHP session handler interface.
  286. *
  287. * @param string $id (Required) The session ID.
  288. * @param string $data (Required) The session data.
  289. * @return boolean Whether or not the operation succeeded.
  290. */
  291. public function write($id, $data)
  292. {
  293. // Write the session data to DynamoDB
  294. $response = $this->_dynamodb->put_item(array(
  295. 'TableName' => $this->_table_name,
  296. 'Item' => $this->_dynamodb->attributes(array(
  297. $this->_hash_key => $this->_id($id),
  298. 'expires' => time() + $this->_session_lifetime,
  299. 'data' => $data,
  300. )),
  301. ));
  302. $this->_session_written = $response->isOK();
  303. return $this->_session_written;
  304. }
  305. /**
  306. * Delete a session stored in DynamoDB.
  307. *
  308. * Part of the standard PHP session handler interface.
  309. *
  310. * @param string $id (Required) The session ID.
  311. * @param boolean $garbage_collect_mode (Optional) Whether or not the handler is doing garbage collection.
  312. * @return boolean Whether or not the operation succeeded.
  313. */
  314. public function destroy($id, $garbage_collect_mode = false)
  315. {
  316. // Make sure we don't prefix the ID a second time
  317. if (!$garbage_collect_mode)
  318. {
  319. $id = $this->_id($id);
  320. }
  321. $delete_options = array(
  322. 'TableName' => $this->_table_name,
  323. 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($id)),
  324. );
  325. // Make sure not to garbage collect locked sessions
  326. if ($garbage_collect_mode && $this->_session_locking)
  327. {
  328. $delete_options['Expected'] = array('lock' => array('Exists' => false));
  329. }
  330. // Send the delete request to DynamoDB
  331. $response = $this->_dynamodb->delete_item($delete_options);
  332. $this->_session_written = $response->isOK();
  333. return $this->_session_written;
  334. }
  335. /**
  336. * Performs garbage collection on the sessions stored in the DynamoDB table.
  337. *
  338. * Part of the standard PHP session handler interface.
  339. *
  340. * @param integer $maxlifetime (Required) The value of <code>session.gc_maxlifetime</code>. Ignored.
  341. * @return boolean Whether or not the operation succeeded.
  342. */
  343. public function garbage_collect($maxlifetime = null)
  344. {
  345. // Send a search request to DynamoDB looking for expired sessions
  346. $response = $this->_dynamodb->scan(array(
  347. 'TableName' => $this->_table_name,
  348. 'ScanFilter' => array('expires' => array(
  349. 'AttributeValueList' => array($this->_dynamodb->attribute(time())),
  350. 'ComparisonOperator' => AmazonDynamoDB::CONDITION_LESS_THAN,
  351. )),
  352. ));
  353. // Delete the expired sessions
  354. if ($response->isOK())
  355. {
  356. $deleted = array();
  357. // Get the ID of and delete each session that is expired
  358. foreach ($response->body->Items as $item)
  359. {
  360. $id = (string) $item->{$this->_hash_key}->{AmazonDynamoDB::TYPE_STRING};
  361. $deleted[$id] = $this->destroy($id, true);
  362. }
  363. // Return true if all of the expired sessions were deleted
  364. return (array_sum($deleted) === count($deleted));
  365. }
  366. return false;
  367. }
  368. /**
  369. * Creates a table in DynamoDB for session storage according to provided configuration options.
  370. *
  371. * Note: Table creation may also be done via the AWS Console, which might make the most sense for this use case.
  372. *
  373. * @param integer $read_capacity_units (Required) Read capacity units for table throughput.
  374. * @param integer $write_capacity_units (Required) Write capacity units for table throughput.
  375. * @return boolean Returns <code>true</code> on success.
  376. */
  377. public function create_sessions_table($read_capacity_units = 10, $write_capacity_units = 5)
  378. {
  379. $response = $this->_dynamodb->create_table(array(
  380. 'TableName' => $this->_table_name,
  381. 'KeySchema' => array(
  382. 'HashKeyElement' => array(
  383. 'AttributeName' => $this->_hash_key,
  384. 'AttributeType' => AmazonDynamoDB::TYPE_STRING,
  385. )
  386. ),
  387. 'ProvisionedThroughput' => array(
  388. 'ReadCapacityUnits' => $read_capacity_units,
  389. 'WriteCapacityUnits' => $write_capacity_units,
  390. )
  391. ));
  392. return $response->isOK();
  393. }
  394. /**
  395. * Deletes the session storage table from DynamoDB.
  396. *
  397. * Note: Table deletion may also be done via the AWS Console, which might make the most sense for this use case.
  398. *
  399. * @return boolean Returns <code>true</code> on success.
  400. */
  401. public function delete_sessions_table()
  402. {
  403. $response = $this->_dynamodb->delete_table(array('TableName' => $this->_table_name));
  404. return $response->isOK();
  405. }
  406. /**
  407. * Prefix the session ID with the session name and prepare for DynamoDB usage
  408. *
  409. * @param string $id (Required) The session ID.
  410. * @return array The HashKeyElement value formatted as an array.
  411. */
  412. protected function _id($id)
  413. {
  414. return trim($this->_session_name . '_' . $id, '_');
  415. }
  416. /**
  417. * Acquires a lock on a session in DynamoDB using conditional updates.
  418. *
  419. * WARNING: There is a <code>while(true);</code> in here.
  420. *
  421. * @param string $id (Required) The session ID.
  422. * @return CFResponse The response from DynamoDB.
  423. */
  424. protected function _lock_and_read($id)
  425. {
  426. $now = time();
  427. $timeout = $now + $this->_max_lock_wait_time;
  428. do
  429. {
  430. // Acquire the lock
  431. $response = $this->_dynamodb->update_item(array(
  432. 'TableName' => $this->_table_name,
  433. 'Key' => array('HashKeyElement' => $this->_dynamodb->attribute($this->_id($id))),
  434. 'AttributeUpdates' => array('lock' => $this->_dynamodb->attribute(1, 'update')),
  435. 'Expected' => array('lock' => array('Exists' => false)),
  436. 'ReturnValues' => 'ALL_NEW',
  437. ));
  438. // If lock succeeds (or times out), exit the loop, otherwise sleep and try again
  439. if ($response->isOK() || $now >= $timeout)
  440. {
  441. return $response;
  442. }
  443. elseif (stripos((string) $response->body->asXML(), 'ConditionalCheckFailedException') !== false)
  444. {
  445. usleep(rand($this->_min_lock_retry_utime, $this->_max_lock_retry_utime));
  446. $now = time();
  447. }
  448. else
  449. {
  450. return null;
  451. }
  452. }
  453. while (true);
  454. }
  455. }