mirror of
https://github.com/ovh/php-ovh.git
synced 2023-11-05 03:20:26 +01:00
Merge pull request #125 from deathiop/v2
feat: allow /v1 or /v2 prefixes in path
This commit is contained in:
commit
9a29f720d0
@ -6,7 +6,10 @@
|
|||||||
<property name="lineLimit" value="180"/>
|
<property name="lineLimit" value="180"/>
|
||||||
</properties>
|
</properties>
|
||||||
</rule>
|
</rule>
|
||||||
|
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
|
||||||
|
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||||
|
</rule>
|
||||||
<file>src</file>
|
<file>src</file>
|
||||||
<file>tests</file>
|
<file>tests</file>
|
||||||
<arg name="encoding" value="utf-8"/>
|
<arg name="encoding" value="utf-8"/>
|
||||||
</ruleset>
|
</ruleset>
|
||||||
|
23
src/Api.php
23
src/Api.php
@ -211,6 +211,24 @@ class Api
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getTarget returns the URL to target given an endpoint and a path.
|
||||||
|
* If the path starts with `/v1` or `/v2`, then remove the trailing `/1.0` from the endpoint.
|
||||||
|
*
|
||||||
|
* @param string path to use prefix from
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getTarget($path) : string
|
||||||
|
{
|
||||||
|
$endpoint = $this->endpoint;
|
||||||
|
if (substr($endpoint, -4) == '/1.0' && (
|
||||||
|
substr($path, 0, 3) == '/v1' ||
|
||||||
|
substr($path, 0, 3) == '/v2')) {
|
||||||
|
$endpoint = substr($endpoint, 0, strlen($endpoint)-4);
|
||||||
|
}
|
||||||
|
return $endpoint . $path;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main method of this wrapper. It will
|
* This is the main method of this wrapper. It will
|
||||||
* sign a given query and return its result.
|
* sign a given query and return its result.
|
||||||
@ -238,7 +256,7 @@ class Api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $this->endpoint . $path;
|
$url = $this->getTarget($path);
|
||||||
$request = new Request($method, $url);
|
$request = new Request($method, $url);
|
||||||
if (isset($content) && $method === 'GET') {
|
if (isset($content) && $method === 'GET') {
|
||||||
$query_string = $request->getUri()->getQuery();
|
$query_string = $request->getUri()->getQuery();
|
||||||
@ -280,9 +298,8 @@ class Api
|
|||||||
}
|
}
|
||||||
$headers['Content-Type'] = 'application/json; charset=utf-8';
|
$headers['Content-Type'] = 'application/json; charset=utf-8';
|
||||||
|
|
||||||
|
$headers['X-Ovh-Application'] = $this->application_key ?? '';
|
||||||
if ($is_authenticated) {
|
if ($is_authenticated) {
|
||||||
$headers['X-Ovh-Application'] = $this->application_key;
|
|
||||||
|
|
||||||
if (!isset($this->time_delta)) {
|
if (!isset($this->time_delta)) {
|
||||||
$this->calculateTimeDelta();
|
$this->calculateTimeDelta();
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,13 @@ class ApiFunctionalTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
protected function setUp() :void
|
protected function setUp() :void
|
||||||
{
|
{
|
||||||
|
foreach (['APP_KEY', 'APP_SECRET', 'CONSUMER', 'ENDPOINT'] as $envName) {
|
||||||
|
if (!getenv($envName)) {
|
||||||
|
$this->markTestSkipped("Skip test due to missing $envName variable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->application_key = getenv('APP_KEY');
|
$this->application_key = getenv('APP_KEY');
|
||||||
$this->application_secret = getenv('APP_SECRET');
|
$this->application_secret = getenv('APP_SECRET');
|
||||||
$this->consumer_key = getenv('CONSUMER');
|
$this->consumer_key = getenv('CONSUMER');
|
||||||
|
@ -29,6 +29,8 @@ namespace Ovh\tests;
|
|||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use GuzzleHttp\Handler\MockHandler;
|
||||||
|
use GuzzleHttp\HandlerStack;
|
||||||
use GuzzleHttp\Middleware;
|
use GuzzleHttp\Middleware;
|
||||||
use GuzzleHttp\Psr7;
|
use GuzzleHttp\Psr7;
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
@ -37,6 +39,29 @@ use Ovh\Api;
|
|||||||
use Ovh\Exceptions\InvalidParameterException;
|
use Ovh\Exceptions\InvalidParameterException;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
# Mock values
|
||||||
|
const MOCK_APPLICATION_KEY = "TDPKJdwZwAQPwKX2";
|
||||||
|
const MOCK_APPLICATION_SECRET = "9ufkBmLaTQ9nz5yMUlg79taH0GNnzDjk";
|
||||||
|
const MOCK_CONSUMER_KEY = "5mBuy6SUQcRw2ZUxg0cG68BoDKpED4KY";
|
||||||
|
const MOCK_TIME = 1457018875;
|
||||||
|
|
||||||
|
class MockClient extends Client
|
||||||
|
{
|
||||||
|
public $calls = [];
|
||||||
|
|
||||||
|
public function __construct(...$responses)
|
||||||
|
{
|
||||||
|
|
||||||
|
$mock = new MockHandler($responses);
|
||||||
|
$history = Middleware::history($this->calls);
|
||||||
|
|
||||||
|
$handlerStack = HandlerStack::create($mock);
|
||||||
|
$handlerStack->push($history);
|
||||||
|
|
||||||
|
parent::__construct(['handler' => $handlerStack]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Api class
|
* Test Api class
|
||||||
*
|
*
|
||||||
@ -45,60 +70,6 @@ use PHPUnit\Framework\TestCase;
|
|||||||
*/
|
*/
|
||||||
class ApiTest extends TestCase
|
class ApiTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var Client
|
|
||||||
*/
|
|
||||||
private $client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $application_key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $consumer_key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $endpoint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $application_secret;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define id to create object
|
|
||||||
*/
|
|
||||||
protected function setUp() :void
|
|
||||||
{
|
|
||||||
$this->application_key = 'app_key';
|
|
||||||
$this->application_secret = 'app_secret';
|
|
||||||
$this->consumer_key = 'consumer';
|
|
||||||
$this->endpoint = 'ovh-eu';
|
|
||||||
|
|
||||||
$this->client = new Client();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get private and protected method to unit test it
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
*
|
|
||||||
* @return \ReflectionMethod
|
|
||||||
*/
|
|
||||||
protected static function getPrivateMethod($name)
|
|
||||||
{
|
|
||||||
$class = new \ReflectionClass('Ovh\Api');
|
|
||||||
$method = $class->getMethod($name);
|
|
||||||
$method->setAccessible(true);
|
|
||||||
|
|
||||||
return $method;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get private and protected property to unit test it
|
* Get private and protected property to unit test it
|
||||||
*
|
*
|
||||||
@ -121,7 +92,8 @@ class ApiTest extends TestCase
|
|||||||
public function testMissingApplicationKey()
|
public function testMissingApplicationKey()
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidParameterException::class);
|
$this->expectException(InvalidParameterException::class);
|
||||||
$api = new Api(null, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
$this->expectExceptionMessage('Application key parameter is empty');
|
||||||
|
$api = new Api(null, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, new MockClient());
|
||||||
$api->get('/me');
|
$api->get('/me');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +103,8 @@ class ApiTest extends TestCase
|
|||||||
public function testMissingApplicationSecret()
|
public function testMissingApplicationSecret()
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidParameterException::class);
|
$this->expectException(InvalidParameterException::class);
|
||||||
$api = new Api($this->application_key, null, $this->endpoint, $this->consumer_key, $this->client);
|
$this->expectExceptionMessage('Application secret parameter is empty');
|
||||||
|
$api = new Api(MOCK_APPLICATION_KEY, null, 'ovh-eu', MOCK_CONSUMER_KEY, new MockClient());
|
||||||
$api->get('/me');
|
$api->get('/me');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,25 +113,18 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testNoCheckAppKeyForUnauthCall()
|
public function testNoCheckAppKeyForUnauthCall()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(new Response(200, [], '{}'));
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
|
||||||
if ($request->getUri()->getPath() == "/1.0/unauthcall") {
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
$api = new Api(null, null, 'ovh-eu', null, $client);
|
||||||
}));
|
$api->get("/me", null, null, false);
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('{}');
|
|
||||||
|
|
||||||
return $response
|
$calls = $client->calls;
|
||||||
->withStatus(200)
|
$this->assertEquals(1, count($calls));
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
$req = $calls[0]['request'];
|
||||||
}));
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
$api = new Api(null, null, $this->endpoint, $this->consumer_key, $this->client);
|
$this->assertEquals('https://eu.api.ovh.com/1.0/me', $req->getUri()->__toString());
|
||||||
$api->get('/unauthcall', null, null, false);
|
$this->assertEquals('', $req->getHeaderLine('X-Ovh-Application'));
|
||||||
$this->assertEquals(1, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -167,7 +133,8 @@ class ApiTest extends TestCase
|
|||||||
public function testMissingApiEndpoint()
|
public function testMissingApiEndpoint()
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidParameterException::class);
|
$this->expectException(InvalidParameterException::class);
|
||||||
new Api($this->application_key, $this->application_secret, null, $this->consumer_key, $this->client);
|
$this->expectExceptionMessage('Endpoint parameter is empty');
|
||||||
|
new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, null, MOCK_CONSUMER_KEY, new MockClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,7 +143,8 @@ class ApiTest extends TestCase
|
|||||||
public function testBadApiEndpoint()
|
public function testBadApiEndpoint()
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidParameterException::class);
|
$this->expectException(InvalidParameterException::class);
|
||||||
new Api($this->application_key, $this->application_secret, 'i_am_invalid', $this->consumer_key, $this->client);
|
$this->expectExceptionMessage('Unknown provided endpoint');
|
||||||
|
new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'i_am_invalid', MOCK_CONSUMER_KEY, new MockClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,8 +152,7 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testClientCreation()
|
public function testClientCreation()
|
||||||
{
|
{
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key);
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY);
|
||||||
|
|
||||||
$this->assertInstanceOf('\\GuzzleHttp\\Client', $api->getHttpClient());
|
$this->assertInstanceOf('\\GuzzleHttp\\Client', $api->getHttpClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,28 +161,28 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testTimeDeltaCompute()
|
public function testTimeDeltaCompute()
|
||||||
{
|
{
|
||||||
$delay = 10;
|
$client = new MockClient(
|
||||||
|
new Response(200, [], time() - 10),
|
||||||
|
new Response(200, [], '{}'),
|
||||||
|
);
|
||||||
|
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
$api->get("/me");
|
||||||
|
|
||||||
$body = Psr7\Utils::streamFor(time() - 10);
|
|
||||||
|
|
||||||
return $response
|
|
||||||
->withStatus(200)
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$invoker = self::getPrivateMethod('calculateTimeDelta');
|
|
||||||
$property = self::getPrivateProperty('time_delta');
|
$property = self::getPrivateProperty('time_delta');
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$invoker->invokeArgs($api, []);
|
|
||||||
|
|
||||||
$time_delta = $property->getValue($api);
|
$time_delta = $property->getValue($api);
|
||||||
$this->assertNotNull($time_delta);
|
$this->assertEquals($time_delta, -10);
|
||||||
$this->assertEquals($time_delta, $delay * -1);
|
|
||||||
|
$calls = $client->calls;
|
||||||
|
$this->assertEquals(2, count($calls));
|
||||||
|
|
||||||
|
$req = $calls[0]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString());
|
||||||
|
|
||||||
|
$req = $calls[1]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/me', $req->getUri()->__toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,32 +190,30 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testIfConsumerKeyIsReplace()
|
public function testIfConsumerKeyIsReplace()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
new Response(200, [], MOCK_TIME),
|
||||||
|
new Response(
|
||||||
|
200,
|
||||||
|
['Content-Type' => 'application/json; charset=utf-8'],
|
||||||
|
'{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
$body = Psr7\Utils::streamFor('{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}');
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
|
$this->assertNotEquals('consumer_remote', $api->getConsumerKey());
|
||||||
|
$credentials = $api->requestCredentials(['method' => 'GET', 'path' => '/*']);
|
||||||
|
$this->assertEquals('consumer_remote', $api->getConsumerKey());
|
||||||
|
|
||||||
return $response
|
$calls = $client->calls;
|
||||||
->withStatus(200)
|
$this->assertEquals(2, count($calls));
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$property = self::getPrivateProperty('consumer_key');
|
$req = $calls[0]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString());
|
||||||
|
|
||||||
$this->assertEquals('consumer', $this->consumer_key);
|
$req = $calls[1]['request'];
|
||||||
$this->assertNotEquals('consumer_remote', $this->consumer_key);
|
$this->assertEquals('POST', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/credential', $req->getUri()->__toString());
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$accessRules = [json_decode(' { "method": "GET", "path": "/*" } ')];
|
|
||||||
|
|
||||||
$credentials = $api->requestCredentials($accessRules);
|
|
||||||
|
|
||||||
$consumer_key = $property->getValue($api);
|
|
||||||
|
|
||||||
$this->assertEquals($consumer_key, $credentials["consumerKey"]);
|
|
||||||
$this->assertEquals('consumer_remote', $credentials["consumerKey"]);
|
|
||||||
$this->assertNotEquals($consumer_key, $this->consumer_key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,30 +221,15 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testInvalidApplicationKey()
|
public function testInvalidApplicationKey()
|
||||||
{
|
{
|
||||||
|
$error = '{"class":"Client::Forbidden","message":"Invalid application key"}';
|
||||||
$this->expectException(ClientException::class);
|
$this->expectException(ClientException::class);
|
||||||
|
$this->expectExceptionCode(403);
|
||||||
|
$this->expectExceptionMessage($error);
|
||||||
|
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(new Response(403, ['Content-Type' => 'application/json; charset=utf-8'], $error));
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
|
|
||||||
$body = Psr7\Utils::streamFor('{\"message\":\"Invalid application key\"}');
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
|
$api->requestCredentials(['method' => 'GET', 'path' => '/*']);
|
||||||
return $response
|
|
||||||
->withStatus(401, 'POUET')
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withHeader('Content-Length', 37)
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$property = self::getPrivateProperty('consumer_key');
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$accessRules = [json_decode(' { "method": "GET", "path": "/*" } ')];
|
|
||||||
|
|
||||||
$credentials = $api->requestCredentials($accessRules);
|
|
||||||
$consumer_key = $property->getValue($api);
|
|
||||||
|
|
||||||
$this->assertEquals($consumer_key, $credentials["consumerKey"]);
|
|
||||||
$this->assertNotEquals($consumer_key, $this->consumer_key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,30 +237,21 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testInvalidRight()
|
public function testInvalidRight()
|
||||||
{
|
{
|
||||||
|
$error = '{"message": "Invalid credentials"}';
|
||||||
$this->expectException(ClientException::class);
|
$this->expectException(ClientException::class);
|
||||||
|
$this->expectExceptionCode(403);
|
||||||
|
$this->expectExceptionMessage($error);
|
||||||
|
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(new Response(403, ['Content-Type' => 'application/json; charset=utf-8'], $error));
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
|
|
||||||
$body = Psr7\Utils::streamFor('{\"message\":\"Invalid credentials\"}');
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
|
$api->get('/me', null, null, false);
|
||||||
return $response
|
|
||||||
->withStatus(403)
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withHeader('Content-Length', 37)
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
|
|
||||||
$invoker = self::getPrivateMethod('rawCall');
|
|
||||||
$invoker->invokeArgs($api, ['GET', '/me']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetConsumerKey()
|
public function testGetConsumerKey()
|
||||||
{
|
{
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY);
|
||||||
$this->assertEquals($this->consumer_key, $api->getConsumerKey());
|
$this->assertEquals(MOCK_CONSUMER_KEY, $api->getConsumerKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -319,32 +260,17 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetQueryArgs()
|
public function testGetQueryArgs()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(new Response(200, [], '{}'));
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query_string = $request->getUri()->getQuery();
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
$this->assertEquals($query_string, 'applicationId=49&status=pendingValidation');
|
$api->get('/me/api/credential?applicationId=49', ['status' => 'pendingValidation'], null, false);
|
||||||
|
|
||||||
$request = $request->withUri($request->getUri()
|
$calls = $client->calls;
|
||||||
->withHost('httpbin.org')
|
$this->assertEquals(1, count($calls));
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('123456789991');
|
|
||||||
|
|
||||||
return $response
|
$req = $calls[0]['request'];
|
||||||
->withStatus(200)
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
$this->assertEquals('https://eu.api.ovh.com/1.0/me/api/credential?applicationId=49&status=pendingValidation', $req->getUri()->__toString());
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$api->get('/me/api/credential?applicationId=49', ['status' => 'pendingValidation']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -352,32 +278,17 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetOverlappingQueryArgs()
|
public function testGetOverlappingQueryArgs()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(new Response(200, [], '{}'));
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query_string = $request->getUri()->getQuery();
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
$this->assertEquals($query_string, 'applicationId=49&status=expired&test=success');
|
$api->get('/me/api/credential?applicationId=49&status=pendingValidation', ['status' => 'expired', 'test' => "success"], null, false);
|
||||||
|
|
||||||
$request = $request->withUri($request->getUri()
|
$calls = $client->calls;
|
||||||
->withHost('httpbin.org')
|
$this->assertEquals(1, count($calls));
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('123456789991');
|
|
||||||
|
|
||||||
return $response
|
$req = $calls[0]['request'];
|
||||||
->withStatus(200)
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
$this->assertEquals('https://eu.api.ovh.com/1.0/me/api/credential?applicationId=49&status=expired&test=success', $req->getUri()->__toString());
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$api->get('/me/api/credential?applicationId=49&status=pendingValidation', ['status' => 'expired', 'test' => "success"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -385,149 +296,41 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetBooleanQueryArgs()
|
public function testGetBooleanQueryArgs()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(new Response(200, [], '{}'));
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query_string = $request->getUri()->getQuery();
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
$this->assertEquals($query_string, 'dryRun=true¬DryRun=false');
|
$api->get('/me/api/credential', ['dryRun' => true, 'notDryRun' => false], null, false);
|
||||||
|
|
||||||
$request = $request->withUri($request->getUri()
|
$calls = $client->calls;
|
||||||
->withHost('httpbin.org')
|
$this->assertEquals(1, count($calls));
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('123456789991');
|
|
||||||
|
|
||||||
return $response
|
$req = $calls[0]['request'];
|
||||||
->withStatus(200)
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
$this->assertEquals('https://eu.api.ovh.com/1.0/me/api/credential?dryRun=true¬DryRun=false', $req->getUri()->__toString());
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$api->get('/me/api/credential', ['dryRun' => true, 'notDryRun' => false]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test valid predefined endpoint
|
* Test valid provided endpoint
|
||||||
*/
|
*/
|
||||||
public function testPredefinedEndPoint()
|
public function testProvidedUrl()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
foreach ([
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
['endpoint' => 'http://api.ovh.com/1.0', 'expectedUrl' => 'http://api.ovh.com/1.0'],
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
['endpoint' => 'https://api.ovh.com/1.0', 'expectedUrl' => 'https://api.ovh.com/1.0'],
|
||||||
return $request;
|
['endpoint' => 'ovh-eu', 'expectedUrl' => 'https://eu.api.ovh.com/1.0'],
|
||||||
}
|
] as $test) {
|
||||||
|
$client = new MockClient(new Response(200, [], '{}'));
|
||||||
|
|
||||||
$host = $request->getUri()->getHost();
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, $test['endpoint'], MOCK_CONSUMER_KEY, $client);
|
||||||
$this->assertEquals($host, 'ca.api.ovh.com');
|
$api->get('/me/api/credential', null, null, false);
|
||||||
|
|
||||||
$resource = $request->getUri()->getPath();
|
$calls = $client->calls;
|
||||||
$this->assertEquals($resource, '/1.0/me/api/credential');
|
$this->assertEquals(1, count($calls));
|
||||||
|
|
||||||
$resource = $request->getUri()->getScheme();
|
$req = $calls[0]['request'];
|
||||||
$this->assertEquals($resource, 'https');
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals($test['expectedUrl'] . '/me/api/credential', $req->getUri()->__toString());
|
||||||
$request = $request->withUri($request->getUri()
|
}
|
||||||
->withHost('httpbin.org')
|
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('123456789991');
|
|
||||||
|
|
||||||
return $response
|
|
||||||
->withStatus(200)
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, 'ovh-ca', $this->consumer_key, $this->client);
|
|
||||||
$api->get('/me/api/credential');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test valid provided HTTP endpoint
|
|
||||||
*/
|
|
||||||
public function testProvidedHttpEndPoint()
|
|
||||||
{
|
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
$host = $request->getUri()->getHost();
|
|
||||||
$this->assertEquals($host, 'api.ovh.com');
|
|
||||||
|
|
||||||
$resource = $request->getUri()->getPath();
|
|
||||||
$this->assertEquals($resource, '/1.0/me/api/credential');
|
|
||||||
|
|
||||||
$resource = $request->getUri()->getScheme();
|
|
||||||
$this->assertEquals($resource, 'http');
|
|
||||||
|
|
||||||
$request = $request->withUri($request->getUri()
|
|
||||||
->withHost('httpbin.org')
|
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('123456789991');
|
|
||||||
|
|
||||||
return $response
|
|
||||||
->withStatus(200)
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, 'http://api.ovh.com/1.0', $this->consumer_key, $this->client);
|
|
||||||
$api->get('/me/api/credential');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test valid provided HTTPS endpoint
|
|
||||||
*/
|
|
||||||
public function testProvidedHttpsEndPoint()
|
|
||||||
{
|
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
|
|
||||||
$host = $request->getUri()->getHost();
|
|
||||||
$this->assertEquals($host, 'api.ovh.com');
|
|
||||||
|
|
||||||
$resource = $request->getUri()->getPath();
|
|
||||||
$this->assertEquals($resource, '/1.0/me/api/credential');
|
|
||||||
|
|
||||||
$resource = $request->getUri()->getScheme();
|
|
||||||
$this->assertEquals($resource, 'https');
|
|
||||||
|
|
||||||
$request = $request->withUri($request->getUri()
|
|
||||||
->withHost('httpbin.org')
|
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('123456789991');
|
|
||||||
|
|
||||||
return $response
|
|
||||||
->withStatus(200)
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, 'https://api.ovh.com/1.0', $this->consumer_key, $this->client);
|
|
||||||
$api->get('/me/api/credential');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -535,32 +338,128 @@ class ApiTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testMissingOvhApplicationHeaderOnRequestCredentials()
|
public function testMissingOvhApplicationHeaderOnRequestCredentials()
|
||||||
{
|
{
|
||||||
$handlerStack = $this->client->getConfig('handler');
|
$client = new MockClient(
|
||||||
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
|
new Response(200, [], MOCK_TIME),
|
||||||
if ($request->getUri()->getPath() == "/1.0/auth/time") {
|
new Response(200, [], '{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}'),
|
||||||
return $request;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
$ovhApplication = $request->getHeader('X-OVH-Application');
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
$this->assertNotNull($ovhApplication);
|
|
||||||
$this->assertEquals($ovhApplication, array($this->application_key));
|
|
||||||
|
|
||||||
$request = $request->withUri($request->getUri()
|
|
||||||
->withHost('httpbin.org')
|
|
||||||
->withPath('/')
|
|
||||||
->withQuery(''));
|
|
||||||
return $request;
|
|
||||||
}));
|
|
||||||
$handlerStack->push(Middleware::mapResponse(function (Response $response) {
|
|
||||||
$body = Psr7\Utils::streamFor('{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}');
|
|
||||||
|
|
||||||
return $response
|
|
||||||
->withStatus(200)
|
|
||||||
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
|
||||||
->withBody($body);
|
|
||||||
}));
|
|
||||||
|
|
||||||
$api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client);
|
|
||||||
$api->requestCredentials([]);
|
$api->requestCredentials([]);
|
||||||
|
|
||||||
|
$calls = $client->calls;
|
||||||
|
$this->assertEquals(2, count($calls));
|
||||||
|
|
||||||
|
$req = $calls[0]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString());
|
||||||
|
|
||||||
|
$req = $calls[1]['request'];
|
||||||
|
$this->assertEquals('POST', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/credential', $req->getUri()->__toString());
|
||||||
|
$this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application'));
|
||||||
|
$this->assertEquals(MOCK_CONSUMER_KEY, $req->getHeaderLine('X-Ovh-Consumer'));
|
||||||
|
$this->assertEquals(MOCK_TIME, $req->getHeaderLine('X-Ovh-Timestamp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCallSignature()
|
||||||
|
{
|
||||||
|
// GET /auth/time
|
||||||
|
$mocks = [new Response(200, [], MOCK_TIME)];
|
||||||
|
// (GET,POST,PUT,DELETE) x (/auth,/unauth)
|
||||||
|
for ($i = 0; $i < 8; $i++) {
|
||||||
|
$mocks[] = new Response(200, [], '{}');
|
||||||
|
}
|
||||||
|
$client = new MockClient(...$mocks);
|
||||||
|
|
||||||
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
|
$body = ["a" => "b", "c" => "d"];
|
||||||
|
|
||||||
|
// Authenticated calls
|
||||||
|
$api->get('/auth');
|
||||||
|
$api->post('/auth', $body);
|
||||||
|
$api->put('/auth', $body);
|
||||||
|
$api->delete('/auth');
|
||||||
|
|
||||||
|
// Unauth calls
|
||||||
|
$api->get('/unauth', null, null, false);
|
||||||
|
$api->post('/unauth', $body, null, false);
|
||||||
|
$api->put('/unauth', $body, null, false);
|
||||||
|
$api->delete('/unauth', null, null, false);
|
||||||
|
|
||||||
|
$calls = $client->calls;
|
||||||
|
$this->assertEquals(9, count($calls));
|
||||||
|
|
||||||
|
$req = $calls[0]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString());
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
1 => ['method' => 'GET', 'sig' => '$1$e9556054b6309771395efa467c22e627407461ad'],
|
||||||
|
2 => ['method' => 'POST', 'sig' => '$1$ec2fb5c7a81f64723c77d2e5b609ae6f58a84fc1'],
|
||||||
|
3 => ['method' => 'PUT', 'sig' => '$1$8a75a9e7c8e7296c9dbeda6a2a735eb6bd58ec4b'],
|
||||||
|
4 => ['method' => 'DELETE', 'sig' => '$1$a1eecd00b3b02b6cf5708b84b9ff42059a950d85'],
|
||||||
|
] as $i => $test) {
|
||||||
|
$req = $calls[$i]['request'];
|
||||||
|
$this->assertEquals($test['method'], $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth', $req->getUri()->__toString());
|
||||||
|
$this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application'));
|
||||||
|
$this->assertEquals(MOCK_CONSUMER_KEY, $req->getHeaderLine('X-Ovh-Consumer'));
|
||||||
|
$this->assertEquals(MOCK_TIME, $req->getHeaderLine('X-Ovh-Timestamp'));
|
||||||
|
$this->assertEquals($test['sig'], $req->getHeaderLine('X-Ovh-Signature'));
|
||||||
|
if ($test['method'] == 'POST' || $test['method'] == 'PUT') {
|
||||||
|
$this->assertEquals('application/json; charset=utf-8', $req->getHeaderLine('Content-Type'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['GET', 'POST', 'PUT', 'DELETE'] as $i => $method) {
|
||||||
|
$req = $calls[$i + 5]['request'];
|
||||||
|
$this->assertEquals($method, $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/unauth', $req->getUri()->__toString());
|
||||||
|
$this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application'));
|
||||||
|
$this->assertNotTrue($req->hasHeader('X-Ovh-Consumer'));
|
||||||
|
$this->assertNotTrue($req->hasHeader('X-Ovh-Timestamp'));
|
||||||
|
$this->assertNotTrue($req->hasHeader('X-Ovh-Signature'));
|
||||||
|
if ($method == 'POST' || $method == 'PUT') {
|
||||||
|
$this->assertEquals('application/json; charset=utf-8', $req->getHeaderLine('Content-Type'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVersionInUrl()
|
||||||
|
{
|
||||||
|
// GET /auth/time
|
||||||
|
$mocks = [new Response(200, [], MOCK_TIME)];
|
||||||
|
// GET) x (/1.0/call,/v1/call,/v2/call)
|
||||||
|
for ($i = 0; $i < 3; $i++) {
|
||||||
|
$mocks[] = new Response(200, [], '{}');
|
||||||
|
}
|
||||||
|
$client = new MockClient(...$mocks);
|
||||||
|
|
||||||
|
$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
|
||||||
|
|
||||||
|
$api->get('/call');
|
||||||
|
$api->get('/v1/call');
|
||||||
|
$api->get('/v2/call');
|
||||||
|
|
||||||
|
$calls = $client->calls;
|
||||||
|
$this->assertEquals(4, count($calls));
|
||||||
|
|
||||||
|
$req = $calls[0]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString());
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
1 => ['path' => '1.0/call', 'sig' => '$1$7f2db49253edfc41891023fcd1a54cf61db05fbb'],
|
||||||
|
2 => ['path' => 'v1/call', 'sig' => '$1$e6e7906d385eb28adcbfbe6b66c1528a42d741ad'],
|
||||||
|
3 => ['path' => 'v2/call', 'sig' => '$1$bb63b132a6f84ad5433d0c534d48d3f7c3804285'],
|
||||||
|
] as $i => $test) {
|
||||||
|
$req = $calls[$i]['request'];
|
||||||
|
$this->assertEquals('GET', $req->getMethod());
|
||||||
|
$this->assertEquals('https://eu.api.ovh.com/' . $test['path'], $req->getUri()->__toString());
|
||||||
|
$this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application'));
|
||||||
|
$this->assertEquals(MOCK_CONSUMER_KEY, $req->getHeaderLine('X-Ovh-Consumer'));
|
||||||
|
$this->assertEquals(MOCK_TIME, $req->getHeaderLine('X-Ovh-Timestamp'));
|
||||||
|
$this->assertEquals($test['sig'], $req->getHeaderLine('X-Ovh-Signature'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user