Completed
Push — master ( 9f360b...fb4085 )
by Thijs
01:22
created

Client   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 94.29%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 15
dl 0
loc 283
ccs 66
cts 70
cp 0.9429
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 1
A authorizationUrl() 0 4 1
A fetchToken() 0 10 1
A refreshToken() 0 15 2
A tokenExpired() 0 4 1
A client() 0 23 3
A setClient() 0 4 1
A get() 0 4 1
A post() 0 4 1
A patch() 0 4 1
A request() 0 16 3
B handleRequestError() 0 20 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\ManagesAttachments,
17
        Actions\ManagesWorkItems,
18
        Actions\ManagesProjects,
19
        Actions\ManagesAccounts,
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\ValidationException
193
     * @return mixed
194
     */
195 16
    protected function get($uri, array $payload = [])
196
    {
197 16
        return $this->request('GET', $uri, $payload);
198
    }
199
200
    /**
201
     * Make a POST request to DevOps servers and return the response.
202
     *
203
     * @param string $uri
204
     * @param array $payload
205
     *
206
     * @throws \GuzzleHttp\Exception\GuzzleException
207
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
208
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
209
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
210
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
211
     * @return mixed
212
     */
213 7
    protected function post($uri, array $payload = [])
214
    {
215 7
        return $this->request('POST', $uri, $payload);
216
    }
217
218
    /**
219
     * Make a PUT request to Forge servers and return the response.
220
     *
221
     * @param string $uri
222
     * @param array $payload
223
     *
224
     * @throws \GuzzleHttp\Exception\GuzzleException
225
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
226
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
227
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
228
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
229
     * @return mixed
230
     */
231 1
    protected function patch($uri, array $payload = [])
232
    {
233 1
        return $this->request('PATCH', $uri, $payload);
234
    }
235
236
    /**
237
     * Make request to DevOps servers and return the response.
238
     *
239
     * @param string $verb
240
     * @param string $uri
241
     * @param array $payload
242
     *
243
     * @throws \GuzzleHttp\Exception\GuzzleException
244
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
245
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
246
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
247
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
248
     * @return mixed
249
     */
250 30
    protected function request($verb, $uri, array $payload = [])
251
    {
252 30
        $response = $this->client()->request(
253 28
            $verb,
254 28
            $uri,
255 28
            $payload
256
        );
257
258 28
        if (! in_array($response->getStatusCode(), [200, 201, 203, 204, 206])) {
259 22
            return $this->handleRequestError($response);
260
        }
261
262 6
        $responseBody = (string) $response->getBody();
263
264 6
        return json_decode($responseBody, true) ?: $responseBody;
265
    }
266
267
    /**
268
     * @param  \Psr\Http\Message\ResponseInterface $response
269
     *
270
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
271
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
272
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
273
     * @throws \Exception
274
     * @return void
275
     */
276 22
    protected function handleRequestError(ResponseInterface $response)
277
    {
278 22
        if ($response->getStatusCode() == 422) {
279 6
            throw new ValidationException(json_decode((string) $response->getBody(), true));
280
        }
281
282 16
        if ($response->getStatusCode() == 404) {
283 5
            throw new NotFoundException();
284
        }
285
286 11
        if ($response->getStatusCode() == 401 || $response->getStatusCode() == 403) {
287 5
            throw new UnauthorizedException();
288
        }
289
290 6
        if ($response->getStatusCode() == 400) {
291 5
            throw new FailedActionException((string) $response->getBody());
292
        }
293
294 1
        throw new Exception((string) $response->getStatusCode());
295
    }
296
}
297