1
0
mirror of synced 2025-02-19 21:43:20 +03:00

fix for signer algorithm, test for signer

This commit is contained in:
Pavel 2020-09-28 17:18:21 +03:00
parent f6a0b56c69
commit 1b6e138e64
14 changed files with 373 additions and 65 deletions

104
src/Component/AppData.php Normal file
View File

@ -0,0 +1,104 @@
<?php
/**
* PHP version 7.3
*
* @category AppData
* @package RetailCrm\Component
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Component;
use RetailCrm\Interfaces\AppDataInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class AppData
*
* @category AppData
* @package RetailCrm\Component
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class AppData implements AppDataInterface
{
public const OVERSEAS_ENDPOINT = 'https://api.taobao.com/router/rest';
public const CHINESE_ENDPOINT = 'https://eco.taobao.com/router/rest';
public const AVAILABLE_ENDPOINTS = [self::OVERSEAS_ENDPOINT, self::CHINESE_ENDPOINT];
/**
* @var string $serviceUrl
* @Assert\Url()
* @Assert\Choice(choices=Client::AVAILABLE_ENDPOINTS, message="Invalid endpoint provided.")
*/
protected $serviceUrl;
/**
* @var string $appKey
* @Assert\NotBlank()
*/
private $appKey;
/**
* @var string $appSecret
* @Assert\NotBlank()
*/
private $appSecret;
/**
* AppData constructor.
*
* @param string $serviceUrl
* @param string $appKey
* @param string $appSecret
*/
public function __construct(string $serviceUrl, string $appKey, string $appSecret)
{
$this->serviceUrl = $serviceUrl;
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}
/**
* Constructor shortcut
*
* @param string $serviceUrl
* @param string $appKey
* @param string $appSecret
*
* @return \RetailCrm\Component\AppData
*/
public static function create(string $serviceUrl, string $appKey, string $appSecret): AppDataInterface
{
return new self($serviceUrl, $appKey, $appSecret);
}
/**
* @return string
*/
public function getServiceUrl(): string
{
return $this->serviceUrl;
}
/**
* @return string
*/
public function getAppKey(): string
{
return $this->appKey;
}
/**
* @return string
*/
public function getAppSecret(): string
{
return $this->appSecret;
}
}

View File

@ -57,20 +57,4 @@ class TokenAuthenticator implements AuthenticatorInterface
$request->appKey = $this->appKey; $request->appKey = $this->appKey;
$request->session = $this->token; $request->session = $this->token;
} }
/**
* @return string
*/
public function getAppKey(): string
{
return $this->appKey;
}
/**
* @return string
*/
public function getAppSecret(): string
{
return $this->token;
}
} }

View File

