PredisStore.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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->lindex($serverKey, -1);
  95. if (!$lastKey) {
  96. // no previous association with this server
  97. return null;
  98. }
  99. // get association, return null if failed
  100. return $this->getAssociationFromServer($lastKey);
  101. }
  102. /**
  103. * Function to actually receive and unserialize the association
  104. * from the server.
  105. */
  106. private function getAssociationFromServer($associationKey)
  107. {
  108. $association = $this->redis->get($associationKey);
  109. return $association ? unserialize($association) : null;
  110. }
  111. /**
  112. * Immediately delete association from Redis.
  113. */
  114. function removeAssociation($server_url, $handle)
  115. {
  116. // create Redis keys
  117. $serverKey = $this->associationServerKey($server_url);
  118. $associationKey = $this->associationKey($server_url,
  119. $handle);
  120. // Removing the association from the server's association list
  121. $removed = $this->redis->lrem($serverKey, 0, $associationKey);
  122. if ($removed < 1) {
  123. return false;
  124. }
  125. // Delete the association itself
  126. return $this->redis->del($associationKey);
  127. }
  128. /**
  129. * Create nonce for server and salt, expiring after
  130. * $Auth_OpenID_SKEW seconds.
  131. */
  132. function useNonce($server_url, $timestamp, $salt)
  133. {
  134. global $Auth_OpenID_SKEW;
  135. // save one request to memcache when nonce obviously expired
  136. if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
  137. return false;
  138. }
  139. // SETNX will set the value only of the key doesn't exist yet.
  140. $nonceKey = $this->nonceKey($server_url, $salt);
  141. $added = $this->redis->setnx($nonceKey, "1");
  142. if ($added) {
  143. // Will set expiration
  144. $this->redis->expire($nonceKey, $Auth_OpenID_SKEW);
  145. return true;
  146. } else {
  147. return false;
  148. }
  149. }
  150. /**
  151. * Build up nonce key
  152. */
  153. private function nonceKey($server_url, $salt)
  154. {
  155. return $this->prefix .
  156. 'openid_nonce_' .
  157. sha1($server_url) . '_' . sha1($salt);
  158. }
  159. /**
  160. * Key is prefixed with $prefix and 'openid_association_' string
  161. */
  162. function associationKey($server_url, $handle = null)
  163. {
  164. return $this->prefix .
  165. 'openid_association_' .
  166. sha1($server_url) . '_' . sha1($handle);
  167. }
  168. /**
  169. * Key is prefixed with $prefix and 'openid_association_server_' string
  170. */
  171. function associationServerKey($server_url)
  172. {
  173. return $this->prefix .
  174. 'openid_association_server_' .
  175. sha1($server_url);
  176. }
  177. /**
  178. * Report that this storage doesn't support cleanup
  179. */
  180. function supportsCleanup()
  181. {
  182. return false;
  183. }
  184. }