Skip to content

Commit d823be3

Browse files
authored
add errorVerbosity (#400)
fixes #335 fixes #398
1 parent c32ab23 commit d823be3

File tree

13 files changed

+158
-72
lines changed

13 files changed

+158
-72
lines changed

doc/setup.rst

+24-8
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,47 @@ Only the ``authUrl`` is mandatory to create the client. But you will have to pro
1313
credentials to each service you create. So it is recommended to provide them when creating the client which
1414
would propagate these options to each service.
1515

16+
Authenticate
17+
------------
18+
1619
There are different ways to provide the authentication credentials. See the :doc:`services/identity/v3/tokens`
1720
section for the full list of options. You should provide credentials to the ``OpenStack`` constructor as an array
1821
the same way you provide options to ``generateToken`` method of the ``Identity`` service.
1922

20-
Authenticate with username
21-
--------------------------
23+
By username
24+
~~~~~~~~~~~
2225

2326
The most common way to authenticate is using the username and password of the user. You should also provide the Domain ID
2427
as usernames will not be unique across an entire OpenStack installation
2528

2629
.. sample:: Setup/username.php
2730

28-
Authenticate with user ID
29-
-------------------------
31+
By user ID
32+
~~~~~~~~~~
3033

3134
.. sample:: Setup/user_id.php
3235

33-
Authenticate application credential ID
34-
--------------------------------------
36+
By application credential ID
37+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3538

3639
.. sample:: Setup/application_credential_id.php
3740

38-
Authenticate using token ID
39-
---------------------------
41+
By token ID
42+
~~~~~~~~~~~
4043

4144
If you already have a valid token, you can use it to authenticate.
4245

4346
.. sample:: Setup/token_id.php
47+
48+
Other options
49+
-------------
50+
51+
For production environments it is recommended to decrease error reporting not to expose sensitive information. It can be done
52+
by setting the ``errorVerbosity`` key to ``0`` in the options array. It is set to 2 by default.
53+
54+
.. code-block:: php
55+
56+
$openstack = new OpenStack\OpenStack([
57+
'errorVerbosity' => 0,
58+
// other options
59+
]);

src/Common/Auth/IdentityService.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ interface IdentityService
99
/**
1010
* Authenticates and retrieves back a token and catalog.
1111
*
12-
* @return array The FIRST key is {@see Token} instance, the SECOND key is a {@see Catalog} instance
12+
* @return array{0: \OpenStack\Common\Auth\Token, 1: string} The FIRST key is {@see Token} instance, the SECOND key is a URL of the service
1313
*/
1414
public function authenticate(array $options): array;
1515
}

src/Common/Error/Builder.php

+23-12
Original file line numberDiff line numberDiff line change
@@ -66,30 +66,41 @@ private function linkIsValid(string $link): bool
6666
/**
6767
* @codeCoverageIgnore
6868
*/
69-
public function str(MessageInterface $message): string
69+
public function str(MessageInterface $message, int $verbosity = 0): string
7070
{
7171
if ($message instanceof RequestInterface) {
72-
$msg = trim($message->getMethod().' '
73-
.$message->getRequestTarget())
74-
.' HTTP/'.$message->getProtocolVersion();
72+
$msg = trim($message->getMethod().' '.$message->getRequestTarget());
73+
$msg .= ' HTTP/'.$message->getProtocolVersion();
7574
if (!$message->hasHeader('host')) {
7675
$msg .= "\r\nHost: ".$message->getUri()->getHost();
7776
}
78-
} elseif ($message instanceof ResponseInterface) {
79-
$msg = 'HTTP/'.$message->getProtocolVersion().' '
80-
.$message->getStatusCode().' '
81-
.$message->getReasonPhrase();
77+
} else {
78+
if ($message instanceof ResponseInterface) {
79+
$msg = 'HTTP/'.$message->getProtocolVersion().' '
80+
.$message->getStatusCode().' '
81+
.$message->getReasonPhrase();
82+
} else {
83+
throw new \InvalidArgumentException('Unknown message type');
84+
}
85+
}
86+
87+
if ($verbosity < 1) {
88+
return $msg;
8289
}
8390

8491
foreach ($message->getHeaders() as $name => $values) {
8592
$msg .= "\r\n{$name}: ".implode(', ', $values);
8693
}
8794

95+
if ($verbosity < 2) {
96+
return $msg;
97+
}
98+
8899
if (ini_get('memory_limit') < 0 || $message->getBody()->getSize() < ini_get('memory_limit')) {
89100
$msg .= "\r\n\r\n".$message->getBody();
90101
}
91102

92-
return $msg;
103+
return trim($msg);
93104
}
94105

