1<?php
2
3namespace OAuth\Common\Storage;
4
5use OAuth\Common\Token\TokenInterface;
6use OAuth\Common\Storage\Exception\TokenNotFoundException;
7use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
8use Predis\Client as Predis;
9
10/*
11 * Stores a token in a Redis server. Requires the Predis library available at https://github.com/nrk/predis
12 */
13class Redis implements TokenStorageInterface
14{
15    /**
16     * @var string
17     */
18    protected $key;
19
20    protected $stateKey;
21
22    /**
23     * @var object|\Redis
24     */
25    protected $redis;
26
27    /**
28     * @var object|TokenInterface
29     */
30    protected $cachedTokens;
31
32    /**
33     * @var object
34     */
35    protected $cachedStates;
36
37    /**
38     * @param Predis $redis An instantiated and connected redis client
39     * @param string $key The key to store the token under in redis
40     * @param string $stateKey The key to store the state under in redis.
41     */
42    public function __construct(Predis $redis, $key, $stateKey)
43    {
44        $this->redis = $redis;
45        $this->key = $key;
46        $this->stateKey = $stateKey;
47        $this->cachedTokens = array();
48        $this->cachedStates = array();
49    }
50
51    /**
52     * {@inheritDoc}
53     */
54    public function retrieveAccessToken($service)
55    {
56        if (!$this->hasAccessToken($service)) {
57            throw new TokenNotFoundException('Token not found in redis');
58        }
59
60        if (isset($this->cachedTokens[$service])) {
61            return $this->cachedTokens[$service];
62        }
63
64        $val = $this->redis->hget($this->key, $service);
65
66        return $this->cachedTokens[$service] = unserialize($val);
67    }
68
69    /**
70     * {@inheritDoc}
71     */
72    public function storeAccessToken($service, TokenInterface $token)
73    {
74        // (over)write the token
75        $this->redis->hset($this->key, $service, serialize($token));
76        $this->cachedTokens[$service] = $token;
77
78        // allow chaining
79        return $this;
80    }
81
82    /**
83     * {@inheritDoc}
84     */
85    public function hasAccessToken($service)
86    {
87        if (isset($this->cachedTokens[$service])
88            && $this->cachedTokens[$service] instanceof TokenInterface
89        ) {
90            return true;
91        }
92
93        return $this->redis->hexists($this->key, $service);
94    }
95
96    /**
97     * {@inheritDoc}
98     */
99    public function clearToken($service)
100    {
101        $this->redis->hdel($this->key, $service);
102        unset($this->cachedTokens[$service]);
103
104        // allow chaining
105        return $this;
106    }
107
108    /**
109     * {@inheritDoc}
110     */
111    public function clearAllTokens()
112    {
113        // memory
114        $this->cachedTokens = array();
115
116        // redis
117        $keys = $this->redis->hkeys($this->key);
118        $me = $this; // 5.3 compat
119
120        // pipeline for performance
121        $this->redis->pipeline(
122            function ($pipe) use ($keys, $me) {
123                foreach ($keys as $k) {
124                    $pipe->hdel($me->getKey(), $k);
125                }
126            }
127        );
128
129        // allow chaining
130        return $this;
131    }
132
133    /**
134     * {@inheritDoc}
135     */
136    public function retrieveAuthorizationState($service)
137    {
138        if (!$this->hasAuthorizationState($service)) {
139            throw new AuthorizationStateNotFoundException('State not found in redis');
140        }
141
142        if (isset($this->cachedStates[$service])) {
143            return $this->cachedStates[$service];
144        }
145
146        $val = $this->redis->hget($this->stateKey, $service);
147
148        return $this->cachedStates[$service] = unserialize($val);
149    }
150
151    /**
152     * {@inheritDoc}
153     */
154    public function storeAuthorizationState($service, $state)
155    {
156        // (over)write the token
157        $this->redis->hset($this->stateKey, $service, $state);
158        $this->cachedStates[$service] = $state;
159
160        // allow chaining
161        return $this;
162    }
163
164    /**
165     * {@inheritDoc}
166     */
167    public function hasAuthorizationState($service)
168    {
169        if (isset($this->cachedStates[$service])
170            && null !== $this->cachedStates[$service]
171        ) {
172            return true;
173        }
174
175        return $this->redis->hexists($this->stateKey, $service);
176    }
177
178    /**
179     * {@inheritDoc}
180     */
181    public function clearAuthorizationState($service)
182    {
183        $this->redis->hdel($this->stateKey, $service);
184        unset($this->cachedStates[$service]);
185
186        // allow chaining
187        return $this;
188    }
189
190    /**
191     * {@inheritDoc}
192     */
193    public function clearAllAuthorizationStates()
194    {
195        // memory
196        $this->cachedStates = array();
197
198        // redis
199        $keys = $this->redis->hkeys($this->stateKey);
200        $me = $this; // 5.3 compat
201
202        // pipeline for performance
203        $this->redis->pipeline(
204            function ($pipe) use ($keys, $me) {
205                foreach ($keys as $k) {
206                    $pipe->hdel($me->getKey(), $k);
207                }
208            }
209        );
210
211        // allow chaining
212        return $this;
213    }
214
215    /**
216     * @return Predis $redis
217     */
218    public function getRedis()
219    {
220        return $this->redis;
221    }
222
223    /**
224     * @return string $key
225     */
226    public function getKey()
227    {
228        return $this->key;
229    }
230}
231