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, 400, [ '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, $token['code'], $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; } }