1<?php
2
3/**
4 * This file supplies a memcached store backend for OpenID servers and
5 * consumers.
6 *
7 * PHP versions 4 and 5
8 *
9 * LICENSE: See the COPYING file included in this distribution.
10 *
11 * @package OpenID
12 * @author Artemy Tregubenko <me@arty.name>
13 * @copyright 2008 JanRain, Inc.
14 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
15 * Contributed by Open Web Technologies <http://openwebtech.ru/>
16 */
17
18/**
19 * Import the interface for creating a new store class.
20 */
21require_once 'Auth/OpenID/Interface.php';
22
23/**
24 * This is a memcached-based store for OpenID associations and
25 * nonces.
26 *
27 * As memcache has limit of 250 chars for key length,
28 * server_url, handle and salt are hashed with sha1().
29 *
30 * Most of the methods of this class are implementation details.
31 * People wishing to just use this store need only pay attention to
32 * the constructor.
33 *
34 * @package OpenID
35 */
36class Auth_OpenID_MemcachedStore extends Auth_OpenID_OpenIDStore {
37
38    /**
39     * Initializes a new {@link Auth_OpenID_MemcachedStore} instance.
40     * Just saves memcached object as property.
41     *
42     * @param resource connection Memcache connection resourse
43     */
44    function Auth_OpenID_MemcachedStore($connection, $compress = false)
45    {
46        $this->connection = $connection;
47        $this->compress = $compress ? MEMCACHE_COMPRESSED : 0;
48    }
49
50    /**
51     * Store association until its expiration time in memcached.
52     * Overwrites any existing association with same server_url and
53     * handle. Handles list of associations for every server.
54     */
55    function storeAssociation($server_url, $association)
56    {
57        // create memcached keys for association itself
58        // and list of associations for this server
59        $associationKey = $this->associationKey($server_url,
60            $association->handle);
61        $serverKey = $this->associationServerKey($server_url);
62
63        // get list of associations
64        $serverAssociations = $this->connection->get($serverKey);
65
66        // if no such list, initialize it with empty array
67        if (!$serverAssociations) {
68            $serverAssociations = array();
69        }
70        // and store given association key in it
71        $serverAssociations[$association->issued] = $associationKey;
72
73        // save associations' keys list
74        $this->connection->set(
75            $serverKey,
76            $serverAssociations,
77            $this->compress
78        );
79        // save association itself
80        $this->connection->set(
81            $associationKey,
82            $association,
83            $this->compress,
84            $association->issued + $association->lifetime);
85    }
86
87    /**
88     * Read association from memcached. If no handle given
89     * and multiple associations found, returns latest issued
90     */
91    function getAssociation($server_url, $handle = null)
92    {
93        // simple case: handle given
94        if ($handle !== null) {
95            // get association, return null if failed
96            $association = $this->connection->get(
97                $this->associationKey($server_url, $handle));
98            return $association ? $association : null;
99        }
100
101        // no handle given, working with list
102        // create key for list of associations
103        $serverKey = $this->associationServerKey($server_url);
104
105        // get list of associations
106        $serverAssociations = $this->connection->get($serverKey);
107        // return null if failed or got empty list
108        if (!$serverAssociations) {
109            return null;
110        }
111
112        // get key of most recently issued association
113        $keys = array_keys($serverAssociations);
114        sort($keys);
115        $lastKey = $serverAssociations[array_pop($keys)];
116
117        // get association, return null if failed
118        $association = $this->connection->get($lastKey);
119        return $association ? $association : null;
120    }
121
122    /**
123     * Immediately delete association from memcache.
124     */
125    function removeAssociation($server_url, $handle)
126    {
127        // create memcached keys for association itself
128        // and list of associations for this server
129        $serverKey = $this->associationServerKey($server_url);
130        $associationKey = $this->associationKey($server_url,
131            $handle);
132
133        // get list of associations
134        $serverAssociations = $this->connection->get($serverKey);
135        // return null if failed or got empty list
136        if (!$serverAssociations) {
137            return false;
138        }
139
140        // ensure that given association key exists in list
141        $serverAssociations = array_flip($serverAssociations);
142        if (!array_key_exists($associationKey, $serverAssociations)) {
143            return false;
144        }
145
146        // remove given association key from list
147        unset($serverAssociations[$associationKey]);
148        $serverAssociations = array_flip($serverAssociations);
149
150        // save updated list
151        $this->connection->set(
152            $serverKey,
153            $serverAssociations,
154            $this->compress
155        );
156
157        // delete association
158        return $this->connection->delete($associationKey);
159    }
160
161    /**
162     * Create nonce for server and salt, expiring after
163     * $Auth_OpenID_SKEW seconds.
164     */
165    function useNonce($server_url, $timestamp, $salt)
166    {
167        global $Auth_OpenID_SKEW;
168
169        // save one request to memcache when nonce obviously expired
170        if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
171            return false;
172        }
173
174        // returns false when nonce already exists
175        // otherwise adds nonce
176        return $this->connection->add(
177            'openid_nonce_' . sha1($server_url) . '_' . sha1($salt),
178            1, // any value here
179            $this->compress,
180            $Auth_OpenID_SKEW);
181    }
182
183    /**
184     * Memcache key is prefixed with 'openid_association_' string.
185     */
186    function associationKey($server_url, $handle = null)
187    {
188        return 'openid_association_' . sha1($server_url) . '_' . sha1($handle);
189    }
190
191    /**
192     * Memcache key is prefixed with 'openid_association_' string.
193     */
194    function associationServerKey($server_url)
195    {
196        return 'openid_association_server_' . sha1($server_url);
197    }
198
199    /**
200     * Report that this storage doesn't support cleanup
201     */
202    function supportsCleanup()
203    {
204        return false;
205    }
206}
207
208