1
|
|
|
<?php |
2
|
|
|
/* vim: set expandtab sw=4 ts=4 sts=4: */ |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* Events controller Github webhook events |
6
|
|
|
* |
7
|
|
|
* phpMyAdmin Error reporting server |
8
|
|
|
* Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/) |
9
|
|
|
* |
10
|
|
|
* Licensed under The MIT License |
11
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
12
|
|
|
* Redistributions of files must retain the above copyright notice. |
13
|
|
|
* |
14
|
|
|
* @copyright Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/) |
15
|
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License |
16
|
|
|
* |
17
|
|
|
* @see https://www.phpmyadmin.net/ |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace App\Controller; |
21
|
|
|
|
22
|
|
|
use Cake\Core\Configure; |
23
|
|
|
use Cake\Event\Event; |
24
|
|
|
use Cake\Log\Log; |
25
|
|
|
use Cake\ORM\TableRegistry; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Events controller Github webhook events |
29
|
|
|
*/ |
30
|
|
|
class EventsController extends AppController |
31
|
|
|
{ |
32
|
|
|
|
33
|
1 |
|
public function initialize() |
34
|
|
|
{ |
35
|
1 |
|
parent::initialize(); |
36
|
1 |
|
$this->loadComponent('Csrf'); |
37
|
|
|
|
38
|
1 |
|
$this->Reports = TableRegistry::get('Reports'); |
|
|
|
|
39
|
1 |
|
} |
40
|
|
|
|
41
|
1 |
|
public function beforeFilter(Event $event) |
42
|
|
|
{ |
43
|
1 |
|
$this->eventManager()->off($this->Csrf); |
|
|
|
|
44
|
1 |
|
} |
45
|
|
|
|
46
|
1 |
|
public function index() |
47
|
|
|
{ |
48
|
|
|
// Only allow POST requests |
49
|
1 |
|
$this->request->allowMethod(['post']); |
50
|
|
|
|
51
|
|
|
// Validate request |
52
|
1 |
|
if (($statusCode = $this->_validateRequest($this->request)) !== 201) { |
|
|
|
|
53
|
1 |
|
Log::error( |
54
|
|
|
'Could not validate the request. Sending a ' |
55
|
1 |
|
. $statusCode . ' response.' |
56
|
|
|
); |
57
|
|
|
|
58
|
|
|
// Send a response |
59
|
1 |
|
$this->auto_render = false; |
|
|
|
|
60
|
1 |
|
$this->response->statusCode($statusCode); |
|
|
|
|
61
|
|
|
|
62
|
1 |
|
return $this->response; |
63
|
1 |
|
} elseif ($statusCode === 200) { |
64
|
|
|
// Send a success response to ping event |
65
|
|
|
$this->auto_render = false; |
66
|
|
|
$this->response->statusCode($statusCode); |
|
|
|
|
67
|
|
|
|
68
|
|
|
return $this->response; |
69
|
|
|
} |
70
|
|
|
|
71
|
1 |
|
$issuesData = $this->request->input('json_decode', true); |
72
|
1 |
|
$eventAction = $issuesData['action']; |
73
|
1 |
|
$issueNumber = $issuesData['issue'] ? $issuesData['issue']['number'] : ''; |
74
|
|
|
|
75
|
1 |
|
if ($eventAction === 'closed' |
76
|
1 |
|
|| $eventAction === 'opened' |
77
|
1 |
|
|| $eventAction === 'reopened' |
78
|
|
|
) { |
79
|
1 |
|
$status = $this->_getAppropriateStatus($eventAction); |
80
|
|
|
|
81
|
1 |
|
if (($reportsUpdated = $this->Reports->setLinkedReportStatus($issueNumber, $status)) > 0) { |
82
|
1 |
|
Log::debug( |
83
|
1 |
|
$reportsUpdated . ' linked reports to issue number ' |
84
|
1 |
|
. $issueNumber . ' were updated according to received action ' |
85
|
1 |
|
. $eventAction |
86
|
|
|
); |
87
|
|
|
} else { |
88
|
1 |
|
Log::info( |
89
|
1 |
|
'No linked report found for issue number \'' . $issueNumber |
90
|
1 |
|
. '\'. Ignoring the event.' |
91
|
|
|
); |
92
|
1 |
|
$statusCode = 204; |
93
|
|
|
} |
94
|
|
|
} else { |
95
|
1 |
|
Log::info( |
96
|
1 |
|
'received a webhook event for action \'' . $eventAction |
97
|
1 |
|
. '\' on issue number ' . $issueNumber . '. Ignoring the event.' |
98
|
|
|
); |
99
|
1 |
|
$statusCode = 204; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// Send a response |
103
|
1 |
|
$this->auto_render = false; |
104
|
1 |
|
$this->response->statusCode($statusCode); |
|
|
|
|
105
|
|
|
|
106
|
1 |
|
return $this->response; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Validate HTTP Request received |
112
|
|
|
* |
113
|
|
|
* @param \Cake\Http\Client\Request $request Request object |
114
|
|
|
* |
115
|
|
|
* @return int status code based on if this is a valid request |
116
|
|
|
*/ |
117
|
1 |
|
protected function _validateRequest($request) |
118
|
|
|
{ |
119
|
|
|
// Default $statusCode |
120
|
1 |
|
$statusCode = 201; |
121
|
|
|
|
122
|
1 |
|
$userAgent = $request->getHeaderLine('User-Agent'); |
123
|
1 |
|
$eventType = $request->getHeaderLine('X-GitHub-Event'); |
124
|
|
|
|
125
|
1 |
|
$receivedHashHeader = $request->getHeaderLine('X-Hub-Signature'); |
126
|
1 |
|
$algo = ''; |
127
|
1 |
|
$receivedHash = ''; |
128
|
1 |
|
if ($receivedHashHeader !== null) { |
|
|
|
|
129
|
1 |
|
$parts = explode('=', $receivedHashHeader); |
130
|
1 |
|
if (count($parts) > 1) { |
131
|
1 |
|
$algo = $parts[0]; |
132
|
1 |
|
$receivedHash = $parts[1]; |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
1 |
|
$expectedHash = $this->_getHash(file_get_contents('php://input'), $algo); |
137
|
|
|
|
138
|
1 |
|
if ($userAgent !== null && strpos($userAgent, 'GitHub-Hookshot') !== 0) { |
139
|
|
|
// Check if the User-agent is Github |
140
|
|
|
// Otherwise, Send a '403: Forbidden' |
141
|
|
|
|
142
|
1 |
|
Log::error( |
143
|
1 |
|
'Invalid User agent: ' . $userAgent |
144
|
1 |
|
. '. Ignoring the event.' |
145
|
|
|
); |
146
|
1 |
|
$statusCode = 403; |
147
|
|
|
|
148
|
1 |
|
return $statusCode; |
149
|
1 |
|
} elseif ($eventType !== null && $eventType === 'ping') { |
150
|
|
|
// Check if the request is based on 'issues' event |
151
|
|
|
// Otherwise, Send a '400: Bad Request' |
152
|
|
|
|
153
|
1 |
|
Log::info( |
154
|
1 |
|
'Ping event type received.' |
155
|
|
|
); |
156
|
1 |
|
$statusCode = 200; |
157
|
|
|
|
158
|
1 |
|
return $statusCode; |
159
|
1 |
|
} elseif ($eventType !== null && $eventType !== 'issues') { |
160
|
|
|
// Check if the request is based on 'issues' event |
161
|
|
|
// Otherwise, Send a '400: Bad Request' |
162
|
|
|
|
163
|
1 |
|
Log::error( |
164
|
1 |
|
'Unexpected event type: ' . $eventType |
165
|
1 |
|
. '. Ignoring the event.' |
166
|
|
|
); |
167
|
1 |
|
$statusCode = 400; |
168
|
|
|
|
169
|
1 |
|
return $statusCode; |
170
|
1 |
|
} elseif ($receivedHash !== $expectedHash) { |
171
|
|
|
// Check if hash matches |
172
|
|
|
// Otherwise, Send a '401: Unauthorized' |
173
|
|
|
|
174
|
1 |
|
Log::error( |
175
|
1 |
|
'received hash ' . $receivedHash . ' does not match ' |
176
|
1 |
|
. ' expected hash ' . $expectedHash |
177
|
1 |
|
. '. Ignoring the event.' |
178
|
|
|
); |
179
|
1 |
|
$statusCode = 401; |
180
|
|
|
|
181
|
1 |
|
return $statusCode; |
182
|
|
|
} |
183
|
|
|
|
184
|
1 |
|
return $statusCode; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Get the hash of raw POST payload |
189
|
|
|
* |
190
|
|
|
* @param string $payload Raw POST body string |
191
|
|
|
* @param string $algo Algorithm used to calculate the hash |
192
|
|
|
* |
193
|
|
|
* @return string Hmac Digest-based hash of payload |
194
|
|
|
*/ |
195
|
1 |
|
protected function _getHash($payload, $algo) |
196
|
|
|
{ |
197
|
1 |
|
if ($algo === '') { |
198
|
1 |
|
return ''; |
199
|
|
|
} |
200
|
1 |
|
$key = Configure::read('GithubWebhookSecret'); |
201
|
|
|
|
202
|
1 |
|
return hash_hmac($algo, $payload, $key); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Get appropriate new status based on action received in github event |
207
|
|
|
* |
208
|
|
|
* @param string $action Action received in Github webhook event |
209
|
|
|
* |
210
|
|
|
* @return string Appropriate new status for the related reports |
211
|
|
|
*/ |
212
|
1 |
|
protected function _getAppropriateStatus($action) |
213
|
|
|
{ |
214
|
1 |
|
$status = 'forwarded'; |
215
|
|
|
|
216
|
|
|
switch ($action) { |
217
|
1 |
|
case 'opened': |
218
|
1 |
|
break; |
219
|
|
|
|
220
|
1 |
|
case 'reopened': |
221
|
1 |
|
break; |
222
|
|
|
|
223
|
1 |
|
case 'closed': |
224
|
1 |
|
$status = 'resolved'; |
225
|
1 |
|
break; |
226
|
|
|
} |
227
|
|
|
|
228
|
1 |
|
return $status; |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
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.