Browse Source

add token fetching and authentication for posts

pull/39/head
Aaron Parecki 7 years ago
parent
commit
565d50b862
No known key found for this signature in database GPG Key ID: 3745E500B4FF7CB1
24 changed files with 543 additions and 30 deletions
  1. +55
    -11
      README.md
  2. +3
    -1
      composer.json
  3. +48
    -2
      composer.lock
  4. +24
    -3
      controllers/Parse.php
  5. +119
    -0
      controllers/Token.php
  6. +18
    -3
      lib/HTTP.php
  7. +6
    -3
      lib/HTTPCurl.php
  8. +6
    -4
      lib/HTTPStream.php
  9. +3
    -3
      lib/HTTPTest.php
  10. +1
    -0
      public/index.php
  11. +156
    -0
      tests/TokenTest.php
  12. +9
    -0
      tests/data/private.example.com/multiple-rels
  13. +7
    -0
      tests/data/private.example.com/no-link-headers
  14. +8
    -0
      tests/data/private.example.com/no-token-endpoint-one-link-header
  15. +9
    -0
      tests/data/private.example.com/no-token-endpoint-two-link-headers
  16. +8
    -0
      tests/data/private.example.com/oauth2-token-endpoint
  17. +11
    -0
      tests/data/private.example.com/token
  18. +8
    -0
      tests/data/private.example.com/token-endpoint
  19. +8
    -0
      tests/data/private.example.com/token-endpoint-bad-response
  20. +8
    -0
      tests/data/private.example.com/token-endpoint-notjson
  21. +8
    -0
      tests/data/private.example.com/token-endpoint-timeout
  22. +9
    -0
      tests/data/private.example.com/token-invalid
  23. +7
    -0
      tests/data/private.example.com/token-notjson
  24. +4
    -0
      tests/data/private.example.com/token-timeout

+ 55
- 11
README.md View File

