1
0
mirror of synced 2024-11-24 21:46:02 +03:00

authorization URI builder, slightly different authenticator usage, fail tests if mocks weren't used

This commit is contained in:
Pavel 2020-10-01 16:29:49 +03:00
parent 18f8675005
commit 694b59d3b8
12 changed files with 383 additions and 27 deletions

View File

@ -1,4 +1,5 @@
ENDPOINT=https://api.taobao.com/router/rest ENDPOINT=https://api.taobao.com/router/rest
APP_KEY=00000000 APP_KEY=00000000
APP_SECRET=d784fa8b6d98d27699781bd9a7cf19f0 APP_SECRET=d784fa8b6d98d27699781bd9a7cf19f0
REDIRECT_URI=https://example.com
SESSION=test SESSION=test

View File

@ -9,6 +9,7 @@ php:
- '7.4' - '7.4'
before_script: before_script:
- cp .env.dist .env
- flags="-o" - flags="-o"
- composer install $flags - composer install $flags

View File

@ -0,0 +1,91 @@
<?php
/**
* PHP version 7.4
*
* @category AuthorizationUriBuilder
* @package RetailCrm\Builder
* @author RetailCRM <integration@retailcrm.ru>
* @license http://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Builder;
use BadMethodCallException;
use RetailCrm\Interfaces\BuilderInterface;
/**
* Class AuthorizationUriBuilder
*
* @category AuthorizationUriBuilder
* @package RetailCrm\Builder
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license https://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class AuthorizationUriBuilder implements BuilderInterface
{
private const AUTHORIZE_URI = 'https://oauth.aliexpress.com/authorize';
/**
* @var string $appKey
*/
private $appKey;
/**
* @var string $redirectUri
*/
private $redirectUri;
/**
* @var bool $withState
*/
private $withState;
/**
* AuthorizationUriBuilder constructor.
*
* @param string $appKey
* @param string $redirectUri
* @param bool $withState Set to true if state should be present in the URI
*
* It doesn't violate SRP because this class doesn't do anything besides URI generation.
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*/
public function __construct(string $appKey, string $redirectUri, bool $withState = false)
{
$this->appKey = $appKey;
$this->redirectUri = $redirectUri;
$this->withState = $withState;
}
/**
* @inheritDoc
*/
public function build(): string
{
return self::AUTHORIZE_URI . '?' . http_build_query($this->getParams());
}
/**
* @return array
* @throws BadMethodCallException
*/
private function getParams(): array
{
if (empty($this->redirectUri)) {
throw new BadMethodCallException('Redirect URI should not be empty');
}
return array_filter([
'client_id' => $this->appKey,
'response_type' => 'code',
'redirect_uri' => $this->redirectUri,
'sp' => 'ae',
'state' => $this->withState ? uniqid('aeauth', true) : null,
'view' => 'web'
]);
}
}

View File

