Client::addUser()   A
last analyzed

Complexity

Conditions 3
Paths 5

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 12
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 27
ccs 11
cts 11
cp 1
crap 3
rs 9.8666
1
<?php
2
3
namespace Spinen\Formio;
4
5
use Exception;
6
use GuzzleHttp\Client as Guzzle;
7
use GuzzleHttp\Exception\GuzzleException;
8
use GuzzleHttp\Exception\RequestException;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Str;
11
use Psr\Http\Message\ResponseInterface;
12
use Spinen\Formio\Contracts\FormioUser;
13
use Spinen\Formio\Exceptions\TokenException;
14
use Spinen\Formio\Exceptions\UserException;
15
16
/**
17
 * Class Client
18
 *
19
 * @package Spinen\Formio
20
 */
21
class Client
22
{
23
    /**
24
     * Configs for the client
25
     *
26
     * @var array
27
     */
28
    protected $configs;
29
30
    /**
31
     * Guzzle instance
32
     *
33
     * @var Guzzle
34
     */
35
    protected $guzzle;
36
37
    /**
38
     * Token instance
39
     *
40
     * @var Token
41
     */
42
    public $token;
43
44
    /**
45
     * Client constructor.
46
     *
47
     * @param array $configs
48
     * @param Guzzle $guzzle
49
     * @param Token|null $token
50
     */
51 15
    public function __construct(array $configs, Guzzle $guzzle, Token $token = null)
52
    {
53 15
        $this->setConfigs($configs);
54 15
        $this->guzzle = $guzzle;
55 15
        $this->token = $token ?? new Token();
56
    }
57
58
    /**
59
     * Add a user to Formio
60
     *
61
     * @param FormioUser $user
62
     * @param null $password
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $password is correct as it would always require null to be passed?
Loading history...
63
     *
64
     * @return Client
65
     * @throws UserException
66
     */
67 3
    public function addUser(FormioUser $user, $password = null)
68
    {
69 3
        $user->formio_password = $password ?? $this->configs['user']['register']['default_password'] ?? Str::random(32);
0 ignored issues
show
Bug introduced by
Accessing formio_password on the interface Spinen\Formio\Contracts\FormioUser suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
70
71
        try {
72 3
            $response = $this->guzzle->post(
73 3
                $this->uri($this->configs['user']['register']['path']),
74
                [
75
                    'form_params' => [
76 3
                        'data' => $user->getRegistrationData(),
77
                    ],
78
                ]
79
            );
80
81 2
            if (!$user->save()) {
82 1
                throw new UserException("Unable to save the user's Formio password");
83
84
                // TODO: Rollback
85
            }
86
87 1
            $this->parseUser($response);
88
89 1
            return $this;
90 2
        } catch (RequestException $e) {
91
            // TODO: Figure out what to do with this error
92
93 1
            throw $e;
94
        }
95
    }
96
97
    /**
98
     * Build admin login
99
     *
100
     * @return array
101
     */
102 2
    protected function getAdminLoginData()
103
    {
104
        return [
105 2
            'email'    => $this->configs['admin']['username'],
106 2
            'password' => $this->configs['admin']['password'],
107
        ];
108
    }
109
110
    /**
111
     * Login user to Formio
112
     *
113
     * If no user provided, then use the admin user
114
     *
115
     * @param FormioUser|null $user
116
     *
117
     * @return Client
118
     * @throws Exception
119
     */
120 4
    public function login(FormioUser $user = null)
121
    {
122
        try {
123 4
            $this->parseUser(
124 4
                $this->guzzle->post(
125 4
                    $this->uri(
126 4
                        $user ? $this->configs['user']['login']['path'] : $this->configs['admin']['login']['path']
127
                    ),
128
                    [
129
                        'form_params' => [
130 4
                            'data' => $user ? $user->getLoginData() : $this->getAdminLoginData(),
131
                        ],
132
                    ]
133
                )
134
            );
135
136 3
            return $this;
137 1
        } catch (RequestException $e) {
138
            // TODO: Figure out what to do with this error
139
140 1
            throw $e;
141
        }
142
    }
143
144
    /**
145
     * Logout
146
     *
147
     * Since the Formio is stateless, just empty the Token
148
     *
149
     * @return $this
150
     */
151 1
    public function logout()
152
    {
153 1
        $this->token = new Token();
154
155 1
        return $this;
156
    }
157
158
    /**
159
     * Parse the user & JWT from the response into the token
160
     *
161
     * @param ResponseInterface $response
162
     */
163 4
    protected function parseUser(ResponseInterface $response)
164
    {
165
        // TODO: Add some error checking to user parsing
166 4
        $this->token = $this->token->setUser(json_decode($response->getBody(), true))
167
                                   ->setJwt(
168 4
                                       $response->getHeader('x-jwt-token')[0],
169 4
                                       $this->configs['jwt']['secret'],
170 4
                                       $this->configs['jwt']['algorithm']
171
                                   );
172
    }
173
174
    /**
175
     * Make an API call to Formio
176
     *
177
     * @param $path
178
     * @param array|null $data
179
     * @param string|null $method
180
     *
181
     * @return array
182
     * @throws GuzzleException
183
     * @throws TokenException
184
     */
185 5
    public function request($path, $data = [], $method = 'GET')
186
    {
187 5
        if (!$this->token) {
188 1
            throw new TokenException('Must be logged in before making a request');
189
        }
190
191 4
        if ($this->token->expired()) {
192 1
            throw new TokenException('Token expired ' . $this->token->expires_at->diffForHumans());
193
        }
194
195
        try {
196 3
            $response = $this->guzzle->request(
197
                $method,
0 ignored issues
show
Bug introduced by
It seems like $method can also be of type null; however, parameter $method of GuzzleHttp\Client::request() 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

197
                /** @scrutinizer ignore-type */ $method,
Loading history...
198 3
                $this->uri($path),
199
                [
200
                    'headers'     => [
201 3
                        'x-jwt-token' => $this->token->jwt,
202
                    ],
203
                    'form_params' => [
204 3
                        'data' => $data,
205
                    ],
206
                ]
207
            );
208
209 2
            return json_decode($response->getBody(), true);
210 1
        } catch (GuzzleException $e) {
211
            // TODO: Figure out what to do with this error
212
213 1
            throw $e;
214
        }
215
    }
216
217
    /**
218
     * Set the configs
219
     *
220
     * @param array $configs
221
     *
222
     * @return $this
223
     */
224 15
    public function setConfigs(array $configs)
225
    {
226 15
        $this->configs = $configs;
227
228 15
        return $this;
229
    }
230
231
    /**
232
     * SSO for User
233
     *
234
     * If the user already has a Formio password, then login the use.
235
     * Otherwise, make a Custom JWT.
236
     *
237
     * @param FormioUser $user
238
     *
239
     * @return Client
240
     * @throws Exception
241
     */
242 2
    public function sso(FormioUser $user)
243
    {
244
        // If the user has a Formio password, then log them in
245 2
        if ($user->formio_password) {
0 ignored issues
show
Bug introduced by
Accessing formio_password on the interface Spinen\Formio\Contracts\FormioUser suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
246 1
            return $this->login($user);
247
        }
248
249 1
        $this->token = $this->token->makeJwt(
250 1
            $this->configs['project']['id'],
251 1
            $this->configs['user']['form'],
252 1
            Arr::except($user->getRegistrationData(), 'password'),
253
            // TODO: Roles from the database?
254 1
            $this->configs['user']['roles'],
255 1
            $this->configs['jwt']['secret'],
256 1
            $this->configs['jwt']['algorithm']
257
        );
258
259 1
        return $this;
260
    }
261
262
    /**
263
     * URL to Formio
264
     *
265
     * If path is passed in, then append it to the end
266
     *
267
     * @param null $path
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $path is correct as it would always require null to be passed?
Loading history...
268
     *
269
     * @return string
270
     */
271 10
    public function uri($path = null)
272
    {
273 10
        return rtrim($this->configs['url'], '/') . '/' . ltrim($path, '/');
0 ignored issues
show
Bug introduced by
$path of type null is incompatible with the type string expected by parameter $string of ltrim(). ( Ignorable by Annotation )

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

273
        return rtrim($this->configs['url'], '/') . '/' . ltrim(/** @scrutinizer ignore-type */ $path, '/');
Loading history...
274
    }
275
}
276