Passed
Push — master ( 083ff7...c84473 )
by William
03:02
created

GithubApiComponent::getAccessToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Github api component handling comunication with github.
5
 *
6
 * phpMyAdmin Error reporting server
7
 * Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
8
 *
9
 * Licensed under The MIT License
10
 * For full copyright and license information, please see the LICENSE.txt
11
 * Redistributions of files must retain the above copyright notice.
12
 *
13
 * @copyright Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
14
 * @license   https://opensource.org/licenses/mit-license.php MIT License
15
 *
16
 * @see      https://www.phpmyadmin.net/
17
 */
18
19
namespace App\Controller\Component;
20
21
use Cake\Controller\Component;
22
use Cake\Core\Configure;
23
use Cake\Log\Log;
24
use Cake\Routing\Router;
25
use const CURLINFO_HTTP_CODE;
26
use const CURLOPT_CUSTOMREQUEST;
27
use const CURLOPT_HTTPHEADER;
28
use const CURLOPT_POSTFIELDS;
29
use const CURLOPT_RETURNTRANSFER;
30
use const CURLOPT_USERAGENT;
31
use function array_merge;
32
use function curl_init;
33
use function curl_setopt;
34
use function http_build_query;
35
use function json_decode;
36
use function json_encode;
37
use function strtoupper;
38
use function curl_error;
39
use function curl_close;
40
41
/**
42
 * Github api component handling comunication with github.
43
 */
44
class GithubApiComponent extends Component
45
{
46
    /**
47
     * perform an api request given a path, the data to send, the method and whether
48
     * or not to return a status.
49
     *
50
     * @param string       $path         the api path to preform the request to
51
     * @param array|string $data         the data to send in the request. This works with both GET
52
     *                            and Post requests
53
     * @param string       $method       the method type of the request
54
     * @param bool         $returnStatus whether to return the status code with the
55
     *                                   request
56
     * @param string       $access_token the github access token
57
     *
58
     * @return array the returned response decoded and optionally the status code,
59
     *               see GithubApiComponent::sendRequest()
60
     *
61
     * @see GithubApiComponent::sendRequest()
62
     */
63 42
    public function apiRequest(
64
        string $path = '',
65
        $data = [],
66
        string $method = 'GET',
67
        bool $returnStatus = false,
68
        string $access_token = ''
69
    ): array {
70 42
        $path = 'https://api.github.com/' . $path;
71 42
        if (strtoupper($method) === 'GET') {
72 28
            $path .= '?' . http_build_query($data);
73 28
            $data = [];
74
        }
75
76 42
        return $this->sendRequest($path, $data, $method, $returnStatus, $access_token);
77
    }
78
79
    /**
80
     * retrieve an access token using a code that has been authorized by a user.
81
     *
82
     * @param string $code the code returned by github to the callback url
83
     *
84
     * @return string|null the access token
85
     */
86 7
    public function getAccessToken(?string $code): ?string
87
    {
88 7
        $url = 'https://github.com/login/oauth/access_token';
89 7
        $data = array_merge(
90 7
            Configure::read('GithubConfig', []),
91 7
            ['code' => $code]
92
        );
93 7
        $decodedResponse = $this->sendRequest($url, http_build_query($data), 'POST');
94
95 7
        return $decodedResponse['access_token'] ?? null;
96
    }
97
98
    /**
99
     * retrieve the github info stored on a user by his access token.
100
     *
101
     * @param string $accessToken the access token belonging to the user being
102
     *                            requested
103
     *
104
     * @return array the github info returned by github as an associative array
105
     */
106 7
    public function getUserInfo(string $accessToken): array
107
    {
108 7
        return $this->apiRequest('user', [], 'GET', true, $accessToken);
109
    }
110
111
    /**
112
     * perform an http request using curl given a url, the post data to send, the
113
     * request method and whether or not to return a status.
114
     *
115
     * @param string       $url          the url to preform the request to
116
     * @param array|string $data         the post data to send in the request. This only works with POST requests. GET requests need the data appended in the url.
117
     *                            with POST requests. GET requests need the data appended
118
     *                            in the url.
119
     * @param string       $method       the method type of the request
120
     * @param bool         $returnCode   whether to return the status code with the
121
     *                                   request
122
     * @param string       $access_token the github access token
123
     *
124
     * @return array the returned response decoded and optionally the status code,
125
     *               eg: array($decodedResponse, $statusCode) or just $decodedResponse
126
     */
127 42
    public function sendRequest(
128
        string $url,
129
        $data,
130
        string $method,
131
        bool $returnCode = false,
132
        string $access_token = ''
133
    ): array {
134 42
        $curlHandle = curl_init($url);
135 42
        curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, $method);
136 42
        $header = ['Accept: application/json'];
137 42
        if ($access_token !== '') {
138 42
            $header[] = 'Authorization: token ' . $access_token;
139
        }
140 42
        curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $header);
