Completed
Push — master ( 72e23c...e2e4ad )
by Paul
04:11
created

Client   B

Complexity

Total Complexity 34

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 320
rs 8.4332
wmc 34
lcom 1
cbo 15

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 3
A setLogger() 0 4 1
A setAPIRoot() 0 4 1
A setCredentials() 0 5 1
A getCredentials() 0 4 1
B authorize() 0 25 2
A setAPIHandler() 0 5 1
A setOAuth2Handler() 0 5 1
A get() 0 4 1
A post() 0 4 1
A put() 0 4 1
B makeAPIRequest() 0 44 3
A resolveEntryPoint() 0 8 2
C getEntryPoint() 0 40 7
A getCache() 0 7 2
A setCache() 0 4 1
A getClient() 0 21 3
A createOAuth2Middleware() 0 22 1
A createCacheMiddleware() 0 8 1
1
<?php
2
3
namespace LibLynx\Connect;
4
5
use GuzzleHttp\Exception\RequestException;
6
use kamermans\OAuth2\GrantType\ClientCredentials;
7
use kamermans\OAuth2\OAuth2Middleware;
8
use GuzzleHttp\HandlerStack;
9
use GuzzleHttp\Client as GuzzleClient;
10
use Kevinrob\GuzzleCache\CacheMiddleware;
11
use Kevinrob\GuzzleCache\Storage\Psr16CacheStorage;
12
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
13
use LibLynx\Connect\SimpleCacheTokenPersistence;
14
use Psr\Log\LoggerAwareInterface;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\NullLogger;
17
use Psr\SimpleCache\CacheInterface;
18
19
/**
20
 * LibLynx Connect API client
21
 *
22
 * $liblynx=new Liblynx\Connect\Client;
23
 * $liblynx->setCredentials('your client id', 'your client secret');
24
 *
25
 * //must set a PSR-16 cache - this can be used for testing
26
 * $liblynx->setCache(new \Symfony\Component\Cache\Simple\ArrayCache);
27
 *
28
 * $identification=$liblynx->authorize(Identification::fromSuperglobals());
29
 * if ($identification->isIdentified()) {
30
 *     //good to go
31
 * }
32
 *
33
 * if ($identification->requiresWayf()) {
34
 *     $identification->doWayfRedirect();
35
 * }
36
 *
37
 * @package LibLynx\Connect
38
 */
