Subversion Repositories Applications.annuaire

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
42 aurelien 1
<?php
2
 
3
/**
4
 * Yadis service manager to be used during yadis-driven authentication
5
 * attempts.
6
 *
7
 * @package Yadis
8
 */
9
 
10
/**
11
 * The base session class used by the Services_Yadis_Manager.  This
12
 * class wraps the default PHP session machinery and should be
13
 * subclassed if your application doesn't use PHP sessioning.
14
 *
15
 * @package Yadis
16
 */
17
class Services_Yadis_PHPSession {
18
    /**
19
     * Set a session key/value pair.
20
     *
21
     * @param string $name The name of the session key to add.
22
     * @param string $value The value to add to the session.
23
     */
24
    function set($name, $value)
25
    {
26
        $_SESSION[$name] = $value;
27
    }
28
 
29
    /**
30
     * Get a key's value from the session.
31
     *
32
     * @param string $name The name of the key to retrieve.
33
     * @param string $default The optional value to return if the key
34
     * is not found in the session.
35
     * @return string $result The key's value in the session or
36
     * $default if it isn't found.
37
     */
38
    function get($name, $default=null)
39
    {
40
        if (array_key_exists($name, $_SESSION)) {
41
            return $_SESSION[$name];
42
        } else {
43
            return $default;
44
        }
45
    }
46
 
47
    /**
48
     * Remove a key/value pair from the session.
49
     *
50
     * @param string $name The name of the key to remove.
51
     */
52
    function del($name)
53
    {
54
        unset($_SESSION[$name]);
55
    }
56
 
57
    /**
58
     * Return the contents of the session in array form.
59
     */
60
    function contents()
61
    {
62
        return $_SESSION;
63
    }
64
}
65
 
66
/**
67
 * A session helper class designed to translate between arrays and
68
 * objects.  Note that the class used must have a constructor that
69
 * takes no parameters.  This is not a general solution, but it works
70
 * for dumb objects that just need to have attributes set.  The idea
71
 * is that you'll subclass this and override $this->check($data) ->
72
 * bool to implement your own session data validation.
73
 */
74
class Services_Yadis_SessionLoader {
75
    /**
76
     * Override this.
77
     */
78
    function check($data)
79
    {
80
        return true;
81
    }
82
 
83
    /**
84
     * Given a session data value (an array), this creates an object
85
     * (returned by $this->newObject()) whose attributes and values
86
     * are those in $data.  Returns null if $data lacks keys found in
87
     * $this->requiredKeys().  Returns null if $this->check($data)
88
     * evaluates to false.  Returns null if $this->newObject()
89
     * evaluates to false.
90
     */
91
    function fromSession($data)
92
    {
93
        if (!$data) {
94
            return null;
95
        }
96
 
97
        $required = $this->requiredKeys();
98
 
99
        foreach ($required as $k) {
100
            if (!array_key_exists($k, $data)) {
101
                return null;
102
            }
103
        }
104
 
105
        if (!$this->check($data)) {
106
            return null;
107
        }
108
 
109
        $data = array_merge($data, $this->prepareForLoad($data));
110
        $obj = $this->newObject($data);
111
 
112
        if (!$obj) {
113
            return null;
114
        }
115
 
116
        foreach ($required as $k) {
117
            $obj->$k = $data[$k];
118
        }
119
 
120
        return $obj;
121
    }
122
 
123
    /**
124
     * Prepares the data array by making any necessary changes.
125
     * Returns an array whose keys and values will be used to update
126
     * the original data array before calling $this->newObject($data).
127
     */
128
    function prepareForLoad($data)
129
    {
130
        return array();
131
    }
132
 
133
    /**
134
     * Returns a new instance of this loader's class, using the
135
     * session data to construct it if necessary.  The object need
136
     * only be created; $this->fromSession() will take care of setting
137
     * the object's attributes.
138
     */
139
    function newObject($data)
140
    {
141
        return null;
142
    }
143
 
144
    /**
145
     * Returns an array of keys and values built from the attributes
146
     * of $obj.  If $this->prepareForSave($obj) returns an array, its keys
147
     * and values are used to update the $data array of attributes
148
     * from $obj.
149
     */
150
    function toSession($obj)
151
    {
152
        $data = array();
153
        foreach ($obj as $k => $v) {
154
            $data[$k] = $v;
155
        }
156
 
157
        $extra = $this->prepareForSave($obj);
158
 
159
        if ($extra && is_array($extra)) {
160
            foreach ($extra as $k => $v) {
161
                $data[$k] = $v;
162
            }
163
        }
164
 
165
        return $data;
166
    }
167
 
168
    /**
169
     * Override this.
170
     */
171
    function prepareForSave($obj)
172
    {
173
        return array();
174
    }
175
}
176
 
