Completed
Push — master ( 38803a...a6786f )
by Thijs
29s queued 13s
created

Client::request()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 3
rs 10
1
<?php
2
3
namespace TestMonitor\DevOps;
4
5
use Psr\Http\Message\ResponseInterface;
6
use TestMonitor\DevOps\Exceptions\Exception;
7
use Jeylabs\OAuth2\Client\Provider\VSTSProvider;
8
use TestMonitor\DevOps\Exceptions\NotFoundException;
9
use TestMonitor\DevOps\Exceptions\ValidationException;
10
use TestMonitor\DevOps\Exceptions\FailedActionException;
11
use TestMonitor\DevOps\Exceptions\TokenExpiredException;
12
use TestMonitor\DevOps\Exceptions\UnauthorizedException;
13
14
class Client
15
{
16
    use Actions\ManagesAccounts,
0 ignored issues
show
introduced by
The trait TestMonitor\DevOps\Actions\ManagesWorkItems requires some properties which are not provided by TestMonitor\DevOps\Client: $path, $description, $workItemType, $title, $stepsToReproduce
Loading history...
introduced by
The trait TestMonitor\DevOps\Actions\ManagesWebhooks requires some properties which are not provided by TestMonitor\DevOps\Client: $eventType, $url, $username, $description, $projectId, $password
Loading history...
17
        Actions\ManagesAttachments,
18
        Actions\ManagesProjects,
19
        Actions\ManagesTeams,
20
        Actions\ManagesWebhooks,
21
        Actions\ManagesWorkItems,
22
        Actions\ManagesWorkItemTypes;
23
24
    /**
25
     * @var \TestMonitor\DevOps\AccessToken
26
     */
27
    protected $token;
28
29
    /**
30
     * @var string
31
     */
32
    protected $organization;
33
34
    /**
35
     * @var string
36
     */
37
    protected $baseUrl = 'https://dev.azure.com';
38
39
    /**
40
     * @var string
41
     */
42
    protected $apiVersion = '5.0';
43
44
    /**
45
     * @var \GuzzleHttp\Client
46
     */
47
    protected $client;
48
49
    /**
50
     * @var VSTSProvider
51
     */
52
    protected $provider;
53
54
    /**
55
     * Create a new client instance.
56
     *
57
     * @param array $credentials
58
     * @param \TestMonitor\DevOps\AccessToken $token
59
     * @param string $organization
60
     * @param \Jeylabs\OAuth2\Client\Provider\VSTSProvider $provider
61
     */
62 51
    public function __construct(
63
        array $credentials,
64
        string $organization = '',
65
        AccessToken $token = null,
66
        VSTSProvider $provider = null
67
    ) {
68 51
        $this->token = $token;
69 51
        $this->organization = $organization;
70
71 51
        $this->provider = $provider ?? new VSTSProvider([
72 51
            'clientId' => $credentials['clientId'],
73 51
            'clientSecret' => $credentials['clientSecret'],
74 51
            'redirectUri' => $credentials['redirectUrl'],
75 51
            'urlAuthorize' => $credentials['authorizeUrl'] ?? 'https://app.vssps.visualstudio.com/oauth2/authorize',
76 51
            'urlAccessToken' => $credentials['accessTokenUrl'] ?? 'https://app.vssps.visualstudio.com/oauth2/token',
77 51
            'urlResourceOwnerDetails' => $credentials['resourceOwnerDetailsUrl'] ??
78 48
                'https://app.vssps.visualstudio.com/oauth2/token/resource',
79 51
            'responseType' => 'Assertion',
80 51
            'scopes' => 'vso.project vso.work_full',
81 51
        ]);
82
    }
83
84
    /**
85
     * Create a new authorization URL for the given state.
86
     *
87
     * @param string $state
88
     * @return string
89
     */
90 1
    public function authorizationUrl($state)
91
    {
92 1
        return $this->provider->getAuthorizationUrl(['state' => $state]);
93
    }
94
95
    /**
96
     * Fetch the access and refresh token based on the authorization code.
97
     *
98
     * @param string $code
99
     * @return \TestMonitor\DevOps\AccessToken
100
     */
101 1
    public function fetchToken(string $code): AccessToken
102
    {
103 1
        $token = $this->provider->getAccessToken('jwt_bearer', [
104 1
            'assertion' => $code,
105 1
        ]);
106
107 1
        $this->token = AccessToken::fromDevOps($token);
108
109 1
        return $this->token;
110
    }
111
112
    /**
113
     * Refresh the current access token.
114
     *
115
     * @throws \Exception
116
     *
117
     * @return \TestMonitor\DevOps\AccessToken
118
     */
119 2
    public function refreshToken(): AccessToken
120
    {
121 2
        if (empty($this->token)) {
122 1
            throw new UnauthorizedException();
123
        }
124
125 1
        $token = $this->provider->getAccessToken('jwt_bearer', [
126 1
            'grant_type' => 'refresh_token',
127 1
            'assertion' => $this->token->refreshToken,
128 1
        ]);
129
130 1
        $this->token = AccessToken::fromDevOps($token);
131
132 1
        return $this->token;
133
    }
134
135
    /**
136
     * Determines if the current access token has expired.
137
     *
138
     * @return bool
139
     */
140 1
    public function tokenExpired()
141
    {
142 1
        return $this->token->expired();
143
    }
144
145
    /**
146
     * Returns an Guzzle client instance.
147
     *
148
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
149
     * @throws TokenExpiredException
150
     *
151
     * @return \GuzzleHttp\Client
152
     */
153 46
    protected function client()
154
    {
155 46
        if (empty($this->token)) {
156 1
            throw new UnauthorizedException();
157
        }
158
159 45
        if ($this->token->expired()) {
160 1
            throw new TokenExpiredException();
161
        }
162
163 44
        return $this->client ?? new \GuzzleHttp\Client([
164 44
            'base_uri' => $this->baseUrl . '/' . $this->organization . '/',
165 44
            'http_errors' => false,
166 44
            'headers' => [
167 44
                'Authorization' => 'Bearer ' . $this->token->accessToken,
168 44
                'Accept' => 'application/json',
169 44
                'Content-Type' => 'application/json',
170 44
            ],
171 44
            'query' => [
172 44
                'api-version' => $this->apiVersion,
173 44
            ],
174 44
        ]);
175
    }
176
177
    /**
178
     * @param \GuzzleHttp\Client $client
179
     */
180 46
    public function setClient(\GuzzleHttp\Client $client)
181
    {
182 46
        $this->client = $client;
183
    }
184
185
    /**
186
     * Make a GET request to DevOps servers and return the response.
187
     *
188
     * @param string $uri
189
     * @param array $payload
190
     *
191
     * @throws \GuzzleHttp\Exception\GuzzleException
192
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
193
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
194
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
195
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
196
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
197
     *
198
     * @return mixed
199
     */
200 35
    protected function get($uri, array $payload = [])
201
    {
202 35
        return $this->request('GET', $uri, $payload);
203
    }
204
205
    /**
206
     * Make a POST request to DevOps servers and return the response.
207
     *
208
     * @param string $uri
209
     * @param array $payload
210
     *
211
     * @throws \GuzzleHttp\Exception\GuzzleException
212
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
213
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
214
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
215
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
216
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
217
     *
218
     * @return mixed
219
     */
220 9
    protected function post($uri, array $payload = [])
221
    {
222 9
        return $this->request('POST', $uri, $payload);
223
    }
224
225
    /**
226
     * Make a PUT request to DevOps servers and return the response.
227
     *
228
     * @param string $uri
229
     * @param array $payload
230
     *
231
     * @throws \GuzzleHttp\Exception\GuzzleException
232
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
233
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
234
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
235
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
236
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
237
     *
238
     * @return mixed
239
     */
240 1
    protected function patch($uri, array $payload = [])
241
    {
242 1
        return $this->request('PATCH', $uri, $payload);
243
    }
244
245
    /**
246
     * Make a DELETE request to DevOps servers and return the response.
247
     *
248
     * @param string $uri
249
     * @param array $payload
250
     *
251
     * @throws \GuzzleHttp\Exception\GuzzleException
252
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
253
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
254
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
255
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
256
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
257
     *
258
     * @return mixed
259
     */
260 2
    protected function delete($uri, array $payload = [])
261
    {
262 2
        return $this->request('DELETE', $uri, $payload);
263
    }
264
265
    /**
266
     * Make request to DevOps servers and return the response.
267
     *
268
     * @param string $verb
269
     * @param string $uri
270
     * @param array $payload
271
     *
272
     * @throws \GuzzleHttp\Exception\GuzzleException
273
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
274
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
275
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
276
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
277
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
278
     *
279
     * @return mixed
280
     */
281 46
    protected function request($verb, $uri, array $payload = [])
282
    {
283 46
        $response = $this->client()->request(
284 46
            $verb,
285 46
            $uri,
286 46
            $payload
287 46
        );
288
289 44
        if (! in_array($response->getStatusCode(), [200, 201, 203, 204, 206])) {
290 34
            return $this->handleRequestError($response);
291
        }
292
293 10
        $responseBody = (string) $response->getBody();
294
295 10
        return json_decode($responseBody, true) ?: $responseBody;
296
    }
297
298
    /**
299
     * @param  \Psr\Http\Message\ResponseInterface $response
300
     *
301
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
302
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
303
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
304
     * @throws \Exception
305
     *
306
     * @return void
307
     */
308 34
    protected function handleRequestError(ResponseInterface $response)
309
    {
310 34
        if ($response->getStatusCode() == 422) {
311 10
            throw new ValidationException(json_decode((string) $response->getBody(), true));
312
        }
313
314 24
        if ($response->getStatusCode() == 404) {
315 8
            throw new NotFoundException();
316
        }
317
318 16
        if ($response->getStatusCode() == 401 || $response->getStatusCode() == 403) {
319 7
            throw new UnauthorizedException();
320
        }
321
322 9
        if ($response->getStatusCode() == 400) {
323 7
            throw new FailedActionException((string) $response->getBody());
324
        }
325
326 2
        throw new Exception((string) $response->getStatusCode());
327
    }
328
}
329