Client::reset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 5
rs 10
ccs 4
cts 4
cp 1
crap 1
1
<?php
2
3
namespace Slides\Connector\Auth;
4
5
use GuzzleHttp\Client as HttpClient;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
use Psr\Http\Message\ResponseInterface;
9
use Slides\Connector\Auth\Exceptions\ValidationException;
10
use Slides\Connector\Auth\Concerns\WritesLogs;
11
12
/**
13
 * Class Client
14
 *
15
 * @package App\Services\Auth
16
 */
17
class Client
18
{
19
    use WritesLogs;
20
21
    /**
22
     * The HTTP client
23
     *
24
     * @var \GuzzleHttp\Client
25
     */
26
    protected $client;
27
28
    /**
29
     * Instance of the last response
30
     *
31
     * @var \Psr\Http\Message\ResponseInterface
32
     */
33
    protected $response;
34
35
    /**
36
     * The formatted last response
37
     *
38
     * @var array
39
     */
40
    protected $formatted = [];
41
42
    /**
43
     * The list of supported requests
44
     *
45
     * @var array
46
     */
47
    protected $requests = [
48
        'login', 'unsafeLogin', 'register', 'refresh', 'me', 'update',
49
        'forgot', 'validateReset', 'reset', 'sync', 'delete', 'restore'
50
    ];
51
52
    /**
53
     * Client constructor.
54
     *
55
     * @param HttpClient|null $client
56
     */
57 19
    public function __construct(HttpClient $client = null)
58
    {
59 19
        $this->client = $client;
60
61 19
        $this->boot();
62 19
    }
63
64
    /**
65
     * Initialize the client.
66
     */
67 20
    protected function boot()
68
    {
69 20
        if(!$publicKey = $this->credential('public')) {
70
            throw new \InvalidArgumentException('Public key must be defined');
71
        }
72
73 20
        if(!$secretKey = $this->credential('secret')) {
74
            throw new \InvalidArgumentException('Secret key must be defined');
75
        }
76
77 20
        if(!$this->client) {
78 3
            $handler = new \GuzzleHttp\HandlerStack();
79 3
            $handler->setHandler(new \GuzzleHttp\Handler\CurlHandler());
80 3
            $handler->push($this->bearerTokenHeader());
81
82 3
            $this->client = new HttpClient([
83 3
                'handler' => $handler,
84 3
                'base_uri' => Str::finish($this->credential('url'), '/'),
85
                'headers' => [
86 3
                    'X-Tenant-Key' => $publicKey,
87 3
                    'X-Tenant-Sign' => $this->signature($publicKey, $secretKey),
88
                    'User-Agent' => null
89
                ],
90
                'http_errors' => false
91
            ]);
92
        }
93 20
    }
94
95
    /**
96
     * Login a user
97
     *
98
     * @param string $email
99
     * @param string $password
100
     * @param bool $remember
101
     *
102
     * @return ResponseInterface
103
     */
104 4
    protected function login(string $email, string $password, bool $remember = false): ResponseInterface
105
    {
106 4
        return $this->client->post('login', ['json' => [
107 4
            'email' => $email,
108 4
            'password' => $password,
109 4
            'remember' => $remember
110
        ]]);
111
    }
112
113
    /**
114
     * Login a user
115
     *
116
     * Warning! This method has implemented temporarily to make able to login users
117
     * who use Social Auth on 24Templates. MUST NOT be used in any other cases.
118
     *
119
     * @param string $email
120
     * @param bool $remember
121
     *
122
     * @return ResponseInterface
123
     */
124 2
    protected function unsafeLogin(string $email, bool $remember = false): ResponseInterface
125
    {
126 2
        return $this->client->post('unsafe-login', ['json' => [
127 2
            'email' => $email,
128 2
            'remember' => $remember
129
        ]]);
130
    }
131
132
    /**
133
     * Register a user
134
     *
135
     * @param int $userId Local user ID
136
     * @param string $name
137
     * @param string $email
138
     * @param string $password
139
     * @param string $country
140
     *
141
     * @return ResponseInterface
142
     */
143 2
    protected function register(int $userId, string $name, string $email, string $password, string $country): ResponseInterface
144
    {
145 2
        return $this->client->post('register', ['json' => [
146 2
            'userId' => $userId,
147 2
            'name' => $name,
148 2
            'email' => $email,
149 2
            'password' => $password,
150 2
            'country' => $country
151
        ]]);
152
    }
153
154
    /**
155
     * Send an email with a password resetting link
156
     *
157
     * @param string $email
158
     *
159
     * @return ResponseInterface
160
     */
161 2
    protected function forgot(string $email): ResponseInterface
162
    {
163 2
        return $this->client->post('forgot', ['json' => [
164 2
            'email' => $email
165
        ]]);
166
    }
167
168
    /**
169
     * Send an email with a password resetting link
170
     *
171
     * @param string $token
172
     * @param string $email
173
     *
174
     * @return ResponseInterface
175
     */
176 2
    protected function validateReset(string $token, string $email): ResponseInterface
177
    {
178 2
        return $this->client->post('reset/' . $token . '/' . $email . '/validate', ['json' => [
179 2
            'email' => $email
180
        ]]);
181
    }
182
183
    /**
184
     * Reset user's password
185
     *
186
     * @param string $token
187
     * @param string $email
188
     * @param string $password
189
     * @param string $confirmation
190
     *
191
     * @return ResponseInterface
192
     */
193 2
    protected function reset(string $token, string $email, string $password, string $confirmation): ResponseInterface
194
    {
195 2
        return $this->client->post('reset/' . $token . '/' . $email, ['json' => [
196 2
            'password' => $password,
197 2
            'password_confirmation' => $confirmation
198
        ]]);
199
    }
200
201
    /**
202
     * Synchronize remote and local users
203
     *
204
     * @param array $users
205
     * @param array $modes
206
     *
207
     * @return ResponseInterface
208
     */
209
    protected function sync(array $users, array $modes): ResponseInterface
210
    {
211
        return $this->client->post('sync', [
212
            'json' => [
213
                'users' => $users,
214
                'modes' => $modes
215
            ],
216
            'timeout' => 0
217
        ]);
218
    }
219
220
    /**
221
     * Update a remote user
222
     *
223
     * @param int $id Local user ID
224
     * @param array $attributes
225
     *
226
     * @return ResponseInterface
227
     */
228 2
    protected function update(int $id, array $attributes): ResponseInterface
229
    {
230 2
        return $this->client->post('update', ['json' => array_merge(
231 2
            ['userId' => $id], $attributes
232
        )]);
233
    }
234
235
    /**
236
     * Retrieve an authenticated user
237
     *
238
     * @return ResponseInterface
239
     */
240
    protected function me(): ResponseInterface
241
    {
242
        return $this->client->get('me');
243
    }
244
245
    /**
246
     * Delete a remote user.
247
     *
248
     * @param int $id Local user ID.
249
     *
250
     * @return ResponseInterface
251
     */
252
    protected function delete(int $id): ResponseInterface
253
    {
254
        return $this->client->post('delete/' . $id);
255
    }
256
257
    /**
258
     * Restore a remote user.
259
     *
260
     * @param int $id Local user ID.
261
     *
262
     * @return ResponseInterface
263
     */
264
    protected function restore(int $id): ResponseInterface
265
    {
266
        return $this->client->post('restore/' . $id);
267
    }
268
269
    /**
270
     * Make a request and retrieve a formatted response.
271
     *
272
     * @param string $name
273
     * @param array $parameters
274
     *
275
     * @return array
276
     *
277
     * @throws
278
     */
279 10
    public function request(string $name, array $parameters = []): array
280
    {
281 10
        if(!$this->hasRequest($name)) {
282 1
            throw new \InvalidArgumentException("Request `{$name}` is not supported");
283
        }
284
285 9
        if(!method_exists($this, $name)) {
286
            throw new \InvalidArgumentException("Request `{$name}` listed but is not implemented");
287
        }
288
289 9
        $this->log("Sending a {$name} request", $parameters);
290
291 9
        return $this->parseResponse(
292 9
            $this->response = call_user_func_array([$this, $name], $parameters)
293
        );
294
    }
295
296
    /**
297
     * Checks whether a request is supported
298
     *
299
     * @param string $name
300
     *
301
     * @return bool
302
     */
303 11
    public function hasRequest(string $name): bool
304
    {
305 11
        return in_array($name, $this->requests);
306
    }
307
308
    /**
309
     * Checks whether status of the response is successful
310
     *
311
     * @param bool $withStatus Whether need to check for "success" status from the response
312
     *
313
     * @return bool
314
     */
315 9
    public function success(bool $withStatus = false): bool
316
    {
317 9
        if(!$this->response || $this->response->getStatusCode() !== 200) {
318 3
            return false;
319
        }
320
321 6
        return $withStatus
322 5
            ? Arr::get($this->formatted, 'status') === 'success'
323 6
            : true;
324
    }
325
326
    /**
327
     * Retrieve a JWT token from the response
328
     *
329
     * @return string|null
330
     */
331 1
    public function getToken()
332
    {
333 1
        return Arr::get($this->formatted, 'token');
334
    }
335
336
    /**
337
     * @param HttpClient $client
338
     */
339
    public function setClient(HttpClient $client)
340
    {
341
        $this->client = $client;
342
    }
343
344
    /**
345
     * @return HttpClient
346
     */
347
    public function getClient()
348
    {
349
        return $this->client;
350
    }
351
352
    /**
353
     * Make a signature based on public and secret keys
354
     *
355
     * @param string $public
356
     * @param string $secret
357
     *
358
     * @return string
359
     */
360 3
    private function signature(string $public, string $secret)
361
    {
362 3
        return hash('sha256', $public . $secret);
363
    }
364
365
    /**
366
     * Parse a response
367
     *
368
     * @param ResponseInterface $response
369
     *
370
     * @return array
371
     *
372
     * @throws \Slides\Connector\Auth\Exceptions\HttpException
373
     */
374 9
    private function parseResponse(ResponseInterface $response): array
375
    {
376 9
        $this->log('Got a response. Status: ' . $response->getStatusCode());
377
378 9
        $decoded = (string) $response->getBody();
379 9
        $decoded = json_decode($decoded, true);
380
381 9
        $this->formatted = $decoded;
382
383 9
        $this->log(null, $decoded ?? []);
384
385 9
        if($this->success()) {
386 6
           return $this->formatted;
387
        }
388
389 3
        $message = Arr::get($this->formatted, 'message');
390
391 3
        switch ($this->response->getStatusCode()) {
392 3
            case \Illuminate\Http\Response::HTTP_UNPROCESSABLE_ENTITY: {
393 3
                throw ValidationException::create($message);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type null; however, parameter $message of Slides\Connector\Auth\Ex...tionException::create() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

393
                throw ValidationException::create(/** @scrutinizer ignore-type */ $message);
Loading history...
394
                break;
395
            }
396
            case \Illuminate\Http\Response::HTTP_UNAUTHORIZED: {
397
                throw new \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException('auth', $message);
398
                break;
399
            }
400
            default: {
401
                throw new \Slides\Connector\Auth\Exceptions\HttpException($message);
402
            }
403
        }
404
    }
405
406
    /**
407
     * Retrieve authorization Bearer token.
408
     *
409
     * @return string|null
410
     */
411
    private function bearerToken()
412
    {
413
        if(!$token = app('auth')->token()) {
414
            return null;
415
        }
416
417
        return 'Bearer ' . $token;
418
    }
419
420
    /**
421
     * A Guzzle middleware of injecting the Bearer authentication token
422
     *
423
     * @return \Closure
424
     */
425 3
    public function bearerTokenHeader(): \Closure
426
    {
427
        return function(callable $handler) {
428
            return function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
429
                $request = $request->withHeader('Authorization', $this->bearerToken());
430
431
                return $handler($request, $options);
432
            };
433 3
        };
434
    }
435
436
    /**
437
     * Retrieve a credential value
438
     *
439
     * @param string $key
440
     * @param mixed $default
441
     *
442
     * @return string|null
443
     */
444 20
    private function credential(string $key, $default = null)
445
    {
446 20
        return Arr::get(config('connector.credentials.auth', []), $key, $default);
447
    }
448
}