177
class Auth_OpenID_ServiceEndpointLoader extends Services_Yadis_SessionLoader {
178
    function newObject($data)
179
    {
180
        return new Auth_OpenID_ServiceEndpoint();
181
    }
182
 
183
    function requiredKeys()
184
    {
185
        $obj = new Auth_OpenID_ServiceEndpoint();
186
        $data = array();
187
        foreach ($obj as $k => $v) {
188
            $data[] = $k;
189
        }
190
        return $data;
191
    }
192
 
193
    function check($data)
194
    {
195
        return is_array($data['type_uris']);
196
    }
197
}
198
 
199
class Services_Yadis_ManagerLoader extends Services_Yadis_SessionLoader {
200
    function requiredKeys()
201
    {
202
        return array('starting_url',
203
                     'yadis_url',
204
                     'services',
205
                     'session_key',
206
                     '_current',
207
                     'stale');
208
    }
209
 
210
    function newObject($data)
211
    {
212
        return new Services_Yadis_Manager($data['starting_url'],
213
                                          $data['yadis_url'],
214
                                          $data['services'],
215
                                          $data['session_key']);
216
    }
217
 
218
    function check($data)
219
    {
220
        return is_array($data['services']);
221
    }
222
 
223
    function prepareForLoad($data)
224
    {
225
        $loader = new Auth_OpenID_ServiceEndpointLoader();
226
        $services = array();
227
        foreach ($data['services'] as $s) {
228
            $services[] = $loader->fromSession($s);
229
        }
230
        return array('services' => $services);
231
    }
232
 
233
    function prepareForSave($obj)
234
    {
235
        $loader = new Auth_OpenID_ServiceEndpointLoader();
236
        $services = array();
237
        foreach ($obj->services as $s) {
238
            $services[] = $loader->toSession($s);
239
        }
240
        return array('services' => $services);
241
    }
242
}
243
 
244
/**
245
 * The Yadis service manager which stores state in a session and
246
 * iterates over <Service> elements in a Yadis XRDS document and lets
247
 * a caller attempt to use each one.  This is used by the Yadis
248
 * library internally.
249
 *
250
 * @package Yadis
251
 */
252
class Services_Yadis_Manager {
253
 
254
    /**
255
     * Intialize a new yadis service manager.
256
     *
257
     * @access private
258
     */
259
    function Services_Yadis_Manager($starting_url, $yadis_url,
260
                                    $services, $session_key)
261
    {
262
        // The URL that was used to initiate the Yadis protocol
263
        $this->starting_url = $starting_url;
264
 
265
        // The URL after following redirects (the identifier)
266
        $this->yadis_url = $yadis_url;
267
 
268
        // List of service elements
269
        $this->services = $services;
270
 
271
        $this->session_key = $session_key;
272
 
273
        // Reference to the current service object
274
        $this->_current = null;
275
 
276
        // Stale flag for cleanup if PHP lib has trouble.
277
        $this->stale = false;
278
    }
279
 
280
    /**
281
     * @access private
282
     */
283
    function length()
284
    {
285
        // How many untried services remain?
286
        return count($this->services);
287
    }
288
 
289
    /**
290
     * Return the next service
291
     *
292
     * $this->current() will continue to return that service until the
293
     * next call to this method.
294
     */
295
    function nextService()
296
    {
297
 
298
        if ($this->services) {
299
            $this->_current = array_shift($this->services);
300
        } else {
301
            $this->_current = null;
302
        }
303
 
304
        return $this->_current;
305
    }
306
 
307
    /**
308
     * @access private
309
     */
310
    function current()
311
    {
312
        // Return the current service.
313
        // Returns None if there are no services left.
314
        return $this->_current;
315
    }
316
 
317
    /**
318
     * @access private
319
     */
320
    function forURL($url)
321
    {
322
        return in_array($url, array($this->starting_url, $this->yadis_url));
323
    }
324
 
325
    /**
326
     * @access private
327
     */
328
    function started()
329
    {
330
        // Has the first service been returned?
331
        return $this->_current !== null;
332
    }
333
}
334
 
