| <?php | |
| use Symfony\Component\HttpFoundation\Request; | |
| use Symfony\Component\HttpFoundation\Response; | |
| 
 | |
| class Token { | |
| 
 | |
|   public $http; | |
|   private $_pretty = false; | |
| 
 | |
|   public function __construct() { | |
|     $this->http = new p3k\HTTP(); | |
|   } | |
| 
 | |
|   public function token(Request $request, Response $response) { | |
| 
 | |
|     if($request->get('pretty')) { | |
|       $this->_pretty = true; | |
|     } | |
| 
 | |
|     $source = $request->get('source'); | |
|     $code = $request->get('code'); | |
| 
 | |
|     if(!$source) { | |
|       return $this->respond($response, 400, [ | |
|         'error' => 'invalid_request', | |
|         'error_description' => 'Provide a source URL' | |
|       ]); | |
|     } | |
| 
 | |
|     if(!$code) { | |
|       return $this->respond($response, 400, [ | |
|         'error' => 'invalid_request', | |
|         'error_description' => 'Provide an authorization code' | |
|       ]); | |
|     } | |
| 
 | |
|     $scheme = parse_url($source, PHP_URL_SCHEME); | |
|     if(!in_array($scheme, ['http','https'])) { | |
|       return $this->respond($response, 400, [ | |
|         'error' => 'invalid_url', | |
|         'error_description' => 'Only http and https URLs are supported' | |
|       ]); | |
|     } | |
| 
 | |
|     // First try to discover the token endpoint | |
|     $head = $this->http->head($source); | |
| 
 | |
|     if(!array_key_exists('Link', $head['headers'])) { | |
|       return $this->respond($response, 200, [ | |
|         'error' => 'no_token_endpoint', | |
|         'error_description' => 'No Link headers were returned' | |
|       ]); | |
|     } | |
| 
 | |
|     if(is_string($head['headers']['Link'])) | |
|       $head['headers']['Link'] = [$head['headers']['Link']]; | |
| 
 | |
|     $rels = p3k\HTTP::link_rels($head['headers']); | |
| 
 | |
|     $endpoint = false; | |
|     if(array_key_exists('token_endpoint', $rels)) { | |
|       $endpoint = $rels['token_endpoint'][0]; | |
|     } elseif(array_key_exists('oauth2-token', $rels)) { | |
|       $endpoint = $rels['oauth2-token'][0]; | |
|     } | |
| 
 | |
|     if(!$endpoint) { | |
|       return $this->respond($response, 200, [ | |
|         'error' => 'no_token_endpoint', | |
|         'error_description' => 'No token endpoint was found in the headers' | |
|       ]); | |
|     } | |
| 
 | |
|     // Resolve the endpoint URL relative to the source URL | |
|     $endpoint = \mf2\resolveUrl($source, $endpoint); | |
| 
 | |
|     // Now exchange the code for a token | |
|     $token = $this->http->post($endpoint, [ | |
|       'grant_type' => 'authorization_code', | |
|       'code' => $code | |
|     ]); | |
| 
 | |
|     // Catch HTTP errors here such as timeouts | |
|     if($token['error']) { | |
|       return $this->respond($response, 200, [ | |
|         'error' => $token['error'], | |
|         'error_description' => $token['error_description'] ?: 'An unknown error occurred trying to fetch the token' | |
|       ]); | |
|     } | |
| 
 | |
|     // Otherwise pass through the response from the token endpoint | |
|     $body = @json_decode($token['body']); | |
| 
 | |
|     // Pass through the content type if we were not able to decode the response as JSON | |
|     $headers = []; | |
|     if(!$body && isset($token['headers']['Content-Type'])) { | |
|       $headers['Content-Type'] = $token['headers']['Content-Type']; | |
|     } | |
| 
 | |
|     return $this->respond($response, 200, $body ?: $token['body'], $headers); | |
|   } | |
| 
 | |
|   private function respond(Response $response, $code, $params, $headers=[]) { | |
|     $response->setStatusCode($code); | |
|     foreach($headers as $k=>$v) { | |
|       $response->headers->set($k, $v); | |
|     } | |
|     if(is_array($params) || is_object($params)) { | |
|       $response->headers->set('Content-Type', 'application/json'); | |
|       $opts = JSON_UNESCAPED_SLASHES; | |
|       if($this->_pretty) $opts += JSON_PRETTY_PRINT; | |
|       $response->setContent(json_encode($params, $opts)."\n"); | |
|     } else { | |
|       $response->setContent($params); | |
|     } | |
|     return $response; | |
|   } | |
| 
 | |
| }
 |