Issues (58)

Controller/MailgunWebhookController.php (11 issues)

1
<?php
2
3
namespace Azine\MailgunWebhooksBundle\Controller;
4
5
use Azine\MailgunWebhooksBundle\DependencyInjection\AzineMailgunWebhooksExtension;
6
use Azine\MailgunWebhooksBundle\Entity\MailgunAttachment;
7
use Azine\MailgunWebhooksBundle\Entity\MailgunCustomVariable;
8
use Azine\MailgunWebhooksBundle\Entity\MailgunEvent;
9
use Azine\MailgunWebhooksBundle\Entity\MailgunMessageSummary;
10
use Azine\MailgunWebhooksBundle\Entity\MailgunWebhookEvent;
11
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
12
use Symfony\Component\HttpFoundation\File\UploadedFile;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\HttpFoundation\Response;
15
16
/**
17
 * MailgunEvent controller.
18
 */
19
class MailgunWebhookController extends AbstractController
20
{
21
    public function createFromWebhookAction(Request $request)
22
    {
23
        // old webhooks api
24
        $params = $request->request->all();
25
26
        if (is_array($params) && !empty($params)) {
27
            $this->get('logger')->info('Creating MailgunEvent via old API.');
0 ignored issues
show
The method info() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

27
            $this->get('logger')->/** @scrutinizer ignore-call */ info('Creating MailgunEvent via old API.');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
28
29
            return $this->createEventOldApi($params);
30
        }
31
        // new webhooks api
32
        $this->get('logger')->info('Creating MailgunEvent via new API.');
33
        $params = json_decode($request->getContent(), true);
34
35
        return $this->createEventNewApi($params);
36
    }
37
38
    private function createEventNewApi($paramsPre)
39
    {
40
        $params = array_change_key_case($paramsPre, CASE_LOWER);
41
42
        if (sizeof($params) != sizeof($paramsPre)) {
43
            $params['params_contained_duplicate_keys'] = $paramsPre;
44
        }
45
46
        /////////////////////////////////////////////////////
47
        // signature validation
48
        $signatureData = $params['signature'];
49
        $eventData = $params['event-data'];
50
51
        // check if the timestamp is fresh
52
        $timestamp = $signatureData['timestamp'];
53
        $tsAge = abs(time() - $timestamp);
54
        if ($tsAge > 15) {
55
            return new Response('Signature verification failed. Timestamp too old abs('.time()." - $timestamp) = $tsAge", 401);
56
        }
57
58
        // validate post-data
59
        $key = $this->container->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::API_KEY);
0 ignored issues
show
The method getParameter() does not exist on Psr\Container\ContainerInterface. It seems like you code against a sub-type of Psr\Container\ContainerInterface such as Symfony\Component\Depend...tion\ContainerInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

59
        /** @scrutinizer ignore-call */ 
60
        $key = $this->container->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::API_KEY);
Loading history...
60
        $token = $signatureData['token'];
61
        $expectedSignature = hash_hmac('SHA256', $timestamp.$token, $key);
62
        if ($expectedSignature != $signatureData['signature']) {
63
            return new Response('Signature verification failed.', 401);
64
        }
65
66
        /////////////////////////////////////////////////////
67
        // create event-entity
