From 565d50b862565f2ac9b6b214b206a69fdff7cb1a Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Thu, 20 Oct 2016 10:36:43 -0700 Subject: [PATCH] add token fetching and authentication for posts --- README.md | 66 ++++++-- composer.json | 4 +- composer.lock | 50 +++++- controllers/Parse.php | 27 ++- controllers/Token.php | 119 +++++++++++++ lib/HTTP.php | 21 ++- lib/HTTPCurl.php | 9 +- lib/HTTPStream.php | 10 +- lib/HTTPTest.php | 6 +- public/index.php | 1 + tests/TokenTest.php | 156 ++++++++++++++++++ tests/data/private.example.com/multiple-rels | 9 + .../data/private.example.com/no-link-headers | 7 + .../no-token-endpoint-one-link-header | 8 + .../no-token-endpoint-two-link-headers | 9 + .../private.example.com/oauth2-token-endpoint | 8 + tests/data/private.example.com/token | 11 ++ tests/data/private.example.com/token-endpoint | 8 + .../token-endpoint-bad-response | 8 + .../token-endpoint-notjson | 8 + .../token-endpoint-timeout | 8 + tests/data/private.example.com/token-invalid | 9 + tests/data/private.example.com/token-notjson | 7 + tests/data/private.example.com/token-timeout | 4 + 24 files changed, 543 insertions(+), 30 deletions(-) create mode 100644 controllers/Token.php create mode 100644 tests/TokenTest.php create mode 100644 tests/data/private.example.com/multiple-rels create mode 100644 tests/data/private.example.com/no-link-headers create mode 100644 tests/data/private.example.com/no-token-endpoint-one-link-header create mode 100644 tests/data/private.example.com/no-token-endpoint-two-link-headers create mode 100644 tests/data/private.example.com/oauth2-token-endpoint create mode 100644 tests/data/private.example.com/token create mode 100644 tests/data/private.example.com/token-endpoint create mode 100644 tests/data/private.example.com/token-endpoint-bad-response create mode 100644 tests/data/private.example.com/token-endpoint-notjson create mode 100644 tests/data/private.example.com/token-endpoint-timeout create mode 100644 tests/data/private.example.com/token-invalid create mode 100644 tests/data/private.example.com/token-notjson create mode 100644 tests/data/private.example.com/token-timeout diff --git a/README.md b/README.md index 3aa629a..344a6c1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The contents of the URL is checked in the following order: * OGP (coming soon) -## API +## Parse API To parse a page and return structured data for the contents of the page, simply pass a url to the parse route. @@ -23,11 +23,25 @@ GET /parse?url=https://aaronparecki.com/2016/01/16/11/ To conditionally parse the page after first checking if it contains a link to a target URL, also include the target URL as a parameter. This is useful if using XRay to verify an incoming webmention. ``` -GET /parse?url=https://aaronparecki.com/2016/01/16/11/&target=http://poetica.com +GET /parse?url=https://aaronparecki.com/2016/01/16/11/&target=http://example.com ``` In both cases, the response will be a JSON object containing a key of "type". If there was an error, "type" will be set to the string "error", otherwise it will refer to the kind of content that was found at the URL, most often "entry". +You can also make a POST request with the same parameter names. + +### Authentication + +If the URL you are fetching requires authentication, include the access token in the parameter "token", and it will be included in an "Authorization" header when fetching the URL. (It is recommended to use a POST request in this case, to avoid the access token potentially being logged as part of the query string.) + +``` +POST /parse + +url=https://aaronparecki.com/2016/01/16/11/ +&target=http://example.com +&token=12341234123412341234 +``` + ### Error Response ```json @@ -37,17 +51,19 @@ In both cases, the response will be a JSON object containing a key of "type". If } ``` -Other possible errors are listed below: +Possible errors are listed below: -* not_found: The URL provided was not found. (Returned 404 when fetching) -* ssl_cert_error: There was an error validating the SSL certificate. This may happen if the SSL certificate has expired. -* ssl_unsupported_cipher: The web server does not support any of the SSL ciphers known by the service. -* timeout: The service timed out trying to connect to the URL. -* invalid_content: The content at the URL was not valid. For example, providing a URL to an image will return this error. -* no_link_found: The target link was not found on the page. When a target parameter is provided, this is the error that will be returned if the target could not be found on the page. -* no_content: No usable content could be found at the given URL. +* `not_found`: The URL provided was not found. (Returned 404 when fetching) +* `ssl_cert_error`: There was an error validating the SSL certificate. This may happen if the SSL certificate has expired. +* `ssl_unsupported_cipher`: The web server does not support any of the SSL ciphers known by the service. +* `timeout`: The service timed out trying to connect to the URL. +* `invalid_content`: The content at the URL was not valid. For example, providing a URL to an image will return this error. +* `no_link_found`: The target link was not found on the page. When a target parameter is provided, this is the error that will be returned if the target could not be found on the page. +* `no_content`: No usable content could be found at the given URL. +* `unauthorized`: The URL returned HTTP 401 Unauthorized. +* `forbidden`: The URL returned HTTP 403 Forbidden. -## Response Format +### Response Format ```json { @@ -128,3 +144,31 @@ In a future version, replies, likes, reposts, etc. of this post will be included ``` +## Token API + +When verifying [Private Webmentions](https://indieweb.org/Private-Webmention#How_to_Receive_Private_Webmentions), you will need to exchange a code for an access token at the token endpoint specified by the source URL. + +XRay provides an API that will do this in one step. You can provide the source URL and code you got from the webmention, and XRay will discover the token endpoint, and then return you an access token. + +``` +POST /token + +source=http://example.com/private-post +&code=1234567812345678 +``` + +The response will be the response from the token endpoint, which will include an `access_token` property, and possibly an `expires_in` property. + +``` +{ + "access_token": "eyJ0eXAXBlIjoI6Imh0dHB8idGFyZ2V0IjoraW0uZGV2bb-ZO6MV-DIqbUn_3LZs", + "token_type": "bearer", + "expires_in": 3600 +} +``` + +If there was a problem fetching the access token, you will get one of the errors below in addition to the HTTP related errors returned by the parse API: + +* `no_token_endpoint` - Unable to find an HTTP header specifying the token endpoint. + + diff --git a/composer.json b/composer.json index 62ccfca..b1296a5 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,8 @@ "league/plates": "3.*", "league/route": "1.*", "mf2/mf2": "~0.3", - "ezyang/htmlpurifier": "4.*" + "ezyang/htmlpurifier": "4.*", + "indieweb/link-rel-parser": "0.1.*" }, "autoload": { "files": [ @@ -11,6 +12,7 @@ "lib/helpers.php", "controllers/Main.php", "controllers/Parse.php", + "controllers/Token.php", "lib/HTTPCurl.php", "lib/HTTPStream.php", "lib/HTTP.php", diff --git a/composer.lock b/composer.lock index 42bf683..a4b3a4c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "76c19995b1b2f77725f5a3cc13022004", - "content-hash": "010ed2acde68ceaa42fb406d3b9bc80e", + "hash": "2b5910cbc964bd8545d6a4737d319e5a", + "content-hash": "9dd49de07b7077eb937199147a258b4b", "packages": [ { "name": "ezyang/htmlpurifier", @@ -51,6 +51,52 @@ ], "time": "2015-08-05 01:03:42" }, + { + "name": "indieweb/link-rel-parser", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/indieweb/link-rel-parser-php.git", + "reference": "7127a92f32cecbf8166fb3b34130a59ea63e2041" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/indieweb/link-rel-parser-php/zipball/7127a92f32cecbf8166fb3b34130a59ea63e2041", + "reference": "7127a92f32cecbf8166fb3b34130a59ea63e2041", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/IndieWeb/link_rel_parser.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Aaron Parecki", + "homepage": "http://aaronparecki.com" + }, + { + "name": "Tantek Çelik", + "homepage": "http://tantek.com" + } + ], + "description": "Parse rel values from HTTP headers", + "homepage": "https://github.com/indieweb/link-rel-parser-php", + "keywords": [ + "http", + "indieweb", + "microformats2" + ], + "time": "2016-04-13 17:48:59" + }, { "name": "ircmaxell/password-compat", "version": "v1.0.4", diff --git a/controllers/Parse.php b/controllers/Parse.php index 58c340b..c6ce4af 100644 --- a/controllers/Parse.php +++ b/controllers/Parse.php @@ -8,7 +8,7 @@ class Parse { public $http; public $mc; - private $_cacheTime = 300; + private $_cacheTime = 120; private $_pretty = false; public function __construct() { @@ -91,7 +91,8 @@ class Parse { $url = \normalize_url($url); // Now fetch the URL and check for any curl errors - if($this->mc) { + // Don't cache the response if a token is used to fetch it + if($this->mc && !$request->get('token')) { $cacheKey = 'xray-'.md5($url); if($cached=$this->mc->get($cacheKey)) { $result = json_decode($cached, true); @@ -104,7 +105,12 @@ class Parse { $this->mc->set($cacheKey, $cacheData, MEMCACHE_COMPRESSED, $this->_cacheTime); } } else { - $result = $this->http->get($url); + $headers = []; + if($request->get('token')) { + $headers[] = 'Authorization: Bearer ' . $request->get('token'); + } + + $result = $this->http->get($url, $headers); } if($result['error']) { @@ -120,6 +126,21 @@ class Parse { 'error_description' => 'We did not get a response body when fetching the URL' ]); } + + // Check for HTTP 401/403 + if($result['code'] == 401) { + return $this->respond($response, 200, [ + 'error' => 'unauthorized', + 'error_description' => 'The URL returned "HTTP 401 Unauthorized"', + ]); + } + if($result['code'] == 403) { + return $this->respond($response, 200, [ + 'error' => 'forbidden', + 'error_description' => 'The URL returned "HTTP 403 Forbidden"', + ]); + } + } // attempt to parse the page as HTML diff --git a/controllers/Token.php b/controllers/Token.php new file mode 100644 index 0000000..b7f12a5 --- /dev/null +++ b/controllers/Token.php @@ -0,0 +1,119 @@ +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; + } + +} diff --git a/lib/HTTP.php b/lib/HTTP.php index 8db944d..9a9e19b 100644 --- a/lib/HTTP.php +++ b/lib/HTTP.php @@ -6,15 +6,15 @@ class HTTP { public $timeout = 4; public $max_redirects = 8; - public function get($url) { + public function get($url, $headers=[]) { $class = $this->_class($url); $http = new $class($url); $http->timeout = $this->timeout; $http->max_redirects = $this->max_redirects; - return $http->get($url); + return $http->get($url, $headers); } - public function post($url, $body, $headers=array()) { + public function post($url, $body, $headers=[]) { $class = $this->_class($url); $http = new $class($url); $http->timeout = $this->timeout; @@ -38,4 +38,19 @@ class HTTP { } } + public static function link_rels($header_array) { + $headers = ''; + foreach($header_array as $k=>$header) { + if(is_string($header)) { + $headers .= $k . ': ' . $header . "\r\n"; + } else { + foreach($header as $h) { + $headers .= $k . ': ' . $h . "\r\n"; + } + } + } + $rels = \IndieWeb\http_rels($headers); + return $rels; + } + } diff --git a/lib/HTTPCurl.php b/lib/HTTPCurl.php index c9d8c60..7ecfad4 100644 --- a/lib/HTTPCurl.php +++ b/lib/HTTPCurl.php @@ -6,9 +6,11 @@ class HTTPCurl { public $timeout = 4; public $max_redirects = 8; - public function get($url) { + public function get($url, $headers=[]) { $ch = curl_init($url); $this->_set_curlopts($ch, $url); + if($headers) + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); return array( @@ -22,12 +24,13 @@ class HTTPCurl { ); } - public function post($url, $body, $headers=array()) { + public function post($url, $body, $headers=[]) { $ch = curl_init($url); $this->_set_curlopts($ch, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + if($headers) + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); return array( diff --git a/lib/HTTPStream.php b/lib/HTTPStream.php index 9ca5636..bb481d3 100644 --- a/lib/HTTPStream.php +++ b/lib/HTTPStream.php @@ -14,13 +14,13 @@ class HTTPStream { throw new \ErrorException($message, 0, $severity, $file, $line); } - public function get($url) { + public function get($url, $headers=[]) { set_error_handler("p3k\HTTPStream::exception_error_handler"); - $context = $this->_stream_context('GET', $url); + $context = $this->_stream_context('GET', $url, false, $headers); return $this->_fetch($url, $context); } - public function post($url, $body, $headers=array()) { + public function post($url, $body, $headers=[]) { set_error_handler("p3k\HTTPStream::exception_error_handler"); $context = $this->_stream_context('POST', $url, $body, $headers); return $this->_fetch($url, $context); @@ -37,6 +37,8 @@ class HTTPStream { try { $body = file_get_contents($url, false, $context); + // This sets $http_response_header + // see http://php.net/manual/en/reserved.variables.httpresponseheader.php } catch(\Exception $e) { $body = false; $http_response_header = []; @@ -84,7 +86,7 @@ class HTTPStream { } if($headers) { - $options['header'] = $headers; + $options['header'] = implode("\r\n", $headers); } // Special-case appspot.com URLs to not follow redirects. diff --git a/lib/HTTPTest.php b/lib/HTTPTest.php index e27d1ad..a0858e3 100644 --- a/lib/HTTPTest.php +++ b/lib/HTTPTest.php @@ -9,11 +9,11 @@ class HTTPTest extends HTTPCurl { $this->_testDataPath = $testDataPath; } - public function get($url) { + public function get($url, $headers=[]) { return $this->_read_file($url); } - public function post($url, $body, $headers=array()) { + public function post($url, $body, $headers=[]) { return $this->_read_file($url); } @@ -59,7 +59,7 @@ class HTTPTest extends HTTPCurl { 'code' => $code, 'headers' => $parsedHeaders, 'body' => $body, - 'error' => '', + 'error' => (isset($parsedHeaders['X-Test-Error']) ? $parsedHeaders['X-Test-Error'] : ''), 'error_description' => '', 'url' => $effectiveUrl ); diff --git a/public/index.php b/public/index.php index 8d8c3cc..5ed72d1 100644 --- a/public/index.php +++ b/public/index.php @@ -10,6 +10,7 @@ $templates = new League\Plates\Engine(dirname(__FILE__).'/../views'); $router->addRoute('GET', '/', 'Main::index'); $router->addRoute('GET', '/parse', 'Parse::parse'); $router->addRoute('POST', '/parse', 'Parse::parse'); +$router->addRoute('POST', '/token', 'Token::token'); $dispatcher = $router->getDispatcher(); $request = Request::createFromGlobals(); diff --git a/tests/TokenTest.php b/tests/TokenTest.php new file mode 100644 index 0000000..686a449 --- /dev/null +++ b/tests/TokenTest.php @@ -0,0 +1,156 @@ +client = new Token(); + $this->client->http = new p3k\HTTPTest(dirname(__FILE__).'/data/'); + } + + private function token($params) { + $request = new Request($params); + $response = new Response(); + return $this->client->token($request, $response); + } + + public function testMissingURL() { + $response = $this->token([]); + + $body = $response->getContent(); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($body); + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('invalid_request', $data->error); + } + + public function testInvalidURL() { + $url = 'ftp://example.com/foo'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($body); + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('invalid_url', $data->error); + } + + public function testMissingCode() { + $response = $this->token(['source' => 'http://example.com/']); + + $body = $response->getContent(); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($body); + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('invalid_request', $data->error); + } + + public function testNoLinkHeaders() { + $url = 'http://private.example.com/no-link-headers'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body); + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('no_token_endpoint', $data->error); + } + + public function testNoTokenEndpointOneLinkHeader() { + $url = 'http://private.example.com/no-token-endpoint-one-link-header'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('no_token_endpoint', $data->error); + } + + public function testNoTokenEndpointTwoLinkHeaders() { + $url = 'http://private.example.com/no-token-endpoint-two-link-headers'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('no_token_endpoint', $data->error); + } + + public function testTokenEndpointInOAuth2Rel() { + $url = 'http://private.example.com/oauth2-token-endpoint'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectNotHasAttribute('error', $data); + $this->assertEquals('1234', $data->access_token); + } + + public function testTokenEndpointInIndieAuthRel() { + $url = 'http://private.example.com/token-endpoint'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectNotHasAttribute('error', $data); + $this->assertEquals('1234', $data->access_token); + } + + public function testTokenEndpointWithMultipleRelLinks() { + $url = 'http://private.example.com/multiple-rels'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectNotHasAttribute('error', $data); + $this->assertEquals('1234', $data->access_token); + } + + public function testBadTokenEndpointResponse() { + $url = 'http://private.example.com/token-endpoint-bad-response'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('this-string-passed-through-from-token-endpoint', $data->error); + } + + public function testTokenEndpointTimeout() { + $url = 'http://private.example.com/token-endpoint-timeout'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($body); + + $this->assertObjectHasAttribute('error', $data); + $this->assertEquals('timeout', $data->error); + } + + public function testTokenEndpointReturnsNotJSON() { + $url = 'http://private.example.com/token-endpoint-notjson'; + $response = $this->token(['source' => $url, 'code' => '1234']); + + $body = $response->getContent(); + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('text/plain', $response->headers->get('content-type')); + $this->assertEquals('Invalid request', $body); + } + +} diff --git a/tests/data/private.example.com/multiple-rels b/tests/data/private.example.com/multiple-rels new file mode 100644 index 0000000..ff3f384 --- /dev/null +++ b/tests/data/private.example.com/multiple-rels @@ -0,0 +1,9 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="token_endpoint" +Link: ; rel="webmention" + +This page uses the "token_endpoint" rel value defined in https://indieweb.org/obtaining-an-access-token diff --git a/tests/data/private.example.com/no-link-headers b/tests/data/private.example.com/no-link-headers new file mode 100644 index 0000000..b5f1be1 --- /dev/null +++ b/tests/data/private.example.com/no-link-headers @@ -0,0 +1,7 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive + +This page has no link headers. diff --git a/tests/data/private.example.com/no-token-endpoint-one-link-header b/tests/data/private.example.com/no-token-endpoint-one-link-header new file mode 100644 index 0000000..7a8c05f --- /dev/null +++ b/tests/data/private.example.com/no-token-endpoint-one-link-header @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="micropub" + +This page has no token endpoint specified. diff --git a/tests/data/private.example.com/no-token-endpoint-two-link-headers b/tests/data/private.example.com/no-token-endpoint-two-link-headers new file mode 100644 index 0000000..26653fe --- /dev/null +++ b/tests/data/private.example.com/no-token-endpoint-two-link-headers @@ -0,0 +1,9 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="micropub" +Link: ; rel="webmention" + +This page has no token endpoint specified. diff --git a/tests/data/private.example.com/oauth2-token-endpoint b/tests/data/private.example.com/oauth2-token-endpoint new file mode 100644 index 0000000..644f412 --- /dev/null +++ b/tests/data/private.example.com/oauth2-token-endpoint @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="oauth2-token" + +This page uses the "oauth2-token" rel value defined in https://tools.ietf.org/html/draft-wmills-oauth-lrdd-07 diff --git a/tests/data/private.example.com/token b/tests/data/private.example.com/token new file mode 100644 index 0000000..f0bda1f --- /dev/null +++ b/tests/data/private.example.com/token @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: application/json +Connection: keep-alive + +{ + "access_token": "1234", + "token_type": "bearer", + "expires_in": 3600 +} diff --git a/tests/data/private.example.com/token-endpoint b/tests/data/private.example.com/token-endpoint new file mode 100644 index 0000000..398b378 --- /dev/null +++ b/tests/data/private.example.com/token-endpoint @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="token_endpoint" + +This page uses the "token_endpoint" rel value defined in https://indieweb.org/obtaining-an-access-token diff --git a/tests/data/private.example.com/token-endpoint-bad-response b/tests/data/private.example.com/token-endpoint-bad-response new file mode 100644 index 0000000..83b0e1c --- /dev/null +++ b/tests/data/private.example.com/token-endpoint-bad-response @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="token_endpoint" + +This page links to a token endpoint that will return a bad response diff --git a/tests/data/private.example.com/token-endpoint-notjson b/tests/data/private.example.com/token-endpoint-notjson new file mode 100644 index 0000000..428bebe --- /dev/null +++ b/tests/data/private.example.com/token-endpoint-notjson @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="token_endpoint" + +This page links to a token endpoint that does not return JSON diff --git a/tests/data/private.example.com/token-endpoint-timeout b/tests/data/private.example.com/token-endpoint-timeout new file mode 100644 index 0000000..2bf3c63 --- /dev/null +++ b/tests/data/private.example.com/token-endpoint-timeout @@ -0,0 +1,8 @@ +HTTP/1.1 401 Unauthorized +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain; charset=utf-8 +Connection: keep-alive +Link: ; rel="token_endpoint" + +This page links to a token endpoint that will time out diff --git a/tests/data/private.example.com/token-invalid b/tests/data/private.example.com/token-invalid new file mode 100644 index 0000000..f119829 --- /dev/null +++ b/tests/data/private.example.com/token-invalid @@ -0,0 +1,9 @@ +HTTP/1.1 400 Bad Request +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: application/json +Connection: keep-alive + +{ + "error": "this-string-passed-through-from-token-endpoint" +} diff --git a/tests/data/private.example.com/token-notjson b/tests/data/private.example.com/token-notjson new file mode 100644 index 0000000..ad3bcca --- /dev/null +++ b/tests/data/private.example.com/token-notjson @@ -0,0 +1,7 @@ +HTTP/1.1 400 Bad Request +Server: Apache +Date: Wed, 09 Dec 2015 03:29:14 GMT +Content-Type: text/plain +Connection: keep-alive + +Invalid request \ No newline at end of file diff --git a/tests/data/private.example.com/token-timeout b/tests/data/private.example.com/token-timeout new file mode 100644 index 0000000..74e6b54 --- /dev/null +++ b/tests/data/private.example.com/token-timeout @@ -0,0 +1,4 @@ +HTTP/1.1 400 Bad Request +X-Test-Error: timeout + +.