Completed
Push — master ( 89830f...07e3a8 )
by William
20:06
created

EventsController::validateRequest()   B

Complexity

Conditions 10
Paths 15

Size

Total Lines 70
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 33
nc 15
nop 1
dl 0
loc 70
ccs 34
cts 34
cp 1
crap 10
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Events controller Github webhook events
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;
20
21
use Cake\Core\Configure;
22
use Cake\Event\Event;
23
use Cake\Http\Response;
24
use Cake\Http\ServerRequest;
25
use Cake\Log\Log;
26
use Cake\ORM\TableRegistry;
27
use function count;
28
use function explode;
29
use function file_get_contents;
30
use function hash_hmac;
31
use function strpos;
32
33 4
/**
34
 * Events controller Github webhook events
35 4
 */
36 4
class EventsController extends AppController
37
{
38 4
    public function initialize(): void
39 4
    {
40
        parent::initialize();
41 4
        $this->loadComponent('Csrf');
42
43 4
        $this->Reports = TableRegistry::getTableLocator()->get('Reports');
0 ignored issues
show
Bug Best Practice introduced by
The property Reports does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
44 4
    }
45
46 4
    public function beforeFilter(Event $event): void
47
    {
48
        $this->eventManager()->off($this->Csrf);
0 ignored issues
show
Deprecated Code introduced by
The function Cake\Controller\Controller::eventManager() has been deprecated: 3.5.0 Use getEventManager()/setEventManager() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

48
        /** @scrutinizer ignore-deprecated */ $this->eventManager()->off($this->Csrf);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
49 4
    }
50
51
    public function index(): ?Response
52 4
    {
53 4
        // Only allow POST requests
54
        $this->request->allowMethod(['post']);
55 4
56
        // Validate request
57
        $statusCode = $this->validateRequest($this->request);
58
        if ($statusCode !== 201) {
59 4
            Log::error(
60 4
                'Could not validate the request. Sending a '
61
                    . $statusCode . ' response.'
62 4
            );
63 4
64
            // Send a response
65
            $this->auto_render = false;
0 ignored issues
show
Bug Best Practice introduced by
The property auto_render does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
66
            $this->response->statusCode($statusCode);
0 ignored issues
show
Deprecated Code introduced by
The function Cake\Http\Response::statusCode() has been deprecated: 3.4.0 Use `getStatusCode()` and `withStatus()` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
            /** @scrutinizer ignore-deprecated */ $this->response->statusCode($statusCode);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
67
68
            return $this->response;
69
        }
70
71 4
        if ($statusCode === 200) {
72 4
           // Send a success response to ping event
73 4
            $this->auto_render = false;
74
            $this->response->statusCode($statusCode);
0 ignored issues
show
Deprecated Code introduced by
The function Cake\Http\Response::statusCode() has been deprecated: 3.4.0 Use `getStatusCode()` and `withStatus()` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

74
            /** @scrutinizer ignore-deprecated */ $this->response->statusCode($statusCode);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
75 4
76 4
            return $this->response;
77 4
        }
78
79 4
        $issuesData = $this->request->input('json_decode', true);
80
        $eventAction = $issuesData['action'];
81 4
        $issueNumber = $issuesData['issue'] ? $issuesData['issue']['number'] : '';
82 4
83 4
        if ($eventAction === 'closed'
84 4
            || $eventAction === 'opened'
85 4
            || $eventAction === 'reopened'
86
        ) {
87
            $status = $this->getAppropriateStatus($eventAction);
88 4
            $reportsUpdated = $this->Reports->setLinkedReportStatus($issueNumber, $status);
89 4
            if ($reportsUpdated > 0) {
90 4
                Log::debug(
91
                    $reportsUpdated . ' linked reports to issue number '
92 4
                        . $issueNumber . ' were updated according to received action '
93
                        . $eventAction
94
                );
95 4
            } else {
96 4
                Log::info(
97 4
                    'No linked report found for issue number \'' . $issueNumber
98
                    . '\'. Ignoring the event.'
99 4
                );
100
                $statusCode = 204;
101
            }
102
        } else {
103 4
            Log::info(
104 4
                'received a webhook event for action \'' . $eventAction
105
                . '\' on issue number ' . $issueNumber . '. Ignoring the event.'
106 4
            );
107
            $statusCode = 204;
108
        }
109
110
        // Send a response
111
        $this->auto_render = false;
112
        $this->response->statusCode($statusCode);
0 ignored issues
show
Deprecated Code introduced by
The function Cake\Http\Response::statusCode() has been deprecated: 3.4.0 Use `getStatusCode()` and `withStatus()` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

112
        /** @scrutinizer ignore-deprecated */ $this->response->statusCode($statusCode);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
113
114
        return $this->response;
115
    }
116
117 4
    /**
118
     * Validate HTTP Request received
119
     *
120 4
     * @param ServerRequest $request Request object
121
     *
122 4
     * @return int status code based on if this is a valid request
123 4
     */
124
    protected function validateRequest(ServerRequest $request): int
125 4
    {
126 4
        // Default $statusCode
127 4
        $statusCode = 201;
128 4
129 4
        $userAgent = $request->getHeaderLine('User-Agent');
130 4
        $eventType = $request->getHeaderLine('X-GitHub-Event');
131 4
132 4
        $receivedHashHeader = $request->getHeaderLine('X-Hub-Signature');
133
        $algo = '';
134
        $receivedHash = '';
135
        if ($receivedHashHeader !== null) {
136 4
            $parts = explode('=', $receivedHashHeader);
137
            if (count($parts) > 1) {
138 4
                $algo = $parts[0];
139
                $receivedHash = $parts[1];
140
            }
141
        }
142 4
143 4
        $expectedHash = $this->getHash(file_get_contents('php://input'), $algo);
144 4
145
        if ($userAgent !== null && strpos($userAgent, 'GitHub-Hookshot') !== 0) {
146 4
            // Check if the User-agent is Github
147
            // Otherwise, Send a '403: Forbidden'
148 4
149 4
            Log::error(
150
                'Invalid User agent: ' . $userAgent
151
                . '. Ignoring the event.'
152
            );
153 4
154 4
            return 403;
155
        }
156 4
157
        if ($eventType !== null && $eventType === 'ping') {
158 4
            // Check if the request is based on 'issues' event
159 4
            // Otherwise, Send a '400: Bad Request'
160
161
            Log::info(
162
                'Ping event type received.'
163 4
            );
164 4
165 4
            return 200;
166
        }
167 4
168
        if ($eventType !== null && $eventType !== 'issues') {
169 4
            // Check if the request is based on 'issues' event
170 4
            // Otherwise, Send a '400: Bad Request'
171
172
            Log::error(
173
                'Unexpected event type: ' . $eventType
174 4
                . '. Ignoring the event.'
175 4
            );
176 4
177 4
            return 400;
178
        }
179 4
180
        if ($receivedHash !== $expectedHash) {
181 4
            // Check if hash matches
182
            // Otherwise, Send a '401: Unauthorized'
183
184 4
            Log::error(
185
                'received hash ' . $receivedHash . ' does not match '
186
                . ' expected hash ' . $expectedHash
187
                . '. Ignoring the event.'
188
            );
189
190
            return 401;
191
        }
192
193
        return $statusCode;
194
    }
195 4
196
    /**
197 4
     * Get the hash of raw POST payload
198 4
     *
199
     * @param string $payload Raw POST body string
200 4
     * @param string $algo    Algorithm used to calculate the hash
201
     *
202 4
     * @return string Hmac Digest-based hash of payload
203
     */
204
    protected function getHash(string $payload, string $algo): string
205
    {
206
        if ($algo === '') {
207
            return '';
208
        }
209
        $key = Configure::read('GithubWebhookSecret');
210
211
        return hash_hmac($algo, $payload, $key);
212 4
    }
213
214 4
    /**
215
     * Get appropriate new status based on action received in github event
216 3
     *
217 4
     * @param string $action Action received in Github webhook event
218 4
     *
219
     * @return string Appropriate new status for the related reports
220 4
     */
221 4
    protected function getAppropriateStatus(string $action): string
222
    {
223 4
        $status = 'forwarded';
224 4
225 4
        switch ($action) {
226
            case 'opened':
227
                break;
228 4
229
            case 'reopened':
230
                break;
231
232
            case 'closed':
233
                $status = 'resolved';
234
                break;
235
        }
236
237
        return $status;
238
    }
239
}
240