Passed
Push — master ( 6aff30...239879 )
by Thijs
09:39
created

Client::request()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

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