Further clean up code, implement signature checking
This commit is contained in:
parent
0f711738bd
commit
faa0f0ae5f
|
@ -7,8 +7,5 @@
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {"GHE\\": "src/"}
|
"psr-4": {"GHE\\": "src/"}
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^6.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1560
php/composer.lock
generated
1560
php/composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -31,21 +31,6 @@ function runner($msg) {
|
||||||
echo "Msg Sha: " . md5($msg->body) . "\n";
|
echo "Msg Sha: " . md5($msg->body) . "\n";
|
||||||
$in = json_decode($msg->body);
|
$in = json_decode($msg->body);
|
||||||
|
|
||||||
try {
|
|
||||||
$etype = \GHE\EventClassifier::classifyEvent($in);
|
|
||||||
|
|
||||||
if ($etype != "pull_request") {
|
|
||||||
echo "Skipping event type: $etype\n";
|
|
||||||
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (\GHE\EventClassifierUnknownException $e) {
|
|
||||||
echo "Skipping unknown event type\n";
|
|
||||||
print_r($in);
|
|
||||||
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\GHE\ACL::isRepoEligible($in->repository->full_name)) {
|
if (!\GHE\ACL::isRepoEligible($in->repository->full_name)) {
|
||||||
echo "Repo not authorized (" . $in->repository->full_name . ")\n";
|
echo "Repo not authorized (" . $in->repository->full_name . ")\n";
|
||||||
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
|
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
|
||||||
|
|
|
@ -1,185 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace GHE;
|
|
||||||
|
|
||||||
class EventClassifier {
|
|
||||||
public static function classifyEvent($payload) {
|
|
||||||
if (self::isIssuesEvent($payload)) {
|
|
||||||
return "issues";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isIssueComment($payload)) {
|
|
||||||
return "issue_comment";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isCommitComment($payload)) {
|
|
||||||
return "commit_comment";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isPullRequestReviewComment($payload)) {
|
|
||||||
return "pull_request_review_comment";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isPullRequestReviewEvent($payload)) {
|
|
||||||
return "pull_request_review";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isPullRequestEvent($payload)) {
|
|
||||||
return "pull_request";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isStatusEvent($payload)) {
|
|
||||||
return "status";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isPushEvent($payload)) {
|
|
||||||
return "push";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isWatchEvent($payload)) {
|
|
||||||
return "watch";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isForkEvent($payload)) {
|
|
||||||
return "fork";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isCreateEvent($payload)) {
|
|
||||||
return "create";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isDeleteEvent($payload)) {
|
|
||||||
return "delete";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isProjectEvent($payload)) {
|
|
||||||
return "project";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isProjectCardEvent($payload)) {
|
|
||||||
return "project_card";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isProjectColumnEvent($payload)) {
|
|
||||||
return "project_column";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::isLabelEvent($payload)) {
|
|
||||||
return "label";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new EventClassifierUnknownException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isIssuesEvent($payload) {
|
|
||||||
return isset($payload->issue)
|
|
||||||
&& !isset($payload->comment)
|
|
||||||
&& isset($payload->action)
|
|
||||||
&& in_array($payload->action,
|
|
||||||
[ "assigned", "unassigned", "labeled",
|
|
||||||
"unlabeled", "opened", "edited",
|
|
||||||
"milestoned", "demilestoned", "closed",
|
|
||||||
"reopened" ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isIssueComment($payload) {
|
|
||||||
return isset($payload->issue)
|
|
||||||
&& isset($payload->comment)
|
|
||||||
&& isset($payload->action)
|
|
||||||
&& in_array($payload->action,
|
|
||||||
['created', 'edited', 'deleted']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isCommitComment($payload) {
|
|
||||||
return !isset($payload->issue)
|
|
||||||
&& !isset($payload->pull_request)
|
|
||||||
&& isset($payload->comment)
|
|
||||||
&& isset($payload->action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isPullRequestReviewComment($payload) {
|
|
||||||
return !isset($payload->issue)
|
|
||||||
&& isset($payload->pull_request)
|
|
||||||
&& isset($payload->comment)
|
|
||||||
&& isset($payload->action)
|
|
||||||
&& in_array($payload->action,
|
|
||||||
['created', 'edited', 'deleted']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isPullRequestReviewEvent($payload) {
|
|
||||||
return isset($payload->review)
|
|
||||||
&& isset($payload->pull_request)
|
|
||||||
&& isset($payload->action)
|
|
||||||
&& in_array($payload->action,
|
|
||||||
['submitted', 'edited', 'dismissed']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isPullRequestEvent($payload) {
|
|
||||||
return isset($payload->number)
|
|
||||||
&& isset($payload->pull_request)
|
|
||||||
&& isset($payload->action)
|
|
||||||
&& in_array($payload->action,
|
|
||||||
[ "assigned", "unassigned",
|
|
||||||
"review_requested",
|
|
||||||
"review_request_removed", "labeled",
|
|
||||||
"unlabeled", "opened", "edited", "closed",
|
|
||||||
"reopened", "synchronize" ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isStatusEvent($payload) {
|
|
||||||
return isset($payload->sha)
|
|
||||||
&& isset($payload->commit)
|
|
||||||
&& isset($payload->state)
|
|
||||||
&& in_array($payload->state,
|
|
||||||
['pending', 'success', 'failure', 'error']);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isPushEvent($payload) {
|
|
||||||
return isset($payload->before)
|
|
||||||
&& isset($payload->after);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isWatchEvent($payload) {
|
|
||||||
return isset($payload->action)
|
|
||||||
&& $payload->action == "started";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isForkEvent($payload) {
|
|
||||||
return isset($payload->forkee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isCreateEvent($payload) {
|
|
||||||
return isset($payload->ref_type)
|
|
||||||
&& isset($payload->ref)
|
|
||||||
&& isset($payload->master_branch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isDeleteEvent($payload) {
|
|
||||||
return isset($payload->ref_type)
|
|
||||||
&& isset($payload->ref)
|
|
||||||
&& !isset($payload->master_branch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isProjectEvent($payload) {
|
|
||||||
return isset($payload->project);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isProjectCardEvent($payload) {
|
|
||||||
return isset($payload->project_card);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isProjectColumnEvent($payload) {
|
|
||||||
return isset($payload->project_column);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isLabelEvent($payload) {
|
|
||||||
return isset($payload->label)
|
|
||||||
&& isset($payload->action)
|
|
||||||
&& in_array($payload->action,
|
|
||||||
['created', 'edited', 'deleted']);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventClassifierUnknownException extends \Exception{};
|
|
|
@ -1,44 +1,153 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
require_once __DIR__ . '/../config.php';
|
require_once __DIR__ . '/../config.php';
|
||||||
use PhpAmqpLib\Message\AMQPMessage;
|
use PhpAmqpLib\Message\AMQPMessage;
|
||||||
|
|
||||||
$connection = rabbitmq_conn();
|
class DumpableException extends \Exception{}
|
||||||
$channel = $connection->channel();
|
class InvalidPayloadException extends DumpableException {}
|
||||||
|
class InvalidSignatureException extends DumpableException {}
|
||||||
|
class InvalidEventTypeException extends DumpableException {}
|
||||||
|
class ValidationFailureException extends DumpableException {}
|
||||||
|
class ExecutionFailureException extends DumpableException {}
|
||||||
|
|
||||||
$raw = file_get_contents('php://input');
|
function payload() {
|
||||||
$input = json_decode($raw);
|
if (!isset($_SERVER)) {
|
||||||
if (!isset($input->repository->full_name)) {
|
throw new InvalidPayloadException('_SERVER undefined');
|
||||||
echo "no full_name set?";
|
}
|
||||||
exit();
|
|
||||||
} else {
|
if (!isset($_SERVER['CONTENT_TYPE'])) {
|
||||||
echo "full_name present\n";
|
throw new InvalidPayloadException('CONTENT_TYPE not set in _SERVER');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($_SERVER['CONTENT_TYPE']) {
|
||||||
|
case 'application/json':
|
||||||
|
$input = file_get_contents('php://input');
|
||||||
|
if ($input === false) {
|
||||||
|
throw new InvalidPayloadException('Failed to read php://input for application/json');
|
||||||
|
} else {
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
case 'application/x-www-form-urlencoded':
|
||||||
|
if (!isset($_POST)) {
|
||||||
|
throw new InvalidPayloadException('_POST undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_POST['payload'])) {
|
||||||
|
throw new InvalidPayloadException('payload not set in _POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $_POST['payload'];
|
||||||
|
default:
|
||||||
|
throw new InvalidPayloadException('Unsupported content type: ' . $_SERVER['HTTP_CONTENT_TYPE']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = strtolower($input->repository->full_name);
|
function signature() {
|
||||||
if (!GHE\ACL::isRepoEligible($name)) {
|
if (!isset($_SERVER)) {
|
||||||
echo "repo not in ok name list";
|
throw new InvalidSignatureException('_SERVER undefined');
|
||||||
exit(1);
|
}
|
||||||
} else {
|
|
||||||
echo "full_name ok\n";
|
if (!isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) {
|
||||||
|
throw new InvalidSignatureException('HTTP_X_HUB_SIGNATURE absent from _SERVER');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $_SERVER['HTTP_X_HUB_SIGNATURE'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$dec = $channel->exchange_declare('github-events', 'topic', false, true, false);
|
function event_type() {
|
||||||
|
if (!isset($_SERVER)) {
|
||||||
|
throw new InvalidEventTypeException('_SERVER undefined');
|
||||||
|
}
|
||||||
|
|
||||||
$message = new AMQPMessage(json_encode($input),
|
if (!isset($_SERVER['HTTP_X_GITHUB_EVENT'])) {
|
||||||
array(
|
throw new InvalidEventTypeException('HTTP_X_GITHUB_EVENT absent from _SERVER');
|
||||||
'content_type' => 'application/json',
|
}
|
||||||
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
|
|
||||||
));
|
$type = trim($_SERVER['HTTP_X_GITHUB_EVENT']);
|
||||||
|
|
||||||
|
if (strlen($type) === 0) {
|
||||||
|
throw new InvalidEventTypeException('After trimming, event type is zero-length');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_payload_signature($secret, $payload, $signature) {
|
||||||
|
if (!extension_loaded('hash')) {
|
||||||
|
throw new ValidationFailureException('Missing hash extension');
|
||||||
|
}
|
||||||
|
|
||||||
|
$components = explode('=', $signature, 2);
|
||||||
|
if (count($components) != 2) {
|
||||||
|
throw new ValidationFailureException('Provided signature seems invalid after splitting on =');
|
||||||
|
}
|
||||||
|
|
||||||
|
$algo = $components[0];
|
||||||
|
$provided_hash = $components[1];
|
||||||
|
|
||||||
|
if (!in_array($algo, hash_algos(), true)) {
|
||||||
|
throw new ValidationFailureException("Hash algorithm '$algo' is not supported by the extension.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok_algos = [
|
||||||
|
'sha1',
|
||||||
|
'sha256',
|
||||||
|
'sha512',
|
||||||
|
];
|
||||||
|
if (!in_array($algo, $ok_algos, true)) {
|
||||||
|
throw new ValidationFailureException("Hash algorithm '$algo' is not considered okay");
|
||||||
|
}
|
||||||
|
|
||||||
|
$calculated_hash = hash_hmac($algo, $payload, $secret);
|
||||||
|
|
||||||
|
return hash_equals($provided_hash, $calculated_hash);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$etype = \GHE\EventClassifier::classifyEvent($input);
|
$raw = payload();
|
||||||
} catch (\GHE\EventClassifierUnknownException $e) {
|
if (!validate_payload_signature(gh_secret(), $raw, signature())) {
|
||||||
$etype = "unknown";
|
throw new ExecutionFailureException('Failed to validate signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode($raw);
|
||||||
|
if ($input === null) {
|
||||||
|
throw new ExecutionFailureException('Failed to decode the JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($input->repository)) {
|
||||||
|
throw new\ExecutionFailureException('Dataset does not have a repository');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($input->repository->full_name)) {
|
||||||
|
throw new ExecutionFailureException('Dataset repository does not have a name');
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = strtolower($input->repository->full_name);
|
||||||
|
$eventtype = event_type();
|
||||||
|
|
||||||
|
$connection = rabbitmq_conn();
|
||||||
|
$channel = $connection->channel();
|
||||||
|
|
||||||
|
$dec = $channel->exchange_declare('github-events', 'topic', false, true, false);
|
||||||
|
|
||||||
|
$message = new AMQPMessage(json_encode($input),
|
||||||
|
array(
|
||||||
|
'content_type' => 'application/json',
|
||||||
|
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
|
||||||
|
));
|
||||||
|
|
||||||
|
$routing_key = "$eventtype.$name";
|
||||||
|
$rec = $channel->basic_publish($message, 'github-events', $routing_key);
|
||||||
|
|
||||||
|
echo "ok";
|
||||||
|
} catch (DumpableException $e) {
|
||||||
|
header($_SERVER["SERVER_PROTOCOL"]." 400 Eh", true, 400);
|
||||||
|
var_dump($e);
|
||||||
|
echo ob_get_clean();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
header($_SERVER["SERVER_PROTOCOL"]." 400 Meh", true, 400);
|
||||||
|
var_dump(get_class($e));
|
||||||
|
echo ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
$routing_key = "$etype.$name";
|
|
||||||
var_dump($routing_key);
|
|
||||||
$rec = $channel->basic_publish($message, 'github-events', $routing_key);
|
|
||||||
|
|
||||||
echo "ok";
|
|
Loading…
Reference in a new issue