68
        try {
69
            // create event & populate with supplied data
70
            $event = new MailgunEvent();
71
72
            // token
73
            if (array_key_exists('token', $signatureData)) {
74
                $event->setToken($signatureData['token']);
75
                unset($signatureData['token']);
76
            }
77
            // timestamp
78
            if (array_key_exists('timestamp', $signatureData)) {
79
                $event->setTimestamp($signatureData['timestamp']);
80
                unset($signatureData['timestamp']);
81
            }
82
            // signature
83
            if (array_key_exists('signature', $signatureData)) {
84
                $event->setSignature($signatureData['signature']);
85
                unset($signatureData['signature']);
86
            }
87
88
            // event
89
            if (array_key_exists('event', $eventData)) {
90
                $event->setEvent($eventData['event']);
91
                unset($eventData['event']);
92
            }
93
            // domain
94
            if (array_key_exists('envelope', $eventData)) {
95
                $envelope = $eventData['envelope'];
96
                $sender = $envelope['sender'];
97
                $event->setDomain(substr($sender, strrpos($sender, '@') + 1));
98
99
                // ip
100
                if (array_key_exists('sending-ip', $envelope)) {
101
                    $event->setIp($envelope['sending-ip']);
102
                    unset($eventData['envelope']['sending-ip']);
103
                }
104
105
                unset($eventData['envelope']['sender']);
106
            }
107
            // description & reason
108
            if (array_key_exists('delivery-status', $eventData)) {
109
                $description = array_key_exists('message', $eventData['delivery-status']) ? $eventData['delivery-status']['message'].' ' : '';
110
                $description .= array_key_exists('description', $eventData['delivery-status']) ? $eventData['delivery-status']['description'] : '';
111
112
                $event->setDescription($description);
113
                unset($eventData['delivery-status']['message'], $eventData['delivery-status']['description']);
114
115
                // delivery status code
116
                if (array_key_exists('code', $eventData['delivery-status'])) {
117
                    $event->setErrorCode($eventData['delivery-status']['code']);
118
                    unset($eventData['delivery-status']['code']);
119
                }
120
            } elseif (array_key_exists('reject', $eventData)) {
121
                $description = array_key_exists('description', $eventData['reject']) ? $eventData['reject']['description'] : '';
122
                $reason = array_key_exists('reason', $eventData['reject']) ? $eventData['reject']['reason'] : '';
123
                $event->setDescription($description);
124
                $event->setReason($reason);
125
                unset($eventData['delivery-status']['description'], $eventData['delivery-status']['reason']);
126
            }
127
            // reason
128
            if (array_key_exists('reason', $eventData)) {
129
                $event->setReason($eventData['reason']);
130
                unset($eventData['reason']);
131
            }
132
            // recipient
133
            if (array_key_exists('recipient', $eventData)) {
134
                $event->setRecipient($eventData['recipient']);
135
                unset($eventData['recipient']);
136
            }
137
            if (array_key_exists('geolocation', $eventData)) {
138
                $geolocation = $eventData['geolocation'];
139
                // country
140
                if (array_key_exists('country', $geolocation)) {
141
                    $event->setCountry($geolocation['country']);
142
                    unset($eventData['geolocation']['country']);
143
                }
144
                // city
145
                if (array_key_exists('city', $geolocation)) {
146
                    $event->setCity($geolocation['city']);
147
                    unset($eventData['geolocation']['city']);
148
                }
149
                // region
150
                if (array_key_exists('region', $geolocation)) {
151
                    $event->setRegion($geolocation['region']);
152
                    unset($eventData['geolocation']['region']);
153
                }
154
            }
155
            if (array_key_exists('client-info', $eventData)) {
156
                $clientInfo = $eventData['client-info'];
157
                // clientName
158
                if (array_key_exists('client-name', $clientInfo)) {
159
                    $event->setClientName($clientInfo['client-name']);
160
                    unset($eventData['client-info']['client-name']);
161
                }
162
                // clientOs
163
                if (array_key_exists('client-os', $clientInfo)) {
164
                    $event->setClientOs($clientInfo['client-os']);
165
                    unset($eventData['client-info']['client-os']);
166
                }
167
                // clientType
168
                if (array_key_exists('client-type', $clientInfo)) {
169
                    $event->setClientType($clientInfo['client-type']);
170
                    unset($eventData['client-info']['client-type']);
171
                }
172
                // deviceType
173
                if (array_key_exists('device-type', $clientInfo)) {
174
                    $event->setDeviceType($clientInfo['device-type']);
175
                    unset($eventData['client-info']['device-type']);
176
                }
177
                // userAgent
178
                if (array_key_exists('user-agent', $clientInfo)) {
179
                    $event->setUserAgent($clientInfo['user-agent']);
180
                    unset($eventData['client-info']['user-agent']);
181
                }
182
            }
183
184
            if (array_key_exists('message', $eventData)) {
185
                $message = $eventData['message'];
186
187
                // messageHeaders
188
                if (array_key_exists('headers', $message)) {
189
                    $headers = $message['headers'];
190
                    // messageId
191
                    if (array_key_exists('message-id', $headers)) {
192
                        $trimmedMessageId = trim(trim($headers['message-id']), '<>');
193
                        $event->setMessageId($trimmedMessageId);
194
195
                        // set message domain from message id
196
                        if (null == $event->getDomain()) {
197
                            $event->setDomain(substr($trimmedMessageId, strrpos($trimmedMessageId, '@') + 1));
198
                        }
199
                    }
200
201
                    // sender
202
                    if (array_key_exists('from', $headers)) {
203
                        $event->setSender($headers['from']);
204
                    }
205
206
                    $event->setMessageHeaders(json_encode($headers));
207
                    unset($eventData['message']['headers']);
208
                }
209
            }
210
            // campaignName && campaignId
211
            if (array_key_exists('campaigns', $eventData)) {
212
                $event->setCampaignName(print_r($eventData['campaigns'], true));
0 ignored issues
show
It seems like print_r($eventData['campaigns'], true) can also be of type true; however, parameter $campaignName of Azine\MailgunWebhooksBun...vent::setCampaignName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

212
                $event->setCampaignName(/** @scrutinizer ignore-type */ print_r($eventData['campaigns'], true));
Loading history...
213
                $event->setCampaignId(print_r($eventData['campaigns'], true));
0 ignored issues
show
It seems like print_r($eventData['campaigns'], true) can also be of type true; however, parameter $campaignId of Azine\MailgunWebhooksBun...nEvent::setCampaignId() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
                $event->setCampaignId(/** @scrutinizer ignore-type */ print_r($eventData['campaigns'], true));
Loading history...
214
                unset($eventData['campaigns']);
215
            }
216
            // tag
217
            if (array_key_exists('tags', $eventData)) {
218
                $event->setTag(print_r($eventData['tags'], true));
0 ignored issues
show
It seems like print_r($eventData['tags'], true) can also be of type true; however, parameter $tag of Azine\MailgunWebhooksBun...\MailgunEvent::setTag() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

218
                $event->setTag(/** @scrutinizer ignore-type */ print_r($eventData['tags'], true));
Loading history...
219
                unset($eventData['tags']);
220
            }
221
            // url
222
            if (array_key_exists('url', $eventData)) {
223
                $event->setUrl($eventData['url']);
224
                unset($eventData['url']);
225
            } elseif (array_key_exists('storage', $eventData)) {
226
                $event->setUrl($eventData['storage']['url']);
227
                unset($eventData['storage']['url']);
228
            }
229
230
            // mailingList
231
            if (array_key_exists('recipients', $eventData)) {
232
                $event->setmailingList(print_r($eventData['recipients'], true));
0 ignored issues
show
It seems like print_r($eventData['recipients'], true) can also be of type true; however, parameter $mailingList of Azine\MailgunWebhooksBun...Event::setMailingList() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

232
                $event->setmailingList(/** @scrutinizer ignore-type */ print_r($eventData['recipients'], true));
Loading history...
233
                unset($eventData['recipients']);
234
            }
235
236
            $manager = $this->container->get('doctrine.orm.entity_manager');
237
            $manager->persist($event);
238
            $eventSummary = $this->getDoctrine()->getRepository(MailgunMessageSummary::class)->createOrUpdateMessageSummary($event);
0 ignored issues
show
The assignment to $eventSummary is dead and can be removed.
Loading history...
239
240
            $eventData = $this->removeEmptyArrayElements($eventData);
241
242
            // process the remaining posted values
243
            foreach ($eventData as $key => $value) {
244
                if (0 === strpos($key, 'attachment-')) {
245
                    // create event attachments
246
                    $attachment = new MailgunAttachment($event);
247
                    $attachment->setCounter(substr($key, 11));
0 ignored issues
show
substr($key, 11) of type string is incompatible with the type integer expected by parameter $counter of Azine\MailgunWebhooksBun...ttachment::setCounter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

247
                    $attachment->setCounter(/** @scrutinizer ignore-type */ substr($key, 11));
Loading history...
248
249
                    // get the file
250
                    /* @var $value UploadedFile */
251
                    $attachment->setContent(file_get_contents($value->getPathname()));
252
                    $attachment->setSize($value->getSize());
253
                    $attachment->setType($value->getMimeType());
254
                    $attachment->setName($value->getFilename());
255
256
                    $manager->persist($attachment);
257
                } else {
258
                    // create custom-variables for event
259
                    $customVar = new MailgunCustomVariable($event);
260
                    $customVar->setVariableName($key);
261
                    $customVar->setContent($value);
262
263
                    $manager->persist($customVar);
264
                }
265
            }
266
267
            // save all entities
268
            $manager->flush();
269
270
            // Dispatch an event about the logging of a Webhook-call
271
            $this->get('event_dispatcher')->dispatch(MailgunEvent::CREATE_EVENT, new MailgunWebhookEvent($event));
272
        } catch (\Exception $e) {
273
            $this->container->get('logger')->warning('AzineMailgunWebhooksBundle: creating entities failed: '.$e->getMessage());
0 ignored issues
show
The method warning() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
            $this->container->get('logger')->/** @scrutinizer ignore-call */ warning('AzineMailgunWebhooksBundle: creating entities failed: '.$e->getMessage());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
274
            $this->container->get('logger')->warning($e->getTraceAsString());
275
276
            return new Response(print_r($params, true).'AzineMailgunWebhooksBundle: creating entities failed: '.$e->getMessage(), 500);
0 ignored issues
show
Are you sure print_r($params, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
            return new Response(/** @scrutinizer ignore-type */ print_r($params, true).'AzineMailgunWebhooksBundle: creating entities failed: '.$e->getMessage(), 500);
Loading history...
277
        }
278
279
        // send response
280
        return new Response('Thanx, for the info.', 200);
281
    }
