Skip to content

Commit 016dd83

Browse files
authored
Merge pull request #4 from SoapBox/feature/add-a-request-signer
[Feature] Adds a Request Generator
2 parents 803efea + ca7d4c5 commit 016dd83

File tree

11 files changed

+399
-21
lines changed

11 files changed

+399
-21
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
"minimum-stability": "stable",
1313
"require": {
1414
"php": ">=7.1",
15+
"guzzlehttp/guzzle": "^6.2",
1516
"illuminate/http": "^5.4",
1617
"illuminate/support": "^5.4",
17-
"orchestra/testbench": "^3.4"
18+
"ramsey/uuid": "^3.6"
1819
},
1920
"require-dev": {
2021
"mockery/mockery": "^0.9.9",
22+
"orchestra/testbench": "^3.4",
2123
"phpunit/phpunit": "^6.0"
2224
},
2325
"autoload": {

resources/config/signed-requests.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
<?php
22

33
return [
4+
/*
5+
|--------------------------------------------------------------------------
6+
| The algorithm to sign the request with
7+
|--------------------------------------------------------------------------
8+
|
9+
| This is the algorithm we'll use to sign the
10+
*/
11+
'algorithm' => env('SIGNED_REQUEST_ALGORITHM', 'sha256'),
12+
413
/*
514
|--------------------------------------------------------------------------
615
| Available header overrides

src/Requests/Generator.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace SoapBox\SignedRequests\Requests;
4+
5+
use Ramsey\Uuid\Uuid;
6+
use GuzzleHttp\Psr7\Request;
7+
use SoapBox\SignedRequests\Signature;
8+
use Illuminate\Support\Facades\Config;
9+
use Illuminate\Contracts\Config\Repository;
10+
11+
class Generator
12+
{
13+
/**
14+
* An instance of the configuration repository.
15+
*
16+
* @var \Illuminate\Contracts\Config\Repository
17+
*/
18+
private $repository;
19+
20+
/**
21+
* Constructs our signed request generator with an instance of the
22+
* configurations.
23+
*
24+
* @param \Illuminate\Contracts\Config\Repository $repository
25+
* A configuration repository.
26+
*/
27+
public function __construct(Repository $repository)
28+
{
29+
$this->repository = $repository;
30+
}
31+
32+
/**
33+
* Signs and returns the request.
34+
*
35+
* @param \GuzzleHttp\Psr7\Request $request
36+
* The request to sign.
37+
*
38+
* @return \GuzzleHttp\Psr7\Request
39+
* The request with an id, algorith, and signature.
40+
*/
41+
public function sign(Request $request) : Request
42+
{
43+
$algorithmHeader = $this->repository->get('signed-requests.headers.algorithm');
44+
$signatureHeader = $this->repository->get('signed-requests.headers.signature');
45+
46+
$algorithm = $this->repository->get('signed-requests.algorithm');
47+
$key = $this->repository->get('signed-requests.key');
48+
49+
$request = $request->withHeader('X-SIGNED-ID', (string) Uuid::uuid4());
50+
51+
$signature = new Signature(new Payload($request), $algorithm, $key);
52+
53+
return $request
54+
->withHeader($algorithmHeader, (string) $algorithm)
55+
->withHeader($signatureHeader, (string) $signature);
56+
}
57+
}

src/Requests/Payload.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace SoapBox\SignedRequests\Requests;
4+
5+
use GuzzleHttp\Psr7\Request as GuzzleRequest;
6+
use Illuminate\Http\Request as IlluminateRequest;
7+
8+
class Payload
9+
{
10+
/**
11+
* A request object. Currently both \GuzzleHttp\Psr7\Request and
12+
* \Illuminate\Http\Request are supported.
13+
*
14+
* @var mixed
15+
*/
16+
private $request;
17+
18+
/**
19+
* Set's the local request to extract a payload from.
20+
*
21+
* @param mixed $request
22+
* The request to extract a payload from. Currently both
23+
* \GuzzleHttp\Psr7\Request and \Illuminate\Http\Request are
24+
* supported.
25+
*/
26+
public function __construct($request)
27+
{
28+
$this->request = $request;
29+
}
30+
31+
/**
32+
* Returns the payload from a guzzle request.
33+
*
34+
* @param \GuzzleHttp\Psr7\Request $request
35+
* An instance of the guzzle request to extract a payload from.
36+
*
37+
* @return string
38+
* The payload.
39+
*/
40+
protected function generateFromGuzzleRequest(GuzzleRequest $request) : string
41+
{
42+
$id = isset($this->request->getHeader('X-SIGNED-ID')[0]) ?
43+
$this->request->getHeader('X-SIGNED-ID')[0] : '';
44+
45+
return json_encode([
46+
'id' => (string) $id,
47+
'method' => $this->request->getMethod(),
48+
'uri' => (string) $this->request->getUri(),
49+
'content' => $this->request->getBody()
50+
], JSON_UNESCAPED_SLASHES);
51+
}
52+
53+
/**
54+
* Retruns the payload from an illuminate request.
55+
*
56+
* @param \Illuminate\Http\Request $request
57+
* An instance of the illuminate request to extract the payload from.
58+
*
59+
* @return string
60+
* The payload.
61+
*/
62+
protected function generateFromIlluminateRequest(IlluminateRequest $request) : string
63+
{
64+
$id = $this->request->headers->get('X-SIGNED-ID', '');
65+
66+
return json_encode([
67+
'id' => (string) $id,
68+
'method' => $this->request->getMethod(),
69+
'uri' => (string) $this->request->fullUrl(),
70+
'content' => $this->request->getContent()
71+
], JSON_UNESCAPED_SLASHES);
72+
}
73+
74+
/**
75+
* Returns a payload with the id, method, uri, and content embedded.
76+
*
77+
* @return string
78+
* A json encoded payload.
79+
*/
80+
public function __toString() : string
81+
{
82+
if ($this->request instanceof GuzzleRequest) {
83+
return $this->generateFromGuzzleRequest($this->request);
84+
}
85+
86+
if ($this->request instanceof IlluminateRequest) {
87+
return $this->generateFromIlluminateRequest($this->request);
88+
}
89+
90+
return '';
91+
}
92+
}

src/Requests/Verifier.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Http\Request;
66
use SoapBox\SignedRequests\Signature;
7+
use SoapBox\SignedRequests\Requests\Payload;
78

89
class Verifier
910
{
@@ -167,7 +168,7 @@ public function isValid(string $key) : bool
167168
return false;
168169
}
169170

170-
$signature = new Signature($this->getContent(), $this->getAlgorithm(), $key);
171+
$signature = new Signature(new Payload($this->request), $this->getAlgorithm(), $key);
171172

172173
return $signature->equals($this->getSignature());
173174
}

