Completed
Pull Request — master (#181)
by
unknown
13:57
created

EventsController   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Test Coverage

Coverage 96.88%

Importance

Changes 0
Metric Value
wmc 26
lcom 2
cbo 7
dl 0
loc 199
ccs 93
cts 96
cp 0.9688
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 7 1
A beforeFilter() 0 4 1
B index() 0 62 8
C _validateRequest() 0 69 10
A _getHash() 0 9 2
A _getAppropriateStatus() 0 18 4
1
<?php
2
3
/* vim: set expandtab sw=4 ts=4 sts=4: */
4
5
/**
6
 * Events controller Github webhook events.
7
 *
8
 * phpMyAdmin Error reporting server
9
 * Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
10
 *
11
 * Licensed under The MIT License
12
 * For full copyright and license information, please see the LICENSE.txt
13
 * Redistributions of files must retain the above copyright notice.
14
 *
15
 * @copyright Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
16
 * @license   https://opensource.org/licenses/mit-license.php MIT License
17
 *
18
 * @see      https://www.phpmyadmin.net/
19
 */
20
21
namespace App\Controller;
22
23
use Cake\Core\Configure;
24
use Cake\Event\Event;
25
use Cake\Log\Log;
26
use Cake\ORM\TableRegistry;
27
28
/**
29
 * Events controller Github webhook events.
30
 */
31
class EventsController extends AppController
32
{
33 1
    public function initialize()
34
    {
35 1
        parent::initialize();
36 1
        $this->loadComponent('Csrf');
37
38 1
        $this->Reports = TableRegistry::get('Reports');
0 ignored issues
show
Documentation introduced by
The property Reports does not exist on object<App\Controller\EventsController>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Deprecated Code introduced by
The method Cake\ORM\TableRegistry::get() has been deprecated with message: 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead.

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

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

Loading history...
39 1
    }
40
41 1
    public function beforeFilter(Event $event)
42
    {
43 1
        $this->eventManager()->off($this->Csrf);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Event\EventDispatcherTrait::eventManager() has been deprecated with message: 3.5.0 Use getEventManager()/setEventManager() instead.

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

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

Loading history...
44 1
    }
45
46 1
    public function index()
47
    {
48
        // Only allow POST requests
49 1
        $this->request->allowMethod(array('post'));
50
51
        // Validate request
52 1
        if (201 !== ($statusCode = $this->_validateRequest($this->request))) {
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;
0 ignored issues
show
Documentation introduced by
The property auto_render does not exist on object<App\Controller\EventsController>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
60 1
            $this->response->statusCode($statusCode);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::statusCode() has been deprecated with message: 3.4.0 Use `getStatusCode()` and `withStatus()` instead.

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

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

Loading history...
61
62 1
            return $this->response;
63 1
        } elseif (200 === $statusCode) {
64
            // Send a success response to ping event
65
            $this->auto_render = false;
0 ignored issues
show
Documentation introduced by
The property auto_render does not exist on object<App\Controller\EventsController>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
66
            $this->response->statusCode($statusCode);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::statusCode() has been deprecated with message: 3.4.0 Use `getStatusCode()` and `withStatus()` instead.

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

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

Loading history...
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 ('closed' === $eventAction
76 1
            || 'opened' === $eventAction
77 1
            || 'reopened' === $eventAction
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 recieved 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
                'Recieved 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;
0 ignored issues
show
Documentation introduced by
The property auto_render does not exist on object<App\Controller\EventsController>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
104 1
        $this->response->statusCode($statusCode);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::statusCode() has been deprecated with message: 3.4.0 Use `getStatusCode()` and `withStatus()` instead.

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

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

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