@ -14,6 +14,7 @@ namespace RetailCrm\Factory;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\AuthenticatorInterface; use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Interfaces\ContainerAwareInterface; use RetailCrm\Interfaces\ContainerAwareInterface;
use RetailCrm\Interfaces\FactoryInterface; use RetailCrm\Interfaces\FactoryInterface;
@ -34,8 +35,8 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
{ {
use ContainerAwareTrait; use ContainerAwareTrait;
/** @var string $serviceUrl */ /** @var \RetailCrm\Interfaces\AppDataInterface $appData */
private $serviceUrl; private $appData;
/*** @var AuthenticatorInterface $authenticator */ /*** @var AuthenticatorInterface $authenticator */
protected $authenticator; protected $authenticator;
@ -54,13 +55,13 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
} }
/** /**
* @param string $serviceUrl * @param \RetailCrm\Interfaces\AppDataInterface $appData
* *
* @return $this * @return ClientFactory
*/ */
public function setServiceUrl(string $serviceUrl): self public function setAppData(AppDataInterface $appData): ClientFactory
{ {
$this->serviceUrl = $serviceUrl; $this->appData = $appData;
return $this; return $this;
} }
@ -81,7 +82,7 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
*/ */
public function create(): Client public function create(): Client
{ {
$client = new Client($this->serviceUrl, $this->authenticator); $client = new Client($this->appData, $this->authenticator);
$client->setHttpClient($this->container->get(Constants::HTTP_CLIENT)); $client->setHttpClient($this->container->get(Constants::HTTP_CLIENT));
$client->setSerializer($this->container->get(Constants::SERIALIZER)); $client->setSerializer($this->container->get(Constants::SERIALIZER));
$client->setValidator($this->container->get(Constants::VALIDATOR)); $client->setValidator($this->container->get(Constants::VALIDATOR));

View File

@ -15,6 +15,7 @@ namespace RetailCrm\Factory;
use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Serializer; use JMS\Serializer\Serializer;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Service\RequestSigner;
use Shieldon\Psr17\StreamFactory; use Shieldon\Psr17\StreamFactory;
use Shieldon\Psr17\UploadedFileFactory as BaseFactory; use Shieldon\Psr17\UploadedFileFactory as BaseFactory;
use JMS\Serializer\SerializerBuilder; use JMS\Serializer\SerializerBuilder;
@ -109,6 +110,9 @@ class ContainerFactory implements FactoryInterface
); );
$container->set(Constants::SERIALIZER, $this->getSerializer()); $container->set(Constants::SERIALIZER, $this->getSerializer());
$container->set(UploadedFileFactory::class, new UploadedFileFactory(new BaseFactory(), new StreamFactory())); $container->set(UploadedFileFactory::class, new UploadedFileFactory(new BaseFactory(), new StreamFactory()));
$container->set(RequestSigner::class, function (ContainerInterface $container) {
return new RequestSigner($container->get(Constants::SERIALIZER));
});
} }
/** /**

View File

@ -0,0 +1,42 @@
<?php
/**
* PHP version 7.3
*
* @category AppDataInterface
* @package RetailCrm\Interfaces
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Interfaces;
/**
* Interface AppDataInterface
*
* @category AppDataInterface
* @package RetailCrm\Interfaces
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
interface AppDataInterface
{
/**
* @return string
*/
public function getServiceUrl(): string;
/**
* @return string
*/
public function getAppKey(): string;
/**
* @return string
*/
public function getAppSecret(): string;
}

View File

@ -31,14 +31,4 @@ interface AuthenticatorInterface
* @param \RetailCrm\Model\Request\BaseRequest $request * @param \RetailCrm\Model\Request\BaseRequest $request
*/ */
public function authenticate(BaseRequest $request): void; public function authenticate(BaseRequest $request): void;
/**
* @return string
*/
public function getAppKey(): string;
/**
* @return string
*/
public function getAppSecret(): string;
} }

View File

@ -30,8 +30,8 @@ interface RequestSignerInterface
/** /**
* Signs provided request. * Signs provided request.
* *
* @param \RetailCrm\Model\Request\BaseRequest $request * @param \RetailCrm\Model\Request\BaseRequest $request
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator * @param \RetailCrm\Interfaces\AppDataInterface $authenticator
*/ */
public function sign(BaseRequest $request, AuthenticatorInterface $authenticator): void; public function sign(BaseRequest $request, AppDataInterface $authenticator): void;
} }

View File

@ -58,7 +58,7 @@ abstract class BaseRequest
/** /**
* @var \DateTime $timestamp * @var \DateTime $timestamp
* *
* @JMS\Type("DateTime<'yyyy-MM-dd H:i:s'>") * @JMS\Type("DateTime<'Y-m-d H:i:s'>")
* @JMS\SerializedName("timestamp") * @JMS\SerializedName("timestamp")
* @Assert\NotBlank() * @Assert\NotBlank()
*/ */

View File

@ -14,7 +14,7 @@ namespace RetailCrm\Service;
use JMS\Serializer\SerializerInterface; use JMS\Serializer\SerializerInterface;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Interfaces\AuthenticatorInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\RequestSignerInterface; use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Model\Request\BaseRequest; use RetailCrm\Model\Request\BaseRequest;
@ -46,10 +46,10 @@ class RequestSigner implements RequestSignerInterface
} }
/** /**
* @param BaseRequest $request * @param BaseRequest $request
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator * @param \RetailCrm\Interfaces\AppDataInterface $appData
*/ */
public function sign(BaseRequest $request, AuthenticatorInterface $authenticator): void public function sign(BaseRequest $request, AppDataInterface $appData): void
{ {
$stringToBeSigned = ''; $stringToBeSigned = '';
$params = $this->getRequestData($request); $params = $this->getRequestData($request);
@ -60,10 +60,12 @@ class RequestSigner implements RequestSignerInterface
switch ($request->signMethod) { switch ($request->signMethod) {
case Constants::SIGN_TYPE_MD5: case Constants::SIGN_TYPE_MD5:
$request->sign = strtoupper(md5($authenticator->getAppSecret() . $stringToBeSigned)); $request->sign = strtoupper(md5(
$appData->getAppSecret() . $stringToBeSigned . $appData->getAppSecret()
));
break; break;
case Constants::SIGN_TYPE_HMAC: case Constants::SIGN_TYPE_HMAC:
$request->sign = strtoupper(hash_hmac('md5', $stringToBeSigned, $authenticator->getAppSecret())); $request->sign = strtoupper(hash_hmac('md5', $stringToBeSigned, $appData->getAppSecret()));
break; break;
} }
} }

