This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace LeKoala\Mailgun; |
||
4 | |||
5 | use Exception; |
||
6 | use Psr\Log\LoggerInterface; |
||
7 | use SilverStripe\Control\Director; |
||
8 | use SilverStripe\Core\Environment; |
||
9 | use SilverStripe\Control\Controller; |
||
10 | use SilverStripe\Control\HTTPRequest; |
||
11 | use SilverStripe\Security\Permission; |
||
12 | use SilverStripe\Core\Injector\Injector; |
||
13 | |||
14 | /** |
||
15 | * Provide extensions points for handling the webhook |
||
16 | * |
||
17 | * @link https://www.mailgun.com/guides/your-guide-to-webhooks/ |
||
18 | * @author LeKoala <[email protected]> |
||
19 | */ |
||
20 | class MailgunController extends Controller |
||
21 | { |
||
22 | const TYPE_CLICKED = 'clicked'; |
||
23 | const TYPE_COMPLAINED = 'complained'; |
||
24 | const TYPE_DELIVERED = 'delivered'; |
||
25 | const TYPE_OPENED = 'opened'; |
||
26 | const TYPE_PERMANENT_FAIL = 'permanent_fail'; |
||
27 | const TYPE_TEMPORARY_FAIL = 'temporary_fail'; |
||
28 | const TYPE_UNSUBSCRIBED = 'unsubscribed'; |
||
29 | |||
30 | protected $eventsCount = 0; |
||
31 | protected $skipCount = 0; |
||
32 | private static $allowed_actions = [ |
||
0 ignored issues
–
show
Comprehensibility
introduced
by
![]() |
|||
33 | 'incoming', |
||
34 | 'test', |
||
35 | 'configure_inbound_emails' |
||
36 | ]; |
||
37 | |||
38 | /** |
||
39 | * Inject public dependencies into the controller |
||
40 | * |
||
41 | * @var array |
||
42 | */ |
||
43 | private static $dependencies = [ |
||
44 | 'logger' => '%$Psr\Log\LoggerInterface', |
||
45 | ]; |
||
46 | |||
47 | /** |
||
48 | * @var Psr\Log\LoggerInterface |
||
49 | */ |
||
50 | public $logger; |
||
51 | |||
52 | public function index(HTTPRequest $req) |
||
0 ignored issues
–
show
|
|||
53 | { |
||
54 | return $this->render([ |
||
55 | 'Title' => 'Mailgun', |
||
56 | 'Content' => 'Please use a dedicated action' |
||
57 | ]); |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * You can also see /resources/webhook.txt |
||
62 | * |
||
63 | * @param HTTPRequest $req |
||
64 | */ |
||
65 | public function test(HTTPRequest $req) |
||
0 ignored issues
–
show
|
|||
66 | { |
||
67 | if (!Director::isDev()) { |
||
68 | return 'You can only test in dev mode'; |
||
69 | } |
||
70 | |||
71 | $file = $this->getRequest()->getVar('file'); |
||
72 | if ($file) { |
||
73 | $data = file_get_contents(Director::baseFolder() . '/' . rtrim($file, '/')); |
||
74 | } else { |
||
75 | $data = file_get_contents(dirname(__DIR__) . '/resources/webhook.txt'); |
||
76 | } |
||
77 | |||
78 | $this->processPayload($data, 'TEST'); |
||
79 | |||
80 | return 'TEST OK - ' . $this->eventsCount . ' events processed / ' . $this->skipCount . ' events skipped'; |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * @link https://support.Mailgun.com/customer/portal/articles/2039614-enabling-inbound-email-relaying-relay-webhooks |
||
85 | * @param HTTPRequest $req |
||
86 | * @return string |
||
87 | */ |
||
88 | public function configure_inbound_emails(HTTPRequest $req) |
||
89 | { |
||
90 | if (!Director::isDev() && !Permission::check('ADMIN')) { |
||
91 | return 'You must be in dev mode or be logged as an admin'; |
||
92 | } |
||
93 | |||
94 | $clearExisting = $req->getVar('clear_existing'); |
||
95 | $clearWebhooks = $req->getVar('clear_webhooks'); |
||
0 ignored issues
–
show
$clearWebhooks is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
96 | $clearInbound = $req->getVar('clear_inbound'); |
||
0 ignored issues
–
show
$clearInbound is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
97 | if ($clearExisting) { |
||
98 | echo '<strong>Existing inbounddomains and relay webhooks will be cleared</strong><br/>'; |
||
99 | } else { |
||
100 | echo 'You can clear existing inbound domains and relay webhooks by passing ?clear_existing=1&clear_webhooks=1&clear_inbound=1<br/>'; |
||
101 | } |
||
102 | |||
103 | $client = MailgunHelper::getClient(); |
||
0 ignored issues
–
show
$client is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
104 | |||
105 | $inbound_domain = Environment::getEnv('MAILGUN_INBOUND_DOMAIN'); |
||
106 | if (!$inbound_domain) { |
||
107 | die('You must define a key MAILGUN_INBOUND_DOMAIN'); |
||
108 | } |
||
109 | |||
110 | //TODO : use routing to implement this |
||
111 | |||
112 | throw new Exception("Not implemented yet"); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Handle incoming webhook |
||
117 | * |
||
118 | * @param HTTPRequest $req |
||
119 | */ |
||
120 | public function incoming(HTTPRequest $req) |
||
121 | { |
||
122 | $batchId = uniqid(); |
||
123 | |||
124 | $json = file_get_contents('php://input'); |
||
125 | |||
126 | // By default, return a valid response |
||
127 | $response = $this->getResponse(); |
||
128 | $response->setStatusCode(200); |
||
129 | $response->setBody('NO DATA'); |
||
130 | |||
131 | if (!$json) { |
||
132 | return $response; |
||
133 | } |
||
134 | |||
135 | $webhookLogDir = Environment::getEnv('MAILGUN_WEBHOOK_LOG_DIR'); |
||
136 | if ($webhookLogDir) { |
||
137 | $dir = rtrim(Director::baseFolder(), '/') . '/' . rtrim($webhookLogDir, '/'); |
||
138 | |||
139 | if (!is_dir($dir) && Director::isDev()) { |
||
140 | mkdir($dir, 0755, true); |
||
141 | } |
||
142 | |||
143 | if (is_dir($dir)) { |
||
144 | $payload['@headers'] = $req->getHeaders(); |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$payload was never initialized. Although not strictly required by PHP, it is generally a good practice to add $payload = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop. ![]() |
|||
145 | $prettyPayload = json_encode(json_decode($json), JSON_PRETTY_PRINT); |
||
146 | $time = date('Ymd-His'); |
||
147 | file_put_contents($dir . '/' . $time . '_' . $batchId . '.json', $prettyPayload); |
||
148 | } else { |
||
149 | $this->getLogger()->debug("Directory $dir does not exist"); |
||
150 | } |
||
151 | } |
||
152 | |||
153 | $payload = json_decode($json, JSON_OBJECT_AS_ARRAY); |
||
154 | |||
155 | try { |
||
156 | $this->processPayload($payload, $batchId); |
||
157 | } catch (Exception $ex) { |
||
158 | // Maybe processing payload will create exceptions, but we |
||
159 | // catch them to send a proper response to the API |
||
160 | $logLevel = self::config()->log_level ? self::config()->log_level : 7; |
||
161 | $this->getLogger()->log($ex->getMessage(), $logLevel); |
||
162 | } |
||
163 | |||
164 | $response->setBody('OK'); |
||
165 | |||
166 | return $response; |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * A receiving URI must be public, so webhooks should be secured with a signature, |
||
171 | * time stamp and token to create a hash map using an API key to verify that the data |
||
172 | * is coming from the developer’s ESP. Users should program their application to check |
||
173 | * that hash map and compare it to that of the ESP, and then allow the post to be made only if it matches. |
||
174 | * |
||
175 | * To verify the webhook is originating from their ESP, users should concatenate time stamp and token values, |
||
176 | * encode the resulting string with the HMAC algorithm (using the ESP’s supplied API Key as a key and SHA256 digest mode), |
||
177 | * and compare the resulting hexdigest to the signature. |
||
178 | * Optionally, users can cache the token value locally and not honor any subsequent request with the same token. This will prevent replay attacks. |
||
179 | * |
||
180 | * @return bool |
||
181 | */ |
||
182 | protected function verifyCall() |
||
183 | { |
||
184 | //TODO: implement this |
||
185 | return true; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Process data |
||
190 | * |
||
191 | * @param string $payload |
||
192 | * @param string $batchId |
||
193 | */ |
||
194 | protected function processPayload($payload, $batchId = null) |
||
195 | { |
||
196 | $this->extend('beforeProcessPayload', $payload, $batchId); |
||
197 | |||
198 | // TODO: parse payload properly |
||
199 | // foreach ($payload as $r) { |
||
200 | // $this->eventsCount++; |
||
201 | // $this->extend('onAnyEvent', $data, $type); |
||
202 | |||
203 | // switch ($type) { |
||
204 | // //Click, Open |
||
205 | // case self::TYPE_CLICKED: |
||
206 | // case self::TYPE_OPENED: |
||
207 | // $this->extend('onEngagementEvent', $data, $type); |
||
208 | // break; |
||
209 | // //Generation Failure, Generation Rejection |
||
210 | // case self::TYPE_DELIVERED: |
||
211 | // $this->extend('onGenerationEvent', $data, $type); |
||
212 | // break; |
||
213 | // //Bounce, Delivery, Injection, SMS Status, Spam Complaint, Out of Band, Policy Rejection, Delay |
||
214 | // case self::TYPE_COMPLAINED: |
||
215 | // case self::TYPE_PERMANENT_FAIL: |
||
216 | // case self::TYPE_TEMPORARY_FAIL: |
||
217 | // $this->extend('onMessageEvent', $data, $type); |
||
218 | // break; |
||
219 | // //List Unsubscribe, Link Unsubscribe |
||
220 | // case self::TYPE_UNSUBSCRIBED: |
||
221 | // $this->extend('onUnsubscribeEvent', $data, $type); |
||
222 | // break; |
||
223 | // } |
||
224 | // } |
||
225 | |||
226 | $this->extend('afterProcessPayload', $payload, $batchId); |
||
227 | } |
||
228 | |||
229 | |||
230 | /** |
||
231 | * Get logger |
||
232 | * |
||
233 | * @return Psr\SimpleCache\CacheInterface |
||
234 | */ |
||
235 | public function getLogger() |
||
236 | { |
||
237 | return $this->logger; |
||
238 | } |
||
239 | } |
||
240 |