Skip to content
This repository has been archived by the owner on May 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5 from lookyman/refactor-authentication
Browse files Browse the repository at this point in the history
Refactor authentication
  • Loading branch information
lookyman authored Jul 21, 2017
2 parents a3e164e + 86e8e30 commit 62e004e
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 108 deletions.
66 changes: 24 additions & 42 deletions src/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
use ParagonIE\Sapient\CryptographyKeys\SigningSecretKey;
use ParagonIE\Sapient\Sapient;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;

final class Api extends AbstractApi implements ApiInterface
Expand Down Expand Up @@ -120,81 +121,45 @@ public function index(): array

public function register(SigningPublicKey $publicKey, string $comment = \null): array
{
if ($this->signingSecretKey === \null || $this->chronicleClientId === \null) {
throw new \InvalidArgumentException('First use the authenticate() method to set credentials');
}
$message = \json_encode([
'publickey' => $publicKey->getString(),
'comment' => $comment,
]);
/** @var RequestInterface $request */
$request = $this->requestFactory->createRequest(
$request = $this->authenticateAndSignMessage($this->requestFactory->createRequest(
'POST',
\sprintf('%s/chronicle/register', $this->chronicleUri)
)->withBody(Stream::fromString($message))->withHeader(
'Content-Type',
'application/json'
)->withHeader(
self::CHRONICLE_CLIENT_KEY_ID,
$this->chronicleClientId
)->withHeader(
Sapient::HEADER_SIGNATURE_NAME,
Base64UrlSafe::encode(\ParagonIE_Sodium_Compat::crypto_sign_detached(
$message,
$this->signingSecretKey->getString(\true)
))
);
));
return $this->verifyAndReturnResponse($this->client->sendRequest($request));
}

public function revoke(string $clientId, SigningPublicKey $publicKey): array
{
if ($this->signingSecretKey === \null || $this->chronicleClientId === \null) {
throw new \InvalidArgumentException('First use the authenticate() method to set credentials');
}
$message = \json_encode([
'clientid' => $clientId,
'publickey' => $publicKey->getString(),
]);
/** @var RequestInterface $request */
$request = $this->requestFactory->createRequest(
$request = $this->authenticateAndSignMessage($this->requestFactory->createRequest(
'POST',
\sprintf('%s/chronicle/revoke', $this->chronicleUri)
)->withBody(Stream::fromString($message))->withHeader(
'Content-Type',
'application/json'
)->withHeader(
self::CHRONICLE_CLIENT_KEY_ID,
$this->chronicleClientId
)->withHeader(
Sapient::HEADER_SIGNATURE_NAME,
Base64UrlSafe::encode(\ParagonIE_Sodium_Compat::crypto_sign_detached(
$message,
$this->signingSecretKey->getString(\true)
))
);
));
return $this->verifyAndReturnResponse($this->client->sendRequest($request));
}

public function publish(string $message): array
{
if ($this->signingSecretKey === \null || $this->chronicleClientId === \null) {
throw new \InvalidArgumentException('First use the authenticate() method to set credentials');
}
/** @var RequestInterface $request */
$request = $this->requestFactory->createRequest(
$request = $this->authenticateAndSignMessage($this->requestFactory->createRequest(
'POST',
\sprintf('%s/chronicle/publish', $this->chronicleUri)
)->withBody(Stream::fromString($message))->withHeader(
self::CHRONICLE_CLIENT_KEY_ID,
$this->chronicleClientId
)->withHeader(
Sapient::HEADER_SIGNATURE_NAME,
Base64UrlSafe::encode(\ParagonIE_Sodium_Compat::crypto_sign_detached(
$message,
$this->signingSecretKey->getString(\true)
))
);
)->withBody(Stream::fromString($message)));
return $this->verifyAndReturnResponse($this->client->sendRequest($request));
}

Expand All @@ -220,4 +185,21 @@ public function replicas(): array
)));
}

