Passed
Push — master ( cf4fbb...361e7c )
by Artem
02:40
created

Client::me()   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 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
crap 2
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
     *
134
     * @return ResponseInterface
135
     */
136 2
    protected function register(int $userId, string $name, string $email, string $password): ResponseInterface
137
    {
138 2
        return $this->client->post('register', ['json' => [
139 2
            'userId' => $userId,
140 2
            'name' => $name,
141 2
            'email' => $email,
142 2
            'password' => $password
143
        ]]);
144
    }
145
146
    /**
147
     * Send an email with a password resetting link
148
     *
149
     * @param string $email
150
     *
151
     * @return ResponseInterface
152
     */
153 2
    protected function forgot(string $email): ResponseInterface
154
    {
155 2
        return $this->client->post('forgot', ['json' => [
156 2
            'email' => $email
157
        ]]);
158
    }
159
160
    /**
161
     * Send an email with a password resetting link
162
     *
163
     * @param string $token
164
     * @param string $email
165
     *
166
     * @return ResponseInterface
167
     */
168 2
    protected function validateReset(string $token, string $email): ResponseInterface
169
    {
170 2
        return $this->client->post('reset/' . $token . '/' . $email . '/validate', ['json' => [
171 2
            'email' => $email
172
        ]]);
173
    }
174
175
    /**
176
     * Reset user's password
177
     *
178
     * @param string $token
179
     * @param string $email
180
     * @param string $password
181
     * @param string $confirmation
182
     *
183
     * @return ResponseInterface
184
     */
185 2
    protected function reset(string $token, string $email, string $password, string $confirmation): ResponseInterface
186
    {
187 2
        return $this->client->post('reset/' . $token . '/' . $email, ['json' => [
188 2
            'password' => $password,
189 2
            'password_confirmation' => $confirmation
190
        ]]);
191
    }
192
193
    /**
194
     * Synchronize remote and local users
195
     *
196
     * @param array $users
197
     * @param array $modes
198
     *
199
     * @return ResponseInterface
200
     */
201
    protected function sync(array $users, array $modes): ResponseInterface
202
    {
203
        return $this->client->post('sync', ['json' => [
204
            'users' => $users,
205
            'modes' => $modes
206
        ]]);
207
    }
208
209
    /**
210
     * Update a remote user
211
     *
212
     * @param int $id Local user ID
213
     * @param array $attributes
214
     *
215
     * @return ResponseInterface
216
     */
217 2
    protected function update(int $id, array $attributes): ResponseInterface
218
    {
219 2
        return $this->client->post('update', ['json' => array_merge(
220 2
            ['userId' => $id], $attributes
221
        )]);
222
    }
223
224
    /**
225
     * Retrieve an authenticated user
226
     *
227
     * @return ResponseInterface
228
     */
229
    protected function me(): ResponseInterface
230
    {
231
        return $this->client->get('me');
232
    }
233
234
    /**
235
     * Make a request and retrieve a formatted response.
236
     *
237
     * @param string $name
238
     * @param array $parameters
239
     *
240
     * @return array
241
     *
242
     * @throws
243
     */
244 10
    public function request(string $name, array $parameters = []): array
245
    {
246 10
        if(!$this->hasRequest($name)) {
247 1
            throw new \InvalidArgumentException("Request `{$name}` is not supported");
248
        }
249
250 9
        if(!method_exists($this, $name)) {
251
            throw new \InvalidArgumentException("Request `{$name}` listed but is not implemented");
252
        }
253
254 9
        return $this->parseResponse(
255 9
            $this->response = call_user_func_array([$this, $name], $parameters)
256
        );
257
    }
258
259
    /**
260
     * Checks whether a request is supported
261
     *
262
     * @param string $name
263
     *
264
     * @return bool
265
     */
266 11
    public function hasRequest(string $name): bool
267
    {
268 11
        return in_array($name, $this->requests);
269
    }
270
271
    /**
272
     * Checks whether status of the response is successful
273
     *
274
     * @param bool $withStatus Whether need to check for "success" status from the response
275
     *
276
     * @return bool
277
     */
278 9
    public function success(bool $withStatus = false): bool
279
    {
280 9
        if(!$this->response || $this->response->getStatusCode() !== 200) {
281 3
            return false;
282
        }
283
284 6
        return $withStatus
285 4
            ? array_get($this->formatted, 'status') === 'success'
286 6
            : true;
287
    }
288
289
    /**
290
     * Retrieve a JWT token from the response
291
     *
292
     * @return string|null
293
     */
294 2
    public function getToken()
295
    {
296 2
        return array_get($this->formatted, 'token');
297
    }
298
299
    /**
300
     * @param HttpClient $client
301
     */
302
    public function setClient(HttpClient $client)
303
    {
304
        $this->client = $client;
305
    }
306
307
    /**
308
     * @return HttpClient
309
     */
310
    public function getClient()
311
    {
312
        return $this->client;
313
    }
314
315
    /**
316
     * Make a signature based on public and secret keys
317
     *
318
     * @param string $public
319
     * @param string $secret
320
     *
321
     * @return string
322
     */
323 3
    private function signature(string $public, string $secret)
324
    {
325 3
        return hash('sha256', $public . $secret);
326
    }
327
328
    /**
329
     * Parse a response
330
     *
331
     * @param ResponseInterface $response
332
     *
333
     * @return array
334
     *
335
     * @throws \Slides\Connector\Auth\Exceptions\HttpException
336
     */
337 9
    private function parseResponse(ResponseInterface $response): array
338
    {
339 9
        $decoded = (string) $response->getBody();
340 9
        $decoded = json_decode($decoded, true);
341
342 9
        $this->formatted = $decoded;
343
344 9
        if($this->success()) {
345 6
           return $this->formatted;
346
        }
347
348 3
        $message = array_get($this->formatted, 'message');
349
350 3
        switch ($this->response->getStatusCode()) {
351 3
            case \Illuminate\Http\Response::HTTP_UNPROCESSABLE_ENTITY: {
352 3
                throw ValidationException::create($message);
353
                break;
354
            }
355
            case \Illuminate\Http\Response::HTTP_UNAUTHORIZED: {
356
                throw new \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException('auth', $message);
357
                break;
358
            }
359
            default: {
360
                throw new \Slides\Connector\Auth\Exceptions\HttpException($message);
361
            }
362
        }
363
    }
364
365
    /**
366
     * Retrieve authorization Bearer token.
367
     *
368
     * @return string|null
369
     */
370
    private function bearerToken()
371
    {
372
        if(!$token = app('auth')->token()) {
373
            return null;
374
        }
375
376
        return 'Bearer ' . $token;
377
    }
378
379
    /**
380
     * A Guzzle middleware of injecting the Bearer authentication token
381
     *
382
     * @return \Closure
383
     */
384 3
    public function bearerTokenHeader(): \Closure
385
    {
386
        return function(callable $handler) {
387
            return function (\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
388
                $request = $request->withHeader('Authorization', $this->bearerToken());
389
390
                return $handler($request, $options);
391
            };
392 3
        };
393
    }
394
395
    /**
396
     * Retrieve a credential value
397
     *
398
     * @param string $key
399
     * @param mixed $default
400
     *
401
     * @return string|null
402
     */
403 20
    private function credential(string $key, $default = null)
404
    {
405 20
        return array_get(config('connector.credentials.auth', []), $key, $default);
406
    }
407
}