src/Signature.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,15 @@ public function equals($signature) : bool
4747

4848
return false;
4949
}
50+
51+
/**
52+
* Returns the generated signature string.
53+
*
54+
* @return string
55+
* The signature.
56+
*/
57+
public function __toString() : string
58+
{
59+
return (string) $this->signature;
60+
}
5061
}

tests/Middlewares/VerifySignatureTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
use Mockery;
66
use Tests\TestCase;
7+
use Ramsey\Uuid\Uuid;
78
use Illuminate\Http\Request;
89
use SoapBox\SignedRequests\Signature;
910
use Illuminate\Contracts\Config\Repository;
1011
use SoapBox\SignedRequests\Requests\Signed;
12+
use SoapBox\SignedRequests\Requests\Payload;
13+
use SoapBox\SignedRequests\Requests\Generator;
1114
use SoapBox\SignedRequests\Middlewares\VerifySignature;
1215

1316
class VerifySignatureTest extends TestCase
@@ -71,6 +74,8 @@ public function it_throws_an_invalid_signature_exception_if_the_request_is_not_v
7174
*/
7275
public function it_should_call_our_callback_if_the_request_is_valid()
7376
{
77+
$id = (string) Uuid::uuid4();
78+
7479
$this->configurations->shouldReceive('get')
7580
->with('signed-requests.headers.signature')
7681
->andReturn('signature');
@@ -89,11 +94,12 @@ public function it_should_call_our_callback_if_the_request_is_valid()
8994
$cookies = [];
9095
$files = [];
9196
$server = [
92-
'HTTP_SIGNATURE' => hash_hmac('sha256', 'a', 'key'),
97+
'HTTP_X-SIGNED-ID' => $id,
9398
'HTTP_ALGORITHM' => 'sha256'
9499
];
95100

96101
$request = new Request($query, $request, $attributes, $cookies, $files, $server, 'a');
102+
$request->headers->set('signature', (string) new Signature(new Payload($request), 'sha256', 'key'));
97103

98104
$this->middleware->handle($request, function () {
99105
// This should be called.

tests/Requests/GeneratorTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Tests\Requests;
4+
5+
use Mockery;
6+
use Tests\TestCase;
7+
use GuzzleHttp\Psr7\Request;
8+
use SoapBox\SignedRequests\Signature;
9+
use Illuminate\Contracts\Config\Repository;
10+
use SoapBox\SignedRequests\Requests\Payload;
11+
use SoapBox\SignedRequests\Requests\Generator;
12+
13+
class GeneratorTest extends TestCase
14+
{
15+
private $repository;
16+
private $generator;
17+
18+
/**
19+
* @before
20+
*/
21+
public function setup_a_local_generator()
22+
{
23+
$this->repository = Mockery::mock(Repository::class);
24+
25+
$this->repository
26+
->shouldReceive('get')
27+
->with('signed-requests.headers.algorithm')
28+
->andReturn('X-ALGORITHM');
29+
$this->repository
30+
->shouldReceive('get')
31+
->with('signed-requests.headers.signature')
32+
->andReturn('X-SIGNATURE');
33+
$this->repository
34+
->shouldReceive('get')
35+
->with('signed-requests.algorithm')
36+
->andReturn('sha256');
37+
$this->repository
38+
->shouldReceive('get')
39+
->with('signed-requests.key')
40+
->andReturn('key');
41+
42+
$this->generator = new Generator($this->repository);
43+
}
44+
45+
/**
46+
* @test
47+
*/
48+
public function it_adds_an_id_header_to_the_request()
49+
{
50+
$request = new Request('GET', 'https://localhost');
51+
$this->assertNotContains('X-SIGNED-ID', array_keys($request->getHeaders()));
52+
$request = $this->generator->sign($request);
53+
$this->assertContains('X-SIGNED-ID', array_keys($request->getHeaders()));
54+
}
55+
56+
/**
57+
* @test
58+
*/
59+
public function it_adds_an_algorithm_header_to_the_request()
60+
{
61+
$request = new Request('GET', 'https://localhost');
62+
$this->assertNotContains('X-ALGORITHM', array_keys($request->getHeaders()));
63+
$request = $this->generator->sign($request);
64+
$this->assertContains('X-ALGORITHM', array_keys($request->getHeaders()));
65+
$this->assertSame('sha256', $request->getHeaders()['X-ALGORITHM'][0]);
66+
}
67+
68+
/**
69+
* @test
70+
*/
71+
public function it_adds_a_signature_header_to_the_request()
72+
{
73+
$request = new Request('GET', 'https://localhost');
74+
$this->assertNotContains('X-SIGNATURE', array_keys($request->getHeaders()));
75+
$request = $this->generator->sign($request);
76+
$this->assertContains('X-SIGNATURE', array_keys($request->getHeaders()));
77+
78+
$signature = new Signature(new Payload($request), 'sha256', 'key');
79+
80+
$this->assertSame((string) $signature, $request->getHeaders()['X-SIGNATURE'][0]);
81+
}
82+
}

0 commit comments

Comments
 (0)