Payload::parse()   B
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 0
1
<?php
2
namespace FlexyProject\GitHub\Event;
3
4
use Exception;
5
use FlexyProject\GitHub\Exception\BadSignatureException;
6
use FlexyProject\GitHub\WebHook;
7
use Symfony\Component\HttpFoundation\ServerBag;
8
9
/**
10
 * Payloads
11
 *
12
 * @link    https://developer.github.com/webhooks/#payloads
13
 * @package FlexyProject\GitHub\Event
14
 */
15
class Payload implements EventInterface
16
{
17
    /** @var WebHook */
18
    protected $webHook;
19
20
    /** @var null|string */
21
    protected $secret = null;
22
23
    /** @var string|resource */
24
    protected $rawData;
25
26
    /** @var mixed */
27
    protected $parsedData;
28
29
    /** @var ServerBag */
30
    protected $serverBag;
31
32
    /**
33
     * Constructor, pass a WebHook object
34
     *
35
     * @param WebHook $webHook
36
     */
37
    public function __construct(WebHook $webHook)
38
    {
39
        $this->setWebHook($webHook);
40
        $request = $webHook->getRequest();
41
        $this->setRawData($request->getContent());
42
        $this->serverBag = $request->server;
43
    }
44
45
    /**
46
     * Get webHook
47
     *
48
     * @return null|WebHook
49
     */
50
    public function getWebHook()
51
    {
52
        return $this->webHook;
53
    }
54
55
    /**
56
     * Set webHook
57
     *
58
     * @param mixed $webHook
59
     *
60
     * @return Payload
61
     */
62
    public function setWebHook($webHook): Payload
63
    {
64
        $this->webHook = $webHook;
65
66
        return $this;
67
    }
68
69
    /**
70
     * Set secret, encode this secret with Hmac, SHA1 method
71
     *
72
     * @param string $secret
73
     *
74
     * @return Payload
75
     */
76
    public function setSecret(string $secret): Payload
77
    {
78
        $this->secret = hash_hmac('sha1', $this->rawData, $secret);
79
80
        return $this;
81
    }
82
83
    /**
84
     * Get secret
85
     *
86
     * @return null|string
87
     */
88
    public function getSecret()
89
    {
90
        return $this->secret;
91
    }
92
93
    /**
94
     * Get rawData
95
     *
96
     * @return resource|string
97
     */
98
    public function getRawData()
99
    {
100
        return $this->rawData;
101
    }
102
103
    /**
104
     * Set rawData
105
     *
106
     * @param resource|string $rawData
107
     *
108
     * @return Payload
109
     */
110
    public function setRawData($rawData): Payload
111
    {
112
        $this->rawData = $rawData;
113
114
        return $this;
115
    }
116
117
    /**
118
     * Get parsedData
119
     *
120
     * @return mixed
121
     */
122
    public function getData()
123
    {
124
        return $this->parsedData;
125
    }
126
127
    /**
128
     * Set parsedData
129
     *
130
     * @param mixed $parsedData
131
     *
132
     * @return Payload
133
     */
134
    protected function setParsedData($parsedData): Payload
135
    {
136
        $data = json_decode($parsedData);
137
        if (JSON_ERROR_NONE === json_last_error()) {
138
            $this->parsedData = $data;
139
        }
140
141
        return $this;
142
    }
143
144
    /**
145
     * Debugger
146
     *
147
     * @return array
148
     */
149
    public function __debugInfo(): array
150
    {
151
        return [
152
            'ramData'     => (array)$this->getRawData(),
153
            'jsonEncoded' => json_decode($this->getRawData())
154
        ];
155
    }
156
157
    /**
158
     * Parse raw data
159
     *
160
     * @return Payload
161
     * @throws BadSignatureException
162
     * @throws \Exception
163
     */
164
    public function parse(): Payload
165
    {
166
        /** Check signature from header */
167
        if (!$this->_checkSignature()) {
168
            throw new BadSignatureException('Hook secret does not match.');
169
        }
170
171
        /** Get data from different locations according to content-type */
172
        switch ($this->serverBag->get('CONTENT_TYPE')) {
173
            case 'application/json':
174
                $data = $this->getRawData();
175
                break;
176
177
            case 'application/x-www-form-urlencoded':
178
                $data = $_POST['payload'];
179
                break;
180
181
            default:
182
                throw new Exception('Unsupported content type: "' . $this->serverBag->get('CONTENT_TYPE') . '"');
183
        }
184
        $this->setParsedData($data);
185
186
        return $this;
187
    }
188
189
    /**
190
     * Check X-Hub-Signature
191
     *
192
     * @throws BadSignatureException
193
     * @return bool
194
     */
195
    private function _checkSignature(): bool
196
    {
197
        $secret           = $this->getSecret();
198
        $httpHubSignature = $this->serverBag->get('HTTP_X_HUB_SIGNATURE');
199
200
        if (null !== $secret) {
201
            if ($httpHubSignature) {
202
                /**
203
                 * Split signature into algorithm and hash
204
                 *
205
                 * @link http://isometriks.com/verify-github-webhooks-with-php
206
                 */
207
                list(, $hash) = explode('=', $httpHubSignature, 2);
208
209
                return $secret == $hash;
210
            }
211
212
            throw new BadSignatureException('HTTP header "X-Hub-Signature" is missing.');
213
        }
214
215
        return true;
216
    }
217
}