webhook management
authorMichael Wallner <mike@php.net>
Mon, 4 May 2015 16:02:09 +0000 (18:02 +0200)
committerMichael Wallner <mike@php.net>
Mon, 4 May 2015 16:02:09 +0000 (18:02 +0200)
13 files changed:
app/Cli.php [new file with mode: 0644]
app/Controller/Github/Hook.php [new file with mode: 0644]
app/Controller/Github/Repo.php
app/Controller/Github/Repo/Hook.php
app/Github/Create/Webhook.php
app/Web.php
app/bootstrap/cli.php [new file with mode: 0644]
app/config.ini
app/routes.ini
app/views/layout.phtml
app/views/modal/hook.phtml [new file with mode: 0644]
bin/pharext.org [new file with mode: 0755]
composer.json

diff --git a/app/Cli.php b/app/Cli.php
new file mode 100644 (file)
index 0000000..114bfed
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+namespace app;
+
+use merry\Config;
+use pharext\Cli\Args;
+
+class Cli
+{
+       /**
+        * @var \merry\Config
+        */
+       private $config;
+       
+       function __construct(Config $config, Args $args) {
+               $this->config = $config;
+               $this->args = $args;
+       }
+       
+       function __invoke($argc, array $argv) {
+               $prog = array_shift($argv);
+               foreach ($this->args->parse(--$argc, $argv) as $error) {
+                       $errs[] = $error;
+               }
+               
+               if ($this->args["help"] || !array_filter($this->args->toArray())) {
+                       $this->help($prog);
+                       exit;
+               }
+               if (!empty($errs)) {
+                       foreach ($errs as $err) {
+                               fprintf(STDERR, "ERROR: %s\n", $err);
+                               exit(-1);
+                       }
+               }
+               
+               if ($this->args["ngrok"]) {
+                       system($this->config->ngrok->command . " ". implode(" ", array_map("escapeshellarg", [
+                               "http",
+                               "--subdomain=pharext",
+                               "--authtoken",
+                               $this->config->ngrok->auth->token,
+                               "--auth",
+                               $this->config->ngrok->auth->user .":". $this->config->ngrok->auth->pass,
+                               "80"
+                       ])));
+               }
+       }
+       
+       function getConfig() {
+               return $this->config;
+       }
+       
+       /**
+        * Output command line help message
+        * @param string $prog
+        */
+       public function help($prog) {
+               printf("Usage:\n\n  \$ %s", $prog);
+               
+               $flags = [];
+               $required = [];
+               $optional = [];
+               foreach ($this->args->getSpec() as $spec) {
+                       if ($spec[3] & Args::REQARG) {
+                               if ($spec[3] & Args::REQUIRED) {
+                                       $required[] = $spec;
+                               } else {
+                                       $optional[] = $spec;
+                               }
+                       } else {
+                               $flags[] = $spec;
+                       }
+               }
+       
+               if ($flags) {
+                       printf(" [-%s]", implode("", array_column($flags, 0)));
+               }
+               foreach ($required as $req) {
+                       printf(" -%s <arg>", $req[0]);
+               }
+               if ($optional) {
+                       printf(" [-%s <arg>]", implode("|-", array_column($optional, 0)));
+               }
+               printf("\n\n");
+               $spc = $this->args->getSpec();
+               $max = $spc ? max(array_map("strlen", array_column($spc, 1))) : 0;
+               $max += $max % 8 + 2;
+               foreach ($spc as $spec) {
+                       if (isset($spec[0])) {
+                               printf("    -%s|", $spec[0]);
+                       } else {
+                               printf("    ");
+                       }
+                       printf("--%s ", $spec[1]);
+                       if ($spec[3] & Args::REQARG) {
+                               printf("<arg>  ");
+                       } elseif ($spec[3] & Args::OPTARG) {
+                               printf("[<arg>]");
+                       } else {
+                               printf("       ");
+                       }
+                       printf("%s%s", str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0])), $spec[2]);
+                       if ($spec[3] & Args::REQUIRED) {
+                               printf(" (REQUIRED)");
+                       }
+                       if (isset($spec[4])) {
+                               printf(" [%s]", $spec[4]);
+                       }
+                       printf("\n");
+               }
+               printf("\n");
+       }
+}
diff --git a/app/Controller/Github/Hook.php b/app/Controller/Github/Hook.php
new file mode 100644 (file)
index 0000000..946bb0c
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace app\Github\API;
+
+use app\Controller;
+use app\Github\API;
+use app\Web;
+
+class Hook implements Controller
+{
+       private $app;
+       private $github;
+       
+       function __construct(Web $app, API $github) {
+               $this->app = $app;
+               $this->github = $github;
+       }
+       
+       function __invoke(array $args = []) {
+               $request = $this->app->getRequest();
+               $response = $this->app->getResponse();
+               
+               if (!($sig = $request->getHeader("X-Github-Signature")) || !($evt = $request->getHeader("X-Github-Event"))) {
+                       $response->setResponseCode(400);
+               } elseif ($sig !== hash_hmac("sha1", $request->getBody(), $this->app->getConfig()->github->client->secret)) {
+                       $response->setResponseCode(403);
+               } elseif ($evt === "ping") {
+                       $response->setReponseStatus("PONG");
+               } elseif ($evt !== "push") {
+                       $this->app->getResponse()->setResponseCode(204);
+               } else {
+                       $push = json_decode($request->getBody());
+               }
+       }
+}
index 2ee935d3cf47035eaa8477017e6678582047bdc8..f194d8e726b91ac52294e1651a542b95d0d8ea4e 100644 (file)
@@ -17,6 +17,13 @@ class Repo extends Github
                        } catch (\app\Github\Exception $exception) {
                                $this->app->getView()->addData(compact("exception", "owner", "name"));
                        }