private function authenticateAndSignMessage(MessageInterface $request): MessageInterface
{
if ($this->signingSecretKey === \null || $this->chronicleClientId === \null) {
throw new UnauthenticatedException('First use the authenticate() method to set credentials');
}
return $request->withHeader(
self::CHRONICLE_CLIENT_KEY_ID,
$this->chronicleClientId
)->withHeader(
Sapient::HEADER_SIGNATURE_NAME,
Base64UrlSafe::encode(\ParagonIE_Sodium_Compat::crypto_sign_detached(
(string) $request->getBody(),
$this->signingSecretKey->getString(\true)
))
);
}

}
9 changes: 9 additions & 0 deletions src/UnauthenticatedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types = 1);

namespace Lookyman\Chronicle;

final class UnauthenticatedException extends \Exception
{
}
93 changes: 27 additions & 66 deletions tests/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,28 +197,14 @@ public function testIndex()
self::assertEquals(['result'], $api->index());
}

/**
* @expectedException \InvalidArgumentException
*/
public function testRegisterUnauthorized()
{
$client = $this->createMock(HttpClient::class);

$requestFactory = $this->createMock(RequestFactoryInterface::class);

$api = new Api(
$client,
$requestFactory,
'uri'
);

$api->register($this->createMock(SigningPublicKey::class));
}

public function testRegister()
{
$stream = $this->createMock(StreamInterface::class);
$stream->expects(self::once())->method('__toString')->willReturn('["result"]');
$requestStream = $this->createMock(StreamInterface::class);
$requestStream->expects(self::once())->method('__toString')
->willReturn('{"publickey":"aAtpZ1BH8GbmKbXx7IN7_pTN9fM9WwGiZmKUajsLi6Q=","comment":"foo"}');

$responseStream = $this->createMock(StreamInterface::class);
$responseStream->expects(self::once())->method('__toString')->willReturn('["result"]');

$request = $this->createMock(RequestInterface::class);
$request->expects(self::at(0))->method('withBody')->willReturn($request);
Expand All @@ -230,13 +216,14 @@ public function testRegister()
Api::CHRONICLE_CLIENT_KEY_ID,
'client'
)->willReturn($request);
$request->expects(self::at(3))->method('withHeader')->with(
$request->expects(self::at(3))->method('getBody')->willReturn($requestStream);
$request->expects(self::at(4))->method('withHeader')->with(
Sapient::HEADER_SIGNATURE_NAME,
'iGV5WcBp7A3eHFeb2OeM9n1i0dPOC5_DsnAvl7p29XUWWjvqSJ827v3Gw8zM8H4hvfyEWAlf8CZ0wvaUpdtZDA=='
)->willReturn($request);

$response = $this->createMock(ResponseInterface::class);
$response->expects(self::once())->method('getBody')->willReturn($stream);
$response->expects(self::once())->method('getBody')->willReturn($responseStream);
$response->expects(self::once())->method('getHeader')->with(Sapient::HEADER_SIGNATURE_NAME)
->willReturn(['Ypkdmzl7uoEmsNf5htTSmRFWKYpQskL5p3ffMjEQq4oHrwrkhQfJ1Pu9v9NF7Mth5Foa6JfSsJLcveU33pUtAQ==']);

Expand Down Expand Up @@ -270,28 +257,14 @@ public function testRegister()
));
}

/**
* @expectedException \InvalidArgumentException
*/
public function testRevokeUnauthorized()
{
$client = $this->createMock(HttpClient::class);

$requestFactory = $this->createMock(RequestFactoryInterface::class);

$api = new Api(
$client,
$requestFactory,
'uri'
);

$api->revoke('id', $this->createMock(SigningPublicKey::class));
}

