Completed
Push — master ( d7e260...fc7aea )
by Artem
10:19
created

Client   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 403
Duplicated Lines 0 %

Test Coverage

Coverage 76.42%

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 403
ccs 81
cts 106
cp 0.7642
rs 9.68
c 0
b 0
f 0
wmc 34

22 Methods

Rating   Name   Duplication   Size   Complexity  
A login() 0 6 1
A __construct() 0 5 1
A boot() 0 23 4
A unsafeLogin() 0 5 1
A forgot() 0 4 1
A reset() 0 5 1
A validateReset() 0 4 1
A register() 0 8 1
A sync() 0 8 1
A getToken() 0 3 1
A me() 0 3 1
A update() 0 4 1
A bearerTokenHeader() 0 7 1
A success() 0 9 4
A credential() 0 3 1
A bearerToken() 0 7 2
A parseResponse() 0 28 4
A request() 0 14 3
A setClient() 0 3 1
A hasRequest() 0 3 1
A signature() 0 3 1
A getClient() 0 3 1
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',
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
     * Make a request and retrieve a formatted response.
241
     *
242
     * @param string $name
243
     * @param array $parameters
244
     *
245
     * @return array
246
     *
247
     * @throws
248
     */
249 10
    public function request(string $name, array $parameters = []): array
250
    {
251 10
        if(!$this->hasRequest($name)) {
252 1
            throw new \InvalidArgumentException("Request `{$name}` is not supported");
253
        }
254
255 9
        if(!method_exists($this, $name)) {
256
            throw new \InvalidArgumentException("Request `{$name}` listed but is not implemented");
257
        }
258
        
259 9
        \Illuminate\Support\Facades\Log::debug("[Connector] Sending a {$name} request", $parameters);
260
261 9
        return $this->parseResponse(
262 9
            $this->response = call_user_func_array([$this, $name], $parameters)
263
        );
264
    }
265
266
    /**
267
     * Checks whether a request is supported
268
     *
269
     * @param string $name
270
     *
271
     * @return bool
272
     */
273 11
    public function hasRequest(string $name): bool
274
    {
275 11
        return in_array($name, $this->requests);
276
    }
277
278
    /**
279
     * Checks whether status of the response is successful
280
     *
281
     * @param bool $withStatus Whether need to check for "success" status from the response
282
     *
283
     * @return bool
284
     */
285 9
    public function success(bool $withStatus = false): bool
286
    {
287 9
        if(!$this->response || $this->response->getStatusCode() !== 200) {
288 3
            return false;
289
        }
290
291 6
        return $withStatus
292 4
            ? array_get($this->formatted, 'status') === 'success'
293 6
            : true;
294
    }
295
296
    /**
297
     * Retrieve a JWT token from the response
298
     *
299
     * @return string|null
300
     */
301 2
    public function getToken()
302
    {
303 2
        return array_get($this->formatted, 'token');
304
    }
305
306
    /**
307
     * @param HttpClient $client
308
     */
309
    public function setClient(HttpClient $client)
310
    {
311
        $this->client = $client;
312
    }
313
314
    /**
315
     * @return HttpClient
316
     */
317
    public function getClient()
318
    {
319
        return $this->client;
320
    }
321
322
    /**
323
     * Make a signature based on public and secret keys
324
     *
325
     * @param string $public
326
     * @param string $secret
327
     *
328
     * @return string
329
     */
330 3
    private function signature(string $public, string $secret)
331
    {
332 3
        return hash('sha256', $public . $secret);
333
    }
334
335
    /**
336
     * Parse a response
337
     *
338
     * @param ResponseInterface $response
339
     *
340
     * @return array
341
     *
342
     * @throws \Slides\Connector\Auth\Exceptions\HttpException
343
     */
344 9
    private function parseResponse(ResponseInterface $response): array
345
    {
346 9
        \Illuminate\Support\Facades\Log::debug("[Connector] Got a response. Status: " . $response->getStatusCode());
347
348 9
        $decoded = (string) $response->getBody();
349 9
        $decoded = json_decode($decoded, true);
350
351 9
        $this->formatted = $decoded;
352
353 9
        \Illuminate\Support\Facades\Log::debug(null, $decoded ?? []);
354
355 9
        if($this->success()) {
356 6
           return $this->formatted;
357
        }
358
359 3
        $message = array_get($this->formatted, 'message');
360
361 3
        switch ($this->response->getStatusCode()) {
362 3
            case \Illuminate\Http\Response::HTTP_UNPROCESSABLE_ENTITY: {
363 3
                throw ValidationException::create($message);
364
                break;
365
            }
366
            case \Illuminate\Http\Response::HTTP_UNAUTHORIZED: {
367
                throw new \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException('auth', $message);
368
                break;
369
            }
370
            default: {
371
                throw new \Slides\Connector\Auth\Exceptions\HttpException($message);
372
            }
373
        }
374
    }
375
376
    /**
377
     * Retrieve authorization Bearer token.
378
     *
379
     * @return string|null
380
     */
381
    private function bearerToken()
382
    {
383
        if(!$token = app('auth')->token()) {
384
            return null;
385
        }
386
387
        return 'Bearer ' . $token;
388
    }
389
390
    /**
391
     * A Guzzle middleware of injecting the Bearer authentication token
392
     *
393
     * @return \Closure
394
     */
395 3
    public function bearerTokenHeader(): \Closure
396
    {
397
        return function(callable $handler) {
398
            return function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
399
                $request = $request->withHeader('Authorization', $this->bearerToken());
400
401
                return $handler($request, $options);
402
            };
403 3
        };
404
    }
405
406
    /**
407
     * Retrieve a credential value
408
     *
409
     * @param string $key
410
     * @param mixed $default
411
     *
412
     * @return string|null
413
     */
414 20
    private function credential(string $key, $default = null)
415
    {
416 20
        return array_get(config('connector.credentials.auth', []), $key, $default);
417
    }
418
}