Completed
Push — master ( 5e0840...1cbad6 )
by Thijs
25s queued 20s
created

Client::client()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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