95106
/**
@@ -98,7 +109,7 @@ public function str(MessageInterface $message): string
98109
* @param RequestInterface $request The faulty request
99110
* @param ResponseInterface $response The error-filled response
100111
*/
101-
public function httpError(RequestInterface $request, ResponseInterface $response): BadResponseError
112+
public function httpError(RequestInterface $request, ResponseInterface $response, int $verbosity = 0): BadResponseError
102113
{
103114
$message = $this->header('HTTP Error');
104115

@@ -109,10 +120,10 @@ public function httpError(RequestInterface $request, ResponseInterface $response
109120
);
110121

111122
$message .= $this->header('Request');
112-
$message .= trim($this->str($request)).PHP_EOL.PHP_EOL;
123+
$message .= $this->str($request, $verbosity).PHP_EOL.PHP_EOL;
113124

114125
$message .= $this->header('Response');
115-
$message .= trim($this->str($response)).PHP_EOL.PHP_EOL;
126+
$message .= $this->str($response, $verbosity).PHP_EOL.PHP_EOL;
116127

117128
$message .= $this->header('Further information');
118129
$message .= $this->getStatusCodeMessage($response->getStatusCode());

src/Common/Service/Builder.php

+3-29
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@
77
use GuzzleHttp\Client;
88
use GuzzleHttp\ClientInterface;
99
use GuzzleHttp\HandlerStack;
10-
use GuzzleHttp\Middleware as GuzzleMiddleware;
1110
use OpenStack\Common\Auth\IdentityService;
12-
use OpenStack\Common\Auth\Token;
1311
use OpenStack\Common\Transport\HandlerStackFactory;
14-
use OpenStack\Common\Transport\Middleware;
1512
use OpenStack\Common\Transport\Utils;
1613

1714
/**
@@ -88,33 +85,18 @@ private function stockHttpClient(array &$options, string $serviceName): void
8885
if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) {
8986
if (false !== stripos($serviceName, 'identity')) {
9087
$baseUrl = $options['authUrl'];
91-
$stack = $this->getStack($options['authHandler']);
88+
$token = null;
9289
} else {
93-
[$token, $baseUrl] = $options['identityService']->authenticate($options);
94-
$stack = $this->getStack($options['authHandler'], $token);
90+
[$token, $baseUrl] = $options['identityService']->authenticate($options);
9591
}
9692

93+
$stack = HandlerStackFactory::createWithOptions(array_merge($options, ['token' => $token]));
9794
$microVersion = $options['microVersion'] ?? null;
9895

99-
$this->addDebugMiddleware($options, $stack);
100-
10196
$options['httpClient'] = $this->httpClient($baseUrl, $stack, $options['catalogType'], $microVersion);
10297
}
10398
}
10499

105-
/**
106-
* @codeCoverageIgnore
107-
*/
108-
private function addDebugMiddleware(array $options, HandlerStack &$stack): void
109-
{
110-
if (!empty($options['debugLog'])
111-
&& !empty($options['logger'])
112-
&& !empty($options['messageFormatter'])
113-
) {
114-
$stack->push(GuzzleMiddleware::log($options['logger'], $options['messageFormatter']));
115-
}
116-
}
117-
118100
/**
119101
* @codeCoverageIgnore
120102
*/
@@ -127,14 +109,6 @@ private function stockAuthHandler(array &$options): void
127109
}
128110
}
129111

