Skip to content

Commit 808ba36

Browse files
committed
Middleware and it's basic tests
1 parent f35b47b commit 808ba36

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ApiClients\Foundation\Oauth1\Middleware;
4+
5+
use ApiClients\Foundation\Middleware\MiddlewareInterface;
6+
use ApiClients\Foundation\Middleware\PostTrait;
7+
use ApiClients\Foundation\Oauth1\Options;
8+
use JacobKiers\OAuth\Consumer\ConsumerInterface;
9+
use JacobKiers\OAuth\Request\Request as OAuthRequest;
10+
use JacobKiers\OAuth\SignatureMethod\SignatureMethodInterface;
11+
use JacobKiers\OAuth\Token\TokenInterface;
12+
use Psr\Http\Message\RequestInterface;
13+
use React\EventLoop\LoopInterface;
14+
use React\Promise\CancellablePromiseInterface;
15+
use function React\Promise\resolve;
16+
use function GuzzleHttp\Psr7\parse_query;
17+
use function WyriHaximus\React\futurePromise;
18+
19+
class Oauth1Middleware implements MiddlewareInterface
20+
{
21+
use PostTrait;
22+
23+
/**
24+
* @var LoopInterface
25+
*/
26+
private $loop;
27+
28+
/**
29+
* @param LoopInterface $loop
30+
*/
31+
public function __construct(LoopInterface $loop)
32+
{
33+
$this->loop = $loop;
34+
}
35+
36+
/**
37+
* @param RequestInterface $request
38+
* @param array $options
39+
* @return CancellablePromiseInterface
40+
*/
41+
public function pre(RequestInterface $request, array $options = []): CancellablePromiseInterface
42+
{
43+
if (!$this->validateOptions($options)) {
44+
return resolve($request);
45+
}
46+
47+
return futurePromise($this->loop, [$request, $options])->then(function ($args) {
48+
return resolve($this->signRequest(...$args));
49+
});
50+
}
51+
52+
private function validateOptions(array $options): bool
53+
{
54+
if (!isset($options[self::class])) {
55+
return false;
56+
}
57+
58+
if (!isset($options[self::class][Options::CONSUMER])) {
59+
return false;
60+
}
61+
62+
if (!($options[self::class][Options::CONSUMER] instanceof ConsumerInterface)) {
63+
return false;
64+
}
65+
66+
if (!isset($options[self::class][Options::TOKEN])) {
67+
return false;
68+
}
69+
70+
if (!($options[self::class][Options::TOKEN] instanceof TokenInterface)) {
71+
return false;
72+
}
73+
74+
if (!isset($options[self::class][Options::SIGNATURE_METHOD])) {
75+
return false;
76+
}
77+
78+
if (!($options[self::class][Options::SIGNATURE_METHOD] instanceof SignatureMethodInterface)) {
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
85+
private function signRequest(RequestInterface $request, array $options): RequestInterface
86+
{
87+
$oauthRequest = OAuthRequest::fromConsumerAndToken(
88+
$options[self::class][Options::CONSUMER],
89+
$options[self::class][Options::TOKEN],
90+
$request->getMethod(),
91+
(string)$request->getUri(),
92+
$this->extractParamsFromQuery(
93+
$request->getUri()->getQuery()
94+
)
95+
);
96+
$oauthRequest->setParameter('oauth_version', '1.0', false);
97+
$oauthRequest->signRequest(
98+
$options[self::class][Options::SIGNATURE_METHOD],
99+
$options[self::class][Options::CONSUMER],
100+
$options[self::class][Options::TOKEN]
101+
);
102+
103+
return $request->withAddedHeader(
104+
'Authorization',
105+
trim(substr($oauthRequest->toHeader(), 15))
106+
);
107+
}
108+
109+
private function extractParamsFromQuery(string $query): array
110+
{
111+
$params = parse_query($query);
112+
113+
uksort($params, 'strcmp');
114+
115+
foreach ($params as $key => $value) {
116+
if ($value !== null) {
117+
continue;
118+
}
119+
120+
unset($params[$key]);
121+
}
122+
123+
return $params;
124+
}
125+
}

src/Options.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ApiClients\Foundation\Oauth1;
4+
5+
class Options
6+
{
7+
const CONSUMER = 'consumer';
8+
const TOKEN = 'token';
9+
const SIGNATURE_METHOD = 'signature_method';
10+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ApiClients\Tests\Foundation\Cache\Middleware;
4+
5+
use ApiClients\Foundation\Oauth1\Middleware\Oauth1Middleware;
6+
use ApiClients\Foundation\Oauth1\Options;
7+
use ApiClients\Tools\TestUtilities\TestCase;
8+
use GuzzleHttp\Psr7\Request;
9+
use JacobKiers\OAuth\Consumer\Consumer;
10+
use JacobKiers\OAuth\SignatureMethod\HmacSha1;
11+
use JacobKiers\OAuth\Token\Token;
12+
use Prophecy\Argument;
13+
use Psr\Http\Message\RequestInterface;
14+
use function Clue\React\Block\await;
15+
use React\EventLoop\Factory;
16+
use function React\Promise\resolve;
17+
18+
class OauthMiddlewareTest extends TestCase
19+
{
20+
public function providerIncompleteRequestOptions()
21+
{
22+
yield [
23+
[],
24+
];
25+
yield [
26+
[
27+
Oauth1Middleware::class => [],
28+
],
29+
];
30+
yield [
31+
[
32+
Oauth1Middleware::class => [
33+
Options::CONSUMER => 'consumer',
34+
],
35+
],
36+
];
37+
yield [
38+
[
39+
Oauth1Middleware::class => [
40+
Options::CONSUMER => new Consumer('key', 'secret'),
41+
],
42+
],
43+
];
44+
yield [
45+
[
46+
Oauth1Middleware::class => [
47+
Options::CONSUMER => new Consumer('key', 'secret'),
48+
Options::TOKEN => 'token',
49+
],
50+
],
51+
];
52+
yield [
53+
[
54+
Oauth1Middleware::class => [
55+
Options::CONSUMER => new Consumer('key', 'secret'),
56+
Options::TOKEN => new Token('key', 'secret'),
57+
],
58+
],
59+
];
60+
yield [
61+
[
62+
Oauth1Middleware::class => [
63+
Options::CONSUMER => new Consumer('key', 'secret'),
64+
Options::TOKEN => new Token('key', 'secret'),
65+
Options::SIGNATURE_METHOD => 'signature_method',
66+
],
67+
],
68+
];
69+
}
70+
71+
/**
72+
* @dataProvider providerIncompleteRequestOptions
73+
*/
74+
public function testIncompleteRequestOptions(array $options)
75+
{
76+
$loop = Factory::create();
77+
$request = $this->prophesize(RequestInterface::class)->reveal();
78+
79+
$middleware = new Oauth1Middleware($loop);
80+
$result = await($middleware->pre($request, $options), $loop);
81+
82+
$this->assertSame($request, $result);
83+
}
84+
85+
public function testRequest()
86+
{
87+
$options = [
88+
Oauth1Middleware::class => [
89+
Options::CONSUMER => new Consumer('key', 'secret'),
90+
Options::TOKEN => new Token('key', 'secret'),
91+
Options::SIGNATURE_METHOD => new HmacSha1(),
92+
],
93+
];
94+
$loop = Factory::create();
95+
$request = new Request(
96+
'GET',
97+
'https://example.com/?b=a&a=b'
98+
);
99+
100+
$middleware = new Oauth1Middleware($loop);
101+
$result = await($middleware->pre($request, $options), $loop);
102+
103+
$this->assertNotSame($request, $result);
104+
105+
$headers = $result->getHeaders();
106+
$this->assertTrue(isset($headers['Host']));
107+
$this->assertSame(
108+
[
109+
'example.com',
110+
],
111+
$headers['Host']
112+
);
113+
}
114+
}

0 commit comments

Comments
 (0)