141 42
        curl_setopt($curlHandle, CURLOPT_USERAGENT, 'phpMyAdmin - Error Reporting Server');
142 42
        curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $data);
143 42
        curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
144 42
        $response = curl_exec($curlHandle);// phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFallbackGlobalName
145 42
        if ($response === false) {
146
            Log::error('Curl error: ' . curl_error($curlHandle));
147
            curl_close($curlHandle);
148
149
            return ['', 0];
150
        }
151 42
        $decodedResponse = json_decode($response, true);
152 42
        if ($returnCode) {
153 42
            $status = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);// phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFallbackGlobalName
154
            // phpcs ignored patterns for mock testing reasons
155 42
            curl_close($curlHandle);
156
157
            return [
158 42
                $decodedResponse,
159 42
                $status,
160
            ];
161
        }
162 7
        curl_close($curlHandle);
163
164 7
        return $decodedResponse;
165
    }
166
167
    /**
168
     * generate the url to redirect the user to for authorization given the
169
     * requested scope.
170
     *
171
     * @param string $scope the api scope for the user to authorize
172
     *
173
     * @return string the generated url to redirect the user to
174
     */
175 7
    public function getRedirectUrl(?string $scope = null): string
176
    {
177 7
        $url = 'https://github.com/login/oauth/authorize';
178 2
        $data = [
179 7
            'client_id' => Configure::read('GithubConfig', ['client_id' => ''])['client_id'],
180 7
            'redirect_uri' => Router::url(
181
                [
182 7
                    'controller' => 'developers',
183
                    'action' => 'callback',
184
                ],
185 7
                true
186
            ),
187 7
            'scope' => $scope,
188
        ];
189
190 7
        return $url . '?' . http_build_query($data);
191
    }
192
193
    /**
194
     * Check if a user can commit to a rep.
195
     *
196
     * @param string $username     the username to check
197
     * @param string $repoPath     the repo path of the repo to check for
198
     * @param string $access_token the github access token
199
     *
200
     * @return bool true if the user is a collaborator and false if they are not
201
     */
202 7
    public function canCommitTo(string $username, string $repoPath, string $access_token): bool
203
    {
204 7
        [, $status] = $this->apiRequest(
205 7
            'repos/' . $repoPath . '/collaborators/' . $username,
206 7
            [],
207 7
            'GET',
208 7
            true,
209 1
            $access_token
210
        );
211 7
        if ($status !== 204 && $status !== 404) {
212
            Log::error('Collaborators call ended in a status code: ' . $status);
213
        }
214
215 7
        return $status === 204;
216
    }
217
218
    /**
219
     * make api request for github issue creation.
220
     *
221
     * @param string $repoPath     The repo slug
222
     * @param array  $data         issue details
223
     * @param string $access_token the github access token
224
     * @return array
225
     */
226 7
    public function createIssue(string $repoPath, array $data, string $access_token): array
227
    {
228 7
        return $this->apiRequest(
229 7
            'repos/' . $repoPath . '/issues',
230 7
            json_encode($data),
231 7
            'POST',
232 7
            true,
233 1
            $access_token
234
        );
235
    }
236
237
    /**
238
     * make api request for github comment creation.
239
     *
240
     * @param string $repoPath     The repo slug
241
     * @param array  $data         The data
242
     * @param int    $issueNumber  The issue number
243
     * @param string $access_token The github access token
244
     * @return array
245
     */
246 14
    public function createComment(string $repoPath, array $data, int $issueNumber, string $access_token): array
247
    {
248 14
        return $this->apiRequest(
249 14
            'repos/' . $repoPath . '/issues/' . $issueNumber . '/comments',
250 14
            json_encode($data),
251 14
            'POST',
252 14
            true,
253 2
            $access_token
254
        );
255
    }
256
257
    /**
258
     * Make API request for getting Github issue's status
259
     *
260
     * @param string $repoPath     The repo slug
261
     * @param array  $data         The data
262
     * @param int    $issueNumber  The issue number
263
     * @param string $access_token The github access token
264
     * @return array
265
     */
266 21
    public function getIssue(string $repoPath, array $data, int $issueNumber, string $access_token): array
267
    {
268 21
        return $this->apiRequest(
269 21
            'repos/' . $repoPath . '/issues/' . $issueNumber,
270 3
            $data,
271 21
            'GET',
272 21
            true,
273 3
            $access_token
274
        );
275
    }
276
}
277