public function testRevoke()
{
$stream = $this->createMock(StreamInterface::class);
$stream->expects(self::once())->method('__toString')->willReturn('["result"]');
$requestStream = $this->createMock(StreamInterface::class);
$requestStream->expects(self::once())->method('__toString')
->willReturn('{"clientid":"foo","publickey":"aAtpZ1BH8GbmKbXx7IN7_pTN9fM9WwGiZmKUajsLi6Q="}');

$responseStream = $this->createMock(StreamInterface::class);
$responseStream->expects(self::once())->method('__toString')->willReturn('["result"]');

$request = $this->createMock(RequestInterface::class);
$request->expects(self::at(0))->method('withBody')->willReturn($request);
Expand All @@ -303,13 +276,14 @@ public function testRevoke()
Api::CHRONICLE_CLIENT_KEY_ID,
'client'
)->willReturn($request);
$request->expects(self::at(3))->method('withHeader')->with(
$request->expects(self::at(3))->method('getBody')->willReturn($requestStream);
$request->expects(self::at(4))->method('withHeader')->with(
Sapient::HEADER_SIGNATURE_NAME,
'q0ILXTcSkyl75zsJgh_6fnGGiRrQpnz8QkQNjfNY6LaDPbCr4mXnWs33KY-lqMPH-9qg9pybHxr3WszznrjmCw=='
)->willReturn($request);

$response = $this->createMock(ResponseInterface::class);
$response->expects(self::once())->method('getBody')->willReturn($stream);
$response->expects(self::once())->method('getBody')->willReturn($responseStream);
$response->expects(self::once())->method('getHeader')->with(Sapient::HEADER_SIGNATURE_NAME)
->willReturn(['Ypkdmzl7uoEmsNf5htTSmRFWKYpQskL5p3ffMjEQq4oHrwrkhQfJ1Pu9v9NF7Mth5Foa6JfSsJLcveU33pUtAQ==']);

Expand Down Expand Up @@ -343,42 +317,29 @@ public function testRevoke()
));
}

/**
* @expectedException \InvalidArgumentException
*/
public function testPublishUnauthorized()
{
$client = $this->createMock(HttpClient::class);

$requestFactory = $this->createMock(RequestFactoryInterface::class);

$api = new Api(
$client,
$requestFactory,
'uri'
);

$api->publish('foo');
}

public function testPublish()
{
$stream = $this->createMock(StreamInterface::class);
$stream->expects(self::once())->method('__toString')->willReturn('["result"]');
$requestStream = $this->createMock(StreamInterface::class);
$requestStream->expects(self::once())->method('__toString')
->willReturn('foo');

$responseStream = $this->createMock(StreamInterface::class);
$responseStream->expects(self::once())->method('__toString')->willReturn('["result"]');

$request = $this->createMock(RequestInterface::class);
$request->expects(self::at(0))->method('withBody')->willReturn($request);
$request->expects(self::at(1))->method('withHeader')->with(
Api::CHRONICLE_CLIENT_KEY_ID,
'client'
)->willReturn($request);
$request->expects(self::at(2))->method('withHeader')->with(
$request->expects(self::at(2))->method('getBody')->willReturn($requestStream);
$request->expects(self::at(3))->method('withHeader')->with(
Sapient::HEADER_SIGNATURE_NAME,
'O42hyULuTzw9atrnPjH4P4ePPgZHxDF0TLG3Co3xj0f7QPLharhEWRAVo7mHwqpNcaaOTh7LK2FnL9rCL1iLDA=='
)->willReturn($request);

$response = $this->createMock(ResponseInterface::class);
$response->expects(self::once())->method('getBody')->willReturn($stream);
$response->expects(self::once())->method('getBody')->willReturn($responseStream);
$response->expects(self::once())->method('getHeader')->with(Sapient::HEADER_SIGNATURE_NAME)
->willReturn(['Ypkdmzl7uoEmsNf5htTSmRFWKYpQskL5p3ffMjEQq4oHrwrkhQfJ1Pu9v9NF7Mth5Foa6JfSsJLcveU33pUtAQ==']);

Expand Down

0 comments on commit 62e004e

Please sign in to comment.