@ -15,6 +15,7 @@ namespace RetailCrm\Builder;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Component\ServiceLocator; use RetailCrm\Component\ServiceLocator;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Interfaces\BuilderInterface; use RetailCrm\Interfaces\BuilderInterface;
use RetailCrm\Interfaces\ContainerAwareInterface; use RetailCrm\Interfaces\ContainerAwareInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface; use RetailCrm\Interfaces\TopRequestFactoryInterface;
@ -39,6 +40,9 @@ class ClientBuilder implements ContainerAwareInterface, BuilderInterface
/** @var \RetailCrm\Interfaces\AppDataInterface $appData */ /** @var \RetailCrm\Interfaces\AppDataInterface $appData */
private $appData; private $appData;
/** @var \RetailCrm\Interfaces\AuthenticatorInterface $authenticator */
private $authenticator;
/** /**
* @return static * @return static
*/ */
@ -58,6 +62,17 @@ class ClientBuilder implements ContainerAwareInterface, BuilderInterface
return $this; return $this;
} }
/**
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
*
* @return ClientBuilder
*/
public function setAuthenticator(AuthenticatorInterface $authenticator): ClientBuilder
{
$this->authenticator = $authenticator;
return $this;
}
/** /**
* @return \RetailCrm\TopClient\Client * @return \RetailCrm\TopClient\Client
* @throws \RetailCrm\Component\Exception\ValidationException * @throws \RetailCrm\Component\Exception\ValidationException
@ -71,6 +86,11 @@ class ClientBuilder implements ContainerAwareInterface, BuilderInterface
$client->setRequestFactory($this->container->get(TopRequestFactoryInterface::class)); $client->setRequestFactory($this->container->get(TopRequestFactoryInterface::class));
$client->setServiceLocator($this->container->get(ServiceLocator::class)); $client->setServiceLocator($this->container->get(ServiceLocator::class));
$client->setProcessor($this->container->get(TopRequestProcessorInterface::class)); $client->setProcessor($this->container->get(TopRequestProcessorInterface::class));
if (null !== $this->authenticator) {
$client->setAuthenticator($this->authenticator);
}
$client->validateSelf(); $client->validateSelf();
return $client; return $client;

View File

@ -50,6 +50,11 @@ class AppData implements AppDataInterface
*/ */
private $appSecret; private $appSecret;
/**
* @var string $redirectUri
*/
private $redirectUri;
/** /**
* AppData constructor. * AppData constructor.
* *
@ -57,11 +62,12 @@ class AppData implements AppDataInterface
* @param string $appKey * @param string $appKey
* @param string $appSecret * @param string $appSecret
*/ */
public function __construct(string $serviceUrl, string $appKey, string $appSecret) public function __construct(string $serviceUrl, string $appKey, string $appSecret, string $redirectUri = '')
{ {
$this->serviceUrl = $serviceUrl; $this->serviceUrl = $serviceUrl;
$this->appKey = $appKey; $this->appKey = $appKey;
$this->appSecret = $appSecret; $this->appSecret = $appSecret;
$this->redirectUri = $redirectUri;
} }
/** /**
@ -87,4 +93,12 @@ class AppData implements AppDataInterface
{ {
return $this->appSecret; return $this->appSecret;
} }
/**
* @return string
*/
public function getRedirectUri(): string
{
return $this->redirectUri;
}
} }

View File

@ -0,0 +1,52 @@
<?php
/**
* PHP version 7.4
*
* @category TokenAuthenticator
* @package RetailCrm\Component\Authenticator
* @author RetailCRM <integration@retailcrm.ru>
* @license http://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Component\Authenticator;
use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Model\Request\BaseRequest;
/**
* Class TokenAuthenticator
*
* @category TokenAuthenticator
* @package RetailCrm\Component\Authenticator
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license https://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class TokenAuthenticator implements AuthenticatorInterface
{
/**
* @var string $token
*/
private $token;
/**
* TokenAuthenticator constructor.
*
* @param string $token
*/
public function __construct(string $token)
{
$this->token = $token;
}
/**
* @inheritDoc
*/
public function authenticate(BaseRequest $request): void
{
$request->session = $this->token;
}
}

View File

@ -39,4 +39,9 @@ interface AppDataInterface
* @return string * @return string
*/ */
public function getAppSecret(): string; public function getAppSecret(): string;
/**
* @return string
*/
public function getRedirectUri(): string;
} }

View File

@ -0,0 +1,36 @@
<?php
/**
* PHP version 7.3
*
* @category AuthenticatorInterface
* @package RetailCrm\Interfaces
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT https://mit-license.org
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Interfaces;
use RetailCrm\Model\Request\BaseRequest;
/**
* Interface AuthenticatorInterface
*
* @category AuthenticatorInterface
* @package RetailCrm\Interfaces
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT https://mit-license.org
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
interface AuthenticatorInterface
{
/**
* Authenticate provided request
*
* @param \RetailCrm\Model\Request\BaseRequest $request
*/
public function authenticate(BaseRequest $request): void;
}

View File

