Completed
Push — master ( 8a2b62...c4ab4e )
by Sebastien
02:08
created

Webhook   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 14
c 9
b 0
f 0
lcom 1
cbo 3
dl 0
loc 182
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getEventClassName() 0 6 1
A validateSignature() 0 7 1
B parseRequest() 0 18 5
B getDefaultEventNames() 0 31 1
A getEventMap() 0 21 2
B checkSecurity() 0 25 3
1
<?php
2
3
namespace Smalot\Github\Webhook;
4
5
use Smalot\Github\Webhook\Event\EventBase;
6
use Symfony\Component\EventDispatcher\EventDispatcher;
7
use Symfony\Component\HttpFoundation\Request;
8
9
/**
10
 * Class Webhook
11
 * @package Smalot\Github\Webhook
12
 */
13
class Webhook
14
{
15
    /**
16
     * @var EventDispatcher
17
     */
18
    protected $eventDispatcher;
19
20
    /**
21
     * @var string
22
     */
23
    protected $eventName;
24
25
    /**
26
     * @var string
27
     */
28
    protected $payload;
29
30
    /**
31
     * @var string
32
     */
33
    protected $delivery;
34
35
    /**
36
     * @var array
37
     */
38
    protected $eventMap;
39
40
    /**
41
     * Webhook constructor.
42
     * @param EventDispatcher $eventDispatcher
43
     */
44
    public function __construct(EventDispatcher $eventDispatcher = null)
45
    {
46
        $this->eventDispatcher = $eventDispatcher;
47
        $this->eventMap = array();
48
    }
49
50
    /**
51
     * @param string $event
52
     * @return string
53
     */
54
    public function getEventClassName($event)
55
    {
56
        $map = $this->getEventMap();
57
58
        return $map[$event];
59
    }
60
61
    /**
62
     * @return array
63
     */
64
    public function getDefaultEventNames()
65
    {
66
        return array(
67
            'commit_comment',
68
            'create',
69
            'delete',
70
            'deployment',
71
            'deployment_status',
72
            'download',
73
            'follow',
74
            'fork',
75
            'fork_apply',
76
            'gist',
77
            'gollum',
78
            'issue_comment',
79
            'issues',
80
            'member',
81
            'membership',
82
            'page_build',
83
            'public',
84
            'ping',
85
            'pull_request',
86
            'pull_request_review_comment',
87
            'push',
88
            'release',
89
            'repository',
90
            'status',
91
            'team_add',
92
            'watch',
93
        );
94
    }
95
96
    /**
97
     * @return array
98
     */
99
    public function getEventMap()
100
    {
101
        if (empty($this->eventMap)) {
102
            $this->eventMap = array();
103
            $namespace = '\\Smalot\\Github\\Webhook\\Event\\';
104
            $eventNames = $this->getDefaultEventNames();
105
106
            $classNames = array_map(
107
                function ($event) use ($namespace) {
108
                    $className = str_replace(' ', '', ucwords(str_replace('_', ' ', $event)));
109
110
                    return $namespace . $className . 'Event';
111
                },
112
                $eventNames
113
            );
114
115
            $this->eventMap = array_combine($eventNames, $classNames);
116
        }
117
118
        return $this->eventMap;
119
    }
120
121
    /**
122
     * @param Request $request
123
     * @param string $secret
124
     * @param bool $dispatch
125
     * @return EventBase
126
     *
127
     * @throws \InvalidArgumentException
128
     */
129
    public function parseRequest(Request $request, $secret, $dispatch = true)
130
    {
131
        if (!$this->checkSecurity($request, $secret)) {
132
            throw new \InvalidArgumentException('Invalid security checksum header.');
133
        }
134
135
        if ($className = $this->getEventClassName($this->eventName)) {
136
            $event = new $className($this->eventName, $this->payload, $this->delivery);
137
        } else {
138
            throw new \InvalidArgumentException('Unknown event type.');
139
        }
140
141
        if (null !== $this->eventDispatcher && $dispatch) {
142
            $this->eventDispatcher->dispatch(Events::WEBHOOK_REQUEST, $event);
143
        }
144
145
        return $event;
146
    }
147
148
    /**
149
     * @param Request $request
150
     * @param string $secret
151
     * @return bool
152
     *
153
     * @throws \InvalidArgumentException
154
     */
155
    protected function checkSecurity(Request $request, $secret)
156
    {
157
        // Reset any previously payload set.
158
        $this->eventName = $this->payload = $this->delivery = null;
159
160
        // Extract Github headers from request.
161
        $signature = (string)$request->headers->get('X-Hub-Signature');
162
        $event = (string)$request->headers->get('X-Github-Event');
163
        $delivery = (string)$request->headers->get('X-Github-Delivery');
164
        $payload = (string)$request->getContent();
165
166
        if (!isset($signature, $event, $delivery)) {
167
            throw new \InvalidArgumentException('Missing Github headers.');
168
        }
169
170
        if ($this->validateSignature($secret, $signature, $payload)) {
171
            $this->eventName = $event;
172
            $this->payload = $payload;
173
            $this->delivery = $delivery;
174
175
            return true;
176
        }
177
178
        return false;
179
    }
180
181
    /**
182
     * @param string $secret
183
     * @param string $signatureHeader
184
     * @param string $payload
185
     * @return bool
186
     */
187
    protected function validateSignature($secret, $signatureHeader, $payload)
188
    {
189
        list ($algo, $gitHubSignature) = explode('=', $signatureHeader);
190
        $payloadHash = hash_hmac($algo, $payload, $secret);
191
192
        return ($payloadHash == $gitHubSignature);
193
    }
194
}
195