Webhook::getCallbackEvents()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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