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