+                       
+                       if (($modal = $this->app->getRequest()->getQuery("modal"))) {
+                               $this->app->getView()->addData(compact("modal") + [
+                                       "action" => $this->app->getRequest()->getQuery($modal)
+                               ]);
+                       }
+                       
                        $this->app->display("github/repo");
                }
        }
index 7bfb54565bd5980264afde502197c404196965d1..1ef33a37cb921cae11284f1c8d95a5c2910a8d8b 100644 (file)
@@ -7,14 +7,22 @@ use app\Controller\Github;
 class Hook extends Github
 {
        function __invoke(array $args = null) {
-               switch ($args["action"]) {
-                       case "add":
-                               $this->addHook($args["owner"], $args["name"]);
-                               break;
-                       
-                       case "del":
-                               $this->delHook($args["owner"], $args["name"]);
-                               break;
+               if ($this->checkToken()) {
+                       if ($this->app->getRequest()->getRequestMethod() != "POST") {
+                               // user had to re-authenticate, and was redirected here
+                               $this->app->redirect($this->app->getBaseUrl()->mod([
+                                       "path" => "./github/repo/" . $args["owner"] ."/". $args["name"],
+                                       "query" => "modal=hook&hook=" . $args["action"]
+                               ]));
+                       } else switch ($args["action"]) {
+                               case "add":
+                                       $this->addHook($args["owner"], $args["name"]);
+                                       break;
+
+                               case "del":
+                                       $this->delHook($args["owner"], $args["name"]);
+                                       break;
+                       }
                }
        }
        
index 6e583fcd91c88428dd4439f7d6c3b803a20c3259..76765e89344a0475c7bbb0137794b3fd54244123 100644 (file)
@@ -20,8 +20,8 @@ class Webhook extends Create
                        "config" => [
                                "url" => $this->config->hook->url,
                                "content_type" => $this->config->hook->content_type,
+                               "insecure_ssl" => $this->config->hook->insecure_ssl,
                                "secret" => $this->config->client->secret, // FIXME: bad idea?
-                               "insecure_ssl" => false,
                        ]
                ]));
                return $request;
index 39cfe8d8e4ccd877f9b59401d94f159b6a5939e3..c9659b68231f276ffd4951b15173c196b2b37acd 100644 (file)
@@ -2,7 +2,6 @@
 
 namespace app;
 
-use http\Url;
 use http\Env\Request;
 use http\Env\Response;
 
diff --git a/app/bootstrap/cli.php b/app/bootstrap/cli.php
new file mode 100644 (file)
index 0000000..4a0239e
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace app;
+
+use pharext\Cli\Args;
+
+require_once __DIR__."/config.php";
+require_once __DIR__."/github.php";
+
+/* @var $injector \Auryn\Injector */
+
+$injector->share(Cli::class);
+
+$injector->share(Args::class)
+       ->define(Args::class, [
+               ":spec" => [
+                       [null, "ngrok", "Run ngrok", Args::SINGLE],
+                       ["h", "help", "Show this help", Args::HALT],
+               ]
+       ]);
index 22dd591e2dc7b45409bbc302db27f52c2101d903..a88c3955b35b5ccc9aecc30fdf010655367cd187 100644 (file)
@@ -1,6 +1,8 @@
 [production]
 
 github.hook.url = https://pharext.org/github/hook
