Completed
Push — master ( 361e7c...d7e260 )
by Artem
03:39
created

Client   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 394
Duplicated Lines 0 %

Test Coverage

Coverage 76.47%

Importance

Changes 0
Metric Value
eloc 89
dl 0
loc 394
ccs 78
cts 102
cp 0.7647
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 sync() 0 5 1
A forgot() 0 4 1
A getToken() 0 3 1
A me() 0 3 1
A update() 0 4 1
A bearerTokenHeader() 0 7 1
A reset() 0 5 1
A success() 0 9 4
A credential() 0 3 1
A bearerToken() 0 7 2
A parseResponse() 0 24 4
A validateReset() 0 4 1
A request() 0 12 3
A setClient() 0 3 1
A hasRequest() 0 3 1
A signature() 0 3 1
A getClient() 0 3 1
A register() 0 8 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', ['json' => [
206
            'users' => $users,
207
            'modes' => $modes
208
        ]]);
209
    }
210
211
    /**
212
     * Update a remote user
213
     *
214
     * @param int $id Local user ID
215
     * @param array $attributes
216
     *
217
     * @return ResponseInterface
218
     */
219 2
    protected function update(int $id, array $attributes): ResponseInterface
220
    {
221 2
        return $this->client->post('update', ['json' => array_merge(
222 2
            ['userId' => $id], $attributes
223
        )]);
224
    }
225
226
    /**
227
     * Retrieve an authenticated user
228
     *
229
     * @return ResponseInterface
230
     */
231
    protected function me(): ResponseInterface
232
    {
233
        return $this->client->get('me');
234
    }
235
236
    /**
237
     * Make a request and retrieve a formatted response.
238
     *
239
     * @param string $name
240
     * @param array $parameters
241
     *
242
     * @return array
243
     *
244
     * @throws
245
     */
246 10
    public function request(string $name, array $parameters = []): array
247
    {
248 10
        if(!$this->hasRequest($name)) {
249 1
            throw new \InvalidArgumentException("Request `{$name}` is not supported");
250
        }
251
252 9
        if(!method_exists($this, $name)) {
253
            throw new \InvalidArgumentException("Request `{$name}` listed but is not implemented");
254
        }
255
256 9
        return $this->parseResponse(
257 9
            $this->response = call_user_func_array([$this, $name], $parameters)
258
        );
259
    }
260
261
    /**
262
     * Checks whether a request is supported
263
     *
264
     * @param string $name
265
     *
266
     * @return bool
267
     */
268 11
    public function hasRequest(string $name): bool
269
    {
270 11
        return in_array($name, $this->requests);
271
    }
272
273
    /**
274
     * Checks whether status of the response is successful
275
     *
276
     * @param bool $withStatus Whether need to check for "success" status from the response
277
     *
278
     * @return bool
279
     */
280 9
    public function success(bool $withStatus = false): bool
281
    {
282 9
        if(!$this->response || $this->response->getStatusCode() !== 200) {
283 3
            return false;
284
        }
285
286 6
        return $withStatus
287 4
            ? array_get($this->formatted, 'status') === 'success'
288 6
            : true;
289
    }
290
291
    /**
292
     * Retrieve a JWT token from the response
293
     *
294
     * @return string|null
295
     */
296 2
    public function getToken()
297
    {
298 2
        return array_get($this->formatted, 'token');
299
    }
300
301
    /**
302
     * @param HttpClient $client
303
     */
304
    public function setClient(HttpClient $client)
305
    {
306
        $this->client = $client;
307
    }
308
309
    /**
310
     * @return HttpClient
311
     */
312
    public function getClient()
313
    {
314
        return $this->client;
315
    }
316
317
    /**
318
     * Make a signature based on public and secret keys
319
     *
320
     * @param string $public
321
     * @param string $secret
322
     *
323
     * @return string
324
     */
325 3
    private function signature(string $public, string $secret)
326
    {
327 3
        return hash('sha256', $public . $secret);
328
    }
329
330
    /**
331
     * Parse a response
332
     *
333
     * @param ResponseInterface $response
334
     *
335
     * @return array
336
     *
337
     * @throws \Slides\Connector\Auth\Exceptions\HttpException
338
     */
339 9
    private function parseResponse(ResponseInterface $response): array
340
    {
341 9
        $decoded = (string) $response->getBody();
342 9
        $decoded = json_decode($decoded, true);
343
344 9
        $this->formatted = $decoded;
345
346 9
        if($this->success()) {
347 6
           return $this->formatted;
348
        }
349
350 3
        $message = array_get($this->formatted, 'message');
351
352 3
        switch ($this->response->getStatusCode()) {
353 3
            case \Illuminate\Http\Response::HTTP_UNPROCESSABLE_ENTITY: {
354 3
                throw ValidationException::create($message);
355
                break;
356
            }
357
            case \Illuminate\Http\Response::HTTP_UNAUTHORIZED: {
358
                throw new \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException('auth', $message);
359
                break;
360
            }
361
            default: {
362
                throw new \Slides\Connector\Auth\Exceptions\HttpException($message);
363
            }
364
        }
365
    }
366
367
    /**
368
     * Retrieve authorization Bearer token.
369
     *
370
     * @return string|null
371
     */
372
    private function bearerToken()
373
    {
374
        if(!$token = app('auth')->token()) {
375
            return null;
376
        }
377
378
        return 'Bearer ' . $token;
379
    }
380
381
    /**
382
     * A Guzzle middleware of injecting the Bearer authentication token
383
     *
384
     * @return \Closure
385
     */
386 3
    public function bearerTokenHeader(): \Closure
387
    {
388
        return function(callable $handler) {
389
            return function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
390
                $request = $request->withHeader('Authorization', $this->bearerToken());
391
392
                return $handler($request, $options);
393
            };
394 3
        };
395
    }
396
397
    /**
398
     * Retrieve a credential value
399
     *
400
     * @param string $key
401
     * @param mixed $default
402
     *
403
     * @return string|null
404
     */
405 20
    private function credential(string $key, $default = null)
406
    {
407 20
        return array_get(config('connector.credentials.auth', []), $key, $default);
408
    }
409
}