Skip to content

Commit

Permalink
Merge pull request #168 from pusher/wh-validation
Browse files Browse the repository at this point in the history
Add pusher signature validation
  • Loading branch information
kn100 authored Jul 4, 2018
2 parents 478044d + 1499d0a commit c13978f
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 4 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ Using presence channels is similar to private channels, but you can specify extr
$pusher->presence_auth('presence-my-channel','socket_id', 'user_id', 'user_info');
```

## Webhooks

This library provides a way of verifying that webhooks you receive from Pusher are actually genuine webhooks from Pusher. It also provides a structure for storing them. A helper method called `webhook` enables this. Pass in the headers and body of the request, and it'll return a Webhook object with your verified events. If the library was unable to validate the signature, an exception is thrown instead.

```php
$webhook = $pusher->webhook($request_headers, $request_body);
$number_of_events = count($webhook->get_events());
$time_recieved = $webhook->get_time_ms();
```


### Presence example

First set this variable in your JS app:
Expand Down
53 changes: 49 additions & 4 deletions src/Pusher.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public function __construct($auth_key, $secret, $app_id, $options = array(), $ho

// ensure host doesn't have a scheme prefix
$this->settings['host'] =
preg_replace('/http[s]?\:\/\//', '', $this->settings['host'], 1);
preg_replace('/http[s]?\:\/\//', '', $this->settings['host'], 1);
}

/**
Expand Down Expand Up @@ -398,9 +398,15 @@ private function ddn_domain()
*
* @return string
*/
public static function build_auth_query_string($auth_key, $auth_secret, $request_method, $request_path,
$query_params = array(), $auth_version = '1.0', $auth_timestamp = null)
{
public static function build_auth_query_string(
$auth_key,
$auth_secret,
$request_method,
$request_path,
$query_params = array(),
$auth_version = '1.0',
$auth_timestamp = null
) {
$params = array();
$params['auth_key'] = $auth_key;
$params['auth_timestamp'] = (is_null($auth_timestamp) ? time() : $auth_timestamp);
Expand Down Expand Up @@ -745,4 +751,43 @@ public function notify($interests, $data = array(), $debug = false)

return false;
}

/**
* Verify that a webhook actually came from Pusher, and marshals them into a Webhook object.
*
* @param array $headers an array of headers from the request (for example, from getallheaders())
* @param string $body the body of the request (for example, from file_get_contents('php://input'))
*
* @return Webhook object with the properties time_ms (an int) and events (an array of event objects)
*/
public function webhook($headers, $body)
{
$this->ensure_valid_signature($headers, $body);
$decoded_json = json_decode($body);
$webhookobj = new Webhook($decoded_json->time_ms, $decoded_json->events);

return $webhookobj;
}

/**
* Verify that a given Pusher Signature is valid.
*
* @param array $headers an array of headers from the request (for example, from getallheaders())
* @param string $body the body of the request (for example, from file_get_contents('php://input'))
*
* @throws PusherException if signature is inccorrect.
*/
public function ensure_valid_signature($headers, $body)
{
$x_pusher_key = $headers['X-Pusher-Key'];
$x_pusher_signature = $headers['X-Pusher-Signature'];
if ($x_pusher_key == $this->settings['auth_key']) {
$expected = hash_hmac('sha256', $body, $this->settings['secret']);
if ($expected === $x_pusher_signature) {
return;
}
}

throw new PusherException(sprintf('Received WebHook with invalid signature: got %s, expected %s.', $x_pusher_signature, $expected));
}
}
25 changes: 25 additions & 0 deletions src/Webhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Pusher;

class Webhook
{
private $time_ms;
private $events = array();

public function __construct($time_ms, $events)
{
$this->time_ms = $time_ms;
$this->events = $events;
}

public function get_events()
{
return $this->events;
}

public function get_time_ms()
{
return $this->time_ms;
}
}
47 changes: 47 additions & 0 deletions tests/unit/webhookTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

class webhookTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
$this->pusher = new Pusher\Pusher('thisisaauthkey', 'thisisasecret', 1, true);
$this->auth_key = 'thisisaauthkey';
}

public function testValidWebhookSignature()
{
$signature = '40e0ad3b9aa49529322879e84de1aaaf18bde1efe839ca263d540cc865510d25';
$body = '{"hello":"world"}';
$headers = array(
'X-Pusher-Key' => $this->auth_key,
'X-Pusher-Signature' => $signature,
);

$this->pusher->ensure_valid_signature($headers, $body);
}

/**
* @expectedException \Pusher\PusherException
*/
public function testInvalidWebhookSignature()
{
$signature = 'potato';
$body = '{"hello":"world"}';
$headers = array(
'X-Pusher-Key' => $this->auth_key,
'X-Pusher-Signature' => $signature,
);
$wrong_signature = $this->pusher->ensure_valid_signature($headers, $body);
}

public function testDecodeWebhook()
{
$headers_json = '{"X-Pusher-Key":"'.$this->auth_key.'","X-Pusher-Signature":"a19cab2af3ca1029257570395e78d5d675e9e700ca676d18a375a7083178df1c"}';
$body = '{"time_ms":1530710011901,"events":[{"name":"client_event","channel":"private-my-channel","event":"client-event","data":"Unencrypted","socket_id":"240621.35780774"}]}';
$headers = json_decode($headers_json, true);

$decodedWebhook = $this->pusher->webhook($headers, $body);
$this->assertEquals($decodedWebhook->get_time_ms(), 1530710011901);
$this->assertEquals(count($decodedWebhook->get_events()), 1);
}
}

0 comments on commit c13978f

Please sign in to comment.