+github.hook.content_type = json
+github.hook.insecure_ssl = 0
 
 github.fetch.repos.per_page = 10
 github.fetch.hooks.per_page = 100
@@ -22,3 +24,8 @@ session.cache_limiter = private_no_expire
 session.cache_expire = 0
 
 [localhost : production]
+
+github.hook.url = https://pharext.ngrok.io/src/pharext.org.git/public/github/hook
+github.hook.insecure_ssl = 1
+
+ngrok.command = ngrok
\ No newline at end of file
index 40782bf8efd45790f9fc68bee200b64c30857308..ad01201599864f1a5fe54a4be4ac40fb7a57abe7 100644 (file)
@@ -7,6 +7,9 @@ GET[] = /pages/{page}
 [Github\Callback]
 GET[] = /github/callback
 
+[Github\Hook]
+POST[] = /github/hook
+
 [Github\Index]
 GET[] = /github
 
@@ -16,6 +19,8 @@ GET[] = /github/repo/{owner}/{name}/{page}
 
 [Github\Repo\Hook]
 POST[] = /github/repo/{owner}/{name}/hook/{action}
+; if the user has to re-authenticate, we'll receive a GET because of a redirect
+GET[] = /github/repo/{owner}/{name}/hook/{action}
 
 [Github\Signin]
 GET[] = /github/signin
index 98597eff0b998063ec7f0aa4739a57bb24af2582..383ae220022e3964618e040c57c86986a62b6f62 100644 (file)
@@ -37,6 +37,9 @@
                        <?= $this->section("content") ?>
                        
                </div>
+               <?php if (isset($modal)) : ?>
+                       <?= $this->fetch("modal/$modal") ?>
+               <?php endif; ?>
                <footer class="footer">
                        <div class="container">
                                <p>
@@ -53,7 +56,7 @@
                </footer>
                <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
                <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
-               
+               <script>$(".modal").modal("show")</script>
                <?php if (!empty($scripts)) : ?>
                        <?php foreach ($scripts as $script) : ?>
                                <?php if ($script instanceof \http\Url) : ?>
diff --git a/app/views/modal/hook.phtml b/app/views/modal/hook.phtml
new file mode 100644 (file)
index 0000000..6a6df0d
--- /dev/null
@@ -0,0 +1,28 @@
+<div class="modal fade">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+        <h4 class="modal-title">Complete Webhook Action</h4>
+      </div>
+      <div class="modal-body">
+        <p>You tried to <?= $action == "del" ? "remove" : "add" ?> the PHARext webhook, 
+                       but you had to re-authenticate.</p>
+               <p>Please complete the desired action by submitting this form.</p>
+      </div>
+      <div class="modal-footer">
+               <?php if ($action === "add") : ?>
+               <form method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/add") ?>"> 
+               <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+                       <button type="submit" class="btn btn-primary">Enable Hook</button>
+               </form>
+               <?php elseif ($action === "del") : ?>
+               <form method="post" action="<?= $baseUrl->mod("./github/repo/". $repo->full_name ."/hook/del") ?>"> 
+               <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+                       <button type="submit" class="btn btn-primary">Disable Hook</button>
+               </form>
+               <?php endif; ?>
+      </div>
+    </div><!-- /.modal-content -->
+  </div><!-- /.modal-dialog -->
+</div><!-- /.modal -->
\ No newline at end of file
diff --git a/bin/pharext.org b/bin/pharext.org
new file mode 100755 (executable)
index 0000000..8937568
--- /dev/null
@@ -0,0 +1,8 @@
+#!/usr/bin/php
+<?php
+
+namespace app;
+
+$bootstrap = require __DIR__."/../app/bootstrap.php";
+$injector = $bootstrap(["config", "github", "cli"]);
+$injector->execute(Cli::class, [$argc, $argv]);
index 573d1d9d3be004d3f1f1be60d9a7a1680545c7df..c8914d0f84663341c1142a9da726b2d62d72406e 100644 (file)
@@ -13,7 +13,8 @@
        }],
        "autoload": {
                "psr-0": {
-                       "app": ""
+                       "app": "",
+                       "pharext": "vendor/m6w6/pharext/src"
                }
        },
        "require": {