PredisStore.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <?php
  2. /**
  3. * Supplies Redis server store backend for OpenID servers and consumers.
  4. * Uses Predis library {@see https://github.com/nrk/predis}.
  5. * Requires PHP >= 5.3.
  6. *
  7. * LICENSE: See the COPYING file included in this distribution.
  8. *
  9. * @package OpenID
  10. * @author Ville Mattila <ville@eventio.fi>
  11. * @copyright 2008 JanRain Inc., 2013 Eventio Oy / Ville Mattila
  12. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  13. * Contributed by Eventio Oy <http://www.eventio.fi/>
  14. */
  15. /**
  16. * Import the interface for creating a new store class.
  17. */
  18. require_once 'Auth/OpenID/Interface.php';
  19. /**
  20. * Supplies Redis server store backend for OpenID servers and consumers.
  21. * Uses Predis library {@see https://github.com/nrk/predis}.
  22. * Requires PHP >= 5.3.
  23. *
  24. * @package OpenID
  25. */
  26. class Auth_OpenID_PredisStore extends Auth_OpenID_OpenIDStore {
  27. /**
  28. * @var \Predis\Client
  29. */
  30. protected $redis;
  31. /**
  32. * Prefix for Redis keys
  33. * @var string
  34. */
  35. protected $prefix;
  36. /**
  37. * Initializes a new {@link Auth_OpenID_PredisStore} instance.
  38. *
  39. * @param \Predis\Client $redis Predis client object
  40. * @param string $prefix Prefix for all keys stored to the Redis
  41. */
  42. function Auth_OpenID_PredisStore(\Predis\Client $redis, $prefix = '')
  43. {
  44. $this->prefix = $prefix;
  45. $this->redis = $redis;
  46. }
  47. /**
  48. * Store association until its expiration time in Redis server.
  49. * Overwrites any existing association with same server_url and
  50. * handle. Handles list of associations for every server.
  51. */
  52. function storeAssociation($server_url, $association)
  53. {
  54. // create Redis keys for association itself
  55. // and list of associations for this server
  56. $associationKey = $this->associationKey($server_url,
  57. $association->handle);
  58. $serverKey = $this->associationServerKey($server_url);
  59. // save association to server's associations' keys list
  60. $this->redis->lpush(
  61. $serverKey,
  62. $associationKey
  63. );
  64. // Will touch the association list expiration, to avoid filling up
  65. $newExpiration = ($association->issued + $association->lifetime);
  66. $expirationKey = $serverKey.'_expires_at';
  67. $expiration = $this->redis->get($expirationKey);
  68. if (!$expiration || $newExpiration > $expiration) {
  69. $this->redis->set($expirationKey, $newExpiration);
  70. $this->redis->expireat($serverKey, $newExpiration);
  71. $this->redis->expireat($expirationKey, $newExpiration);
  72. }
  73. // save association itself, will automatically expire
  74. $this->redis->setex(
  75. $associationKey,
  76. $newExpiration - time(),
  77. serialize($association)
  78. );
  79. }
  80. /**
  81. * Read association from Redis. If no handle given
  82. * and multiple associations found, returns latest issued
  83. */
  84. function getAssociation($server_url, $handle = null)
  85. {
  86. // simple case: handle given
  87. if ($handle !== null) {
  88. return $this->getAssociationFromServer(
  89. $this->associationKey($server_url, $handle)
  90. );
  91. }
  92. // no handle given, receiving the latest issued
  93. $serverKey = $this->associationServerKey($server_url);
  94. $lastKey = $this->redis->lpop($serverKey);
  95. if (!$lastKey) { return null; }
  96. // get association, return null if failed
  97. return $this->getAssociationFromServer($lastKey);
  98. }
  99. /**
  100. * Function to actually receive and unserialize the association
  101. * from the server.
  102. */
  103. private function getAssociationFromServer($associationKey)
  104. {
  105. $association = $this->redis->get($associationKey);
  106. return $association ? unserialize($association) : null;
  107. }
  108. /**
  109. * Immediately delete association from Redis.
  110. */
  111. function removeAssociation($server_url, $handle)
  112. {
  113. // create Redis keys
  114. $serverKey = $this->associationServerKey($server_url);
  115. $associationKey = $this->associationKey($server_url,
  116. $handle);
  117. // Removing the association from the server's association list
  118. $removed = $this->redis->lrem($serverKey, 0, $associationKey);
  119. if ($removed < 1) {
  120. return false;
  121. }
  122. // Delete the association itself
  123. return $this->redis->del($associationKey);
  124. }
  125. /**
  126. * Create nonce for server and salt, expiring after
  127. * $Auth_OpenID_SKEW seconds.
  128. */
  129. function useNonce($server_url, $timestamp, $salt)
  130. {
  131. global $Auth_OpenID_SKEW;
  132. // save one request to memcache when nonce obviously expired
  133. if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
  134. return false;
  135. }
  136. // SETNX will set the value only of the key doesn't exist yet.
  137. $nonceKey = $this->nonceKey($server_url, $salt);
  138. $added = $this->predis->setnx($nonceKey);
  139. if ($added) {
  140. // Will set expiration
  141. $this->predis->expire($nonceKey, $Auth_OpenID_SKEW);
  142. return true;
  143. } else {
  144. return false;
  145. }
  146. }
  147. /**
  148. * Build up nonce key
  149. */
  150. private function nonceKey($server_url, $salt)
  151. {
  152. return $this->prefix .
  153. 'openid_nonce_' .
  154. sha1($server_url) . '_' . sha1($salt);
  155. }
  156. /**
  157. * Key is prefixed with $prefix and 'openid_association_' string
  158. */
  159. function associationKey($server_url, $handle = null)
  160. {
  161. return $this->prefix .
  162. 'openid_association_' .
  163. sha1($server_url) . '_' . sha1($handle);
  164. }
  165. /**
  166. * Key is prefixed with $prefix and 'openid_association_server_' string
  167. */
  168. function associationServerKey($server_url)
  169. {
  170. return $this->prefix .
  171. 'openid_association_server_' .
  172. sha1($server_url);
  173. }
  174. /**
  175. * Report that this storage doesn't support cleanup
  176. */
  177. function supportsCleanup()
  178. {
  179. return false;
  180. }
  181. }