|
3 | 3 |
|
4 | 4 | class GithubWebhook |
5 | 5 | { |
6 | | - private string $EventType; |
7 | | - private array $Payload; |
| 6 | + private string $EventType; |
| 7 | + private array $Payload; |
8 | 8 |
|
9 | | - /** |
10 | | - * Validates and processes current request |
11 | | - */ |
12 | | - public function ProcessRequest() : bool { |
13 | | - if (!array_key_exists('HTTP_X_GITHUB_EVENT', $_SERVER)) |
14 | | - throw new Exception('Missing event header.'); |
15 | | - $this->EventType = $_SERVER[ 'HTTP_X_GITHUB_EVENT' ]; |
16 | | - if (preg_match('/^[a-z_]+$/', $this->EventType) !== 1) |
17 | | - throw new Exception('Invalid event header.'); |
18 | | - if (!array_key_exists('REQUEST_METHOD', $_SERVER) || $_SERVER[ 'REQUEST_METHOD' ] !== 'POST') |
19 | | - throw new Exception('Invalid request method.'); |
20 | | - if (!array_key_exists('CONTENT_TYPE', $_SERVER)) |
21 | | - throw new Exception('Missing content type.'); |
22 | | - $ContentType = $_SERVER[ 'CONTENT_TYPE' ]; |
23 | | - $RawPayload = null; |
24 | | - if ($ContentType === 'application/x-www-form-urlencoded') { |
25 | | - if (!array_key_exists('payload', $_POST)) |
26 | | - throw new Exception('Missing payload.'); |
27 | | - $RawPayload = $_POST[ 'payload' ]; |
28 | | - } elseif ($ContentType === 'application/json') |
29 | | - $RawPayload = file_get_contents('php://input'); |
30 | | - else |
31 | | - throw new Exception('Unknown content type.'); |
32 | | - $this->Payload = json_decode($RawPayload, true); |
33 | | - if ($this->Payload === null) |
34 | | - throw new Exception('Failed to decode JSON: '.(function_exists('json_last_error_msg') ? json_last_error_msg() : json_last_error())); |
35 | | - if (!isset($this->Payload['repository'])) { |
36 | | - if (!isset($this->Payload['organization'])) |
37 | | - throw new Exception('Missing repository information.'); |
38 | | - // This is a silly hack to handle org-only events |
39 | | - $this->Payload['repository'] = array( |
40 | | - // Add "/repositories" because repo matching code would expect a "<org>/<repo>" format |
41 | | - 'full_name' => $this->Payload['organization']['login'] . '/repositories', |
42 | | - 'name' => 'org: ' . $this->Payload['organization']['login'], |
43 | | - 'owner' => array( |
44 | | - 'name' => $this->Payload['organization']['login'], |
45 | | - 'login' => $this->Payload['organization']['login'], |
46 | | - ), |
47 | | - ); |
48 | | - } |
49 | | - return true; |
50 | | - } |
| 9 | + /** |
| 10 | + * Validates and processes current request |
| 11 | + */ |
| 12 | + public function ProcessRequest() : bool |
| 13 | + { |
| 14 | + if (!array_key_exists('HTTP_X_GITHUB_EVENT', $_SERVER)) { |
| 15 | + throw new Exception('Missing event header.'); |
| 16 | + } |
| 17 | + $this->EventType = $_SERVER[ 'HTTP_X_GITHUB_EVENT' ]; |
| 18 | + if (preg_match('/^[a-z_]+$/', $this->EventType) !== 1) { |
| 19 | + throw new Exception('Invalid event header.'); |
| 20 | + } |
| 21 | + if (!array_key_exists('REQUEST_METHOD', $_SERVER) || $_SERVER[ 'REQUEST_METHOD' ] !== 'POST') { |
| 22 | + throw new Exception('Invalid request method.'); |
| 23 | + } |
| 24 | + if (!array_key_exists('CONTENT_TYPE', $_SERVER)) { |
| 25 | + throw new Exception('Missing content type.'); |
| 26 | + } |
| 27 | + $ContentType = $_SERVER[ 'CONTENT_TYPE' ]; |
| 28 | + $RawPayload = null; |
| 29 | + if ($ContentType === 'application/x-www-form-urlencoded') { |
| 30 | + if (!array_key_exists('payload', $_POST)) { |
| 31 | + throw new Exception('Missing payload.'); |
| 32 | + } |
| 33 | + $RawPayload = $_POST[ 'payload' ]; |
| 34 | + } elseif ($ContentType === 'application/json') { |
| 35 | + $RawPayload = file_get_contents('php://input'); |
| 36 | + } else { |
| 37 | + throw new Exception('Unknown content type.'); |
| 38 | + } |
| 39 | + $this->Payload = json_decode($RawPayload, true); |
| 40 | + if ($this->Payload === null) { |
| 41 | + throw new Exception('Failed to decode JSON: '.(function_exists('json_last_error_msg') ? json_last_error_msg() : json_last_error())); |
| 42 | + } |
| 43 | + if (!isset($this->Payload['repository'])) { |
| 44 | + if (!isset($this->Payload['organization'])) { |
| 45 | + throw new Exception('Missing repository information.'); |
| 46 | + } |
| 47 | + // This is a silly hack to handle org-only events |
| 48 | + $this->Payload['repository'] = [ |
| 49 | + // Add "/repositories" because repo matching code would expect a "<org>/<repo>" format |
| 50 | + 'full_name' => $this->Payload['organization']['login'] . '/repositories', |
| 51 | + 'name' => 'org: ' . $this->Payload['organization']['login'], |
| 52 | + 'owner' => [ |
| 53 | + 'name' => $this->Payload['organization']['login'], |
| 54 | + 'login' => $this->Payload['organization']['login'], |
| 55 | + ], |
| 56 | + ]; |
| 57 | + } |
| 58 | + return true; |
| 59 | + } |
51 | 60 |
|
52 | | - /** |
53 | | - * Optional function to check if request came from GitHub's IP range. |
54 | | - * |
55 | | - * @return bool |
56 | | - */ |
57 | | - public function ValidateIPAddress() : bool { |
58 | | - if (!array_key_exists('REMOTE_ADDR', $_SERVER)) |
59 | | - throw new Exception('Missing remote address.'); |
60 | | - $Remote = ip2long($_SERVER[ 'REMOTE_ADDR' ]); |
61 | | - // https://api.github.com/meta |
62 | | - $Addresses = [ |
63 | | - ['192.30.252.0', 22], |
64 | | - ['185.199.108.0', 22], |
65 | | - ['140.82.112.0', 20], |
66 | | - ['143.55.64.0', 20], |
67 | | - //['2a0a:a440::', 29], |
68 | | - //['2606:50c0::', 32], |
69 | | - ]; |
70 | | - foreach ($Addresses as $CIDR) { |
71 | | - $Base = ip2long($CIDR[ 0 ]); |
72 | | - $Mask = pow(2, (32 - $CIDR[ 1 ])) - 1; |
73 | | - if ($Base === ($Remote & ~$Mask)) |
74 | | - return true; |
75 | | - } |
76 | | - return false; |
77 | | - } |
| 61 | + /** |
| 62 | + * Optional function to check if request came from GitHub's IP range. |
| 63 | + * |
| 64 | + * @return bool |
| 65 | + */ |
| 66 | + public function ValidateIPAddress() : bool |
| 67 | + { |
| 68 | + if (!array_key_exists('REMOTE_ADDR', $_SERVER)) { |
| 69 | + throw new Exception('Missing remote address.'); |
| 70 | + } |
| 71 | + $Remote = ip2long($_SERVER[ 'REMOTE_ADDR' ]); |
| 72 | + // https://api.github.com/meta |
| 73 | + $Addresses = [ |
| 74 | + ['192.30.252.0', 22], |
| 75 | + ['185.199.108.0', 22], |
| 76 | + ['140.82.112.0', 20], |
| 77 | + ['143.55.64.0', 20], |
| 78 | + //['2a0a:a440::', 29], |
| 79 | + //['2606:50c0::', 32], |
| 80 | + ]; |
| 81 | + foreach ($Addresses as $CIDR) { |
| 82 | + $Base = ip2long($CIDR[ 0 ]); |
| 83 | + $Mask = pow(2, (32 - $CIDR[ 1 ])) - 1; |
| 84 | + if ($Base === ($Remote & ~$Mask)) { |
| 85 | + return true; |
| 86 | + } |
| 87 | + } |
| 88 | + return false; |
| 89 | + } |
78 | 90 |
|
79 | | - /** |
80 | | - * Optional function to check if HMAC hex digest of the payload matches GitHub's. |
81 | | - * |
82 | | - * @return bool |
83 | | - */ |
84 | | - public function ValidateHubSignature(string $SecretKey) : bool { |
85 | | - if (!array_key_exists('HTTP_X_HUB_SIGNATURE_256', $_SERVER)) |
86 | | - throw new Exception('Missing X-Hub-Signature-256 header. Did you configure secret token in hook settings?'); |
87 | | - $Payload = file_get_contents( 'php://input' ); |
88 | | - if( $Payload === false ) |
89 | | - throw new Exception( 'Failed to read php://input.' ); |
90 | | - $KnownAlgo = 'sha256'; |
91 | | - $CalculatedHash = $KnownAlgo . '=' . hash_hmac( $KnownAlgo, $Payload, $SecretKey, false ); |
92 | | - return hash_equals($CalculatedHash, $_SERVER[ 'HTTP_X_HUB_SIGNATURE_256' ]); |
93 | | - } |
| 91 | + /** |
| 92 | + * Optional function to check if HMAC hex digest of the payload matches GitHub's. |
| 93 | + * |
| 94 | + * @return bool |
| 95 | + */ |
| 96 | + public function ValidateHubSignature(string $SecretKey) : bool |
| 97 | + { |
| 98 | + if (!array_key_exists('HTTP_X_HUB_SIGNATURE_256', $_SERVER)) { |
| 99 | + throw new Exception('Missing X-Hub-Signature-256 header. Did you configure secret token in hook settings?'); |
| 100 | + } |
| 101 | + $Payload = file_get_contents('php://input'); |
| 102 | + if ($Payload === false) { |
| 103 | + throw new Exception('Failed to read php://input.'); |
| 104 | + } |
| 105 | + $KnownAlgo = 'sha256'; |
| 106 | + $CalculatedHash = $KnownAlgo . '=' . hash_hmac($KnownAlgo, $Payload, $SecretKey, false); |
| 107 | + return hash_equals($CalculatedHash, $_SERVER[ 'HTTP_X_HUB_SIGNATURE_256' ]); |
| 108 | + } |
94 | 109 |
|
95 | | - /** |
96 | | - * Returns event type |
97 | | - * @see https://developer.github.com/webhooks/#events |
98 | | - * |
99 | | - * @return string |
100 | | - */ |
101 | | - public function GetEventType() : string { |
102 | | - return $this->EventType; |
103 | | - } |
| 110 | + /** |
| 111 | + * Returns event type |
| 112 | + * @see https://developer.github.com/webhooks/#events |
| 113 | + * |
| 114 | + * @return string |
| 115 | + */ |
| 116 | + public function GetEventType() : string |
| 117 | + { |
| 118 | + return $this->EventType; |
| 119 | + } |
104 | 120 |
|
105 | | - /** |
106 | | - * Returns decoded payload |
107 | | - * |
108 | | - * @return array |
109 | | - */ |
110 | | - public function GetPayload() : array { |
111 | | - return $this->Payload; |
112 | | - } |
| 121 | + /** |
| 122 | + * Returns decoded payload |
| 123 | + * |
| 124 | + * @return array |
| 125 | + */ |
| 126 | + public function GetPayload() : array |
| 127 | + { |
| 128 | + return $this->Payload; |
| 129 | + } |
113 | 130 |
|
114 | | - /** |
115 | | - * Returns full name of the repository |
116 | | - * |
117 | | - * @return string |
118 | | - */ |
119 | | - public function GetFullRepositoryName() : string { |
120 | | - if (isset($this->Payload['repository']['full_name'])) |
121 | | - return $this->Payload['repository']['full_name']; |
122 | | - return sprintf('%s/%s', $this->Payload['repository']['owner']['name'], $this->Payload['repository']['name']); |
123 | | - } |
| 131 | + /** |
| 132 | + * Returns full name of the repository |
| 133 | + * |
| 134 | + * @return string |
| 135 | + */ |
| 136 | + public function GetFullRepositoryName() : string |
| 137 | + { |
| 138 | + if (isset($this->Payload['repository']['full_name'])) { |
| 139 | + return $this->Payload['repository']['full_name']; |
| 140 | + } |
| 141 | + return sprintf('%s/%s', $this->Payload['repository']['owner']['name'], $this->Payload['repository']['name']); |
| 142 | + } |
124 | 143 | } |
125 | | - |
|
0 commit comments