├── .gitignore ├── composer.json ├── LICENSE ├── src └── Handler.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor/ -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dintel/php-github-webhook", 3 | "description": "Simple class for handling GitHub webhook calls", 4 | "keywords": ["git","webhook"], 5 | "license": "BSD-3-Clause", 6 | "authors": [ 7 | { 8 | "name": "Dmitry Zbarski", 9 | "email": "dmitry.zbarski@gmail.com", 10 | "homepage": "http://zbarski.info", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "GitHubWebhook\\": "src/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Zend Technologies ltd. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of [project] nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/Handler.php: -------------------------------------------------------------------------------- 1 | secret = $secret; 18 | $this->remote = $remote; 19 | $this->gitDir = $gitDir; 20 | } 21 | 22 | public function getData() 23 | { 24 | return $this->data; 25 | } 26 | 27 | public function getDelivery() 28 | { 29 | return $this->delivery; 30 | } 31 | 32 | public function getEvent() 33 | { 34 | return $this->event; 35 | } 36 | 37 | public function getGitDir() 38 | { 39 | return $this->gitDir; 40 | } 41 | 42 | public function getGitOutput() 43 | { 44 | return $this->gitOutput; 45 | } 46 | 47 | public function getRemote() 48 | { 49 | return $this->remote; 50 | } 51 | 52 | public function getSecret() 53 | { 54 | return $this->secret; 55 | } 56 | 57 | public function getGitExitCode() 58 | { 59 | return $this->gitExitCode; 60 | } 61 | 62 | public function handle() 63 | { 64 | if (!$this->validate()) { 65 | return false; 66 | } 67 | 68 | exec("git -C {$this->gitDir} pull -f {$this->remote} 2>&1", $this->gitOutput, $this->gitExitCode); 69 | return $this->gitExitCode == 0; 70 | } 71 | 72 | public function validate() 73 | { 74 | $signature = @$_SERVER['HTTP_X_HUB_SIGNATURE']; 75 | $event = @$_SERVER['HTTP_X_GITHUB_EVENT']; 76 | $delivery = @$_SERVER['HTTP_X_GITHUB_DELIVERY']; 77 | 78 | if (!isset($signature, $event, $delivery)) { 79 | return false; 80 | } 81 | 82 | $payload = file_get_contents('php://input'); 83 | 84 | // Check if the payload is json or urlencoded. 85 | if (strpos($payload, 'payload=') === 0) { 86 | $payload = substr(urldecode($payload), 8); 87 | } 88 | 89 | if (!$this->validateSignature($signature, $payload)) { 90 | return false; 91 | } 92 | 93 | $this->data = json_decode($payload,true); 94 | $this->event = $event; 95 | $this->delivery = $delivery; 96 | return true; 97 | } 98 | 99 | protected function validateSignature($gitHubSignatureHeader, $payload) 100 | { 101 | list ($algo, $gitHubSignature) = explode("=", $gitHubSignatureHeader); 102 | 103 | if ($algo !== 'sha1') { 104 | // see https://developer.github.com/webhooks/securing/ 105 | return false; 106 | } 107 | 108 | $payloadHash = hash_hmac($algo, $payload, $this->secret); 109 | return ($payloadHash === $gitHubSignature); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP GitHub webhook package 2 | ========================== 3 | 4 | ## Overview ## 5 | 6 | This package contains classes that allow keeping up to date git repository by 7 | performing `git pull` on every `git push` to your GitHub repository. To achieve 8 | this you have to add simple script to your git repository and configure webhook 9 | on your GitHub repository. 10 | 11 | ## Webhook script ## 12 | 13 | For a webhook script to work you have to do 3 things: 14 | 1. Add this package to your composer dependencies 15 | 2. Add a simple PHP script that will actually handle calls to webhook 16 | 3. Configure your GitHub repository webhook to be called every time commits are 17 | pushed to GitHub 18 | 19 | ### Composer ### 20 | 21 | To add dependency, simply edit your `composer.json` and add to your `require` 22 | block following dependency - `"dintel/php-github-webhook": "0.1.*"`. This is the 23 | simplest way to bring php-github-webhook libraries into your project. 24 | 25 | ### Webhook script ### 26 | 27 | To actually handle webhook calls, you have to add PHP script that will be 28 | accessible publicly and it will run `git pull` every time GitHub calls it. 29 | A simplest example of such webhook script: 30 | ```php 31 | ", __DIR__); 37 | if($handler->handle()) { 38 | echo "OK"; 39 | } else { 40 | echo "Wrong secret"; 41 | } 42 | ``` 43 | 44 | In the script above `` should be some random string you choose and 45 | it should be later supplied to GitHub when defining webhook. For more 46 | information about secrets and how they are used in GitHub webhook read 47 | [Webhooks | GitHub API](https://developer.github.com/webhooks/). 48 | 49 | NOTE: Since this script has sensitive data in it (`secret` that is used to 50 | validate requests), it is advised to not put that script under git control by 51 | excluding it in `.gitignore` file. Another option is to take `secret` from some 52 | environment variable that would be defined by other means (like `SetEnv` in 53 | apache configuration). 54 | 55 | ### GitHub repository configuration ### 56 | 57 | To set up a repository webhook on GitHub, head over to the **Settings** page of your 58 | repository, and click on **Webhooks & services**. After that, click on **Add webhook**. 59 | 60 | Fill in following values in form: 61 | * **Payload URL** - Enter full URL to your webhook script 62 | * **Content type** - Can be either "application/json" or "application/x-www-form-urlencoded" 63 | * **Secret** - Same secret you pass to constructor of `Handler` object 64 | * Webhook should receive only push events and of course be active 65 | 66 | Click **Add webhook** button and that's it. 67 | 68 | ## Classes ## 69 | 70 | ### Handler ### 71 | 72 | Handler class actually handles webhook calls. It first checks GitHub signature 73 | and then executes `git pull` if signature and secret match. 74 | 75 | Here is a complete list of methods: 76 | * **\_\_construct($secret, $gitDir, $remote = null)** - Constructor. Constructs new 77 | webhook handler that will verify that requests coming to it are signed with 78 | `$secret`. `$gitDir` must be set to path to git repo that must be updated. 79 | Optional `$remote` specifies which remote should be pulled. 80 | * **getData()** - Getter. After successful validation returns parsed array of data 81 | in payload. Otherwise returns `null`. 82 | * **getDelivery()** - Getter. After successful validation returns unique delivery 83 | number coming from GitHub. Otherwise returns `null`. 84 | * **getEvent** - Getter. After successful validation returns name of event that 85 | triggered this webhook. Otherwise returns `null`. 86 | * **getGitDir()** - Getter. Returns `$gitDir` that was passed to constructor. 87 | * **getGitOutput** - Getter. After successful validation returns output of git 88 | as array of lines. Otherwise returns `null`. 89 | * **getRemote()** - Getter. Returns `$remote` that was passed to constructor. 90 | * **getSecret()** - Getter. Returns `$secret` that was passed to constructor. 91 | * **handle()** - Handle the request. Validates that incoming request is signed 92 | correctly with `$secret` and executes `git pull` upon successful validation. 93 | Returns `true` on succes or `false` if validation failed. 94 | * **validate()** - Validate request only. Returns boolean that indicates whether 95 | the request is correctly signed by `$secret`. 96 | --------------------------------------------------------------------------------