335
/**
336
 * State management for discovery.
337
 *
338
 * High-level usage pattern is to call .getNextService(discover) in
339
 * order to find the next available service for this user for this
340
 * session. Once a request completes, call .finish() to clean up the
341
 * session state.
342
 *
343
 * @package Yadis
344
 */
345
class Services_Yadis_Discovery {
346
 
347
    /**
348
     * @access private
349
     */
350
    var $DEFAULT_SUFFIX = 'auth';
351
 
352
    /**
353
     * @access private
354
     */
355
    var $PREFIX = '_yadis_services_';
356
 
357
    /**
358
     * Initialize a discovery object.
359
     *
360
     * @param Services_Yadis_PHPSession $session An object which
361
     * implements the Services_Yadis_PHPSession API.
362
     * @param string $url The URL on which to attempt discovery.
363
     * @param string $session_key_suffix The optional session key
364
     * suffix override.
365
     */
366
    function Services_Yadis_Discovery(&$session, $url,
367
                                      $session_key_suffix = null)
368
    {
369
        /// Initialize a discovery object
370
        $this->session =& $session;
371
        $this->url = $url;
372
        if ($session_key_suffix === null) {
373
            $session_key_suffix = $this->DEFAULT_SUFFIX;
374
        }
375
 
376
        $this->session_key_suffix = $session_key_suffix;
377
        $this->session_key = $this->PREFIX . $this->session_key_suffix;
378
    }
379
 
380
    /**
381
     * Return the next authentication service for the pair of
382
     * user_input and session. This function handles fallback.
383
     */
384
    function getNextService($discover_cb, &$fetcher)
385
    {
386
        $manager = $this->getManager();
387
        if (!$manager || (!$manager->services)) {
388
            $this->destroyManager();
389
            $http_response = array();
390
 
391
            $services = call_user_func($discover_cb, $this->url,
392
                                       $fetcher);
393
 
394
            $manager = $this->createManager($services, $this->url);
395
        }
396
 
397
        if ($manager) {
398
            $loader = new Services_Yadis_ManagerLoader();
399
            $service = $manager->nextService();
400
            $this->session->set($this->session_key,
401
                                serialize($loader->toSession($manager)));
402
        } else {
403
            $service = null;
404
        }
405
 
406
        return $service;
407
    }
408
 
409
    /**
410
     * Clean up Yadis-related services in the session and return the
411
     * most-recently-attempted service from the manager, if one
412
     * exists.
413
     */
414
    function cleanup()
415
    {
416
        $manager = $this->getManager();
417
        if ($manager) {
418
            $service = $manager->current();
419
            $this->destroyManager();
420
        } else {
421
            $service = null;
422
        }
423
 
424
        return $service;
425
    }
426
 
427
    /**
428
     * @access private
429
     */
430
    function getSessionKey()
431
    {
432
        // Get the session key for this starting URL and suffix
433
        return $this->PREFIX . $this->session_key_suffix;
434
    }
435
 
436
    /**
437
     * @access private
438
     */
439
    function &getManager()
440
    {
441
        // Extract the YadisServiceManager for this object's URL and
442
        // suffix from the session.
443
 
444
        $manager_str = $this->session->get($this->getSessionKey());
445
        $manager = null;
446
 
447
        if ($manager_str !== null) {
448
            $loader = new Services_Yadis_ManagerLoader();
449
            $manager = $loader->fromSession(unserialize($manager_str));
450
        }
451
 
452
        if ($manager && $manager->forURL($this->url)) {
453
            return $manager;
454
        } else {
455
            $unused = null;
456
            return $unused;
457
        }
458
    }
459
 
460
    /**
461
     * @access private
462
     */
463
    function &createManager($services, $yadis_url = null)
464
    {
465
        $key = $this->getSessionKey();
466
        if ($this->getManager()) {
467
            return $this->getManager();
468
        }
469
 
470
        if ($services) {
471
            $loader = new Services_Yadis_ManagerLoader();
472
            $manager = new Services_Yadis_Manager($this->url, $yadis_url,
473
                                              $services, $key);
474
            $this->session->set($this->session_key,
475
                                serialize($loader->toSession($manager)));
476
            return $manager;
477
        } else {
478
            // Oh, PHP.
479
            $unused = null;
480
            return $unused;
481
        }
482
    }
483
 
484
    /**
485
     * @access private
486
     */
487
    function destroyManager()
488
    {
489
        if ($this->getManager() !== null) {
490
            $key = $this->getSessionKey();
491
            $this->session->del($key);
492
        }
493
    }
494
}
495
 
496
?>