Skip to content

Commit c32ab23

Browse files
authored
Fix Swift container requests with "tokens" in its name (#396)
1 parent ba44841 commit c32ab23

File tree

14 files changed

+98
-56
lines changed

14 files changed

+98
-56
lines changed

src/Common/Api/Operation.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class Operation
2727
/** @var []Parameter The parameters of this operation */
2828
private $params;
2929

30+
/** @var bool Whether this operation should skip authentication */
31+
private $skipAuth;
32+
3033
/**
3134
* @param array $definition The data definition (in array form) that will populate this
3235
* operation. Usually this is retrieved from an {@see ApiInterface}
@@ -41,7 +44,8 @@ public function __construct(array $definition)
4144
$this->jsonKey = $definition['jsonKey'];
4245
}
4346

44-
$this->params = self::toParamArray($definition['params']);
47+
$this->params = self::toParamArray($definition['params']);
48+
$this->skipAuth = $definition['skipAuth'] ?? false;
4549
}
4650

4751
public function getPath(): string
@@ -54,10 +58,18 @@ public function getMethod(): string
5458
return $this->method;
5559
}
5660

61+
/**
62+
* Indicates if operation must be run without authentication. This is useful for getting authentication tokens.
63+
*/
64+
public function getSkipAuth(): bool
65+
{
66+
return $this->skipAuth;
67+
}
68+
5769
/**
5870
* Indicates whether this operation supports a parameter.
5971
*
60-
* @param $key The name of a parameter
72+
* @param string $key The name of a parameter
6173
*/
6274
public function hasParam(string $key): bool
6375
{

src/Common/Api/OperatorTrait.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ public function __debugInfo()
5555
* {@see Promise} object. In order for this to happen, the called methods need to be in the
5656
* following format: `createAsync`, where `create` is the sequential method being wrapped.
5757
*
58-
* @param $methodName the name of the method being invoked
59-
* @param $args the arguments to be passed to the sequential method
58+
* @param string $methodName the name of the method being invoked
59+
* @param array $args the arguments to be passed to the sequential method
6060
*
6161
* @return Promise
6262
*
@@ -92,22 +92,21 @@ public function getOperation(array $definition): Operation
9292
return new Operation($definition);
9393
}
9494

95-
/**
96-
* @throws \Exception
97-
*/
9895
protected function sendRequest(Operation $operation, array $userValues = [], bool $async = false)
9996
{
10097
$operation->validate($userValues);
10198

10299
$options = (new RequestSerializer())->serializeOptions($operation, $userValues);
103100
$method = $async ? 'requestAsync' : 'request';
104101

105-
$uri = Utils::uri_template($operation->getPath(), $userValues);
102+
$uri = Utils::uri_template($operation->getPath(), $userValues);
106103

107104
if (array_key_exists('requestOptions', $userValues)) {
108105
$options += $userValues['requestOptions'];
109106
}
110107

108+
$options['openstack.skip_auth'] = $operation->getSkipAuth();
109+
111110
return $this->client->$method($operation->getMethod(), $uri, $options);
112111
}
113112

src/Common/Auth/AuthHandler.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ public function __invoke(RequestInterface $request, array $options)
4343
{
4444
$fn = $this->nextHandler;
4545

46-
if ($this->shouldIgnore($request)) {
46+
if (!isset($options['openstack.skip_auth'])) {
47+
// Deprecated. Left for backward compatibility only.
48+
if ($this->shouldIgnore($request)) {
49+
return $fn($request, $options);
50+
}
51+
} elseif ($options['openstack.skip_auth']) {
4752
return $fn($request, $options);
4853
}
4954

src/Common/Service/Builder.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function createService(string $namespace, array $serviceOptions = []): Se
8383
return new $serviceClass($options['httpClient'], new $apiClass());
8484
}
8585

86-
private function stockHttpClient(array &$options, string $serviceName)
86+
private function stockHttpClient(array &$options, string $serviceName): void
8787
{
8888
if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) {
8989
if (false !== stripos($serviceName, 'identity')) {
@@ -105,7 +105,7 @@ private function stockHttpClient(array &$options, string $serviceName)
105105
/**
106106
* @codeCoverageIgnore
107107
*/
108-
private function addDebugMiddleware(array $options, HandlerStack &$stack)
108+
private function addDebugMiddleware(array $options, HandlerStack &$stack): void
109109
{
110110
if (!empty($options['debugLog'])
111111
&& !empty($options['logger'])
@@ -118,7 +118,7 @@ private function addDebugMiddleware(array $options, HandlerStack &$stack)
118118
/**
119119
* @codeCoverageIgnore
120120
*/
121-
private function stockAuthHandler(array &$options)
121+
private function stockAuthHandler(array &$options): void
122122
{
123123
if (!isset($options['authHandler'])) {
124124
$options['authHandler'] = function () use ($options) {

src/Identity/v2/Api.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@ class Api implements ApiInterface
1414
public function postToken(): array
1515
{
1616
return [
17-
'method' => 'POST',
18-
'path' => 'tokens',
19-
'params' => [
20-
'username' => [
17+
'method' => 'POST',
18+
'path' => 'tokens',
19+
'skipAuth' => true,
20+
'params' => [
21+
'username' => [
2122
'type' => 'string',
2223
'required' => true,
2324
'path' => 'auth.passwordCredentials',
2425
],
25-
'password' => [
26+
'password' => [
2627
'type' => 'string',
2728
'required' => true,
2829
'path' => 'auth.passwordCredentials',
2930
],
30-
'tenantId' => [
31+
'tenantId' => [
3132
'type' => 'string',
3233
'path' => 'auth',
3334
],

src/Identity/v3/Api.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ public function __construct()
1616
public function postTokens(): array
1717
{
1818
return [
19-
'method' => 'POST',
20-
'path' => 'auth/tokens',
21-
'params' => [
19+
'method' => 'POST',
20+
'path' => 'auth/tokens',
21+
'skipAuth' => true,
22+
'params' => [
2223
'methods' => $this->params->methods(),
2324
'user' => $this->params->user(),
2425
'application_credential' => $this->params->applicationCredential(),

src/Identity/v3/Models/Token.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,27 @@ public function retrieve()
8989
}
9090

9191
/**
92-
* @param array $data {@see \OpenStack\Identity\v3\Api::postTokens}
92+
* @param array $userOptions {@see \OpenStack\Identity\v3\Api::postTokens}
9393
*/
94-
public function create(array $data): Creatable
94+
public function create(array $userOptions): Creatable
9595
{
96-
if (isset($data['user'])) {
97-
$data['methods'] = ['password'];
98-
if (!isset($data['user']['id']) && empty($data['user']['domain'])) {
96+
if (isset($userOptions['user'])) {
97+
$userOptions['methods'] = ['password'];
98+
if (!isset($userOptions['user']['id']) && empty($userOptions['user']['domain'])) {
9999
throw new InvalidArgumentException('When authenticating with a username, you must also provide either the domain name '.'or domain ID to which the user belongs to. Alternatively, if you provide a user ID instead, '.'you do not need to provide domain information.');
100100
}
101-
} elseif (isset($data['application_credential'])) {
102-
$data['methods'] = ['application_credential'];
103-
if (!isset($data['application_credential']['id']) || !isset($data['application_credential']['secret'])) {
101+
} elseif (isset($userOptions['application_credential'])) {
102+
$userOptions['methods'] = ['application_credential'];
103+
if (!isset($userOptions['application_credential']['id']) || !isset($userOptions['application_credential']['secret'])) {
104104
throw new InvalidArgumentException('When authenticating with a application_credential, you must provide application credential ID '.' and application credential secret.');
105105
}
106-
} elseif (isset($data['tokenId'])) {
107-
$data['methods'] = ['token'];
106+
} elseif (isset($userOptions['tokenId'])) {
107+
$userOptions['methods'] = ['token'];
108108
} else {
109109
throw new InvalidArgumentException('Either a user, tokenId or application_credential must be provided.');
110110
}
111111

112-
$response = $this->execute($this->api->postTokens(), $data);
112+
$response = $this->execute($this->api->postTokens(), $userOptions);
113113
$token = $this->populateFromResponse($response);
114114

115115
// Cache response as an array to export if needed.

tests/sample/Compute/v2/TestCase.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ protected function createServer(): Server
6868
]
6969
);
7070

71-
$server->waitUntilActive(60);
71+
$server->waitUntilActive(120);
72+
$this->assertEquals('ACTIVE', $server->status);
7273

7374
return $server;
7475
}

tests/sample/ObjectStore/v1/ContainerTest.php

+9
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,13 @@ public function testDelete(Container $createdContainer)
132132
$this->expectException(BadResponseError::class);
133133
$createdContainer->retrieve();
134134
}
135+
136+
public function testTokensContainer()
137+
{
138+
$container = $this->getService()->createContainer(['name' => $this->randomStr() . '_tokens']);
139+
$this->assertNotNull($container->name);
140+
141+
// this would send POST request with 'tokens' in the URL
142+
$container->resetMetadata([]);
143+
}
135144
}

tests/unit/Common/Api/OperatorTraitTest.php

+16-15
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,25 @@ public function test_it_returns_operations()
3838
{
3939
self::assertInstanceOf(
4040
Operation::class,
41-
$this->operator->getOperation($this->def, [])
41+
$this->operator->getOperation($this->def)
4242
);
4343
}
4444

4545
public function test_it_sends_a_request_when_operations_are_executed()
4646
{
47-
$this->client->request('GET', 'test', ['headers' => []])->willReturn(new Response());
47+
$this->mockRequest('GET', 'test', new Response());
4848

49-
$this->operator->execute($this->def, []);
50-
51-
$this->addToAssertionCount(1);
49+
$this->operator->execute($this->def);
5250
}
5351

5452
public function test_it_sends_a_request_when_async_operations_are_executed()
5553
{
56-
$this->client->requestAsync('GET', 'test', ['headers' => []])->willReturn(new Promise());
57-
58-
$this->operator->executeAsync($this->def, []);
54+
$this->client
55+
->requestAsync('GET', 'test', ['headers' => [], 'openstack.skip_auth' => false])
56+
->shouldBeCalled()
57+
->willReturn(new Promise());
5958

60-
$this->addToAssertionCount(1);
59+
$this->operator->executeAsync($this->def);
6160
}
6261

6362
public function test_it_wraps_sequential_ops_in_promise_when_async_is_appended_to_method_name()
@@ -75,13 +74,13 @@ public function test_it_wraps_sequential_ops_in_promise_when_async_is_appended_t
7574

7675
public function test_it_throws_exception_when_async_is_called_on_a_non_existent_method()
7776
{
78-
$this->expectException(\RuntimeException::class);
77+
$this->expectException(\RuntimeException::class);
7978
$this->operator->fooAsync();
8079
}
8180

8281
public function test_undefined_methods_result_in_error()
8382
{
84-
$this->expectException(\Exception::class);
83+
$this->expectException(\Exception::class);
8584
$this->operator->foo();
8685
}
8786

@@ -103,16 +102,18 @@ public function test_it_populates_models_from_arrays()
103102

104103
public function test_guzzle_options_are_forwarded()
105104
{
106-
$this->client->request('GET', 'test', ['headers' => [], 'stream' => true])->willReturn(new Response());
105+
$this->client
106+
->request('GET', 'test', ['headers' => [], 'openstack.skip_auth' => false, 'stream' => true])
107+
->shouldBeCalled()
108+
->willReturn(new Response());
107109

108110
$this->operator->execute($this->def, [
109-
'requestOptions' => ['stream' => true]
111+
'requestOptions' => ['stream' => true],
110112
]);
111-
112-
$this->addToAssertionCount(1);
113113
}
114114
}
115115

116+
116117
class TestResource extends AbstractResource
117118
{
118119
}

tests/unit/Common/Auth/AuthHandlerTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ public function test_it_should_bypass_auth_http_requests()
3535
// Fake a Keystone request
3636
$request = new Request('POST', 'https://my-openstack.org:5000/v2.0/tokens');
3737

38+
self::assertEquals($request, call_user_func_array($this->handler, [$request, ['openstack.skip_auth' => true]]));
39+
}
40+
41+
public function test_it_should_bypass_auth_http_requests_backward_compatibility()
42+
{
43+
// Fake a Keystone request
44+
$request = new Request('POST', 'https://my-openstack.org:5000/v2.0/tokens');
45+
3846
self::assertEquals($request, call_user_func_array($this->handler, [$request, []]));
3947
}
4048

tests/unit/Identity/v2/ServiceTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function test_it_authenticates()
4040
'tenantId' => $options['tenantId'],
4141
]];
4242

43-
$this->mockRequest('POST', 'tokens', 'token-post', $expectedJson, []);
43+
$this->mockRequest('POST', 'tokens', 'token-post', $expectedJson, [], true);
4444

4545
[$token, $baseUrl] = $this->service->authenticate($options);
4646

@@ -64,7 +64,7 @@ public function test_it_generates_tokens()
6464
'tenantId' => $options['tenantId'],
6565
]];
6666

67-
$this->mockRequest('POST', 'tokens', 'token-post', $expectedJson, []);
67+
$this->mockRequest('POST', 'tokens', 'token-post', $expectedJson, [], true);
6868

6969
self::assertInstanceOf(Token::class, $this->service->generateToken($options));
7070
}

tests/unit/Identity/v3/ServiceTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function test_it_authenticates()
5858
]
5959
];
6060

61-
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson]);
61+
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson], [], true);
6262

6363
[$token, $url] = $this->service->authenticate($userOptions);
6464

@@ -231,7 +231,7 @@ public function test_it_throws_exception_if_no_endpoint_found()
231231
]
232232
];
233233

234-
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson]);
234+
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson], [], true);
235235
$this->expectException(\RuntimeException::class);
236236

