Completed
Pull Request — 4.0 (#16)
by Jeroen
03:57
created

TokenPool   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 88.06%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 12
dl 0
loc 210
ccs 59
cts 67
cp 0.8806
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A getPublicUrl() 0 3 1
A getEndpointUrl() 0 8 2
A getTokenId() 0 8 2
B getToken() 0 28 6
B requestToken() 0 29 3
A createToken() 0 10 2
A getEndpointUrlFromToken() 0 15 3
A getCacheKey() 0 4 1
1
<?php
2
3
namespace TreeHouse\Keystone\Client;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\ClientInterface;
7
use GuzzleHttp\Exception\RequestException;
8
use GuzzleHttp\RequestOptions;
9
use Psr\Cache\CacheItemPoolInterface;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
use TreeHouse\Keystone\Client\Exception\TokenException;
13
use TreeHouse\Keystone\Client\Model\Tenant;
14
use TreeHouse\Keystone\Client\Model\Token;
15
16
class TokenPool
17
{
18
    const TOKEN_KEY_FORMAT = 'keystone_token_3_%s';
19
20
    /**
21
     * @var Tenant
22
     */
23
    protected $tenant;
24
25
    /**
26
     * The cache where tokens are stored.
27
     *
28
     * @var CacheItemPoolInterface
29
     */
30
    protected $cache;
31
32
    /**
33
     * Optional logger.
34
     *
35
     * @var LoggerInterface
36
     */
37
    protected $logger;
38
39
    /**
40
     * Internal client to request a new token with.
41
     *
42
     * @var ClientInterface
43
     */
44
    protected $client;
45
46
    /**
47
     * The endpoint url that was obtained via the token.
48
     *
49
     * @var string
50
     */
51
    protected $endpointUrl;
52
53
    /**
54
     * Cached copy of the token id.
55
     *
56
     * @var string
57
     */
58
    protected $tokenId;
59
60
    /**
61
     * @param Tenant          $tenant
62
     * @param CacheItemPoolInterface $cache
63
     * @param LoggerInterface $logger
64
     */
65 14
    public function __construct(Tenant $tenant, CacheItemPoolInterface $cache, LoggerInterface $logger = null)
66
    {
67 14
        $this->tenant = $tenant;
68 14
        $this->cache = $cache;
69 14
        $this->logger = $logger ?: new NullLogger();
70 14
        $this->client = new Client();
71 14
    }
72
73
    /**
74
     * @deprecated Please use getEndpointUrl() in favor of this
75
     * @return string
76
     */
77
    public function getPublicUrl() {
78
        return $this->getEndpointUrl();
79
    }
80
81
    /**
82
     * @return string
83
     */
84 9
    public function getEndpointUrl()
85
    {
86 9
        if (null === $this->endpointUrl) {
87 5
            $this->getToken();
88 5
        }
89
90 9
        return $this->endpointUrl;
91
    }
92
93
    /**
94
     * @return string
95
     */
96 9
    public function getTokenId()
97
    {
98 9
        if (null === $this->tokenId) {
99
            $this->getToken();
100
        }
101
102 9
        return $this->tokenId;
103
    }
104
105
    /**
106
     * Returns a token to use for the keystone service. Uses cached instance
107
     * whenever possible.
108
     *
109
     * @param bool $forceNew Whether to force creating a new token
110
     *
111
     * @return Token
112
     */
113 10
    public function getToken($forceNew = false)
114
    {
115 10
        $tokenName = $this->getCacheKey();
116
117 10
        $cacheItem = $this->cache->getItem($tokenName);
118
119
        // see if token is in cache
120 10
        if (!$forceNew && $cachedToken = $cacheItem->get()) {
121 7
            $this->logger->debug('Obtained token from cache');
122 7
            $token = $this->createToken($cachedToken);
123 6
        }
124
125 9
        if (!isset($token) || !($token instanceof Token) || $token->isExpired()) {
126
            // cache the token and set it to expire 5 seconds before the
127
            // expiration date, to avoid any concurrence errors.
128 7
            $token = $this->requestToken();
129
130 6
            $cacheItem->set(json_encode($token));
131 6
            $cacheItem->expiresAfter($token->getExpirationDate()->getTimestamp() - time() - 5);
132 6
            $this->cache->save($cacheItem);
133 6
        }
134
135
        // cache token properties
136 9
        $this->endpointUrl = $this->getEndpointUrlFromToken($token);
137 9
        $this->tokenId = $token->getId();
138
139 9
        return $token;
140
    }
141
142
    /**
143
     * @throws TokenException
144
     *
145
     * @return Token
146
     */
147 4
    protected function requestToken()
148
    {
149 4
        $this->logger->debug('Requesting a new token');
150
151
        $data = [
152
            'auth' => [
153
                'passwordCredentials' => [
154 4
                    'password' => $this->tenant->getPassword(),
155 4
                    'username' => $this->tenant->getUsername(),
156 4
                ],
157 4
            ],
158 4
        ];
159
160 4
        if ($name = $this->tenant->getTenantName()) {
161
            $data['auth']['tenantName'] = $name;
162
        }
163
164
        try {
165 4
            $response = $this->client->request('POST', $this->tenant->getTokenUrl(), [
166 4
                RequestOptions::JSON => $data,
167 4
            ]);
168
169 3
            return $this->createToken($response->getBody()->getContents());
170 1
        } catch (RequestException $e) {
171 1
            $this->logger->error(sprintf('Error requesting token: %s', $e->getMessage()));
172
173 1
            throw new TokenException($e->getMessage(), $e->getCode(), $e);
174
        }
175
    }
176
177
    /**
178
     * @param string $jsonData
179
     *
180
     * @throws TokenException
181
     *
182
     * @return Token
183
     */
184 8
    private function createToken($jsonData)
185
    {
186 8
        $content = json_decode($jsonData, true);
187
188 8
        if (!is_array($content)) {
189 1
            throw new TokenException(sprintf('Could not decode JSON string: "%s"', $jsonData));
190
        }
191
192 7
        return Token::create($content);
193
    }
194
195
    /**
196
     * @param Token $token
197
     *
198
     * @throws TokenException
199
     *
200
     * @return string
201
     */
202 9
    private function getEndpointUrlFromToken(Token $token)
203
    {
204 9
        $catalog = $token->getServiceCatalog($this->tenant->getServiceType(), $this->tenant->getServiceName());
205
206
        // use the first endpoint that has was requested
207 9
        $endpointType = $this->tenant->getServiceEndpoint() . 'url';
208 9
        foreach ($catalog as $endpoint) {
209 9
            $endpoint = array_change_key_case($endpoint, CASE_LOWER);
210 9
            if (array_key_exists($endpointType, $endpoint)) {
211 9
                return $endpoint[$endpointType];
212
            }
213
        }
214
215
        throw new TokenException('No endpoint with a ' . $this->tenant->getServiceEndpoint() . ' url found');
216
    }
217
218
    /**
219
     * @return string
220
     */
221 10
    private function getCacheKey()
222
    {
223 10
        return sprintf(self::TOKEN_KEY_FORMAT, rawurlencode($this->tenant->getTokenUrl()));
224
    }
225
}
226