Passed
Push — master ( 0167b7...38803a )
by Thijs
06:50 queued 05:04
created

Client   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 72
c 3
b 0
f 0
dl 0
loc 292
ccs 78
cts 78
cp 1
rs 10
wmc 22

12 Methods

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