AccessToken.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. namespace OAuth2\ResponseType;
  3. use OAuth2\Storage\AccessTokenInterface as AccessTokenStorageInterface;
  4. use OAuth2\Storage\RefreshTokenInterface;
  5. use RuntimeException;
  6. /**
  7. * @author Brent Shaffer <bshafs at gmail dot com>
  8. */
  9. class AccessToken implements AccessTokenInterface
  10. {
  11. /**
  12. * @var AccessTokenInterface
  13. */
  14. protected $tokenStorage;
  15. /**
  16. * @var RefreshTokenInterface
  17. */
  18. protected $refreshStorage;
  19. /**
  20. * @var array
  21. */
  22. protected $config;
  23. /**
  24. * @param AccessTokenStorageInterface $tokenStorage - REQUIRED Storage class for saving access token information
  25. * @param RefreshTokenInterface $refreshStorage - OPTIONAL Storage class for saving refresh token information
  26. * @param array $config - OPTIONAL Configuration options for the server
  27. * @code
  28. * $config = array(
  29. * 'token_type' => 'bearer', // token type identifier
  30. * 'access_lifetime' => 3600, // time before access token expires
  31. * 'refresh_token_lifetime' => 1209600, // time before refresh token expires
  32. * );
  33. * @endcode
  34. */
  35. public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array())
  36. {
  37. $this->tokenStorage = $tokenStorage;
  38. $this->refreshStorage = $refreshStorage;
  39. $this->config = array_merge(array(
  40. 'token_type' => 'bearer',
  41. 'access_lifetime' => 3600,
  42. 'refresh_token_lifetime' => 1209600,
  43. ), $config);
  44. }
  45. /**
  46. * Get authorize response
  47. *
  48. * @param array $params
  49. * @param mixed $user_id
  50. * @return array
  51. */
  52. public function getAuthorizeResponse($params, $user_id = null)
  53. {
  54. // build the URL to redirect to
  55. $result = array('query' => array());
  56. $params += array('scope' => null, 'state' => null);
  57. /*
  58. * a refresh token MUST NOT be included in the fragment
  59. *
  60. * @see http://tools.ietf.org/html/rfc6749#section-4.2.2
  61. */
  62. $includeRefreshToken = false;
  63. $result["fragment"] = $this->createAccessToken($params['client_id'], $user_id, $params['scope'], $includeRefreshToken);
  64. if (isset($params['state'])) {
  65. $result["fragment"]["state"] = $params['state'];
  66. }
  67. return array($params['redirect_uri'], $result);
  68. }
  69. /**
  70. * Handle the creation of access token, also issue refresh token if supported / desirable.
  71. *
  72. * @param mixed $client_id - client identifier related to the access token.
  73. * @param mixed $user_id - user ID associated with the access token
  74. * @param string $scope - OPTIONAL scopes to be stored in space-separated string.
  75. * @param bool $includeRefreshToken - if true, a new refresh_token will be added to the response
  76. * @return array
  77. *
  78. * @see http://tools.ietf.org/html/rfc6749#section-5
  79. * @ingroup oauth2_section_5
  80. */
  81. public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
  82. {
  83. $token = array(
  84. "access_token" => $this->generateAccessToken(),
  85. "expires_in" => $this->config['access_lifetime'],
  86. "token_type" => $this->config['token_type'],
  87. "scope" => $scope
  88. );
  89. $this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
  90. /*
  91. * Issue a refresh token also, if we support them
  92. *
  93. * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
  94. * is supplied in the constructor
  95. */
  96. if ($includeRefreshToken && $this->refreshStorage) {
  97. $token["refresh_token"] = $this->generateRefreshToken();
  98. $expires = 0;
  99. if ($this->config['refresh_token_lifetime'] > 0) {
  100. $expires = time() + $this->config['refresh_token_lifetime'];
  101. }
  102. $this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id, $expires, $scope);
  103. }
  104. return $token;
  105. }
  106. /**
  107. * Generates an unique access token.
  108. *
  109. * Implementing classes may want to override this function to implement
  110. * other access token generation schemes.
  111. *
  112. * @return string - A unique access token.
  113. *
  114. * @ingroup oauth2_section_4
  115. */
  116. protected function generateAccessToken()
  117. {
  118. if (function_exists('random_bytes')) {
  119. $randomData = random_bytes(20);
  120. if ($randomData !== false && strlen($randomData) === 20) {
  121. return bin2hex($randomData);
  122. }
  123. }
  124. if (function_exists('openssl_random_pseudo_bytes')) {
  125. $randomData = openssl_random_pseudo_bytes(20);
  126. if ($randomData !== false && strlen($randomData) === 20) {
  127. return bin2hex($randomData);
  128. }
  129. }
  130. if (function_exists('mcrypt_create_iv')) {
  131. $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
  132. if ($randomData !== false && strlen($randomData) === 20) {
  133. return bin2hex($randomData);
  134. }
  135. }
  136. if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
  137. $randomData = file_get_contents('/dev/urandom', false, null, 0, 20);
  138. if ($randomData !== false && strlen($randomData) === 20) {
  139. return bin2hex($randomData);
  140. }
  141. }
  142. // Last resort which you probably should just get rid of:
  143. $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
  144. return substr(hash('sha512', $randomData), 0, 40);
  145. }
  146. /**
  147. * Generates an unique refresh token
  148. *
  149. * Implementing classes may want to override this function to implement
  150. * other refresh token generation schemes.
  151. *
  152. * @return string - A unique refresh token.
  153. *
  154. * @ingroup oauth2_section_4
  155. * @see OAuth2::generateAccessToken()
  156. */
  157. protected function generateRefreshToken()
  158. {
  159. return $this->generateAccessToken(); // let's reuse the same scheme for token generation
  160. }
  161. /**
  162. * Handle the revoking of refresh tokens, and access tokens if supported / desirable
  163. * RFC7009 specifies that "If the server is unable to locate the token using
  164. * the given hint, it MUST extend its search across all of its supported token types"
  165. *
  166. * @param $token
  167. * @param null $tokenTypeHint
  168. * @throws RuntimeException
  169. * @return boolean
  170. */
  171. public function revokeToken($token, $tokenTypeHint = null)
  172. {
  173. if ($tokenTypeHint == 'refresh_token') {
  174. if ($this->refreshStorage && $revoked = $this->refreshStorage->unsetRefreshToken($token)) {
  175. return true;
  176. }
  177. }
  178. /** @TODO remove in v2 */
  179. if (!method_exists($this->tokenStorage, 'unsetAccessToken')) {
  180. throw new RuntimeException(
  181. sprintf('Token storage %s must implement unsetAccessToken method', get_class($this->tokenStorage)
  182. ));
  183. }
  184. $revoked = $this->tokenStorage->unsetAccessToken($token);
  185. // if a typehint is supplied and fails, try other storages
  186. // @see https://tools.ietf.org/html/rfc7009#section-2.1
  187. if (!$revoked && $tokenTypeHint != 'refresh_token') {
  188. if ($this->refreshStorage) {
  189. $revoked = $this->refreshStorage->unsetRefreshToken($token);
  190. }
  191. }
  192. return $revoked;
  193. }
  194. }