Webhook   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 13
Bugs 2 Features 1
Metric Value
wmc 18
c 13
b 2
f 1
lcom 1
cbo 3
dl 0
loc 198
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getEventClassName() 0 6 1
B getDefaultEventNames() 0 31 1
A getEventMap() 0 21 2
A isValidRequest() 0 10 2
B parseRequest() 0 18 5
B checkSecurity() 0 25 5
A validateSignature() 0 7 1
1
<?php
2
3
namespace Smalot\Github\Webhook;
4
5
use Smalot\Github\Webhook\Event\EventBase;
6
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
7
use Symfony\Component\HttpFoundation\Request;
8
9
/**
10
 * Class Webhook
11
 * @package Smalot\Github\Webhook
12
 */
13
class Webhook
14
{
15
    /**
16
     * @var EventDispatcherInterface
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 EventDispatcherInterface $eventDispatcher
43
     */
44
    public function __construct(EventDispatcherInterface $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
     * @return bool
125
     */
126
    public function isValidRequest(Request $request, $secret)
127
    {
128
        try {
129
            $valid = $this->checkSecurity($request, $secret);
130
        } catch (\Exception $e) {
131
            return false;
132
        }
133
134
        return $valid;
135
    }
136
137
    /**
138
     * @param Request $request
139
     * @param string $secret
140
     * @param bool $dispatch
141
     * @return EventBase
142
     *
143
     * @throws \InvalidArgumentException
144
     */
145
    public function parseRequest(Request $request, $secret, $dispatch = true)
146
    {
147
        if (!$this->checkSecurity($request, $secret)) {
148
            throw new \InvalidArgumentException('Invalid security checksum header.');
149
        }
150
151
        if ($className = $this->getEventClassName($this->eventName)) {
152
            $event = new $className($this->eventName, $this->payload, $this->delivery);
153
        } else {
154
            throw new \InvalidArgumentException('Unknown event type.');
155
        }
156
157
        if (null !== $this->eventDispatcher && $dispatch) {
158
            $this->eventDispatcher->dispatch(Events::WEBHOOK_REQUEST, $event);
159
        }
160
161
        return $event;
162
    }
163
164
    /**
165
     * @param Request $request
166
     * @param string $secret
167
     * @return bool
168
     *
169
     * @throws \InvalidArgumentException
170
     */
171
    protected function checkSecurity(Request $request, $secret)
172
    {
173
        // Reset any previously payload set.
174
        $this->eventName = $this->payload = $this->delivery = null;
175
176
        // Extract Github headers from request.
177
        $signature = (string) $request->headers->get('X-Hub-Signature');
178
        $event = (string) $request->headers->get('X-Github-Event');
179
        $delivery = (string) $request->headers->get('X-Github-Delivery');
180
        $payload = (string) $request->getContent();
181
182
        if (empty($signature) || empty($event) || empty($delivery)) {
183
            throw new \InvalidArgumentException('Missing Github headers.');
184
        }
185
186
        if ($this->validateSignature($secret, $signature, $payload)) {
187
            $this->eventName = $event;
188
            $this->payload = $payload;
189
            $this->delivery = $delivery;
190
191
            return true;
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * @param string $secret
199
     * @param string $signatureHeader
200
     * @param string $payload
201
     * @return bool
202
     */
203
    protected function validateSignature($secret, $signatureHeader, $payload)
204
    {
205
        list ($algo, $gitHubSignature) = explode('=', $signatureHeader);
206
        $payloadHash = hash_hmac($algo, $payload, $secret);
207
208
        return ($payloadHash == $gitHubSignature);
209
    }
210
}
211