View File

@ -14,6 +14,7 @@ namespace RetailCrm\TopClient;
use JMS\Serializer\SerializerInterface; use JMS\Serializer\SerializerInterface;
use Psr\Http\Client\ClientInterface; use Psr\Http\Client\ClientInterface;
use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\AuthenticatorInterface; use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Traits\ValidatorAwareTrait; use RetailCrm\Traits\ValidatorAwareTrait;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -32,16 +33,10 @@ class Client
{ {
use ValidatorAwareTrait; use ValidatorAwareTrait;
public const OVERSEAS_ENDPOINT = 'https://api.taobao.com/router/rest';
public const CHINESE_ENDPOINT = 'https://eco.taobao.com/router/rest';
public const AVAILABLE_ENDPOINTS = [self::OVERSEAS_ENDPOINT, self::CHINESE_ENDPOINT];
/** /**
* @var string $serviceUrl * @var \RetailCrm\Interfaces\AppDataInterface $appData
* @Assert\Url()
* @Assert\Choice(choices=Client::AVAILABLE_ENDPOINTS, message="Invalid endpoint provided.")
*/ */
protected $serviceUrl; protected $appData;
/** /**
* @var AuthenticatorInterface $authenticator * @var AuthenticatorInterface $authenticator
@ -64,12 +59,12 @@ class Client
/** /**
* Client constructor. * Client constructor.
* *
* @param string $serviceUrl * @param \RetailCrm\Interfaces\AppDataInterface $appData
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator * @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
*/ */
public function __construct(string $serviceUrl, AuthenticatorInterface $authenticator) public function __construct(AppDataInterface $appData, AuthenticatorInterface $authenticator)
{ {
$this->serviceUrl = $serviceUrl; $this->appData = $appData;
$this->authenticator = $authenticator; $this->authenticator = $authenticator;
} }

View File

@ -0,0 +1,33 @@
<?php
namespace RetailCrm\Test;
use Psr\Container\ContainerInterface;
use RetailCrm\Component\Environment;
use RetailCrm\Factory\ContainerFactory;
/**
* Class TestCase
*
* @category TestCase
* @package ${NAMESPACE}
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class TestCase extends \PHPUnit\Framework\TestCase
{
private $container;
public function getContainer($recreate = false): ContainerInterface
{
if (null === $this->container || $recreate) {
$this->container = ContainerFactory::withEnv(Environment::TEST)
->withClient(new \GuzzleHttp\Client())
->create();
}
return $this->container;
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* PHP version 7.3
*
* @category TestRequest
* @package RetailCrm\Test
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Test;
use JMS\Serializer\Annotation as JMS;
use RetailCrm\Model\Request\BaseRequest;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class TestRequest
*
* @category TestRequest
* @package RetailCrm\Test
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class TestSignerRequest extends BaseRequest
{
/**
* @var string $serviceName
*
* @JMS\Type("string")
* @JMS\SerializedName("service_name")
* @Assert\NotBlank()
*/
public $serviceName;
/**
* @var string $outRef
*
* @JMS\Type("string")
* @JMS\SerializedName("out_ref")
* @Assert\NotBlank()
*/
public $outRef;
/**
* @var string $sendType
*
* @JMS\Type("string")
* @JMS\SerializedName("send_type")
* @Assert\NotBlank()
*/
public $sendType;
/**
* @var string $logisitics_no
*
* @JMS\Type("string")
* @JMS\SerializedName("logisitics_no")
* @Assert\NotBlank()
*/
public $logisticsNo;
}

View File

@ -4,26 +4,25 @@
* PHP version 7.4 * PHP version 7.4
* *
* @category ClientFactoryTest * @category ClientFactoryTest
* @package Component * @package RetailCrm\Tests\Component
* @author RetailCRM <integration@retailcrm.ru> * @author RetailCRM <integration@retailcrm.ru>
* @license MIT * @license MIT
* @link http://retailcrm.ru * @link http://retailcrm.ru
* @see http://help.retailcrm.ru * @see http://help.retailcrm.ru
*/ */
namespace Component; namespace RetailCrm\Tests\Component;
use PHPUnit\Framework\TestCase; use RetailCrm\Component\AppData;
use RetailCrm\Component\Authenticator\TokenAuthenticator; use RetailCrm\Component\Authenticator\TokenAuthenticator;
use RetailCrm\Component\Environment;
use RetailCrm\Factory\ClientFactory; use RetailCrm\Factory\ClientFactory;
use RetailCrm\Factory\ContainerFactory; use RetailCrm\Test\TestCase;
use RetailCrm\TopClient\Client; use RetailCrm\TopClient\Client;
/** /**
* Class ClientFactoryTest * Class ClientFactoryTest
* *
* @category ClientFactoryTest * @category ClientFactoryTest
* @package Component * @package RetailCrm\Tests\Component
* @author RetailDriver LLC <integration@retailcrm.ru> * @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT * @license MIT
* @link http://retailcrm.ru * @link http://retailcrm.ru
@ -33,11 +32,8 @@ class ClientFactoryTest extends TestCase
{ {
public function testCreateClient() public function testCreateClient()
{ {
$client = ClientFactory::withContainer( $client = ClientFactory::withContainer($this->getContainer())
ContainerFactory::withEnv(Environment::DEV) ->setAppData(AppData::create(AppData::OVERSEAS_ENDPOINT, 'appKey', 'helloworld'))
->withClient(new \GuzzleHttp\Client())
->create()
)->setServiceUrl(Client::OVERSEAS_ENDPOINT)
->setAuthenticator(new TokenAuthenticator('appKey', 'token')) ->setAuthenticator(new TokenAuthenticator('appKey', 'token'))
->create(); ->create();

View File

@ -0,0 +1,91 @@
<?php
/**
* PHP version 7.3
*
* @category RequestSigner
* @package RetailCrm\Tests\Service
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Tests\Service;
use DateTime;
use RetailCrm\Component\AppData;
use RetailCrm\Component\Authenticator\TokenAuthenticator;
use RetailCrm\Component\Constants;
use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Service\RequestSigner;
use RetailCrm\Test\TestCase;
use RetailCrm\Test\TestSignerRequest;
/**
* Class RequestSigner
*
* @category RequestSigner
* @package RetailCrm\Tests\Service
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class RequestSignerTest extends TestCase
{
/**
* @dataProvider signDataProvider
*
* @param \RetailCrm\Test\TestSignerRequest $request
* @param \RetailCrm\Interfaces\AppDataInterface $appData
* @param string $expectedHash
*/
public function testSign(TestSignerRequest $request, AppDataInterface $appData, string $expectedHash): void
{
/** @var RequestSignerInterface $signer */
$signer = $this->getContainer()->get(RequestSigner::class);
$signer->sign($request, $appData);
self::assertEquals($expectedHash, $request->sign);
}
public function signDataProvider(): array
{
$appData = AppData::create(AppData::OVERSEAS_ENDPOINT, 'appKey', 'helloworld');
return [
[
$this->getRequestWithSignMethod(Constants::SIGN_TYPE_MD5),
$appData,
'4BC79C5FAA1B5E254E95A97E65BACEAB'
],
[
$this->getRequestWithSignMethod(Constants::SIGN_TYPE_HMAC),
$appData,
'497FA7FCAD98F4F335EFAE2451F8291D'
]
];
}
/**
* @param string $signMethod
*
* @return \RetailCrm\Test\TestSignerRequest
*/
private function getRequestWithSignMethod(string $signMethod): TestSignerRequest
{
$request = new TestSignerRequest();
$request->method = 'aliexpress.solution.order.fulfill';
$request->appKey = '12345678';
$request->session = 'test';
$request->timestamp = DateTime::createFromFormat('Y-m-d H:i:s', '2016-01-01 12:00:00');
$request->signMethod = $signMethod;
$request->serviceName = 'SPAIN_LOCAL_CORREOS';
$request->outRef = '1000006270175804';
$request->sendType = 'all';
$request->logisticsNo = 'ES2019COM0000123456';
return $request;
}
}