Completed
Push — master ( b812a3...c78ccd )
by Thijs
10:26 queued 08:16
created

Client::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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 10
    public function __construct(
61
        array $credentials,
62
        string $organization,
63
        AccessToken $token = null,
64
        VSTSProvider $provider = null
65
    ) {
66 10
        $this->token = $token;
67 10
        $this->organization = $organization;
68
69 10
        $this->provider = $provider ?? new VSTSProvider([
70 7
            'clientId' => $credentials['clientId'],
71 7
            'clientSecret' => $credentials['clientSecret'],
72 7
            'redirectUri' => $credentials['redirectUrl'],
73 7
            'urlAuthorize' => $credentials['authorizeUrl'] ?? 'https://app.vssps.visualstudio.com/oauth2/authorize',
74 7
            'urlAccessToken' => $credentials['accessTokenUrl'] ?? 'https://app.vssps.visualstudio.com/oauth2/token',
75 7
            'urlResourceOwnerDetails' => $credentials['resourceOwnerDetailsUrl'] ??
76 7
                'https://app.vssps.visualstudio.com/oauth2/token/resource',
77 7
            'responseType' => 'Assertion',
78 7
            'scopes' => 'vso.project vso.work_full',
79
        ]);
80 10
    }
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 1
    public function refreshToken(): AccessToken
118
    {
119 1
        $token = $this->provider->getAccessToken('jwt_bearer', [
120 1
            'grant_type' => 'refresh_token',
121 1
            'assertion' => $this->token->refreshToken,
122
        ]);
123
124 1
        $this->token = AccessToken::fromDevOps($token);
125
126 1
        return $this->token;
127
    }
128
129
    /**
130
     * Determines if the current access token has expired.
131
     *
132
     * @return bool
133
     */
134 1
    public function tokenExpired()
135
    {
136 1
        return $this->token->expired();
137
    }
138
139
    /**
140
     * Returns an Guzzle client instance.
141
     *
142
     *@throws TokenExpiredException
143
     * @return \GuzzleHttp\Client
144
     */
145 6
    protected function client()
146
    {
147 6
        if ($this->token->expired()) {
148 1
            throw new TokenExpiredException();
149
        }
150
151 5
        return $this->client ?? new \GuzzleHttp\Client([
152
            'base_uri' => $this->baseUrl . '/' . $this->organization . '/',
153
            'http_errors' => false,
154
            'headers' => [
155
                'Authorization' => 'Bearer ' . $this->token->accessToken,
156
                'Accept' => 'application/json',
157
                'Content-Type' => 'application/json',
158
            ],
159
            'query' => [
160 5
                'api-version' => $this->apiVersion,
161
            ],
162
        ]);
163
    }
164
165
    /**
166
     * @param \GuzzleHttp\Client $client
167
     */
168 7
    public function setClient(\GuzzleHttp\Client $client)
169
    {
170 7
        $this->client = $client;
171 7
    }
172
173
    /**
174
     * Make a GET request to DevOps servers and return the response.
175
     *
176
     * @param string $uri
177
     *
178
     * @param array $payload
179
     * @throws \GuzzleHttp\Exception\GuzzleException
180
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
181
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
182
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
183
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
184
     * @return mixed
185
     */
186
    protected function get($uri, array $payload = [])
187
    {
188
        return $this->request('GET', $uri, $payload);
189
    }
190
191
    /**
192
     * Make a POST request to DevOps servers and return the response.
193
     *
194
     * @param string $uri
195
     * @param array $payload
196
     *
197
     * @throws \GuzzleHttp\Exception\GuzzleException
198
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
199
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
200
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
201
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
202
     * @return mixed
203
     */
204
    protected function post($uri, array $payload = [])
205
    {
206
        return $this->request('POST', $uri, $payload);
207
    }
208
209
    /**
210
     * Make a PUT request to Forge servers and return the response.
211
     *
212
     * @param string $uri
213
     * @param array $payload
214
     *
215
     * @throws \GuzzleHttp\Exception\GuzzleException
216
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
217
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
218
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
219
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
220
     * @return mixed
221
     */
222
    protected function patch($uri, array $payload = [])
223
    {
224
        return $this->request('PATCH', $uri, $payload);
225
    }
226
227
    /**
228
     * Make request to DevOps servers and return the response.
229
     *
230
     * @param string $verb
231
     * @param string $uri
232
     * @param array $payload
233
     *
234
     * @throws \GuzzleHttp\Exception\GuzzleException
235
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
236
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
237
     * @throws \TestMonitor\DevOps\Exceptions\TokenExpiredException
238
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
239
     * @return mixed
240
     */
241 6
    protected function request($verb, $uri, array $payload = [])
242
    {
243 6
        $response = $this->client()->request(
244 5
            $verb,
245
            $uri,
246
            $payload
247
        );
248
249 5
        if (! in_array($response->getStatusCode(), [200, 201, 203, 204, 206])) {
250 4
            return $this->handleRequestError($response);
251
        }
252
253 1
        $responseBody = (string) $response->getBody();
254
255 1
        return json_decode($responseBody, true) ?: $responseBody;
256
    }
257
258
    /**
259
     * @param  \Psr\Http\Message\ResponseInterface $response
260
     *
261
     * @throws \TestMonitor\DevOps\Exceptions\ValidationException
262
     * @throws \TestMonitor\DevOps\Exceptions\NotFoundException
263
     * @throws \TestMonitor\DevOps\Exceptions\FailedActionException
264
     * @throws \Exception
265
     * @return void
266
     */
267 4
    protected function handleRequestError(ResponseInterface $response)
268
    {
269 4
        if ($response->getStatusCode() == 422) {
270 1
            throw new ValidationException(json_decode((string) $response->getBody(), true));
271
        }
272
273 3
        if ($response->getStatusCode() == 404) {
274 1
            throw new NotFoundException();
275
        }
276
277 2
        if ($response->getStatusCode() == 401 || $response->getStatusCode() == 403) {
278 1
            throw new UnauthorizedException();
279
        }
280
281 1
        if ($response->getStatusCode() == 400) {
282 1
            throw new FailedActionException((string) $response->getBody());
283
        }
284
285
        throw new Exception((string) $response->getStatusCode());
286
    }
287
}
288