Completed
Push — master ( b4ee6f...6b7a74 )
by Thijs
01:44
created

Client::handleRequestError()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 8.9777
c 0
b 0
f 0
cc 6
nc 5
nop 1
crap 6
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,
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
     *
98
     * @return \TestMonitor\DevOps\AccessToken
99
     */
100 1
    public function fetchToken(string $code): AccessToken
101
    {
102 1
        $token = $this->provider->getAccessToken('jwt_bearer', [
103 1
            'assertion' => $code,
104
        ]);
105
106 1
        $this->token = AccessToken::fromDevOps($token);
107
108 1
        return $this->token;
109
    }
110
111
    /**
112
     * Refresh the current access token.
113
     *
114
     * @throws \Exception
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
     * @return \GuzzleHttp\Client
149
     */
150 30
    protected function client()
151
    {
152 30
        if (empty($this->token)) {
153 1
            throw new UnauthorizedException();
154
        }
155
156 29
        if ($this->token->expired()) {
157 1
            throw new TokenExpiredException();
158
        }
159
160 28
        return $this->client ?? new \GuzzleHttp\Client([
161
            'base_uri' => $this->baseUrl . '/' . $this->organization . '/',
162
            'http_errors' => false,
163
            'headers' => [
164
                'Authorization' => 'Bearer ' . $this->token->accessToken,
165
                'Accept' => 'application/json',
166
                'Content-Type' => 'application/json',
167
            ],
168
            'query' => [
169 28
                'api-version' => $this->apiVersion,
170
            ],
171
        ]);
172
    }
173
174
    /**
175
     * @param \GuzzleHttp\Client $client
176
     */
177 30
    public function setClient(\GuzzleHttp\Client $client)
178
    {
179 30
        $this->client = $client;
180 30
    }
181
182
    /**
183
     * Make a GET request to DevOps servers and return the response.
184
     *
185
     * @param string $uri
186
     *
187
     * @param array $payload
188
     * @throws \GuzzleHttp\Exception\GuzzleException
189
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
190
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
191
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
192
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
193
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
194
     * @return mixed
195
     */
196 23
    protected function get($uri, array $payload = [])
197
    {
198 23
        return $this->request('GET', $uri, $payload);
199
    }
200
201
    /**
202
     * Make a POST request to DevOps servers and return the response.
203
     *
204
     * @param string $uri
205
     * @param array $payload
206
     *
207
     * @throws \GuzzleHttp\Exception\GuzzleException
208
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
209
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
210
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
211
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
212
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
213
     * @return mixed
214
     */
215 7
    protected function post($uri, array $payload = [])
216
    {
217 7
        return $this->request('POST', $uri, $payload);
218
    }
219
220
    /**
221
     * Make a PUT request to Forge servers and return the response.
222
     *
223
     * @param string $uri
224
     * @param array $payload
225
     *
226
     * @throws \GuzzleHttp\Exception\GuzzleException
227
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
228
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
229
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
230
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
231
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
232
     * @return mixed
233
     */
234 1
    protected function patch($uri, array $payload = [])
235
    {
236 1
        return $this->request('PATCH', $uri, $payload);
237
    }
238
239
    /**
240
     * Make request to DevOps servers and return the response.
241
     *
242
     * @param string $verb
243
     * @param string $uri
244
     * @param array $payload
245
     *
246
     * @throws \GuzzleHttp\Exception\GuzzleException
247
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
248
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
249
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
250
     * @throws \TestMonitor\DevOps\Exceptions\UnauthorizedException
251
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
252
     * @return mixed
253
     */
254 30
    protected function request($verb, $uri, array $payload = [])
255
    {
256 30
        $response = $this->client()->request(
257 28
            $verb,
258
            $uri,
259
            $payload
260
        );
261
262 28
        if (! in_array($response->getStatusCode(), [200, 201, 203, 204, 206])) {
263 22
            return $this->handleRequestError($response);
264
        }
265
266 6
        $responseBody = (string) $response->getBody();
267
268 6
        return json_decode($responseBody, true) ?: $responseBody;
269
    }
270
271
    /**
272
     * @param  \Psr\Http\Message\ResponseInterface $response
273
     *
274
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
275
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
276
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
277
     * @throws \Exception
278
     * @return void
279
     */
280 22
    protected function handleRequestError(ResponseInterface $response)
281
    {
282 22
        if ($response->getStatusCode() == 422) {
283 6
            throw new ValidationException(json_decode((string) $response->getBody(), true));
284
        }
285
286 16
        if ($response->getStatusCode() == 404) {
287 5
            throw new NotFoundException();
288
        }
289
290 11
        if ($response->getStatusCode() == 401 || $response->getStatusCode() == 403) {
291 5
            throw new UnauthorizedException();
292
        }
293
294 6
        if ($response->getStatusCode() == 400) {
295 5
            throw new FailedActionException((string) $response->getBody());
296
        }
297
298 1
        throw new Exception((string) $response->getStatusCode());
299
    }
300
}
301