Passed
Push — master ( 8b12f2...70edcf )
by Romain
46s
created

Webhook   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 6

Test Coverage

Coverage 92.19%

Importance

Changes 0
Metric Value
wmc 27
lcom 3
cbo 6
dl 0
loc 217
ccs 59
cts 64
cp 0.9219
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A getInstance() 0 8 2
A challenge() 0 6 1
A isValidToken() 0 13 4
A subscribe() 0 7 1
A isValidCallback() 0 13 3
A getBody() 0 8 2
A getDecodedBody() 0 13 4
A getCallbackEntries() 0 4 1
A getCallbackEvents() 0 10 2
A getHydratedEntries() 0 15 3
A isValidHubSignature() 0 13 2
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
     * @var null|\Kerox\Messenger\Api\Webhook
16
     */
17
    private static $_instance;
18
19
    /**
20
     * @var string
21
     */
22
    protected $appSecret;
23
24
    /**
25
     * @var string
26
     */
27
    protected $verifyToken;
28
29
    /**
30
     * @var \Psr\Http\Message\ServerRequestInterface
31
     */
32
    protected $request;
33
34
    /**
35
     * @var string
36
     */
37
    protected $body;
38
39
    /**
40
     * @var array
41
     */
42
    protected $decodedBody;
43
44
    /**
45
     * @var \Kerox\Messenger\Model\Entry[]
46
     */
47
    protected $hydratedEntries;
48
49
    /**
50
     * Webhook constructor.
51
     *
52
     * @param string                                   $appSecret
53
     * @param string                                   $verifyToken
54
     * @param string                                   $pageToken
55
     * @param \GuzzleHttp\ClientInterface              $client
56
     * @param \Psr\Http\Message\ServerRequestInterface $request
57
     */
58 7
    public function __construct(string $appSecret, string $verifyToken, string $pageToken, ClientInterface $client, ServerRequestInterface $request = null)
59
    {
60 7
        parent::__construct($pageToken, $client);
61
62 7
        $this->appSecret = $appSecret;
63 7
        $this->verifyToken = $verifyToken;
64 7
        $this->request = $request ?: ServerRequest::fromGlobals();
65 7
    }
66
67
    /**
68
     * @param string                                   $appSecret
69
     * @param string                                   $verifyToken
70
     * @param string                                   $pageToken
71
     * @param \GuzzleHttp\ClientInterface              $client
72
     * @param \Psr\Http\Message\ServerRequestInterface $request
73
     *
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 self($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
     * @return \Kerox\Messenger\Response\WebhookResponse
114
     */
115 1
    public function subscribe(): WebhookResponse
116
    {
117 1
        $request = new WebhookRequest($this->pageToken);
118 1
        $response = $this->client->post('me/subscribed_apps', $request->build());
119
120 1
        return new WebhookResponse($response);
121
    }
122
123
    /**
124
     * @return bool
125
     */
126 1
    public function isValidCallback(): bool
127
    {
128 1
        if (!$this->isValidHubSignature()) {
129
            return false;
130
        }
131
132 1
        $decodedBody = $this->getDecodedBody();
133
134 1
        $object = $decodedBody['object'] ?? null;
135 1
        $entry = $decodedBody['entry'] ?? null;
136
137 1
        return $object === 'page' && $entry !== null;
138
    }
139
140
    /**
141
     * @return string
142
     */
143 4
    public function getBody(): string
144
    {
145 4
        if ($this->body === null) {
146 4
            $this->body = (string) $this->request->getBody();
147
        }
148
149 4
        return $this->body;
150
    }
151
152
    /**
153
     * @throws \Exception
154
     *
155
     * @return array
156
     */
157 4
    public function getDecodedBody(): array
158
    {
159 4
        if ($this->decodedBody === null) {
160 4
            $decodedBody = json_decode($this->getBody(), true);
161 4
            if (json_last_error() !== JSON_ERROR_NONE || $decodedBody === null) {
162
                $decodedBody = [];
163
            }
164
165 4
            $this->decodedBody = $decodedBody;
166
        }
167
168 4
        return $this->decodedBody;
169
    }
170
171
    /**
172
     * @return \Kerox\Messenger\Model\Entry[]
173
     */
174 1
    public function getCallbackEntries(): array
175
    {
176 1
        return $this->getHydratedEntries();
177
    }
178
179
    /**
180
     * @return array
181
     */
182 1
    public function getCallbackEvents(): array
183
    {
184 1
        $events = [];
185 1
        foreach ($this->getHydratedEntries() as $hydratedEntry) {
186
            /** @var \Kerox\Messenger\Model\Callback\Entry $hydratedEntry */
187 1
            $events = array_merge($events, $hydratedEntry->getEvents());
188
        }
189
190 1
        return $events;
191
    }
192
193
    /**
194
     * @return \Kerox\Messenger\Model\Callback\Entry[]
195
     */
196 2
    private function getHydratedEntries(): array
197
    {
198 2
        if ($this->hydratedEntries === null) {
199 2
            $decodedBody = $this->getDecodedBody();
200
201 2
            $hydrated = [];
202 2
            foreach ($decodedBody['entry'] as $entry) {
203 2
                $hydrated[] = Entry::create($entry);
204
            }
205
206 2
            $this->hydratedEntries = $hydrated;
207
        }
208
209 2
        return $this->hydratedEntries;
210
    }
211
212
    /**
213
     * @return bool
214
     */
215 1
    private function isValidHubSignature(): bool
216
    {
217 1
        $headers = $this->request->getHeader('X-Hub-Signature');
218 1
        $content = $this->getBody();
219
220 1
        if (empty($headers)) {
221
            return false;
222
        }
223
224 1
        list($algorithm, $hash) = explode('=', $headers[0]);
225
226 1
        return hash_equals(hash_hmac($algorithm, $content, $this->appSecret), $hash);
227
    }
228
}
229