]> git.mxchange.org Git - friendica.git/blob - library/oauth2-php/lib/OAuth2.inc
Merge pull request #4234 from annando/participation-2
[friendica.git] / library / oauth2-php / lib / OAuth2.inc
1 <?php
2
3 /**
4  * @mainpage
5  * OAuth 2.0 server in PHP, originally written for
6  * <a href="http://www.opendining.net/"> Open Dining</a>. Supports
7  * <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10">IETF draft v10</a>.
8  *
9  * Source repo has sample servers implementations for
10  * <a href="http://php.net/manual/en/book.pdo.php"> PHP Data Objects</a> and
11  * <a href="http://www.mongodb.org/">MongoDB</a>. Easily adaptable to other
12  * storage engines.
13  *
14  * PHP Data Objects supports a variety of databases, including MySQL,
15  * Microsoft SQL Server, SQLite, and Oracle, so you can try out the sample
16  * to see how it all works.
17  *
18  * We're expanding the wiki to include more helpful documentation, but for
19  * now, your best bet is to view the oauth.php source - it has lots of
20  * comments.
21  *
22  * @author Tim Ridgely <tim.ridgely@gmail.com>
23  * @author Aaron Parecki <aaron@parecki.com>
24  * @author Edison Wong <hswong3i@pantarei-design.com>
25  *
26  * @see http://code.google.com/p/oauth2-php/
27  */
28
29
30 /**
31  * The default duration in seconds of the access token lifetime.
32  */
33 define("OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME", 3600);
34
35 /**
36  * The default duration in seconds of the authorization code lifetime.
37  */
38 define("OAUTH2_DEFAULT_AUTH_CODE_LIFETIME", 30);
39
40 /**
41  * The default duration in seconds of the refresh token lifetime.
42  */
43 define("OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME", 1209600);
44
45
46 /**
47  * @defgroup oauth2_section_2 Client Credentials
48  * @{
49  *
50  * When interacting with the authorization server, the client identifies
51  * itself using a client identifier and authenticates using a set of
52  * client credentials. This specification provides one mechanism for
53  * authenticating the client using password credentials.
54  *
55  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2
56  */
57
58 /**
59  * Regex to filter out the client identifier (described in Section 2 of IETF draft).
60  *
61  * IETF draft does not prescribe a format for these, however I've arbitrarily
62  * chosen alphanumeric strings with hyphens and underscores, 3-32 characters
63  * long.
64  *
65  * Feel free to change.
66  */
67 define("OAUTH2_CLIENT_ID_REGEXP", "/^[a-z0-9-_]{3,32}$/i");
68
69 /**
70  * @}
71  */
72
73
74 /**
75  * @defgroup oauth2_section_3 Obtaining End-User Authorization
76  * @{
77  *
78  * When the client interacts with an end-user, the end-user MUST first
79  * grant the client authorization to access its protected resources.
80  * Once obtained, the end-user access grant is expressed as an
81  * authorization code which the client uses to obtain an access token.
82  * To obtain an end-user authorization, the client sends the end-user to
83  * the end-user authorization endpoint.
84  *
85  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
86  */
87
88 /**
89  * Denotes "token" authorization response type.
90  */
91 define("OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN", "token");
92
93 /**
94  * Denotes "code" authorization response type.
95  */
96 define("OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE", "code");
97
98 /**
99  * Denotes "code-and-token" authorization response type.
100  */
101 define("OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN", "code-and-token");
102
103 /**
104  * Regex to filter out the authorization response type.
105  */
106 define("OAUTH2_AUTH_RESPONSE_TYPE_REGEXP", "/^(token|code|code-and-token)$/");
107
108 /**
109  * @}
110  */
111
112
113 /**
114  * @defgroup oauth2_section_4 Obtaining an Access Token
115  * @{
116  *
117  * The client obtains an access token by authenticating with the
118  * authorization server and presenting its access grant (in the form of
119  * an authorization code, resource owner credentials, an assertion, or a
120  * refresh token).
121  *
122  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4
123  */
124
125 /**
126  * Denotes "authorization_code" grant types (for token obtaining).
127  */
128 define("OAUTH2_GRANT_TYPE_AUTH_CODE", "authorization_code");
129
130 /**
131  * Denotes "password" grant types (for token obtaining).
132  */
133 define("OAUTH2_GRANT_TYPE_USER_CREDENTIALS", "password");
134
135 /**
136  * Denotes "assertion" grant types (for token obtaining).
137  */
138 define("OAUTH2_GRANT_TYPE_ASSERTION", "assertion");
139
140 /**
141  * Denotes "refresh_token" grant types (for token obtaining).
142  */
143 define("OAUTH2_GRANT_TYPE_REFRESH_TOKEN", "refresh_token");
144
145 /**
146  * Denotes "none" grant types (for token obtaining).
147  */
148 define("OAUTH2_GRANT_TYPE_NONE", "none");
149
150 /**
151  * Regex to filter out the grant type.
152  */
153 define("OAUTH2_GRANT_TYPE_REGEXP", "/^(authorization_code|password|assertion|refresh_token|none)$/");
154
155 /**
156  * @}
157  */
158
159
160 /**
161  * @defgroup oauth2_section_5 Accessing a Protected Resource
162  * @{
163  *
164  * Clients access protected resources by presenting an access token to
165  * the resource server. Access tokens act as bearer tokens, where the
166  * token string acts as a shared symmetric secret. This requires
167  * treating the access token with the same care as other secrets (e.g.
168  * end-user passwords). Access tokens SHOULD NOT be sent in the clear
169  * over an insecure channel.
170  *
171  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5
172  */
173
174 /**
175  * Used to define the name of the OAuth access token parameter (POST/GET/etc.).
176  *
177  * IETF Draft sections 5.1.2 and 5.1.3 specify that it should be called
178  * "oauth_token" but other implementations use things like "access_token".
179  *
180  * I won't be heartbroken if you change it, but it might be better to adhere
181  * to the spec.
182  *
183  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.2
184  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.3
185  */
186 define("OAUTH2_TOKEN_PARAM_NAME", "oauth_token");
187
188 /**
189  * @}
190  */
191
192
193 /**
194  * @defgroup oauth2_http_status HTTP status code
195  * @{
196  */
197
198 /**
199  * "Found" HTTP status code.
200  *
201  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
202  */
203 define("OAUTH2_HTTP_FOUND", "302 Found");
204
205 /**
206  * "Bad Request" HTTP status code.
207  *
208  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
209  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
210  */
211 define("OAUTH2_HTTP_BAD_REQUEST", "400 Bad Request");
212
213 /**
214  * "Unauthorized" HTTP status code.
215  *
216  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
217  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
218  */
219 define("OAUTH2_HTTP_UNAUTHORIZED", "401 Unauthorized");
220
221 /**
222  * "Forbidden" HTTP status code.
223  *
224  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
225  */
226 define("OAUTH2_HTTP_FORBIDDEN", "403 Forbidden");
227
228 /**
229  * @}
230  */
231
232
233 /**
234  * @defgroup oauth2_error Error handling
235  * @{
236  *
237  * @todo Extend for i18n.
238  */
239
240 /**
241  * The request is missing a required parameter, includes an unsupported
242  * parameter or parameter value, or is otherwise malformed.
243  *
244  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
245  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
246  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
247  */
248 define("OAUTH2_ERROR_INVALID_REQUEST", "invalid_request");
249
250 /**
251  * The client identifier provided is invalid.
252  *
253  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
254  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
255  */
256 define("OAUTH2_ERROR_INVALID_CLIENT", "invalid_client");
257
258 /**
259  * The client is not authorized to use the requested response type.
260  *
261  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
262  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
263  */
264 define("OAUTH2_ERROR_UNAUTHORIZED_CLIENT", "unauthorized_client");
265
266 /**
267  * The redirection URI provided does not match a pre-registered value.
268  *
269  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
270  */
271 define("OAUTH2_ERROR_REDIRECT_URI_MISMATCH", "redirect_uri_mismatch");
272
273 /**
274  * The end-user or authorization server denied the request.
275  *
276  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
277  */
278 define("OAUTH2_ERROR_USER_DENIED", "access_denied");
279
280 /**
281  * The requested response type is not supported by the authorization server.
282  *
283  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
284  */
285 define("OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE", "unsupported_response_type");
286
287 /**
288  * The requested scope is invalid, unknown, or malformed.
289  *
290  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1
291  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
292  */
293 define("OAUTH2_ERROR_INVALID_SCOPE", "invalid_scope");
294
295 /**
296  * The provided access grant is invalid, expired, or revoked (e.g. invalid
297  * assertion, expired authorization token, bad end-user password credentials,
298  * or mismatching authorization code and redirection URI).
299  *
300  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
301  */
302 define("OAUTH2_ERROR_INVALID_GRANT", "invalid_grant");
303
304 /**
305  * The access grant included - its type or another attribute - is not
306  * supported by the authorization server.
307  *
308  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1
309  */
310 define("OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE", "unsupported_grant_type");
311
312 /**
313  * The access token provided is invalid. Resource servers SHOULD use this
314  * error code when receiving an expired token which cannot be refreshed to
315  * indicate to the client that a new authorization is necessary. The resource
316  * server MUST respond with the HTTP 401 (Unauthorized) status code.
317  *
318  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
319  */
320 define("OAUTH2_ERROR_INVALID_TOKEN", "invalid_token");
321
322 /**
323  * The access token provided has expired. Resource servers SHOULD only use
324  * this error code when the client is expected to be able to handle the
325  * response and request a new access token using the refresh token issued
326  * with the expired access token. The resource server MUST respond with the
327  * HTTP 401 (Unauthorized) status code.
328  *
329  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
330  */
331 define("OAUTH2_ERROR_EXPIRED_TOKEN", "expired_token");
332
333 /**
334  * The request requires higher privileges than provided by the access token.
335  * The resource server SHOULD respond with the HTTP 403 (Forbidden) status
336  * code and MAY include the "scope" attribute with the scope necessary to
337  * access the protected resource.
338  *
339  * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1
340  */
341 define("OAUTH2_ERROR_INSUFFICIENT_SCOPE", "insufficient_scope");
342
343 /**
344  * @}
345  */
346
347 /**
348  * OAuth2.0 draft v10 server-side implementation.
349  *
350  * @author Originally written by Tim Ridgely <tim.ridgely@gmail.com>.
351  * @author Updated to draft v10 by Aaron Parecki <aaron@parecki.com>.
352  * @author Debug, coding style clean up and documented by Edison Wong <hswong3i@pantarei-design.com>.
353  */
354 abstract class OAuth2 {
355
356   /**
357    * Array of persistent variables stored.
358    */
359   protected $conf = array();
360
361   /**
362    * Returns a persistent variable.
363    *
364    * To avoid problems, always use lower case for persistent variable names.
365    *
366    * @param $name
367    *   The name of the variable to return.
368    * @param $default
369    *   The default value to use if this variable has never been set.
370    *
371    * @return
372    *   The value of the variable.
373    */
374   public function getVariable($name, $default = NULL) {
375     return isset($this->conf[$name]) ? $this->conf[$name] : $default;
376   }
377
378   /**
379    * Sets a persistent variable.
380    *
381    * To avoid problems, always use lower case for persistent variable names.
382    *
383    * @param $name
384    *   The name of the variable to set.
385    * @param $value
386    *   The value to set.
387    */
388   public function setVariable($name, $value) {
389     $this->conf[$name] = $value;
390     return $this;
391   }
392
393   // Subclasses must implement the following functions.
394
395   /**
396    * Make sure that the client credentials is valid.
397    *
398    * @param $client_id
399    *   Client identifier to be check with.
400    * @param $client_secret
401    *   (optional) If a secret is required, check that they've given the right one.
402    *
403    * @return
404    *   TRUE if client credentials are valid, and MUST return FALSE if invalid.
405    *
406    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2.1
407    *
408    * @ingroup oauth2_section_2
409    */
410   abstract protected function checkClientCredentials($client_id, $client_secret = NULL);
411
412   /**
413    * Get the registered redirect URI of corresponding client_id.
414    *
415    * OAuth says we should store request URIs for each registered client.
416    * Implement this function to grab the stored URI for a given client id.
417    *
418    * @param $client_id
419    *   Client identifier to be check with.
420    *
421    * @return
422    *   Registered redirect URI of corresponding client identifier, and MUST
423    *   return FALSE if the given client does not exist or is invalid.
424    *
425    * @ingroup oauth2_section_3
426    */
427   abstract protected function getRedirectUri($client_id);
428
429   /**
430    * Look up the supplied oauth_token from storage.
431    *
432    * We need to retrieve access token data as we create and verify tokens.
433    *
434    * @param $oauth_token
435    *   oauth_token to be check with.
436    *
437    * @return
438    *   An associative array as below, and return NULL if the supplied oauth_token
439    *   is invalid:
440    *   - client_id: Stored client identifier.
441    *   - expires: Stored expiration in unix timestamp.
442    *   - scope: (optional) Stored scope values in space-separated string.
443    *
444    * @ingroup oauth2_section_5
445    */
446   abstract protected function getAccessToken($oauth_token);
447
448   /**
449    * Store the supplied access token values to storage.
450    *
451    * We need to store access token data as we create and verify tokens.
452    *
453    * @param $oauth_token
454    *   oauth_token to be stored.
455    * @param $client_id
456    *   Client identifier to be stored.
457    * @param $expires
458    *   Expiration to be stored.
459    * @param $scope
460    *   (optional) Scopes to be stored in space-separated string.
461    *
462    * @ingroup oauth2_section_4
463    */
464   abstract protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL);
465
466   // Stuff that should get overridden by subclasses.
467   //
468   // I don't want to make these abstract, because then subclasses would have
469   // to implement all of them, which is too much work.
470   //
471   // So they're just stubs. Override the ones you need.
472
473   /**
474    * Return supported grant types.
475    *
476    * You should override this function with something, or else your OAuth
477    * provider won't support any grant types!
478    *
479    * @return
480    *   A list as below. If you support all grant types, then you'd do:
481    * @code
482    * return array(
483    *   OAUTH2_GRANT_TYPE_AUTH_CODE,
484    *   OAUTH2_GRANT_TYPE_USER_CREDENTIALS,
485    *   OAUTH2_GRANT_TYPE_ASSERTION,
486    *   OAUTH2_GRANT_TYPE_REFRESH_TOKEN,
487    *   OAUTH2_GRANT_TYPE_NONE,
488    * );
489    * @endcode
490    *
491    * @ingroup oauth2_section_4
492    */
493   protected function getSupportedGrantTypes() {
494     return array();
495   }
496
497   /**
498    * Return supported authorization response types.
499    *
500    * You should override this function with your supported response types.
501    *
502    * @return
503    *   A list as below. If you support all authorization response types,
504    *   then you'd do:
505    * @code
506    * return array(
507    *   OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
508    *   OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
509    *   OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN,
510    * );
511    * @endcode
512    *
513    * @ingroup oauth2_section_3
514    */
515   protected function getSupportedAuthResponseTypes() {
516     return array(
517       OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
518       OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
519       OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN
520     );
521   }
522
523   /**
524    * Return supported scopes.
525    *
526    * If you want to support scope use, then have this function return a list
527    * of all acceptable scopes (used to throw the invalid-scope error).
528    *
529    * @return
530    *   A list as below, for example:
531    * @code
532    * return array(
533    *   'my-friends',
534    *   'photos',
535    *   'whatever-else',
536    * );
537    * @endcode
538    *
539    * @ingroup oauth2_section_3
540    */
541   protected function getSupportedScopes() {
542     return array();
543   }
544
545   /**
546    * Check restricted authorization response types of corresponding Client
547    * identifier.
548    *
549    * If you want to restrict clients to certain authorization response types,
550    * override this function.
551    *
552    * @param $client_id
553    *   Client identifier to be check with.
554    * @param $response_type
555    *   Authorization response type to be check with, would be one of the
556    *   values contained in OAUTH2_AUTH_RESPONSE_TYPE_REGEXP.
557    *
558    * @return
559    *   TRUE if the authorization response type is supported by this
560    *   client identifier, and FALSE if it isn't.
561    *
562    * @ingroup oauth2_section_3
563    */
564   protected function checkRestrictedAuthResponseType($client_id, $response_type) {
565     return TRUE;
566   }
567
568   /**
569    * Check restricted grant types of corresponding client identifier.
570    *
571    * If you want to restrict clients to certain grant types, override this
572    * function.
573    *
574    * @param $client_id
575    *   Client identifier to be check with.
576    * @param $grant_type
577    *   Grant type to be check with, would be one of the values contained in
578    *   OAUTH2_GRANT_TYPE_REGEXP.
579    *
580    * @return
581    *   TRUE if the grant type is supported by this client identifier, and
582    *   FALSE if it isn't.
583    *
584    * @ingroup oauth2_section_4
585    */
586   protected function checkRestrictedGrantType($client_id, $grant_type) {
587     return TRUE;
588   }
589
590   // Functions that help grant access tokens for various grant types.
591
592   /**
593    * Fetch authorization code data (probably the most common grant type).
594    *
595    * Retrieve the stored data for the given authorization code.
596    *
597    * Required for OAUTH2_GRANT_TYPE_AUTH_CODE.
598    *
599    * @param $code
600    *   Authorization code to be check with.
601    *
602    * @return
603    *   An associative array as below, and NULL if the code is invalid:
604    *   - client_id: Stored client identifier.
605    *   - redirect_uri: Stored redirect URI.
606    *   - expires: Stored expiration in unix timestamp.
607    *   - scope: (optional) Stored scope values in space-separated string.
608    *
609    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.1
610    *
611    * @ingroup oauth2_section_4
612    */
613   protected function getAuthCode($code) {
614     return NULL;
615   }
616
617   /**
618    * Take the provided authorization code values and store them somewhere.
619    *
620    * This function should be the storage counterpart to getAuthCode().
621    *
622    * If storage fails for some reason, we're not currently checking for
623    * any sort of success/failure, so you should bail out of the script
624    * and provide a descriptive fail message.
625    *
626    * Required for OAUTH2_GRANT_TYPE_AUTH_CODE.
627    *
628    * @param $code
629    *   Authorization code to be stored.
630    * @param $client_id
631    *   Client identifier to be stored.
632    * @param $redirect_uri
633    *   Redirect URI to be stored.
634    * @param $expires
635    *   Expiration to be stored.
636    * @param $scope
637    *   (optional) Scopes to be stored in space-separated string.
638    *
639    * @ingroup oauth2_section_4
640    */
641   protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {
642   }
643
644   /**
645    * Grant access tokens for basic user credentials.
646    *
647    * Check the supplied username and password for validity.
648    *
649    * You can also use the $client_id param to do any checks required based
650    * on a client, if you need that.
651    *
652    * Required for OAUTH2_GRANT_TYPE_USER_CREDENTIALS.
653    *
654    * @param $client_id
655    *   Client identifier to be check with.
656    * @param $username
657    *   Username to be check with.
658    * @param $password
659    *   Password to be check with.
660    *
661    * @return
662    *   TRUE if the username and password are valid, and FALSE if it isn't.
663    *   Moreover, if the username and password are valid, and you want to
664    *   verify the scope of a user's access, return an associative array
665    *   with the scope values as below. We'll check the scope you provide
666    *   against the requested scope before providing an access token:
667    * @code
668    * return array(
669    *   'scope' => <stored scope values (space-separated string)>,
670    * );
671    * @endcode
672    *
673    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.2
674    *
675    * @ingroup oauth2_section_4
676    */
677   protected function checkUserCredentials($client_id, $username, $password) {
678     return FALSE;
679   }
680
681   /**
682    * Grant access tokens for assertions.
683    *
684    * Check the supplied assertion for validity.
685    *
686    * You can also use the $client_id param to do any checks required based
687    * on a client, if you need that.
688    *
689    * Required for OAUTH2_GRANT_TYPE_ASSERTION.
690    *
691    * @param $client_id
692    *   Client identifier to be check with.
693    * @param $assertion_type
694    *   The format of the assertion as defined by the authorization server.
695    * @param $assertion
696    *   The assertion.
697    *
698    * @return
699    *   TRUE if the assertion is valid, and FALSE if it isn't. Moreover, if
700    *   the assertion is valid, and you want to verify the scope of an access
701    *   request, return an associative array with the scope values as below.
702    *   We'll check the scope you provide against the requested scope before
703    *   providing an access token:
704    * @code
705    * return array(
706    *   'scope' => <stored scope values (space-separated string)>,
707    * );
708    * @endcode
709    *
710    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
711    *
712    * @ingroup oauth2_section_4
713    */
714   protected function checkAssertion($client_id, $assertion_type, $assertion) {
715     return FALSE;
716   }
717
718   /**
719    * Grant refresh access tokens.
720    *
721    * Retrieve the stored data for the given refresh token.
722    *
723    * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN.
724    *
725    * @param $refresh_token
726    *   Refresh token to be check with.
727    *
728    * @return
729    *   An associative array as below, and NULL if the refresh_token is
730    *   invalid:
731    *   - client_id: Stored client identifier.
732    *   - expires: Stored expiration unix timestamp.
733    *   - scope: (optional) Stored scope values in space-separated string.
734    *
735    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.4
736    *
737    * @ingroup oauth2_section_4
738    */
739   protected function getRefreshToken($refresh_token) {
740     return NULL;
741   }
742
743   /**
744    * Take the provided refresh token values and store them somewhere.
745    *
746    * This function should be the storage counterpart to getRefreshToken().
747    *
748    * If storage fails for some reason, we're not currently checking for
749    * any sort of success/failure, so you should bail out of the script
750    * and provide a descriptive fail message.
751    *
752    * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN.
753    *
754    * @param $refresh_token
755    *   Refresh token to be stored.
756    * @param $client_id
757    *   Client identifier to be stored.
758    * @param $expires
759    *   expires to be stored.
760    * @param $scope
761    *   (optional) Scopes to be stored in space-separated string.
762    *
763    * @ingroup oauth2_section_4
764    */
765   protected function setRefreshToken($refresh_token, $client_id, $expires, $scope = NULL) {
766     return;
767   }
768
769   /**
770    * Expire a used refresh token.
771    *
772    * This is not explicitly required in the spec, but is almost implied.
773    * After granting a new refresh token, the old one is no longer useful and
774    * so should be forcibly expired in the data store so it can't be used again.
775    *
776    * If storage fails for some reason, we're not currently checking for
777    * any sort of success/failure, so you should bail out of the script
778    * and provide a descriptive fail message.
779    *
780    * @param $refresh_token
781    *   Refresh token to be expirse.
782    *
783    * @ingroup oauth2_section_4
784    */
785   protected function unsetRefreshToken($refresh_token) {
786     return;
787   }
788
789   /**
790    * Grant access tokens for the "none" grant type.
791    *
792    * Not really described in the IETF Draft, so I just left a method
793    * stub... Do whatever you want!
794    *
795    * Required for OAUTH2_GRANT_TYPE_NONE.
796    *
797    * @ingroup oauth2_section_4
798    */
799   protected function checkNoneAccess($client_id) {
800     return FALSE;
801   }
802
803   /**
804    * Get default authentication realm for WWW-Authenticate header.
805    *
806    * Change this to whatever authentication realm you want to send in a
807    * WWW-Authenticate header.
808    *
809    * @return
810    *   A string that you want to send in a WWW-Authenticate header.
811    *
812    * @ingroup oauth2_error
813    */
814   protected function getDefaultAuthenticationRealm() {
815     return "Service";
816   }
817
818   // End stuff that should get overridden.
819
820   /**
821    * Creates an OAuth2.0 server-side instance.
822    *
823    * @param $config
824    *   An associative array as below:
825    *   - access_token_lifetime: (optional) The lifetime of access token in
826    *     seconds.
827    *   - auth_code_lifetime: (optional) The lifetime of authorization code in
828    *     seconds.
829    *   - refresh_token_lifetime: (optional) The lifetime of refresh token in
830    *     seconds.
831    *   - display_error: (optional) Whether to show verbose error messages in
832    *     the response.
833    */
834   public function __construct($config = array()) {
835     foreach ($config as $name => $value) {
836       $this->setVariable($name, $value);
837     }
838   }
839
840   // Resource protecting (Section 5).
841
842   /**
843    * Check that a valid access token has been provided.
844    *
845    * The scope parameter defines any required scope that the token must have.
846    * If a scope param is provided and the token does not have the required
847    * scope, we bounce the request.
848    *
849    * Some implementations may choose to return a subset of the protected
850    * resource (i.e. "public" data) if the user has not provided an access
851    * token or if the access token is invalid or expired.
852    *
853    * The IETF spec says that we should send a 401 Unauthorized header and
854    * bail immediately so that's what the defaults are set to.
855    *
856    * @param $scope
857    *   A space-separated string of required scope(s), if you want to check
858    *   for scope.
859    * @param $exit_not_present
860    *   If TRUE and no access token is provided, send a 401 header and exit,
861    *   otherwise return FALSE.
862    * @param $exit_invalid
863    *   If TRUE and the implementation of getAccessToken() returns NULL, exit,
864    *   otherwise return FALSE.
865    * @param $exit_expired
866    *   If TRUE and the access token has expired, exit, otherwise return FALSE.
867    * @param $exit_scope
868    *   If TRUE the access token does not have the required scope(s), exit,
869    *   otherwise return FALSE.
870    * @param $realm
871    *   If you want to specify a particular realm for the WWW-Authenticate
872    *   header, supply it here.
873    *
874    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5
875    *
876    * @ingroup oauth2_section_5
877    */
878   public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) {
879     $token_param = $this->getAccessTokenParams();
880     if ($token_param === FALSE) // Access token was not provided
881       return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE;
882     // Get the stored token data (from the implementing subclass)
883     $token = $this->getAccessToken($token_param);
884     if ($token === NULL)
885       return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE;
886
887     // Check token expiration (I'm leaving this check separated, later we'll fill in better error messages)
888     if (isset($token["expires"]) && time() > $token["expires"])
889       return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE;
890
891     // Check scope, if provided
892     // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error
893     if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"])))
894       return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE;
895
896     return TRUE;
897   }
898
899   /**
900    * Check if everything in required scope is contained in available scope.
901    *
902    * @param $required_scope
903    *   Required scope to be check with.
904    * @param $available_scope
905    *   Available scope to be compare with.
906    *
907    * @return
908    *   TRUE if everything in required scope is contained in available scope,
909    *   and False if it isn't.
910    *
911    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5
912    *
913    * @ingroup oauth2_section_5
914    */
915   private function checkScope($required_scope, $available_scope) {
916     // The required scope should match or be a subset of the available scope
917     if (!is_array($required_scope))
918       $required_scope = explode(" ", $required_scope);
919
920     if (!is_array($available_scope))
921       $available_scope = explode(" ", $available_scope);
922
923     return (count(array_diff($required_scope, $available_scope)) == 0);
924   }
925
926   /**
927    * Pulls the access token out of the HTTP request.
928    *
929    * Either from the Authorization header or GET/POST/etc.
930    *
931    * @return
932    *   Access token value if present, and FALSE if it isn't.
933    *
934    * @todo Support PUT or DELETE.
935    *
936    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1
937    *
938    * @ingroup oauth2_section_5
939    */
940   private function getAccessTokenParams() {
941     $auth_header = $this->getAuthorizationHeader();
942
943     if ($auth_header !== FALSE) {
944       // Make sure only the auth header is set
945       if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME]) || isset($_POST[OAUTH2_TOKEN_PARAM_NAME]))
946         $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth token found in GET or POST when token present in header');
947
948       $auth_header = trim($auth_header);
949
950       // Make sure it's Token authorization
951       if (strcmp(substr($auth_header, 0, 5), "OAuth ") !== 0)
952         $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth header found that doesn\'t start with "OAuth"');
953
954       // Parse the rest of the header
955       if (preg_match('/\s*OAuth\s*="(.+)"/', substr($auth_header, 5), $matches) == 0 || count($matches) < 2)
956         $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Malformed auth header');
957
958       return $matches[1];
959     }
960
961     if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME])) {
962       if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) // Both GET and POST are not allowed
963         $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Only send the token in GET or POST, not both');
964
965       return $_GET[OAUTH2_TOKEN_PARAM_NAME];
966     }
967
968     if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME]))
969       return $_POST[OAUTH2_TOKEN_PARAM_NAME];
970
971     return FALSE;
972   }
973
974   // Access token granting (Section 4).
975
976   /**
977    * Grant or deny a requested access token.
978    *
979    * This would be called from the "/token" endpoint as defined in the spec.
980    * Obviously, you can call your endpoint whatever you want.
981    *
982    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4
983    *
984    * @ingroup oauth2_section_4
985    */
986   public function grantAccessToken() {
987     $filters = array(
988       "grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
989       "scope" => array("flags" => FILTER_REQUIRE_SCALAR),
990       "code" => array("flags" => FILTER_REQUIRE_SCALAR),
991       "redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
992       "username" => array("flags" => FILTER_REQUIRE_SCALAR),
993       "password" => array("flags" => FILTER_REQUIRE_SCALAR),
994       "assertion_type" => array("flags" => FILTER_REQUIRE_SCALAR),
995       "assertion" => array("flags" => FILTER_REQUIRE_SCALAR),
996       "refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR),
997     );
998
999     $input = filter_input_array(INPUT_POST, $filters);
1000
1001     // Grant Type must be specified.
1002     if (!$input["grant_type"])
1003       $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
1004
1005     // Make sure we've implemented the requested grant type
1006     if (!in_array($input["grant_type"], $this->getSupportedGrantTypes()))
1007       $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE);
1008
1009     // Authorize the client
1010     $client = $this->getClientCredentials();
1011
1012     if ($this->checkClientCredentials($client[0], $client[1]) === FALSE)
1013       $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
1014
1015     if (!$this->checkRestrictedGrantType($client[0], $input["grant_type"]))
1016       $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT);
1017
1018     // Do the granting
1019     switch ($input["grant_type"]) {
1020       case OAUTH2_GRANT_TYPE_AUTH_CODE:
1021         if (!$input["code"] || !$input["redirect_uri"])
1022           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
1023
1024         $stored = $this->getAuthCode($input["code"]);
1025
1026         // Ensure that the input uri starts with the stored uri
1027         if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"])
1028           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
1029
1030         if ($stored["expires"] < time())
1031           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);
1032
1033         break;
1034       case OAUTH2_GRANT_TYPE_USER_CREDENTIALS:
1035         if (!$input["username"] || !$input["password"])
1036           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required');
1037
1038         $stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]);
1039
1040         if ($stored === FALSE)
1041           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
1042
1043         break;
1044       case OAUTH2_GRANT_TYPE_ASSERTION:
1045         if (!$input["assertion_type"] || !$input["assertion"])
1046           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
1047
1048         $stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]);
1049
1050         if ($stored === FALSE)
1051           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
1052
1053         break;
1054       case OAUTH2_GRANT_TYPE_REFRESH_TOKEN:
1055         if (!$input["refresh_token"])
1056           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found');
1057
1058         $stored = $this->getRefreshToken($input["refresh_token"]);
1059
1060         if ($stored === NULL || $client[0] != $stored["client_id"])
1061           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT);
1062
1063         if ($stored["expires"] < time())
1064           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN);
1065
1066         // store the refresh token locally so we can delete it when a new refresh token is generated
1067         $this->setVariable('_old_refresh_token', $stored["token"]);
1068
1069         break;
1070       case OAUTH2_GRANT_TYPE_NONE:
1071         $stored = $this->checkNoneAccess($client[0]);
1072
1073         if ($stored === FALSE)
1074           $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST);
1075     }
1076
1077     // Check scope, if provided
1078     if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"])))
1079       $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE);
1080
1081     if (!$input["scope"])
1082       $input["scope"] = NULL;
1083
1084     $token = $this->createAccessToken($client[0], $input["scope"]);
1085
1086     $this->sendJsonHeaders();
1087     echo json_encode($token);
1088   }
1089
1090   /**
1091    * Internal function used to get the client credentials from HTTP basic
1092    * auth or POST data.
1093    *
1094    * @return
1095    *   A list containing the client identifier and password, for example
1096    * @code
1097    * return array(
1098    *   $_POST["client_id"],
1099    *   $_POST["client_secret"],
1100    * );
1101    * @endcode
1102    *
1103    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2
1104    *
1105    * @ingroup oauth2_section_2
1106    */
1107   protected function getClientCredentials() {
1108     if (isset($_SERVER["PHP_AUTH_USER"]) && $_POST && isset($_POST["client_id"]))
1109       $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
1110
1111     // Try basic auth
1112     if (isset($_SERVER["PHP_AUTH_USER"]))
1113       return array($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]);
1114
1115     // Try POST
1116     if ($_POST && isset($_POST["client_id"])) {
1117       if (isset($_POST["client_secret"]))
1118         return array($_POST["client_id"], $_POST["client_secret"]);
1119
1120       return array($_POST["client_id"], NULL);
1121     }
1122
1123     // No credentials were specified
1124     $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT);
1125   }
1126
1127   // End-user/client Authorization (Section 3 of IETF Draft).
1128
1129   /**
1130    * Pull the authorization request data out of the HTTP request.
1131    *
1132    * @return
1133    *   The authorization parameters so the authorization server can prompt
1134    *   the user for approval if valid.
1135    *
1136    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
1137    *
1138    * @ingroup oauth2_section_3
1139    */
1140   public function getAuthorizeParams() {
1141     $filters = array(
1142       "client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
1143       "response_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_AUTH_RESPONSE_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
1144       "redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
1145       "state" => array("flags" => FILTER_REQUIRE_SCALAR),
1146       "scope" => array("flags" => FILTER_REQUIRE_SCALAR),
1147     );
1148
1149     $input = filter_input_array(INPUT_GET, $filters);
1150
1151     // Make sure a valid client id was supplied
1152     if (!$input["client_id"]) {
1153       if ($input["redirect_uri"])
1154         $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]);
1155
1156       $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); // We don't have a good URI to use
1157     }
1158
1159     // redirect_uri is not required if already established via other channels
1160     // check an existing redirect URI against the one supplied
1161     $redirect_uri = $this->getRedirectUri($input["client_id"]);
1162
1163     // At least one of: existing redirect URI or input redirect URI must be specified
1164     if (!$redirect_uri && !$input["redirect_uri"])
1165       $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST);
1166
1167     // getRedirectUri() should return FALSE if the given client ID is invalid
1168     // this probably saves us from making a separate db call, and simplifies the method set
1169     if ($redirect_uri === FALSE)
1170       $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]);
1171
1172     // If there's an existing uri and one from input, verify that they match
1173     if ($redirect_uri && $input["redirect_uri"]) {
1174       // Ensure that the input uri starts with the stored uri
1175       if (strcasecmp(substr($input["redirect_uri"], 0, strlen($redirect_uri)), $redirect_uri) !== 0)
1176         $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]);
1177     }
1178     elseif ($redirect_uri) { // They did not provide a uri from input, so use the stored one
1179       $input["redirect_uri"] = $redirect_uri;
1180     }
1181
1182     // type and client_id are required
1183     if (!$input["response_type"])
1184       $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]);
1185
1186     // Check requested auth response type against the list of supported types
1187     if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE)
1188       $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]);
1189
1190     // Restrict clients to certain authorization response types
1191     if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE)
1192       $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]);
1193
1194     // Validate that the requested scope is supported
1195     if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes()))
1196       $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]);
1197
1198     return $input;
1199   }
1200
1201   /**
1202    * Redirect the user appropriately after approval.
1203    *
1204    * After the user has approved or denied the access request the
1205    * authorization server should call this function to redirect the user
1206    * appropriately.
1207    *
1208    * @param $is_authorized
1209    *   TRUE or FALSE depending on whether the user authorized the access.
1210    * @param $params
1211    *   An associative array as below:
1212    *   - response_type: The requested response: an access token, an
1213    *     authorization code, or both.
1214    *   - client_id: The client identifier as described in Section 2.
1215    *   - redirect_uri: An absolute URI to which the authorization server
1216    *     will redirect the user-agent to when the end-user authorization
1217    *     step is completed.
1218    *   - scope: (optional) The scope of the access request expressed as a
1219    *     list of space-delimited strings.
1220    *   - state: (optional) An opaque value used by the client to maintain
1221    *     state between the request and callback.
1222    *
1223    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3
1224    *
1225    * @ingroup oauth2_section_3
1226    */
1227   public function finishClientAuthorization($is_authorized, $params = array()) {
1228     $params += array(
1229       'scope' => NULL,
1230       'state' => NULL,
1231     );
1232     extract($params);
1233
1234     if ($state !== NULL)
1235       $result["query"]["state"] = $state;
1236
1237     if ($is_authorized === FALSE) {
1238       $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED;
1239     }
1240     else {
1241       if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN)
1242         $result["query"]["code"] = $this->createAuthCode($client_id, $redirect_uri, $scope);
1243
1244       if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN)
1245         $result["fragment"] = $this->createAccessToken($client_id, $scope);
1246     }
1247
1248     $this->doRedirectUriCallback($redirect_uri, $result);
1249   }
1250
1251   // Other/utility functions.
1252
1253   /**
1254    * Redirect the user agent.
1255    *
1256    * Handle both redirect for success or error response.
1257    *
1258    * @param $redirect_uri
1259    *   An absolute URI to which the authorization server will redirect
1260    *   the user-agent to when the end-user authorization step is completed.
1261    * @param $params
1262    *   Parameters to be pass though buildUri().
1263    *
1264    * @ingroup oauth2_section_3
1265    */
1266   private function doRedirectUriCallback($redirect_uri, $params) {
1267     header("HTTP/1.1 ". OAUTH2_HTTP_FOUND);
1268     header("Location: " . $this->buildUri($redirect_uri, $params));
1269     exit;
1270   }
1271
1272   /**
1273    * Build the absolute URI based on supplied URI and parameters.
1274    *
1275    * @param $uri
1276    *   An absolute URI.
1277    * @param $params
1278    *   Parameters to be append as GET.
1279    *
1280    * @return
1281    *   An absolute URI with supplied parameters.
1282    *
1283    * @ingroup oauth2_section_3
1284    */
1285   private function buildUri($uri, $params) {
1286     $parse_url = parse_url($uri);
1287
1288     // Add our params to the parsed uri
1289     foreach ($params as $k => $v) {
1290       if (isset($parse_url[$k]))
1291         $parse_url[$k] .= "&" . http_build_query($v);
1292       else
1293         $parse_url[$k] = http_build_query($v);
1294     }
1295
1296     // Put humpty dumpty back together
1297     return
1298       ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
1299       . ((isset($parse_url["user"])) ? $parse_url["user"] . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
1300       . ((isset($parse_url["host"])) ? $parse_url["host"] : "")
1301       . ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
1302       . ((isset($parse_url["path"])) ? $parse_url["path"] : "")
1303       . ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "")
1304       . ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "");
1305   }
1306
1307   /**
1308    * Handle the creation of access token, also issue refresh token if support.
1309    *
1310    * This belongs in a separate factory, but to keep it simple, I'm just
1311    * keeping it here.
1312    *
1313    * @param $client_id
1314    *   Client identifier related to the access token.
1315    * @param $scope
1316    *   (optional) Scopes to be stored in space-separated string.
1317    *
1318    * @ingroup oauth2_section_4
1319    */
1320   protected function createAccessToken($client_id, $scope = NULL) {
1321     $token = array(
1322       "access_token" => $this->genAccessToken(),
1323       "expires_in" => $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME),
1324       "scope" => $scope
1325     );
1326
1327     $this->setAccessToken($token["access_token"], $client_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope);
1328
1329     // Issue a refresh token also, if we support them
1330     if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) {
1331       $token["refresh_token"] = $this->genAccessToken();
1332       $this->setRefreshToken($token["refresh_token"], $client_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope);
1333       // If we've granted a new refresh token, expire the old one
1334       if ($this->getVariable('_old_refresh_token'))
1335         $this->unsetRefreshToken($this->getVariable('_old_refresh_token'));
1336     }
1337
1338     return $token;
1339   }
1340
1341   /**
1342    * Handle the creation of auth code.
1343    *
1344    * This belongs in a separate factory, but to keep it simple, I'm just
1345    * keeping it here.
1346    *
1347    * @param $client_id
1348    *   Client identifier related to the access token.
1349    * @param $redirect_uri
1350    *   An absolute URI to which the authorization server will redirect the
1351    *   user-agent to when the end-user authorization step is completed.
1352    * @param $scope
1353    *   (optional) Scopes to be stored in space-separated string.
1354    *
1355    * @ingroup oauth2_section_3
1356    */
1357   private function createAuthCode($client_id, $redirect_uri, $scope = NULL) {
1358     $code = $this->genAuthCode();
1359     $this->setAuthCode($code, $client_id, $redirect_uri, time() + $this->getVariable('auth_code_lifetime', OAUTH2_DEFAULT_AUTH_CODE_LIFETIME), $scope);
1360     return $code;
1361   }
1362
1363   /**
1364    * Generate unique access token.
1365    *
1366    * Implementing classes may want to override these function to implement
1367    * other access token or auth code generation schemes.
1368    *
1369    * @return
1370    *   An unique access token.
1371    *
1372    * @ingroup oauth2_section_4
1373    */
1374   protected function genAccessToken() {
1375     return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
1376   }
1377
1378   /**
1379    * Generate unique auth code.
1380    *
1381    * Implementing classes may want to override these function to implement
1382    * other access token or auth code generation schemes.
1383    *
1384    * @return
1385    *   An unique auth code.
1386    *
1387    * @ingroup oauth2_section_3
1388    */
1389   protected function genAuthCode() {
1390     return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
1391   }
1392
1393   /**
1394    * Pull out the Authorization HTTP header and return it.
1395    *
1396    * Implementing classes may need to override this function for use on
1397    * non-Apache web servers.
1398    *
1399    * @return
1400    *   The Authorization HTTP header, and FALSE if does not exist.
1401    *
1402    * @todo Handle Authorization HTTP header for non-Apache web servers.
1403    *
1404    * @ingroup oauth2_section_5
1405    */
1406   private function getAuthorizationHeader() {
1407     if (array_key_exists("HTTP_AUTHORIZATION", $_SERVER))
1408       return $_SERVER["HTTP_AUTHORIZATION"];
1409
1410     if (function_exists("apache_request_headers")) {
1411       $headers = apache_request_headers();
1412
1413       if (array_key_exists("Authorization", $headers))
1414         return $headers["Authorization"];
1415     }
1416
1417     return FALSE;
1418   }
1419
1420   /**
1421    * Send out HTTP headers for JSON.
1422    *
1423    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.2
1424    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
1425    *
1426    * @ingroup oauth2_section_4
1427    */
1428   private function sendJsonHeaders() {
1429     header("Content-Type: application/json");
1430     header("Cache-Control: no-store");
1431   }
1432
1433   /**
1434    * Redirect the end-user's user agent with error message.
1435    *
1436    * @param $redirect_uri
1437    *   An absolute URI to which the authorization server will redirect the
1438    *   user-agent to when the end-user authorization step is completed.
1439    * @param $error
1440    *   A single error code as described in Section 3.2.1.
1441    * @param $error_description
1442    *   (optional) A human-readable text providing additional information,
1443    *   used to assist in the understanding and resolution of the error
1444    *   occurred.
1445    * @param $error_uri
1446    *   (optional) A URI identifying a human-readable web page with
1447    *   information about the error, used to provide the end-user with
1448    *   additional information about the error.
1449    * @param $state
1450    *   (optional) REQUIRED if the "state" parameter was present in the client
1451    *   authorization request. Set to the exact value received from the client.
1452    *
1453    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2
1454    *
1455    * @ingroup oauth2_error
1456    */
1457   private function errorDoRedirectUriCallback($redirect_uri, $error, $error_description = NULL, $error_uri = NULL, $state = NULL) {
1458     $result["query"]["error"] = $error;
1459
1460     if ($state)
1461       $result["query"]["state"] = $state;
1462
1463     if ($this->getVariable('display_error') && $error_description)
1464       $result["query"]["error_description"] = $error_description;
1465
1466     if ($this->getVariable('display_error') && $error_uri)
1467       $result["query"]["error_uri"] = $error_uri;
1468
1469     $this->doRedirectUriCallback($redirect_uri, $result);
1470   }
1471
1472   /**
1473    * Send out error message in JSON.
1474    *
1475    * @param $http_status_code
1476    *   HTTP status code message as predefined.
1477    * @param $error
1478    *   A single error code.
1479    * @param $error_description
1480    *   (optional) A human-readable text providing additional information,
1481    *   used to assist in the understanding and resolution of the error
1482    *   occurred.
1483    * @param $error_uri
1484    *   (optional) A URI identifying a human-readable web page with
1485    *   information about the error, used to provide the end-user with
1486    *   additional information about the error.
1487    *
1488    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3
1489    *
1490    * @ingroup oauth2_error
1491    */
1492   private function errorJsonResponse($http_status_code, $error, $error_description = NULL, $error_uri = NULL) {
1493     $result['error'] = $error;
1494
1495     if ($this->getVariable('display_error') && $error_description)
1496       $result["error_description"] = $error_description;
1497
1498     if ($this->getVariable('display_error') && $error_uri)
1499       $result["error_uri"] = $error_uri;
1500
1501     header("HTTP/1.1 " . $http_status_code);
1502     $this->sendJsonHeaders();
1503     echo json_encode($result);
1504
1505     exit;
1506   }
1507
1508   /**
1509    * Send a 401 unauthorized header with the given realm and an error, if
1510    * provided.
1511    *
1512    * @param $http_status_code
1513    *   HTTP status code message as predefined.
1514    * @param $realm
1515    *   The "realm" attribute is used to provide the protected resources
1516    *   partition as defined by [RFC2617].
1517    * @param $scope
1518    *   A space-delimited list of scope values indicating the required scope
1519    *   of the access token for accessing the requested resource.
1520    * @param $error
1521    *   The "error" attribute is used to provide the client with the reason
1522    *   why the access request was declined.
1523    * @param $error_description
1524    *   (optional) The "error_description" attribute provides a human-readable text
1525    *   containing additional information, used to assist in the understanding
1526    *   and resolution of the error occurred.
1527    * @param $error_uri
1528    *   (optional) The "error_uri" attribute provides a URI identifying a human-readable
1529    *   web page with information about the error, used to offer the end-user
1530    *   with additional information about the error. If the value is not an
1531    *   absolute URI, it is relative to the URI of the requested protected
1532    *   resource.
1533    *
1534    * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2
1535    *
1536    * @ingroup oauth2_error
1537    */
1538   private function errorWWWAuthenticateResponseHeader($http_status_code, $realm, $error, $error_description = NULL, $error_uri = NULL, $scope = NULL) {
1539     $realm = $realm === NULL ? $this->getDefaultAuthenticationRealm() : $realm;
1540
1541     $result = "WWW-Authenticate: OAuth realm='" . $realm . "'";
1542
1543     if ($error)
1544       $result .= ", error='" . $error . "'";
1545
1546     if ($this->getVariable('display_error') && $error_description)
1547       $result .= ", error_description='" . $error_description . "'";
1548
1549     if ($this->getVariable('display_error') && $error_uri)
1550       $result .= ", error_uri='" . $error_uri . "'";
1551
1552     if ($scope)
1553       $result .= ", scope='" . $scope . "'";
1554
1555     header("HTTP/1.1 ". $http_status_code);
1556     header($result);
1557
1558     exit;
1559   }
1560 }