@ -14,12 +14,15 @@ namespace RetailCrm\TopClient;
use DateTime; use DateTime;
use JMS\Serializer\SerializerInterface; use JMS\Serializer\SerializerInterface;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface; use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use RetailCrm\Builder\AuthorizationUriBuilder;
use RetailCrm\Component\Exception\TopApiException; use RetailCrm\Component\Exception\TopApiException;
use RetailCrm\Component\Exception\TopClientException; use RetailCrm\Component\Exception\TopClientException;
use RetailCrm\Component\ServiceLocator; use RetailCrm\Component\ServiceLocator;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface; use RetailCrm\Interfaces\TopRequestFactoryInterface;
use RetailCrm\Interfaces\TopRequestProcessorInterface; use RetailCrm\Interfaces\TopRequestProcessorInterface;
use RetailCrm\Model\Request\BaseRequest; use RetailCrm\Model\Request\BaseRequest;
@ -80,6 +83,11 @@ class Client
*/ */
protected $processor; protected $processor;
/**
* @var \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
*/
protected $authenticator;
/** /**
* Client constructor. * Client constructor.
* *
@ -130,14 +138,6 @@ class Client
$this->serviceLocator = $serviceLocator; $this->serviceLocator = $serviceLocator;
} }
/**
* @return \RetailCrm\Component\ServiceLocator
*/
public function getServiceLocator(): ServiceLocator
{
return $this->serviceLocator;
}
/** /**
* @param \RetailCrm\Interfaces\TopRequestProcessorInterface $processor * @param \RetailCrm\Interfaces\TopRequestProcessorInterface $processor
* *
@ -150,10 +150,45 @@ class Client
} }
/** /**
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
*
* @return Client
*/
public function setAuthenticator(AuthenticatorInterface $authenticator): Client
{
$this->authenticator = $authenticator;
return $this;
}
/**
* @return \RetailCrm\Component\ServiceLocator
*/
public function getServiceLocator(): ServiceLocator
{
return $this->serviceLocator;
}
/**
* @param bool $withState
*
* @return string
*
* $withState is passed to AuthorizationUriBuilder.
* @see AuthorizationUriBuilder::__construct
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*/
public function getAuthorizationUri(bool $withState = false): string
{
$builder = new AuthorizationUriBuilder($this->appData->getAppKey(), $this->appData->getAppSecret(), $withState);
return $builder->build();
}
/**
* Send TOP request
*
* @param \RetailCrm\Model\Request\BaseRequest $request * @param \RetailCrm\Model\Request\BaseRequest $request
* *
* @return TopResponseInterface * @return TopResponseInterface
* @throws \Psr\Http\Client\ClientExceptionInterface
* @throws \RetailCrm\Component\Exception\ValidationException * @throws \RetailCrm\Component\Exception\ValidationException
* @throws \RetailCrm\Component\Exception\FactoryException * @throws \RetailCrm\Component\Exception\FactoryException
* @throws \RetailCrm\Component\Exception\TopClientException * @throws \RetailCrm\Component\Exception\TopClientException
@ -168,7 +203,12 @@ class Client
$this->processor->process($request, $this->appData); $this->processor->process($request, $this->appData);
$httpRequest = $this->requestFactory->fromModel($request, $this->appData); $httpRequest = $this->requestFactory->fromModel($request, $this->appData);
try {
$httpResponse = $this->httpClient->sendRequest($httpRequest); $httpResponse = $this->httpClient->sendRequest($httpRequest);
} catch (ClientExceptionInterface $exception) {
throw new TopClientException(sprintf('Error sending request: %s', $exception->getMessage()), $exception);
}
/** @var BaseResponse $response */ /** @var BaseResponse $response */
$response = $this->serializer->deserialize( $response = $this->serializer->deserialize(
@ -188,6 +228,28 @@ class Client
return $response; return $response;
} }
/**
* Send authenticated TOP request
*
* @param \RetailCrm\Model\Request\BaseRequest $request
*
* @return \RetailCrm\Model\Response\TopResponseInterface
* @throws \RetailCrm\Component\Exception\FactoryException
* @throws \RetailCrm\Component\Exception\TopApiException
* @throws \RetailCrm\Component\Exception\TopClientException
* @throws \RetailCrm\Component\Exception\ValidationException
*/
public function sendAuthenticatedRequest(BaseRequest $request): TopResponseInterface
{
if (null === $this->authenticator) {
throw new TopClientException('Authenticator is not provided');
}
$this->authenticator->authenticate($request);
return $this->sendRequest($request);
}
/** /**
* Returns body stream data (it should work like that in order to keep compatibility with some implementations). * Returns body stream data (it should work like that in order to keep compatibility with some implementations).
* *

View File

@ -0,0 +1,41 @@
<?php
/**
* PHP version 7.4
*
* @category MatcherException
* @package RetailCrm\Test
* @author RetailCRM <integration@retailcrm.ru>
* @license http://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Test;
use Exception;
use Throwable;
/**
* Class MatcherException
*
* @category MatcherException
* @package RetailCrm\Test
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license https://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class MatcherException extends Exception
{
/**
* MatcherException constructor.
*
* @param string $message
* @param int $code
* @param \Throwable|null $previous
*/
public function __construct($message = "Cannot match any request", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@ -14,11 +14,13 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use RetailCrm\Builder\ContainerBuilder; use RetailCrm\Builder\ContainerBuilder;
use RetailCrm\Component\AppData; use RetailCrm\Component\AppData;
use RetailCrm\Component\Authenticator\TokenAuthenticator;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Component\Environment; use RetailCrm\Component\Environment;
use RetailCrm\Component\Logger\StdoutLogger; use RetailCrm\Component\Logger\StdoutLogger;
use RetailCrm\Factory\FileItemFactory; use RetailCrm\Factory\FileItemFactory;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Interfaces\FileItemFactoryInterface; use RetailCrm\Interfaces\FileItemFactoryInterface;
/** /**
@ -65,8 +67,9 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
{ {
return $this->getAppData( return $this->getAppData(
self::getenv('ENDPOINT', AppData::OVERSEAS_ENDPOINT), self::getenv('ENDPOINT', AppData::OVERSEAS_ENDPOINT),
self::getenv('APP_KEY', 'appKey'), self::getEnvAppKey(),
self::getenv('APP_SECRET', 'helloworld') self::getenv('APP_SECRET', 'helloworld'),
self::getenv('REDIRECT_URI', 'https://example.com'),
); );
} }
@ -75,14 +78,32 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
* @param string $appKey * @param string $appKey
* @param string $appSecret * @param string $appSecret
* *
* @param string $redirectUri
*
* @return \RetailCrm\Interfaces\AppDataInterface * @return \RetailCrm\Interfaces\AppDataInterface
*/ */
protected function getAppData( protected function getAppData(
string $endpoint = AppData::OVERSEAS_ENDPOINT, string $endpoint = AppData::OVERSEAS_ENDPOINT,
string $appKey = 'appKey', string $appKey = 'appKey',
string $appSecret = 'helloworld' string $appSecret = 'helloworld',
string $redirectUri = 'https://example.com'
): AppDataInterface{ ): AppDataInterface{
return new AppData($endpoint, $appKey, $appSecret); return new AppData($endpoint, $appKey, $appSecret, $redirectUri);
}
protected function getEnvTokenAuthenticator(): AuthenticatorInterface
{
return $this->getTokenAuthenticator(self::getenv('SESSION', 'test'));
}
/**
* @param string $token
*
* @return \RetailCrm\Interfaces\AuthenticatorInterface
*/
protected function getTokenAuthenticator(string $token): AuthenticatorInterface
{
return new TokenAuthenticator($token);
} }
/** /**
@ -156,6 +177,14 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
->withBody($data); ->withBody($data);
} }
/**
* @return string
*/
protected static function getEnvAppKey(): string
{
return self::getenv('APP_KEY', 'appKey');
}
/** /**
* @param string $variable * @param string $variable
* @param mixed $default * @param mixed $default
@ -176,7 +205,9 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
*/ */
protected static function getMockClient(): MockClient protected static function getMockClient(): MockClient
{ {
return new MockClient(); $client = new MockClient();
$client->setDefaultException(new MatcherException());
return $client;
} }
/** /**

View File

@ -59,7 +59,7 @@ class ClientTest extends TestCase
$client = ClientBuilder::create() $client = ClientBuilder::create()
->setContainer($this->getContainer($mockClient)) ->setContainer($this->getContainer($mockClient))
->setAppData(new AppData(AppData::OVERSEAS_ENDPOINT, 'appKey', 'appSecret')) ->setAppData($this->getEnvAppData())
->build(); ->build();
$this->expectExceptionMessage($errorBody->msg); $this->expectExceptionMessage($errorBody->msg);
@ -71,7 +71,7 @@ class ClientTest extends TestCase
{ {
$client = ClientBuilder::create() $client = ClientBuilder::create()
->setContainer($this->getContainer(self::getMockClient())) ->setContainer($this->getContainer(self::getMockClient()))
->setAppData(new AppData(AppData::OVERSEAS_ENDPOINT, 'appKey', 'appSecret')) ->setAppData($this->getEnvAppData())
->build(); ->build();
$request = new HttpDnsGetRequest(); $request = new HttpDnsGetRequest();
@ -118,7 +118,7 @@ EOF;
RequestMatcher::createMatcher('api.taobao.com') RequestMatcher::createMatcher('api.taobao.com')
->setPath('/router/rest') ->setPath('/router/rest')
->setOptionalQueryParams([ ->setOptionalQueryParams([
'app_key' => 'appKey', 'app_key' => self::getEnvAppKey(),
'method' => 'aliexpress.solution.seller.category.tree.query', 'method' => 'aliexpress.solution.seller.category.tree.query',
'category_id' => '5090300', 'category_id' => '5090300',
'filter_no_permission' => 1 'filter_no_permission' => 1
@ -127,7 +127,8 @@ EOF;
); );
$client = ClientBuilder::create() $client = ClientBuilder::create()
->setContainer($this->getContainer($mock)) ->setContainer($this->getContainer($mock))
->setAppData(new AppData(AppData::OVERSEAS_ENDPOINT, 'appKey', 'appSecret')) ->setAppData($this->getEnvAppData())
->setAuthenticator($this->getEnvTokenAuthenticator())
->build(); ->build();
$request = new SolutionSellerCategoryTreeQuery(); $request = new SolutionSellerCategoryTreeQuery();
@ -135,7 +136,7 @@ EOF;
$request->filterNoPermission = true; $request->filterNoPermission = true;
/** @var SolutionSellerCategoryTreeQueryResponse $response */ /** @var SolutionSellerCategoryTreeQueryResponse $response */
$result = $client->sendRequest($request); $result = $client->sendAuthenticatedRequest($request);
self::assertInstanceOf(SolutionSellerCategoryTreeQueryResponseData::class, $result->responseData); self::assertInstanceOf(SolutionSellerCategoryTreeQueryResponseData::class, $result->responseData);
self::assertInstanceOf( self::assertInstanceOf(
@ -184,14 +185,15 @@ EOF;
RequestMatcher::createMatcher('api.taobao.com') RequestMatcher::createMatcher('api.taobao.com')
->setPath('/router/rest') ->setPath('/router/rest')
->setOptionalQueryParams([ ->setOptionalQueryParams([
'app_key' => 'appKey', 'app_key' => self::getEnvAppKey(),
'method' => 'aliexpress.postproduct.redefining.categoryforecast' 'method' => 'aliexpress.postproduct.redefining.categoryforecast'
]), ]),
$this->responseJson(200, $json) $this->responseJson(200, $json)
); );
$client = ClientBuilder::create() $client = ClientBuilder::create()
->setContainer($this->getContainer($mock)) ->setContainer($this->getContainer($mock))
->setAppData(new AppData(AppData::OVERSEAS_ENDPOINT, 'appKey', 'appSecret')) ->setAppData($this->getEnvAppData())
->setAuthenticator($this->getEnvTokenAuthenticator())
->build(); ->build();
$request = new PostproductRedefiningCategoryForecast(); $request = new PostproductRedefiningCategoryForecast();
@ -199,7 +201,7 @@ EOF;
$request->locale = 'en'; $request->locale = 'en';
/** @var PostproductRedefiningCategoryForecastResponse $response */ /** @var PostproductRedefiningCategoryForecastResponse $response */
$response = $client->sendRequest($request); $response = $client->sendAuthenticatedRequest($request);
self::assertInstanceOf(PostproductRedefiningCategoryForecastResponse::class, $response); self::assertInstanceOf(PostproductRedefiningCategoryForecastResponse::class, $response);
self::assertEquals( self::assertEquals(