Completed
Pull Request — master (#1)
by Artem
03:18
created

Client::restore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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