@ -12,7 +12,7 @@ The contents of the URL is checked in the following order:
* OGP (coming soon) * 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. 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. 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". 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 ### Error Response
```json ```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 ```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.

+ 3
- 1
composer.json View File

@ -3,7 +3,8 @@
"league/plates": "3.*", "league/plates": "3.*",
"league/route": "1.*", "league/route": "1.*",
"mf2/mf2": "~0.3", "mf2/mf2": "~0.3",
"ezyang/htmlpurifier": "4.*"
"ezyang/htmlpurifier": "4.*",
"indieweb/link-rel-parser": "0.1.*"
}, },
"autoload": { "autoload": {
"files": [ "files": [
@ -11,6 +12,7 @@
"lib/helpers.php", "lib/helpers.php",
"controllers/Main.php", "controllers/Main.php",
"controllers/Parse.php", "controllers/Parse.php",
"controllers/Token.php",
"lib/HTTPCurl.php", "lib/HTTPCurl.php",
"lib/HTTPStream.php", "lib/HTTPStream.php",
"lib/HTTP.php", "lib/HTTP.php",

+ 48
- 2
composer.lock View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "76c19995b1b2f77725f5a3cc13022004",
"content-hash": "010ed2acde68ceaa42fb406d3b9bc80e",
"hash": "2b5910cbc964bd8545d6a4737d319e5a",
"content-hash": "9dd49de07b7077eb937199147a258b4b",
"packages": [ "packages": [
{ {
"name": "ezyang/htmlpurifier", "name": "ezyang/htmlpurifier",
@ -51,6 +51,52 @@
], ],
"time": "2015-08-05 01:03:42" "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", "name": "ircmaxell/password-compat",
"version": "v1.0.4", "version": "v1.0.4",

+ 24
- 3
controllers/Parse.php View File

@ -8,7 +8,7 @@ class Parse {
public $http; public $http;
public $mc; public $mc;
private $_cacheTime = 300;
private $_cacheTime = 120;
private $_pretty = false; private $_pretty = false;
public function __construct() { public function __construct() {
@ -91,7 +91,8 @@ class Parse {
$url = \normalize_url($url); $url = \normalize_url($url);
// Now fetch the URL and check for any curl errors // 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); $cacheKey = 'xray-'.md5($url);
if($cached=$this->mc->get($cacheKey)) { if($cached=$this->mc->get($cacheKey)) {
$result = json_decode($cached, true); $result = json_decode($cached, true);
@ -104,7 +105,12 @@ class Parse {
$this->mc->set($cacheKey, $cacheData, MEMCACHE_COMPRESSED, $this->_cacheTime); $this->mc->set($cacheKey, $cacheData, MEMCACHE_COMPRESSED, $this->_cacheTime);
} }
} else { } 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']) { if($result['error']) {
@ -120,6 +126,21 @@ class Parse {
'error_description' => 'We did not get a response body when fetching the URL' '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 // attempt to parse the page as HTML

+ 119
- 0
controllers/Token.php View File

@ -0,0 +1,119 @@
<?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, 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;
}
}

+ 18
- 3
lib/HTTP.php View File

@ -6,15 +6,15 @@ class HTTP {
public $timeout = 4; public $timeout = 4;
public $max_redirects = 8; public $max_redirects = 8;
public function get($url) {
public function get($url, $headers=[]) {
$class = $this->_class($url); $class = $this->_class($url);
$http = new $class($url); $http = new $class($url);
$http->timeout = $this->timeout; $http->timeout = $this->timeout;
$http->max_redirects = $this->max_redirects; $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); $class = $this->_class($url);
$http = new $class($url); $http = new $class($url);
$http->timeout = $this->timeout; $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;
}
} }

+ 6
- 3
lib/HTTPCurl.php View File

@ -6,9 +6,11 @@ class HTTPCurl {
public $timeout = 4; public $timeout = 4;
public $max_redirects = 8; public $max_redirects = 8;
public function get($url) {
public function get($url, $headers=[]) {
$ch = curl_init($url); $ch = curl_init($url);
$this->_set_curlopts($ch, $url); $this->_set_curlopts($ch, $url);
if($headers)
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch); $response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
return array( 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); $ch = curl_init($url);
$this->_set_curlopts($ch, $url); $this->_set_curlopts($ch, $url);
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body); curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if($headers)
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch); $response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
return array( return array(

+ 6
- 4
lib/HTTPStream.php View File

@ -14,13 +14,13 @@ class HTTPStream {
throw new \ErrorException($message, 0, $severity, $file, $line); 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"); 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); 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"); set_error_handler("p3k\HTTPStream::exception_error_handler");
$context = $this->_stream_context('POST', $url, $body, $headers); $context = $this->_stream_context('POST', $url, $body, $headers);
return $this->_fetch($url, $context); return $this->_fetch($url, $context);
@ -37,6 +37,8 @@ class HTTPStream {
try { try {
$body = file_get_contents($url, false, $context); $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) { } catch(\Exception $e) {
$body = false; $body = false;
$http_response_header = []; $http_response_header = [];
@ -84,7 +86,7 @@ class HTTPStream {
} }
if($headers) { if($headers) {
$options['header'] = $headers;
$options['header'] = implode("\r\n", $headers);
} }
// Special-case appspot.com URLs to not follow redirects. // Special-case appspot.com URLs to not follow redirects.

+ 3
- 3
lib/HTTPTest.php View File

@ -9,11 +9,11 @@ class HTTPTest extends HTTPCurl {
$this->_testDataPath = $testDataPath; $this->_testDataPath = $testDataPath;
} }
public function get($url) {
public function get($url, $headers=[]) {
return $this->_read_file($url); return $this->_read_file($url);
} }
public function post($url, $body, $headers=array()) {
public function post($url, $body, $headers=[]) {
return $this->_read_file($url); return $this->_read_file($url);
} }
@ -59,7 +59,7 @@ class HTTPTest extends HTTPCurl {
'code' => $code, 'code' => $code,
'headers' => $parsedHeaders, 'headers' => $parsedHeaders,
'body' => $body, 'body' => $body,
'error' => '',
'error' => (isset($parsedHeaders['X-Test-Error']) ? $parsedHeaders['X-Test-Error'] : ''),
'error_description' => '', 'error_description' => '',
'url' => $effectiveUrl 'url' => $effectiveUrl
); );

+ 1
- 0
public/index.php View File

@ -10,6 +10,7 @@ $templates = new League\Plates\Engine(dirname(__FILE__).'/../views');
$router->addRoute('GET', '/', 'Main::index'); $router->addRoute('GET', '/', 'Main::index');
$router->addRoute('GET', '/parse', 'Parse::parse'); $router->addRoute('GET', '/parse', 'Parse::parse');
$router->addRoute('POST', '/parse', 'Parse::parse'); $router->addRoute('POST', '/parse', 'Parse::parse');
$router->addRoute('POST', '/token', 'Token::token');
$dispatcher = $router->getDispatcher(); $dispatcher = $router->getDispatcher();
$request = Request::createFromGlobals(); $request = Request::createFromGlobals();

+ 156
- 0
tests/TokenTest.php View File

@ -0,0 +1,156 @@
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class TokenTest extends PHPUnit_Framework_TestCase {
private $http;
public function setUp() {
$this->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);
}
}

+ 9
- 0
tests/data/private.example.com/multiple-rels View File

@ -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: </token>; rel="token_endpoint"
Link: </webmention>; rel="webmention"
This page uses the "token_endpoint" rel value defined in https://indieweb.org/obtaining-an-access-token

+ 7
- 0
tests/data/private.example.com/no-link-headers View File

@ -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.

+ 8
- 0
tests/data/private.example.com/no-token-endpoint-one-link-header View File

@ -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: </micropub>; rel="micropub"
This page has no token endpoint specified.

+ 9
- 0
tests/data/private.example.com/no-token-endpoint-two-link-headers View File

@ -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: </micropub>; rel="micropub"
Link: </webmention>; rel="webmention"
This page has no token endpoint specified.

+ 8
- 0
tests/data/private.example.com/oauth2-token-endpoint View File

@ -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: </token>; rel="oauth2-token"
This page uses the "oauth2-token" rel value defined in https://tools.ietf.org/html/draft-wmills-oauth-lrdd-07

+ 11
- 0
tests/data/private.example.com/token View File

@ -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
}

+ 8
- 0
tests/data/private.example.com/token-endpoint View File

@ -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: </token>; rel="token_endpoint"
This page uses the "token_endpoint" rel value defined in https://indieweb.org/obtaining-an-access-token

+ 8
- 0
tests/data/private.example.com/token-endpoint-bad-response View File

@ -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: </token-invalid>; rel="token_endpoint"
This page links to a token endpoint that will return a bad response

+ 8
- 0
tests/data/private.example.com/token-endpoint-notjson View File

@ -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: </token-notjson>; rel="token_endpoint"
This page links to a token endpoint that does not return JSON

+ 8
- 0
tests/data/private.example.com/token-endpoint-timeout View File

@ -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: </token-timeout>; rel="token_endpoint"
This page links to a token endpoint that will time out

+ 9
- 0
tests/data/private.example.com/token-invalid View File

@ -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"
}

+ 7
- 0
tests/data/private.example.com/token-notjson View File

@ -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

+ 4
- 0
tests/data/private.example.com/token-timeout View File

@ -0,0 +1,4 @@
HTTP/1.1 400 Bad Request
X-Test-Error: timeout
.

Loading…
Cancel
Save