282
283
    private function createEventOldApi($paramsPre)
284
    {
285
        $params = array_change_key_case($paramsPre, CASE_LOWER);
286
287
        if (sizeof($params) != sizeof($paramsPre)) {
288
            $params['params_contained_duplicate_keys'] = $paramsPre;
289
        }
290
291
        // validate post-data
292
        $key = $this->container->getParameter(AzineMailgunWebhooksExtension::PREFIX.'_'.AzineMailgunWebhooksExtension::API_KEY);
293
        $timestamp = $params['timestamp'];
294
295
        // check if the timestamp is fresh
296
        $now = time();
297
        $tsAge = abs($now - $timestamp);
298
        if ($tsAge > 15) {
299
            return new Response("Signature verification failed. Timestamp too old abs($now - $timestamp)=$tsAge", 401);
300
        }
301
302
        $token = $params['token'];
303
        $expectedSignature = hash_hmac('SHA256', $timestamp.$token, $key);
304
        if ($expectedSignature != $params['signature']) {
305
            return new Response('Signature verification failed.', 401);
306
        }
307
308
        // drop unused variables
309
        if (array_key_exists('x-mailgun-sid', $params)) {
310
            unset($params['x-mailgun-sid']);
311
        }
312
        if (array_key_exists('attachment-count', $params)) {
313
            unset($params['attachment-count']);
314
        }
315
316
        try {
317
            // create event & populate with supplied data
318
            $event = new MailgunEvent();
319
320
            // event
321
            if (array_key_exists('event', $params)) {
322
                $event->setEvent($params['event']);
323
                unset($params['event']);
324
            }
325
326
            // domain
327
            if (array_key_exists('domain', $params)) {
328
                $event->setDomain($params['domain']);
329
                unset($params['domain']);
330
            }
331
            // description
332
            if (array_key_exists('description', $params)) {
333
                $event->setDescription($params['description']);
334
                unset($params['description']);
335
            }
336
            // reason
337
            if (array_key_exists('reason', $params)) {
338
                $event->setReason($params['reason']);
339
                unset($params['reason']);
340
            }
341
            // recipient
342
            if (array_key_exists('recipient', $params)) {
343
                $event->setRecipient($params['recipient']);
344
                unset($params['recipient']);
345
            }
346
            // errorCode
347
            if (array_key_exists('code', $params)) {
348
                $event->setErrorCode($params['code']);
349
                unset($params['code']);
350
            }
351
            // ip
352
            if (array_key_exists('ip', $params)) {
353
                $event->setIp($params['ip']);
354
                unset($params['ip']);
355
            }
356
            // error
357
            if (array_key_exists('error', $params)) {
358
                $event->setDescription($params['error']);
359
                unset($params['error']);
360
            }
361
            // country
362
            if (array_key_exists('country', $params)) {
363
                $event->setCountry($params['country']);
364
                unset($params['country']);
365
            }
366
            // city
367
            if (array_key_exists('city', $params)) {
368
                $event->setCity($params['city']);
369
                unset($params['city']);
370
            }
371
            // region
372
            if (array_key_exists('region', $params)) {
373
                $event->setRegion($params['region']);
374
                unset($params['region']);
375
            }
376
            // campaignId
377
            if (array_key_exists('campaign-id', $params)) {
378
                $event->setCampaignId($params['campaign-id']);
379
                unset($params['campaign-id']);
380
            }
381
            // campaignName	{
382
            if (array_key_exists('campaign-name', $params)) {
383
                $event->setCampaignName($params['campaign-name']);
384
                unset($params['campaign-name']);
385
            }
386
            // clientName
387
            if (array_key_exists('client-name', $params)) {
388
                $event->setClientName($params['client-name']);
389
                unset($params['client-name']);
390
            }
391
            // clientOs
392
            if (array_key_exists('client-os', $params)) {
393
                $event->setClientOs($params['client-os']);
394
                unset($params['client-os']);
395
            }
396
            // clientType
397
            if (array_key_exists('client-type', $params)) {
398
                $event->setClientType($params['client-type']);
399
                unset($params['client-type']);
400
            }
401
            // deviceType
402
            if (array_key_exists('device-type', $params)) {
403
                $event->setDeviceType($params['device-type']);
404
                unset($params['device-type']);
405
            }
406
            // mailingList
407
            if (array_key_exists('mailing-list', $params)) {
408
                $event->setmailingList($params['mailing-list']);
409
                unset($params['mailing-list']);
410
            }
411
            // messageHeaders
412
            if (array_key_exists('message-headers', $params)) {
413
                $headers = json_decode($params['message-headers']);
414
                foreach ($headers as $header) {
415
                    // sender
416
                    if ('Sender' == $header[0]) {
417
                        $event->setSender($header[1]);
418
                    }
419
                }
420
                $event->setMessageHeaders($params['message-headers']);
421
                unset($params['message-headers']);
422
            }
423
            // messageId
424
            if (array_key_exists('message-id', $params)) {
425
                $trimmedMessageId = trim(trim($params['message-id']), '<>');
426
                $event->setMessageId($trimmedMessageId);
427
                unset($params['message-id']);
428
            }
429
            // tag
430
            if (array_key_exists('tag', $params)) {
431
                $event->setTag($params['tag']);
432
                unset($params['tag']);
433
            }
434
            // x-mailgun-tag
435
            if (array_key_exists('x-mailgun-tag', $params)) {
436
                $event->setTag($params['x-mailgun-tag']);
437
                unset($params['x-mailgun-tag']);
438
            }
439
            // userAgent
440
            if (array_key_exists('user-agent', $params)) {
441
                $event->setUserAgent($params['user-agent']);
442
                unset($params['user-agent']);
443
            }
444
            // url
445
            if (array_key_exists('url', $params)) {
446
                $event->setUrl($params['url']);
447
                unset($params['url']);
448
            }
449
            // token
450
            if (array_key_exists('token', $params)) {
451
                $event->setToken($params['token']);
452
                unset($params['token']);
453
            }
454
            // timestamp
455
            if (array_key_exists('timestamp', $params)) {
456
                $event->setTimestamp($params['timestamp']);
457
                unset($params['timestamp']);
458
            }
459
            // signature
460
            if (array_key_exists('signature', $params)) {
461
                $event->setSignature($params['signature']);
462
                unset($params['signature']);
463
            }
464
465
            $manager = $this->container->get('doctrine.orm.entity_manager');
466
            $manager->persist($event);
467
468
            $this->getDoctrine()->getRepository(MailgunMessageSummary::class)->createOrUpdateMessageSummary($event);
469
470
            $params = $this->removeEmptyArrayElements($params);
471
472
            // process the remaining posted values
473
            foreach ($params as $key => $value) {
474
                if (0 === strpos($key, 'attachment-')) {
475
                    // create event attachments
476
                    $attachment = new MailgunAttachment($event);
477
                    $attachment->setCounter(substr($key, 11));
0 ignored issues
show
substr($key, 11) of type string is incompatible with the type integer expected by parameter $counter of Azine\MailgunWebhooksBun...ttachment::setCounter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

477
                    $attachment->setCounter(/** @scrutinizer ignore-type */ substr($key, 11));
Loading history...
478
479
                    // get the file
480
                    /* @var UploadedFile $value */
481
                    $attachment->setContent(file_get_contents($value->getPathname()));
482
                    $attachment->setSize($value->getSize());
483
                    $attachment->setType($value->getMimeType());
484
                    $attachment->setName($value->getFilename());
485
                    $manager->persist($attachment);
486
                } else {
487
                    // create custom-variables for event
488
                    $customVar = new MailgunCustomVariable($event);
489
                    $customVar->setVariableName($key);
490
                    $customVar->setContent($value);
491
                    $manager->persist($customVar);
492
                }
493
            }
494
495
            // save all entities
496
            $manager->flush();
497
498
            // Dispatch an event about the logging of a Webhook-call
499
            $this->get('event_dispatcher')->dispatch(MailgunEvent::CREATE_EVENT, new MailgunWebhookEvent($event));
500
        } catch (\Exception $e) {
501
            $this->container->get('logger')->warning('AzineMailgunWebhooksBundle: creating entities failed: '.$e->getMessage());
502
            $this->container->get('logger')->warning($e->getTraceAsString());
503
504
            return new Response('AzineMailgunWebhooksBundle: creating entities failed: '.$e->getMessage(), 500);
505
        }
506
507
        // send response
508
        return new Response('Thanx, for the info.', 200);
509
    }
510
511
    /**
512
     * @param $haystack
513
     *
514
     * @return array without empty elements (recursively)
515
     */
516
    private function removeEmptyArrayElements($haystack)
517
    {
518
        foreach ($haystack as $key => $value) {
519
            if (is_array($value)) {
520
                $haystack[$key] = $this->removeEmptyArrayElements($haystack[$key]);
521
            }
522
523
            if (empty($haystack[$key])) {
524
                unset($haystack[$key]);
525
            }
526
        }
527
528
        return $haystack;
529
    }
530
}
531