You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.4 KiB

  1. <?php
  2. use Symfony\Component\HttpFoundation\Request;
  3. use Symfony\Component\HttpFoundation\Response;
  4. class Token {
  5. public $http;
  6. private $_pretty = false;
  7. public function __construct() {
  8. $this->http = new p3k\HTTP();
  9. }
  10. public function token(Request $request, Response $response) {
  11. if($request->get('pretty')) {
  12. $this->_pretty = true;
  13. }
  14. $source = $request->get('source');
  15. $code = $request->get('code');
  16. if(!$source) {
  17. return $this->respond($response, 400, [
  18. 'error' => 'invalid_request',
  19. 'error_description' => 'Provide a source URL'
  20. ]);
  21. }
  22. if(!$code) {
  23. return $this->respond($response, 400, [
  24. 'error' => 'invalid_request',
  25. 'error_description' => 'Provide an authorization code'
  26. ]);
  27. }
  28. $scheme = parse_url($source, PHP_URL_SCHEME);
  29. if(!in_array($scheme, ['http','https'])) {
  30. return $this->respond($response, 400, [
  31. 'error' => 'invalid_url',
  32. 'error_description' => 'Only http and https URLs are supported'
  33. ]);
  34. }
  35. // First try to discover the token endpoint
  36. $head = $this->http->head($source);
  37. if(!array_key_exists('Link', $head['headers'])) {
  38. return $this->respond($response, 200, [
  39. 'error' => 'no_token_endpoint',
  40. 'error_description' => 'No Link headers were returned'
  41. ]);
  42. }
  43. if(is_string($head['headers']['Link']))
  44. $head['headers']['Link'] = [$head['headers']['Link']];
  45. $rels = p3k\HTTP::link_rels($head['headers']);
  46. $endpoint = false;
  47. if(array_key_exists('token_endpoint', $rels)) {
  48. $endpoint = $rels['token_endpoint'][0];
  49. } elseif(array_key_exists('oauth2-token', $rels)) {
  50. $endpoint = $rels['oauth2-token'][0];
  51. }
  52. if(!$endpoint) {
  53. return $this->respond($response, 200, [
  54. 'error' => 'no_token_endpoint',
  55. 'error_description' => 'No token endpoint was found in the headers'
  56. ]);
  57. }
  58. // Resolve the endpoint URL relative to the source URL
  59. $endpoint = \mf2\resolveUrl($source, $endpoint);
  60. // Now exchange the code for a token
  61. $token = $this->http->post($endpoint, [
  62. 'grant_type' => 'authorization_code',
  63. 'code' => $code
  64. ]);
  65. // Catch HTTP errors here such as timeouts
  66. if($token['error']) {
  67. return $this->respond($response, 400, [
  68. 'error' => $token['error'],
  69. 'error_description' => $token['error_description'] ?: 'An unknown error occurred trying to fetch the token'
  70. ]);
  71. }
  72. // Otherwise pass through the response from the token endpoint
  73. $body = @json_decode($token['body']);
  74. // Pass through the content type if we were not able to decode the response as JSON
  75. $headers = [];
  76. if(!$body && isset($token['headers']['Content-Type'])) {
  77. $headers['Content-Type'] = $token['headers']['Content-Type'];
  78. }
  79. return $this->respond($response, $token['code'], $body ?: $token['body'], $headers);
  80. }
  81. private function respond(Response $response, $code, $params, $headers=[]) {
  82. $response->setStatusCode($code);
  83. foreach($headers as $k=>$v) {
  84. $response->headers->set($k, $v);
  85. }
  86. if(is_array($params) || is_object($params)) {
  87. $response->headers->set('Content-Type', 'application/json');
  88. $opts = JSON_UNESCAPED_SLASHES;
  89. if($this->_pretty) $opts += JSON_PRETTY_PRINT;
  90. $response->setContent(json_encode($params, $opts)."\n");
  91. } else {
  92. $response->setContent($params);
  93. }
  94. return $response;
  95. }
  96. }