## Apache Zeta Components Manual :: File Source for typekey_filter.php

## Source for file typekey_filter.php

Documentation is available at typekey_filter.php

1. <?php
2. /**
3. * File containing the ezcAuthenticationTypekeyFilter class.
4. *
5. * Licensed to the Apache Software Foundation (ASF) under one
6. * or more contributor license agreements. See the NOTICE file
7. * distributed with this work for additional information
8. * regarding copyright ownership. The ASF licenses this file
9. * to you under the Apache License, Version 2.0 (the
10. * "License"); you may not use this file except in compliance
11. * with the License. You may obtain a copy of the License at
12. *
13. * http://www.apache.org/licenses/LICENSE-2.0
14. *
15. * Unless required by applicable law or agreed to in writing,
16. * software distributed under the License is distributed on an
17. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18. * KIND, either express or implied. See the License for the
19. * specific language governing permissions and limitations
20. * under the License.
21. *
22. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
23. * @filesource
24. * @package Authentication
25. * @version //autogen//
26. */
28. /**
29. * Filter to authenticate against TypeKey.
30. *
31. * The filter deals with the validation of information returned by the TypeKey
32. * server in response to a login command.
33. *
34. * Specifications: {@link http://www.sixapart.com/typekey/api}
35. *
36. * In order to access a protected page, user logs in by using a request like:
37. * - https://www.typekey.com/t/typekey/login?
38. * t=391jbj25WAQANzJrKvb5&
39. * _return=http://example.com/login.php
40. *
41. * where:
42. * - t = TypeKey token generated for each TypeKey account.
43. * It is found at https://www.typekey.com/t/typekey/prefs.
44. * This value is also used as a session key, so it must be passed to the
45. * page performing the TypeKey authentication via the _return URL.
46. * - _return = the URL where to return after user logs in with his TypeKey
47. * username and password. The URL can contain query arguments, such
48. * as the value t which can be used as a session key.
49. *
50. * The login link can also contain these 2 optional values:
51. * - v = TypeKey version to use. Default is 1.
52. * - need_email = the mail address which was used to register with TypeKey. It
53. * needs to be set to a value different than 0 in order to get
54. * the email address of the user when calling fetchData() after
55. * the authentication process has been completed.
56. *
57. * So the TypeKey authentication filter will run in the _return page and will
58. * verify the signature and the other information in the URL.
59. *
60. * The application link (eg. http://example.com) must be registered in the
61. * TypeKey preferences page (https://www.typekey.com/t/typekey/prefs) in one
62. * of the 5 lines from "Your Weblog Preferences", otherwise TypeKey will
63. * not accept the login request.
64. *
65. * The link returned by TypeKey after user logs in with his TypeKey username
66. * and password looks like this:
67. * - http://example.com/typekey.php?
68. * ts=1177319974&email=5098f1e87a608675ded4d933f31899cae6b4f968&
69. * name=ezc&nick=ezctest&
70. * sig=I9Dop72+oahY82bpL7ymBoxdQ+k=:Vj/t7oZVL2zMSzwHzdOWop5NG/g=
71. *
72. * where:
73. * - ts = timestamp (in seconds) of the TypeKey server time at login.
74. * The TypeKey filter compares this timestamp with the application
75. * server's timestamp to make sure the login is in a reasonable
76. * time window (specified by the validity option). Don't use a too small
77. * value for validity, because servers are not always synchronized.
78. * - email = sha1 hash of "mailto:$mail", where $mail is the mail address
79. * used to register with TypeKey.
80. * - nick = TypeKey nickname/display name.
81. * - sig = signature which must be validated by the TypeKey filter.
82. *
83. * For more information on the login request and the TypeKey response link see
84. * {@link http://www.sixapart.com/typekey/api}.
85. *
86. * Example of use (authentication + input form):
87. * <code>
88. * <?php
89. * // no headers should be sent before calling $session->start()
90. * $session = new ezcAuthenticationSession();
91. * $session->start();
92. *
93. * // $token is used as a key in the session to store the authenticated state between requests
94. * $token = isset( $_GET['token'] ) ? $_GET['token'] : $session->load();
95. *
96. * $credentials = new ezcAuthenticationIdCredentials( $token );
97. * $authentication = new ezcAuthentication( $credentials );
98. * $authentication->session = $session;
99. *
100. * $filter = new ezcAuthenticationTypekeyFilter();
101. * $authentication->addFilter( $filter );
102. * // add other filters if needed
103. *
104. * if ( !$authentication->run() )
105. * {
106. * // authentication did not succeed, so inform the user
107. * $status = $authentication->getStatus();
108. * $err = array(
109. * 'ezcAuthenticationTypekeyFilter' => array(
110. * ezcAuthenticationTypekeyFilter::STATUS_SIGNATURE_INCORRECT => 'Signature returned by TypeKey is incorrect',
111. * ezcAuthenticationTypekeyFilter::STATUS_SIGNATURE_EXPIRED => 'The signature returned by TypeKey expired'
112. * ),
113. * 'ezcAuthenticationSession' => array(
114. * ezcAuthenticationSession::STATUS_EMPTY => '',
115. * ezcAuthenticationSession::STATUS_EXPIRED => 'Session expired'
116. * )
117. * );
118. * foreach ( $status as $line )
119. * {
120. * list( $key, $value ) = each( $line );
121. * echo $err[$key][$value] . "\n";
122. * }
123. * ?>
124. * <!-- OnSubmit hack to append the value of t to the _return value, to pass
125. * the TypeKey token after the TypeKey request -->
126. * <form method="GET" action="https://www.typekey.com/t/typekey/login" onsubmit="document.getElementById('_return').value += '?token=' + document.getElementById('t').value;">
127. * TypeKey token: <input type="text" name="t" id="t" />
128. * <input type="hidden" name="_return" id="_return" value="http://localhost/typekey.php" />
129. * <input type="submit" />
130. * </form>
131. * <?php
132. * }
133. * else
134. * {
135. * // authentication succeeded, so allow the user to see his content
136. * echo "<b>Logged-in</b>";
137. * }
138. * ?>
139. * </code>
140. *
141. * Another method, which doesn't use JavaScript, is using an intermediary page
142. * which saves the token in the session, then calls the TypeKey login page:
143. *
144. * - original file is modified as follows:
145. * <code>
146. * <form method="GET" action="save_typekey.php">
147. * TypeKey token: <input type="text" name="t" id="t" />
148. * <input type="hidden" name="_return" id="_return" value="http://localhost/typekey.php" />
149. * <input type="submit" />
150. * </form>
151. * </code>
152. *
153. * - intermediary page:
154. * <code>
155. * <?php
156. * // no headers should be sent before calling $session->start()
157. * $session = new ezcAuthenticationSession();
158. * $session->start();
159. *
160. * // $token is used as a key in the session to store the authenticated state between requests
161. * $token = isset( $_GET['t'] ) ? $_GET['t'] : $session->load();
162. * if ( $token !== null )
163. * {
164. * $session->save( $token );
165. * }
166. * $url = isset( $_GET['_return'] ) ? $_GET['_return'] : null;
167. * $url .= "?token={$token}";
168. * header( "Location: https://www.typekey.com/t/typekey/login?t={$token}&_return={$url}" );
169. * ?>
170. * </code>
171. *
172. * Extra data can be fetched from the TypeKey server during the authentication
173. * process. Different from the other filters, for TypeKey there is no registration
174. * needed for fetching the extra data, because all the possible extra data is
175. * available in the response sent by the TypeKey server.
176. *
177. * To be able to get the email address of the user, need_email must be set
178. * to a value different than 0 in the initial request sent to the TypeKey
179. * server (along with the t and _return values). Example:
180. * - https://www.typekey.com/t/typekey/login?t=<token>&_return=<url>&need_email=1
181. *
182. * Example of fetching the extra data after the initial request has been sent:
183. * <code>
184. * // after run()
185. * // $filter is an ezcAuthenticationTypekeyFilter object
186. * $data = $filter->fetchData();
187. * </code>
188. *
189. * The $data array contains name (TypeKey username), nick (TypeKey display name)
190. * and optionally email (if the user allowed the sharing of his email address
191. * in the TypeKey profile page; otherwise it is not set).
192. * <code>
193. * array( 'name' => array( 'john' ),
194. * 'nick' => array( 'John Doe' ),
195. * 'email' => array( 'john.doe@example.com' ) // or not set
196. * );
197. * </code>
198. *
199. * @property ezcAuthenticationBignumLibrary $lib
200. * The wrapper for the PHP extension to use for big number operations.
201. * This will be autodetected in the constructor, but you can specify
202. * your own wrapper before calling run().
203. *
204. * @package Authentication
205. * @version //autogen//
206. * @mainclass
207. */
208. class ezcAuthenticationTypekeyFilter extends ezcAuthenticationFilter implements ezcAuthenticationDataFetch
209. {
210. /**
211. * The request does not contain the needed information (like $_GET['sig']).
212. */
213. const STATUS_SIGNATURE_MISSING = 1;
215. /**
216. * Signature verification was incorect.
217. */
218. const STATUS_SIGNATURE_INCORRECT = 2;
220. /**
221. * Login is outside of the timeframe.
222. */
223. const STATUS_SIGNATURE_EXPIRED = 3;
225. /**
226. * Holds the extra data fetched during the authentication process.
227. *
228. * Contains name (TypeKey username), nick (TypeKey display name) and
229. * optionally email (if the user allowed the sharing of his email address
230. * in the TypeKey profile page; otherwise it is not set).
231. *
232. * Usually it has this structure:
233. * <code>
234. * array( 'name' => array( 'john' ),
235. * 'nick' => array( 'John Doe' ),
236. * 'email' => array( 'john.doe@example.com' ) // or not set
237. * );
238. * </code>
239. *
240. * @var array(string=>mixed)
241. */
242. protected $data = array();
244. /**
245. * Holds the properties of this class.
246. *
247. * @var array(string=>mixed)
248. */
249. private $properties = array();
251. /**
252. * Creates a new object of this class.
253. *
254. * @throws ezcBaseExtensionNotFoundException
255. * if neither of the PHP gmp and bcmath extensions are installed
256. * @param ezcAuthenticationTypekeyOptions $options Options for this class
257. */
258. public function __construct ( ezcAuthenticationTypekeyOptions $options = null )
259. {
260. $this-> options = ( $options === null ) ? new ezcAuthenticationTypekeyOptions () : $options;
261. $this->lib = ezcAuthenticationMath::createBignumLibrary();
262. }
264. /**
265. * Sets the property $name to $value.
266. *
267. * @throws ezcBasePropertyNotFoundException
268. * if the property $name does not exist
269. * @throws ezcBaseValueException
270. * if $value is not correct for the property $name
271. * @param string $name The name of the property to set
272. * @param mixed $value The new value of the property
273. * @ignore
274. */
275. public function __set( $name, $value )
276. {
277. switch ( $name )
278. {
279. case 'lib':
280. if ( $value instanceof ezcAuthenticationBignumLibrary )
281. {
282. $this->properties[$name] = $value;
283. }
284. else
285. {
286. throw new ezcBaseValueException ( $name, $value, 'ezcAuthenticationBignumLibrary' );
287. }
288. break;
290. default:
291. throw new ezcBasePropertyNotFoundException ( $name );
292. }
293. }
295. /**
296. * Returns the value of the property $name.
297. *
298. * @throws ezcBasePropertyNotFoundException
299. * if the property $name does not exist
300. * @param string $name The name of the property for which to return the value
301. * @return mixed
302. * @ignore
303. */
304. public function __get( $name )
305. {
306. switch ( $name )
307. {
308. case 'lib':
309. return $this->properties[$name];
311. default:
312. throw new ezcBasePropertyNotFoundException ( $name );
313. }
314. }
316. /**
317. * Returns true if the property $name is set, otherwise false.
318. *
319. * @param string $name The name of the property to test if it is set
320. * @return bool
321. * @ignore
322. */
323. public function __isset( $name )
324. {
325. switch ( $name )
326. {
327. case 'lib':
328. return isset( $this->properties[$name] );
330. default:
331. return false;
332. }
333. }
335. /**
336. * Runs the filter and returns a status code when finished.
337. *
338. * @throws ezcAuthenticationTypekeyException
339. * if the keys from the TypeKey public keys file could not be fetched
340. * @param ezcAuthenticationIdCredentials $credentials Authentication credentials
341. * @return int
342. */
343. public function run ( $credentials )
344. {
345. $source = $this-> options ->requestSource;
346. if ( isset( $source['name'] ) && isset( $source['email'] ) && isset( $source['nick'] ) && isset( $source['ts'] ) && isset( $source['sig'] ) )
347. {
348. // parse the response URL sent by the TypeKey server
349. $id = isset( $source['name'] ) ? $source['name'] : null;
350. $mail = isset( $source['email'] ) ? $source['email'] : null;
351. $nick = isset( $source['nick'] ) ? $source['nick'] : null;
352. $timestamp = isset( $source['ts'] ) ? $source['ts'] : null;
353. $signature = isset( $source['sig'] ) ? $source['sig'] : null;
355. // extra data which will be returned by fetchData()
356. $this-> data ['name'] = array( $id );
357. $this-> data ['nick'] = array( $nick );
358. if ( strpos ( $mail, '@' ) !== false )
359. {
360. $this-> data ['email'] = array( $mail );
361. }
362. }
363. else
364. {
365. return self::STATUS_SIGNATURE_MISSING;
366. }
367. if ( $this-> options ->validity !== 0 &&
368. time () - $timestamp >= $this-> options ->validity
369. )
370. {
371. return self::STATUS_SIGNATURE_EXPIRED;
372. }
373. $keys = $this-> fetchPublicKeys ( $this-> options ->keysFile );
374. $msg = "{$mail}::{$id}::{$nick}::{$timestamp}";
375. $signature = rawurldecode ( urlencode ( $signature ) );
376. list( $r, $s ) = explode ( ':', $signature );
377. if ( $this-> checkSignature ( $msg, $r, $s, $keys ) )
378. {
379. return self::STATUS_OK;
380. }
381. return self::STATUS_SIGNATURE_INCORRECT;
382. }
384. /**
385. * Checks the information returned by the TypeKey server.
386. *
387. * @param string $msg Plain text signature which needs to be verified
388. * @param string $r First part of the signature retrieved from TypeKey
389. * @param string $s Second part of the signature retrieved from TypeKey
390. * @param array(string=>string) $keys Public keys retrieved from TypeKey
391. * @return bool
392. */
393. protected function checkSignature ( $msg, $r, $s, $keys )
394. {
395. $lib = $this->lib;
397. $r = base64_decode ( $r );
398. $s = base64_decode ( $s );
400. foreach ( $keys as $key => $value )
401. {
402. $keys[$key] = $lib->init( (string) $value );
403. }
405. $s1 = $lib->init( $lib->binToDec( $r ) );
406. $s2 = $lib->init( $lib->binToDec( $s ) );
408. $w = $lib->invert( $s2, $keys['q'] );
410. $msg = $lib->hexToDec( sha1 ( $msg ) );
412. $u1 = $lib->mod( $lib->mul( $msg, $w ), $keys['q'] );
413. $u2 = $lib->mod( $lib->mul( $s1, $w ), $keys['q'] );
415. $v = $lib->mul( $lib->powmod( $keys['g'], $u1, $keys['p'] ), $lib->powmod( $keys['pub_key'], $u2, $keys['p'] ) );
416. $v = $lib->mod( $lib->mod( $v, $keys['p'] ), $keys['q'] );
418. return ( $lib->cmp( $v, $s1 ) === 0 );
419. }
421. /**
422. * Fetches the public keys from the specified file or URL $file.
423. *
424. * The file must be composed of space-separated values for p, g, q, and
425. * pub_key, like this:
426. * p=<value> g=<value> q=<value> pub_key=<value>
427. *
428. * The format of the returned array is:
429. * <code>
430. * array( 'p' => p_val, 'g' => g_val, 'q' => q_val, 'pub_key' => pub_key_val )
431. * </code>
432. *
433. * @throws ezcAuthenticationTypekeyPublicKeysMissingException
434. * if the keys from the TypeKey public keys file could not be fetched
435. * @throws ezcAuthenticationTypekeyPublicKeysInvalidException
436. * if the keys fetched from the TypeKey public keys file are invalid
437. * @param string $file The public keys file or URL
438. * @return array(string=>string)
439. */
440. protected function fetchPublicKeys ( $file )
441. {
442. // suppress warnings caused by file_get_contents() if $file could not be opened
443. $data = @ file_get_contents ( $file );
444. if ( empty( $data ) )
445. {
446. throw new ezcAuthenticationTypekeyPublicKeysMissingException ( "Could not fetch public keys from '{$file}'." );
447. }
448. $lines = explode ( ' ', trim ( $data ) );
449. foreach ( $lines as $line )
450. {
451. $val = explode ( '=', $line );
452. if ( count ( $val ) < 2 )
453. {
454. throw new ezcAuthenticationTypekeyPublicKeysInvalidException ( "The data retrieved from '{$file}' is invalid." );
455. }
456. $keys[$val[0]] = $val[1];
457. }
458. return $keys;
459. }
461. /**
462. * Registers the extra data which will be fetched by the filter during the
463. * authentication process.
464. *
465. * For TypeKey there is no registration needed, because all the possible
466. * extra data is available in the response sent by the TypeKey server. So
467. * a call to this function is not needed.
468. *
469. * To be able to get the email address of the user, need_email must be set
470. * to a value different than 0 in the initial request sent to the TypeKey
471. * server (along with the t and _return values).
472. *
473. * @param array(string) $data A list of attributes to fetch during authentication
474. */
475. public function registerFetchData ( array $data = array() )
476. {
477. // does not need to do anything because all the extra data is returned by default
478. }
480. /**
481. * Returns the extra data which was fetched during the authentication process.
482. *
483. * The TypeKey extra data is an array containing the values for name (the
484. * TypeKey username), nick (the TypeKey display name) and email (the email
485. * address of the user, fetched only if the initial request to the TypeKey
486. * server contains need_email, and the user allowed the sharing of his email
487. * address).
488. *
489. * Example of returned array:
490. * <code>
491. * array( 'name' => array( 'john' ),
492. * 'nick' => array( 'John Doe' ),
493. * 'email' => array( 'john.doe@example.com' ) // or not set
494. * );
495. * </code>
496. *
497. * @return array(string=>mixed)
498. */
499. public function fetchData ()
500. {
501. return $this-> data ;
502. }
503. }
504. ?>

Documentation generated by phpDocumentor 1.4.3