130-
private function getStack(callable $authHandler, Token $token = null): HandlerStack
131-
{
132-
$stack = HandlerStackFactory::create();
133-
$stack->push(Middleware::authHandler($authHandler, $token));
134-
135-
return $stack;
136-
}
137-
138112
private function httpClient(string $baseUrl, HandlerStack $stack, string $serviceType = null, string $microVersion = null): ClientInterface
139113
{
140114
$clientOptions = [

src/Common/Transport/HandlerStack.php

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
*/
88
class HandlerStack
99
{
10+
/**
11+
* @deprecated use \OpenStack\Common\Transport\HandlerStackFactory::createWithOptions instead
12+
*/
1013
public static function create(callable $handler = null): \GuzzleHttp\HandlerStack
1114
{
1215
return HandlerStackFactory::create($handler);

src/Common/Transport/HandlerStackFactory.php

+38
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
namespace OpenStack\Common\Transport;
66

77
use GuzzleHttp\HandlerStack;
8+
use GuzzleHttp\Middleware as GuzzleMiddleware;
89
use GuzzleHttp\Utils;
910

1011
class HandlerStackFactory
1112
{
13+
/**
14+
* @deprecated use \OpenStack\Common\Transport\HandlerStackFactory::createWithOptions instead
15+
*/
1216
public static function create(callable $handler = null): HandlerStack
1317
{
1418
$stack = new HandlerStack($handler ?: Utils::chooseHandler());
@@ -17,4 +21,38 @@ public static function create(callable $handler = null): HandlerStack
1721

1822
return $stack;
1923
}
24+
25+
/**
26+
* Creates a new HandlerStack with the given options.
27+
*
28+
* @param array{
29+
* handler: callable,
30+
* authHandler: callable,
31+
* token: \OpenStack\Common\Auth\Token,
32+
* errorVerbosity: int,
33+
* debugLog: bool,
34+
* logger: \Psr\Log\LoggerInterface,
35+
* messageFormatter: \GuzzleHttp\MessageFormatter
36+
* } $options
37+
*/
38+
public static function createWithOptions(array $options): HandlerStack
39+
{
40+
$stack = new HandlerStack($options['handler'] ?? Utils::chooseHandler());
41+
$stack->push(Middleware::httpErrors($options['errorVerbosity'] ?? 0), 'http_errors');
42+
$stack->push(GuzzleMiddleware::prepareBody(), 'prepare_body');
43+
44+
if (!empty($options['authHandler'])) {
45+
$stack->push(Middleware::authHandler($options['authHandler'], $options['token'] ?? null));
46+
}
47+
48+
if (!empty($options['debugLog'])
49+
&& !empty($options['logger'])
50+
&& !empty($options['messageFormatter'])
51+
) {
52+
$logMiddleware = GuzzleMiddleware::log($options['logger'], $options['messageFormatter']);
53+
$stack->push($logMiddleware, 'logger');
54+
}
55+
56+
return $stack;
57+
}
2058
}

src/Common/Transport/Middleware.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515

1616
final class Middleware
1717
{
18-
public static function httpErrors(): callable
18+
public static function httpErrors(int $verbosity = 0): callable
1919
{
20-
return function (callable $handler) {
21-
return function ($request, array $options) use ($handler) {
20+
return function (callable $handler) use ($verbosity) {
21+
return function ($request, array $options) use ($handler, $verbosity) {
2222
return $handler($request, $options)->then(
23-
function (ResponseInterface $response) use ($request) {
23+
function (ResponseInterface $response) use ($request, $verbosity) {
2424
if ($response->getStatusCode() < 400) {
2525
return $response;
2626
}
27-
throw (new Builder())->httpError($request, $response);
27+
throw (new Builder())->httpError($request, $response, $verbosity);
2828
}
2929
);
3030
};

src/Identity/v3/Service.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function authenticate(array $options): array
4545
$name = $options['catalogName'];
4646
$type = $options['catalogType'];
4747
$region = $options['region'];
48-
$interface = isset($options['interface']) ? $options['interface'] : Enum::INTERFACE_PUBLIC;
48+
$interface = $options['interface'] ?? Enum::INTERFACE_PUBLIC;
4949

5050
if ($baseUrl = $token->catalog->getServiceUrl($name, $type, $region, $interface)) {
5151
return [$token, $baseUrl];

src/OpenStack.php

+4-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace OpenStack;
66

77
use GuzzleHttp\Client;
8-
use GuzzleHttp\Middleware as GuzzleMiddleware;
98
use OpenStack\Common\Service\Builder;
109
use OpenStack\Common\Transport\HandlerStackFactory;
1110
use OpenStack\Common\Transport\Utils;
@@ -36,6 +35,9 @@ class OpenStack
3635
*/
3736
public function __construct(array $options = [], Builder $builder = null)
3837
{
38+
$defaults = ['errorVerbosity' => 2];
39+
$options = array_merge($defaults, $options);
40+
3941
if (!isset($options['identityService'])) {
4042
$options['identityService'] = $this->getDefaultIdentityService($options);
4143
}
@@ -49,15 +51,7 @@ private function getDefaultIdentityService(array $options): Service
4951
throw new \InvalidArgumentException("'authUrl' is a required option");
5052
}
5153

52-
$stack = HandlerStackFactory::create();
53-
54-
if (!empty($options['debugLog'])
55-
&& !empty($options['logger'])
56-
&& !empty($options['messageFormatter'])
57-
) {
58-
$logMiddleware = GuzzleMiddleware::log($options['logger'], $options['messageFormatter']);
59-
$stack->push($logMiddleware, 'logger');
60-
}
54+
$stack = HandlerStackFactory::createWithOptions(array_merge($options, ['token' => null]));
6155

6256
$clientOptions = [
6357
'base_uri' => Utils::normalizeUrl($options['authUrl']),

tests/sample/Compute/v2/TestCase.php

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

71-
$server->waitUntilActive(120);
71+
$server->waitUntilActive(300);
7272
$this->assertEquals('ACTIVE', $server->status);
7373

7474
return $server;

tests/sample/Identity/v3/TokenTest.php

+36
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace OpenStack\Sample\Identity\v3;
44

5+
use GuzzleHttp\Exception\ClientException;
6+
use OpenStack\Common\Error\BadResponseError;
57
use OpenStack\Identity\v3\Models\Token;
8+
use OpenStack\OpenStack;
69

710
class TokenTest extends TestCase
811
{
@@ -73,4 +76,37 @@ public function testRevoke(): void
7376
$this->assertFalse($token->validate());
7477
}
7578

79+
public function testInvalidPassword()
80+
{
81+
$options = $this->getAuthOpts();
82+
$password = $options['user']['password'] . $this->randomStr();
83+
$options['user']['id'] = $password;
84+
$options['user']['password'] = $password;
85+
86+
$openstack = new OpenStack($options);
87+
$this->expectException(BadResponseError::class);
88+
89+
$openstack->objectStoreV1();
90+
}
91+
92+
public function testInvalidPasswordHidesPassword()
93+
{
94+
$options = $this->getAuthOpts();
95+
96+
$password = $options['user']['password'] . $this->randomStr();
97+
$options['user']['id'] = $password;
98+
$options['user']['password'] = $password;
99+
100+
$openstack = new OpenStack(array_merge($options, ['errorVerbosity' => 0]));
101+
$this->expectException(BadResponseError::class);
102+
103+
try {
104+
$openstack->objectStoreV1();
105+
} catch (BadResponseError $e) {
106+
$this->assertStringNotContainsString($password, $e->getMessage());
107+
108+
throw $e;
109+
}
110+
111+
}
76112
}

0 commit comments

Comments
 (0)