Completed
Pull Request — master (#14)
by
unknown
04:36
created

TokenPool::getEndpointUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
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\Log\LoggerInterface;
10
use Psr\Log\NullLogger;
11
use TreeHouse\Cache\CacheInterface;
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 CacheInterface
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 CacheInterface  $cache
63
     * @param LoggerInterface $logger
64
     */
65 14
    public function __construct(Tenant $tenant, CacheInterface $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
     * @return string
75
     */
76 9
    public function getEndpointUrl()
77
    {
78 9
        if (null === $this->endpointUrl) {
79 5
            $this->getToken();
80 5
        }
81
82 9
        return $this->endpointUrl;
83
    }
84
85
    /**
86
     * @return string
87
     */
88 9
    public function getTokenId()
89
    {
90 9
        if (null === $this->tokenId) {
91
            $this->getToken();
92
        }
93
94 9
        return $this->tokenId;
95
    }
96
97
    /**
98
     * Returns a token to use for the keystone service. Uses cached instance
99
     * whenever possible.
100
     *
101
     * @param bool $forceNew Whether to force creating a new token
102
     *
103
     * @return Token
104
     */
105 10
    public function getToken($forceNew = false)
106
    {
107 10
        $tokenName = $this->getCacheKey();
108
109
        // see if token is in cache
110 10
        if (!$forceNew && $cachedToken = $this->cache->get($tokenName)) {
111 7
            $this->logger->debug('Obtained token from cache');
112 7
            $token = $this->createToken($cachedToken);
113 6
        }
114
115 9
        if (!isset($token) || !($token instanceof Token) || $token->isExpired()) {
116
            // cache the token and set it to expire 5 seconds before the
117
            // expiration date, to avoid any concurrence errors.
118 7
            $token = $this->requestToken();
119 6
            $this->cache->set($tokenName, json_encode($token), $token->getExpirationDate()->getTimestamp() - time() - 5);
120 6
        }
121
122
        // cache token properties
123 9
        $this->endpointUrl = $this->getEndpointUrlFromToken($token);
124 9
        $this->tokenId = $token->getId();
125
126 9
        return $token;
127
    }
128
129
    /**
130
     * @throws TokenException
131
     *
132
     * @return Token
133
     */
134 4
    protected function requestToken()
135
    {
136 4
        $this->logger->debug('Requesting a new token');
137
138
        $data = [
139
            'auth' => [
140
                'passwordCredentials' => [
141 4
                    'password' => $this->tenant->getPassword(),
142 4
                    'username' => $this->tenant->getUsername(),
143 4
                ],
144 4
            ],
145 4
        ];
146
147 4
        if ($name = $this->tenant->getTenantName()) {
148
            $data['auth']['tenantName'] = $name;
149
        }
150
151
        try {
152 4
            $response = $this->client->request('POST', $this->tenant->getTokenUrl(), [
153 4
                RequestOptions::JSON => $data,
154 4
            ]);
155
156 3
            return $this->createToken($response->getBody()->getContents());
157 1
        } catch (RequestException $e) {
158 1
            $this->logger->error(sprintf('Error requesting token: %s', $e->getMessage()));
159
160 1
            throw new TokenException($e->getMessage(), $e->getCode(), $e);
161
        }
162
    }
163
164
    /**
165
     * @param string $jsonData
166
     *
167
     * @throws TokenException
168
     *
169
     * @return Token
170
     */
171 8
    private function createToken($jsonData)
172
    {
173 8
        $content = json_decode($jsonData, true);
174
175 8
        if (!is_array($content)) {
176 1
            throw new TokenException(sprintf('Could not decode JSON string: "%s"', $jsonData));
177
        }
178
179 7
        return Token::create($content);
180
    }
181
182
    /**
183
     * @param Token $token
184
     *
185
     * @throws TokenException
186
     *
187
     * @return string
188
     */
189 9
    private function getEndpointUrlFromToken(Token $token)
190
    {
191 9
        $catalog = $token->getServiceCatalog($this->tenant->getServiceType(), $this->tenant->getServiceName());
192
193
        // use the first endpoint that has was requested
194 9
        $endpointType = $this->tenant->getServiceEndpoint() . 'url';
195 9
        foreach ($catalog as $endpoint) {
196 9
            $endpoint = array_change_key_case($endpoint, CASE_LOWER);
197 9
            if (array_key_exists($endpointType, $endpoint)) {
198 9
                return $endpoint[$endpointType];
199
            }
200
        }
201
202
        throw new TokenException('No endpoint with a ' . $this->tenant->getServiceEndpoint() . ' url found');
203
    }
204
205
    /**
206
     * @return string
207
     */
208 10
    private function getCacheKey()
209
    {
210 10
        return sprintf(self::TOKEN_KEY_FORMAT, rawurlencode($this->tenant->getTokenUrl()));
211
    }
212
}
213