Completed
Pull Request — master (#51)
by Romain
02:30 queued 18s
created

Webhook::isValidHubSignature()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 6
cts 7
cp 0.8571
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 0
crap 2.0116
1
<?php
2
3
namespace Kerox\Messenger\Api;
4
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Psr7\ServerRequest;
7
use Kerox\Messenger\Model\Callback\Entry;
8
use Kerox\Messenger\Request\WebhookRequest;
9
use Kerox\Messenger\Response\WebhookResponse;
10
use Psr\Http\Message\ServerRequestInterface;
11
12
class Webhook extends AbstractApi
13
{
14
15
    /**
16
     * @var null|\Kerox\Messenger\Api\Webhook
17
     */
18
    private static $_instance;
19
20
    /**
21
     * @var string
22
     */
23
    protected $appSecret;
24
25
    /**
26
     * @var string
27
     */
28
    protected $verifyToken;
29
30
    /**
31
     * @var \Psr\Http\Message\ServerRequestInterface
32
     */
33
    protected $request;
34
35
    /**
36
     * @var string
37
     */
38
    protected $body;
39
40
    /**
41
     * @var array
42
     */
43
    protected $decodedBody;
44
45
    /**
46
     * @var \Kerox\Messenger\Model\Entry[]
47
     */
48
    protected $hydratedEntries;
49
50
    /**
51
     * Webhook constructor.
52
     *
53
     * @param string $appSecret
54
     * @param string $verifyToken
55
     * @param string $pageToken
56
     * @param \GuzzleHttp\ClientInterface $client
57
     * @param \Psr\Http\Message\ServerRequestInterface $request
58
     */
59 7
    public function __construct(string $appSecret, string $verifyToken, string $pageToken, ClientInterface $client, ServerRequestInterface $request = null)
60
    {
61 7
        parent::__construct($pageToken, $client);
62
63 7
        $this->appSecret = $appSecret;
64 7
        $this->verifyToken = $verifyToken;
65 7
        $this->request = $request ?: ServerRequest::fromGlobals();
66 7
    }
67
68
    /**
69
     * @param string $appSecret
70
     * @param string $verifyToken
71
     * @param string $pageToken
72
     * @param \GuzzleHttp\ClientInterface $client
73
     * @param \Psr\Http\Message\ServerRequestInterface $request
74
     * @return \Kerox\Messenger\Api\Webhook
75
     */
76 1
    public static function getInstance(string $appSecret, string $verifyToken, string $pageToken, ClientInterface $client, ServerRequestInterface $request = null): Webhook
77
    {
78 1
        if (self::$_instance === null) {
79 1
            self::$_instance = new Webhook($appSecret, $verifyToken, $pageToken, $client, $request);
80
        }
81
82 1
        return self::$_instance;
83
    }
84
85
    /**
86
     * @return bool
87
     */
88 1
    public function isValidToken(): bool
89
    {
90 1
        if ($this->request->getMethod() !== 'GET') {
91
            return false;
92
        }
93
94 1
        $params = $this->request->getQueryParams();
95 1
        if (!isset($params['hub_verify_token'])) {
96
            return false;
97
        }
98
99 1
        return ($params['hub_mode'] === 'subscribe' && $params['hub_verify_token'] === $this->verifyToken);
100
    }
101
102
    /**
103
     * @return string|null
104
     */
105 1
    public function challenge()
106
    {
107 1
        $params = $this->request->getQueryParams();
108
109 1
        return $params['hub_challenge'] ?? null;
110
    }
111
112
    /**
113
     * @deprecated since 1.2.0 and will be removed in 1.3.0.
114
     * @see challenge()
115
     * @return null|string
116
     */
117
    public function getChallenge()
118
    {
119
        return $this->challenge();
120
    }
121
122
    /**
123
     * @return \Kerox\Messenger\Response\WebhookResponse
124
     */
125 1
    public function subscribe(): WebhookResponse
126
    {
127 1
        $request = new WebhookRequest($this->pageToken);
128 1
        $response = $this->client->post('me/subscribed_apps', $request->build());
129
130 1
        return new WebhookResponse($response);
131
    }
132
133
    /**
134
     * @deprecated since 1.2.0 and will be removed in 1.3.0.
135
     * @see subscribe()
136
     * @return \Kerox\Messenger\Response\WebhookResponse
137
     */
138
    public function sendSubscribe(): WebhookResponse
139
    {
140
        return $this->subscribe();
141
    }
142
143
    /**
144
     * @return bool
145
     */
146 1
    public function isValidCallback(): bool
147
    {
148 1
        if (!$this->isValidHubSignature()) {
149
            return false;
150
        }
151
152 1
        $decodedBody = $this->getDecodedBody();
153
154 1
        $object = $decodedBody['object'] ?? null;
155 1
        $entry = $decodedBody['entry'] ?? null;
156
157 1
        return ($object === 'page' && $entry !== null);
158
    }
159
160
    /**
161
     * @return string
162
     */
163 4
    public function getBody(): string
164
    {
165 4
        if ($this->body === null) {
166 4
            $this->body = (string) $this->request->getBody();
167
        }
168
169 4
        return $this->body;
170
    }
171
172
    /**
173
     * @return array
174
     * @throws \Exception
175
     */
176 4
    public function getDecodedBody(): array
177
    {
178 4
        if ($this->decodedBody === null) {
179 4
            $decodedBody = json_decode($this->getBody(), true);
180 4
            if (json_last_error() !== JSON_ERROR_NONE || $decodedBody === null) {
181
                $decodedBody = [];
182
            }
183
184 4
            $this->decodedBody = $decodedBody;
185
        }
186
187 4
        return $this->decodedBody;
188
    }
189
190
    /**
191
     * @return \Kerox\Messenger\Model\Entry[]
192
     */
193 1
    public function getCallbackEntries(): array
194
    {
195 1
        return $this->getHydratedEntries();
196
    }
197
198
    /**
199
     * @return array
200
     */
201 1
    public function getCallbackEvents(): array
202
    {
203 1
        $events = [];
204 1
        foreach ($this->getHydratedEntries() as $hydratedEntry) {
205
            /** @var \Kerox\Messenger\Model\Callback\Entry $hydratedEntry */
206 1
            $events = array_merge($events, $hydratedEntry->getEvents());
207
        }
208
209 1
        return $events;
210
    }
211
212
    /**
213
     * @return \Kerox\Messenger\Model\Callback\Entry[]
214
     */
215 2
    private function getHydratedEntries(): array
216
    {
217 2
        if ($this->hydratedEntries === null) {
218 2
            $decodedBody = $this->getDecodedBody();
219
220 2
            $hydrated = [];
221 2
            foreach ($decodedBody['entry'] as $entry) {
222 2
                $hydrated[] = Entry::create($entry);
223
            }
224
225 2
            $this->hydratedEntries = $hydrated;
226
        }
227
228 2
        return $this->hydratedEntries;
229
    }
230
231
    /**
232
     * @return bool
233
     */
234 1
    private function isValidHubSignature(): bool
235
    {
236 1
        $headers = $this->request->getHeader('X-Hub-Signature');
237 1
        $content = $this->getBody();
238
239 1
        if (empty($headers)) {
240
            return false;
241
        }
242
243 1
        list($algorithm, $hash) = explode('=', $headers[0]);
244
245 1
        return hash_equals(hash_hmac($algorithm, $content, $this->appSecret), $hash);
246
    }
247
}
248