39
class Client implements LoggerAwareInterface
40
{
41
    private $apiroot = 'https://connect.liblynx.com';
42
43
    /** @var string client ID obtain from LibLynx Connect admin portal */
44
    private $clientId;
45
46
    /** @var string client secret obtain from LibLynx Connect admin portal */
47
    private $clientSecret;
48
49
    /** @var GuzzleClient HTTP client for API requests */
50
    private $guzzle;
51
52
    /** @var array entry point resource */
53
    private $entrypoint;
54
55
    /** @var callable RequestStack handler for API requests */
56
    private $apiHandler = null;
57
58
    /** @var callable RequestStack handler for OAuth2 requests */
59
    private $oauth2Handler = null;
60
61
    /** @var CacheInterface */
62
    protected $cache;
63
64
    /** @var LoggerInterface */
65
    protected $log;
66
67
    /**
68
     * Create new LibLynx API client
69
     */
70
    public function __construct()
71
    {
72
        if (isset($_SERVER['LIBLYNX_CLIENT_ID'])) {
73
            $this->clientId = $_SERVER['LIBLYNX_CLIENT_ID'];
74
        }
75
        if (isset($_SERVER['LIBLYNX_CLIENT_SECRET'])) {
76
            $this->clientSecret = $_SERVER['LIBLYNX_CLIENT_SECRET'];
77
        }
78
79
        $this->log = new NullLogger();
80
    }
81
82
    /**
83
     * @inheritdoc
84
     */
85
    public function setLogger(LoggerInterface $logger)
86
    {
87
        $this->log = $logger;
88
    }
89
90
    /**
91
     * Set API root
92
     * This will be provided to you by LibLynx Technical Support
93
     * @param string $url
94
     */
95
    public function setAPIRoot($url)
96
    {
97
        $this->apiroot = $url;
98
    }
99
100
    /**
101
     * Set OAuth2.0 client credentials
102
     * These will be provided to you by LibLynx Technical Support
103
     *
104
     * @param string $clientId
105
     * @param string $clientSecret
106
     */
107
    public function setCredentials($clientId, $clientSecret)
108
    {
109
        $this->clientId = $clientId;
110
        $this->clientSecret = $clientSecret;
111
    }
112
113
    /**
114
     * @return array containing client id and secret
115
     */
116
    public function getCredentials()
117
    {
118
        return [$this->clientId, $this->clientSecret];
119
    }
120
121
    /**
122
     * Attempt an identification using the LibLynx API
123
     *
124
     * @param Identification $request
125
     * @return Identification|null
126
     */
127
    public function authorize(Identification $request)
128
    {
129
        $identification = null;
0 ignored issues
show
Unused Code introduced by
$identification is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
130
131
        $payload = $request->getRequestJSON();
132
        $response = $this->post('@new_identification', $payload);
133
        if (isset($response->id)) {
134
            $identification = Identification::fromJSON($response);
135
            $this->log->info(
136
                'Identification request for ip {ip} on URL {url} succeeded status={status} id={id}',
137
                [
138
                    'status' => $identification->status,
139
                    'id' => $identification->id,
0 ignored issues
show
Bug introduced by
The property id does not seem to exist in LibLynx\Connect\Identification.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
140
                    'ip' => $identification->ip,
141
                    'url' => $identification->url
142
                ]
143
            );
144
        } else {
145
            //failed
146
            $this->log->critical('Identification request failed {payload}', ['payload' => $payload]);
147
            return null;
148
        }
149
150
        return $identification;
151
    }
152
153
    /**
154
     * This is primarily to facilitate testing - we can add a MockHandler to return
155
     * test responses
156
     *
157
     * @param callable $handler
158
     * @return self
159
     */
160
    public function setAPIHandler(callable $handler)
161
    {
162
        $this->apiHandler = $handler;
163
        return $this;
164
    }
165
166
    /**
167
     * This is primarily to facilitate testing - we can add a MockHandler to return
168
     * test responses
169
     *
170
     * @param callable $handler
171
     * @return self
172
     */
173
    public function setOAuth2Handler(callable $handler)
174
    {
175
        $this->oauth2Handler = $handler;
176
        return $this;
177
    }
178
179
    public function get($entrypoint)
180
    {
181
        return $this->makeAPIRequest('GET', $entrypoint);
182
    }
183
184
    public function post($entrypoint, $json)
185
    {
186
        return $this->makeAPIRequest('POST', $entrypoint, $json);
187
    }
188
189
    public function put($entrypoint, $json)
190
    {
191
        return $this->makeAPIRequest('PUT', $entrypoint, $json);
192
    }
193
194
    protected function makeAPIRequest($method, $entrypoint, $json = null)
195
    {
196
        $this->log->debug('{method} {entrypoint} {json}', [
197
                'method' => $method,
198
                'entrypoint' => $entrypoint,
199
                'json' => $json
200
            ]);
201
        $url = $this->resolveEntryPoint($entrypoint);
202
        $client = $this->getClient();
203
204
        $this->log->debug('{entrypoint} = {url}', ['entrypoint' => $entrypoint, 'url' => $url]);
205
206
        $headers = ['Accept' => 'application/json'];
207
        if (!empty($body)) {
0 ignored issues
show
Bug introduced by
The variable $body seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
208
            $headers['Content-Type'] = 'application/json';
209
        }
210
211
        $request = new \GuzzleHttp\Psr7\Request($method, $url, $headers, $json);
212
213
        try {
214
            $response = $client->send($request);
215
216
            $this->log->debug('{method} {entrypoint} succeeded {status}', [
217
                'method' => $method,
218
                'entrypoint' => $entrypoint,
219
                'status' => $response->getStatusCode(),
220
            ]);
221
        } catch (RequestException $e) {
222
            $response = $e->getResponse();
223
            $this->log->error(
224
                '{method} {entrypoint} {json} failed ({status}): {body}',
225
                [
226
                    'method' => $method,
227
                    'json' => $json,
228
                    'entrypoint' => $entrypoint,
229
                    'status' => $response->getStatusCode(),
230
                    'body' => $response->getBody()
231
                ]
232
            );
233
        }
234
235
        $payload = json_decode($response->getBody());
236
        return $payload;
237
    }
238
239
    public function resolveEntryPoint($nameOrUrl)
240
    {
241
        if ($nameOrUrl[0] === '@') {
242
            return $this->getEntryPoint($nameOrUrl);
243
        }
244
        //it's a URL
245
        return $nameOrUrl;
246
    }
247
248
    public function getEntryPoint($name)
249
    {
250
        if (!is_array($this->entrypoint)) {
251
            $cache = $this->getCache();
252
            $key = 'entrypoint' . $this->clientId;
253
            if ($cache->has($key)) {
254
                $this->log->debug('loading entrypoint from persistent cache');
255
                $this->entrypoint = $cache->get($key);
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache->get($key) of type * is incompatible with the declared type array of property $entrypoint.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
256
            } else {
257
                $this->log->debug('entrypoint not cached, requesting from API');
258
                $client = $this->getClient();
259
260
                $request = new \GuzzleHttp\Psr7\Request('GET', 'api', [
261
                    'Content-Type' => 'application/json',
262
                    'Accept' => 'application/json',
263
                ]);
264
265
                try {
266
                    $response = $client->send($request);
267
268
                    $payload = json_decode($response->getBody());
269
                    if (is_object($payload) && isset($payload->_links)) {
270
                        $this->log->info('entrypoint loaded from API and cached');
271
                        $this->entrypoint = $payload;
0 ignored issues
show
Documentation Bug introduced by
It seems like $payload of type object is incompatible with the declared type array of property $entrypoint.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
272
                        $cache->set($key, $payload, 86400);
273
                    }
274
                } catch (RequestException $e) {
275
                    throw $e;
276
                }
277
            }
278
        } else {
279
            $this->log->debug('using previously loaded entrypoint');
280
        }
281
282
        if (!isset($this->entrypoint->_links->$name->href)) {
283
            throw new \InvalidArgumentException("Invalid LibLynx API entrypoint $name requested");
284
        }
285
286
        return $this->entrypoint->_links->$name->href;
287
    }
288
289
    public function getCache()
290
    {
291
        if (is_null($this->cache)) {
292
            throw new \RuntimeException('LibLynx Connect Client requires a PSR-16 compatible cache');
293
        }
294
        return $this->cache;
295
    }
296
297
    public function setCache(CacheInterface $cache)
298
    {
299
        $this->cache = $cache;
300
    }
301
302
    /**
303
     * Internal helper to provide an OAuth2 capable HTTP client
304
     */
305
    protected function getClient()
306
    {
307
        if (empty($this->clientId)) {
308
            throw new \BadMethodCallException('Cannot make API calls until setCredentials has been called');
309
        }
310
        if (!is_object($this->guzzle)) {
311
            //create our handler stack (which may be mocked in tests) and add the oauth and cache middleware
312
            $handlerStack = HandlerStack::create($this->apiHandler);
313
            $handlerStack->push($this->createOAuth2Middleware());
314
            $handlerStack->push($this->createCacheMiddleware(), 'cache');
315
316
            //now we can make our client
317
            $this->guzzle = new GuzzleClient([
318
                'handler' => $handlerStack,
319
                'auth' => 'oauth',
320
                'base_uri' => $this->apiroot
321
            ]);
322
        }
323
324
        return $this->guzzle;
325
    }
326
327
    protected function createOAuth2Middleware(): OAuth2Middleware
328
    {
329
        $handlerStack = HandlerStack::create($this->oauth2Handler);
330
331
        // Authorization client - this is used to request OAuth access tokens
332
        $reauth_client = new GuzzleClient([
333
            'handler' => $handlerStack,
334
            // URL for access_token request
335
            'base_uri' => $this->apiroot . '/oauth/v2/token',
336
        ]);
337
        $reauth_config = [
338
            "client_id" => $this->clientId,
339
            "client_secret" => $this->clientSecret
340
        ];
341
        $grant_type = new ClientCredentials($reauth_client, $reauth_config);
342
        $oauth = new OAuth2Middleware($grant_type);
343
344
        //use our cache to store tokens
345
        $oauth->setTokenPersistence(new SimpleCacheTokenPersistence($this->getCache()));
346
347
        return $oauth;
348
    }
349
350
    protected function createCacheMiddleware(): CacheMiddleware
351
    {
352
        return new CacheMiddleware(
353
            new PrivateCacheStrategy(
354
                new Psr16CacheStorage($this->cache)
355
            )
356
        );
357
    }
358
}
359