237237
$this->service->authenticate([
@@ -626,7 +626,7 @@ public function test_it_generates_tokens_with_user_creds()
626626
]
627627
];
628628

629-
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson]);
629+
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson], [], true);
630630

631631
$token = $this->service->generateToken($userOptions);
632632
self::assertInstanceOf(Models\Token::class, $token);
@@ -651,7 +651,7 @@ public function test_it_generates_token_with_token_id()
651651
]
652652
];
653653

654-
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson]);
654+
$this->mockRequest('POST', 'auth/tokens', 'token', ['auth' => $expectedJson], [], true);
655655

656656
$token = $this->service->generateToken($userOptions);
657657
self::assertInstanceOf(Models\Token::class, $token);

tests/unit/TestCase.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ protected function getFixture($file)
4343
return Message::parseResponse(file_get_contents($path));
4444
}
4545

46+
4647
/**
4748
* Mocks request
4849
*
@@ -51,12 +52,16 @@ protected function getFixture($file)
5152
* @param string|\GuzzleHttp\Psr7\Response|\Throwable $response the file name of the response fixture or a Response object
5253
* @param string|array|null $body request body. If type is array, it will be encoded as JSON.
5354
* @param array $headers request headers
55+
* @param bool $skipAuth true if the api call skips authentication
5456
*
5557
* @throws \GuzzleHttp\Exception\GuzzleException
5658
*/
57-
protected function mockRequest(string $method, $uri, $response = null, $body = null, array $headers = []): MethodProphecy
59+
protected function mockRequest(string $method, $uri, $response = null, $body = null, array $headers = [], $skipAuth = false): MethodProphecy
5860
{
59-
$options = ['headers' => $headers];
61+
$options = [
62+
'headers' => $headers,
63+
'openstack.skip_auth' => $skipAuth,
64+
];
6065

6166
if (!empty($body)) {
6267
$options[is_array($body) ? 'json' : 'body'] = $body;

0 commit comments

Comments
 (0)