Issues (13)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/UnitPay.php (3 issues)

Upgrade to new PHP Analysis Engine

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 ActionM\UnitPay;
4
5
use Illuminate\Http\Request;
6
use ActionM\UnitPay\Events\UnitPayEvent;
7
use Illuminate\Support\Facades\Validator;
8
use ActionM\UnitPay\Exceptions\InvalidConfiguration;
9
10
class UnitPay
11
{
12
    public function __construct()
13
    {
14
    }
15
16
    /**
17
     * Allow access, if the ip address is in the whitelist.
18
     * @param $ip
19
     * @return bool
20
     */
21
    public function allowIP($ip)
22
    {
23
        // Allow local ip
24
        if ($ip == '127.0.0.1') {
25
            return true;
26
        }
27
28
        return in_array($ip, config('unitpay.allowed_ips'));
29
    }
30
31
    /**
32
     * Return JSON error message.
33
     * @param $message
34
     * @return mixed
35
     */
36
    public function responseError($message)
37
    {
38
        $result['error']['message'] = $message;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = 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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

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.

Loading history...
39
40
        return $result;
41
    }
42
43
    /**
44
     * Return JSON success message.
45
     * @param $message
46
     * @return mixed
47
     */
48
    public function responseOK($message)
49
    {
50
        $result['result']['message'] = $message;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = 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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

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.

Loading history...
51
52
        return $result;
53
    }
54
55
    /**
56
     * Fill event details to pass the title and request params as array.
57
     * @param $event_type
58
     * @param $event_title
59
     * @param Request $request
60
     */
61
    public function eventFillAndSend($event_type, $event_title, Request $request)
62
    {
63
        $event_details = [
64
            'title' => 'UnitPay: '.$event_title,
65
            'ip' => $request->ip(),
66
            'request' => $request->all(),
67
        ];
68
69
        event(
70
            new UnitPayEvent($event_type, $event_details)
71
        );
72
    }
73
74
    /**
75
     * Return hash for the order form params.
76
     * @param $account
77
     * @param $currency
78
     * @param $desc
79
     * @param $sum
80
     * @param $secretKey
81
     * @return string
82
     */
83
    public function getFormSignature($account, $currency, $desc, $sum, $secretKey)
84
    {
85
        $hashStr = $account.'{up}'.$currency.'{up}'.$desc.'{up}'.$sum.'{up}'.$secretKey;
86
87
        return hash('sha256', $hashStr);
88
    }
89
90
    /**
91
     * Return hash for params from UnitPay gate.
92
     * @param $method
93
     * @param array $params
94
     * @param $secretKey
95
     * @return string
96
     */
97
    public function getSignature($method, array $params, $secretKey)
98
    {
99
        ksort($params);
100
        unset($params['sign'], $params['signature']);
101
        array_push($params, $secretKey);
102
        array_unshift($params, $method);
103
104
        return hash('sha256', implode('{up}', $params));
105
    }
106
107
    /**
108
     * Generate UnitPay order array with required array for order form.
109
     * @param $payment_amount
110
     * @param $payment_no
111
     * @param $user_email
112
     * @param $item_name
113
     * @param $currency
114
     * @return array
115
     */
116
    public function generateUnitPayOrderWithRequiredFields($payment_amount, $payment_no, $user_email, $item_name, $currency)
117
    {
118
        $order = [
119
            'PAYMENT_AMOUNT' => $payment_amount,
120
            'PAYMENT_NO' => $payment_no,
121
            'USER_EMAIL' => $user_email,
122
            'ITEM_NAME' => $item_name,
123
            'CURRENCY' => $currency,
124
        ];
125
126
        $this->requiredOrderParamsCheck($order);
127
128
        return $order;
129
    }
130
131
    /**
132
     * Check required order params for order form and raise an exception if fails.
133
     * @param $order
134
     * @throws InvalidConfiguration
135
     */
136
    public function requiredOrderParamsCheck($order)
137
    {
138
        $required_fields = [
139
            'PAYMENT_AMOUNT',
140
            'PAYMENT_NO',
141
            'USER_EMAIL',
142
            'ITEM_NAME',
143
            'CURRENCY',
144
        ];
145
146
        foreach ($required_fields as $key => $value) {
147
            if (! array_key_exists($value, $order) || empty($order[$value])) {
148
                throw InvalidConfiguration::generatePaymentFormOrderParamsNotSet($value);
149
            }
150
        }
151
152
        $currency_arr = [
153
            'RUB',
154
            'UAH',
155
            'BYR',
156
            'EUR',
157
            'USD',
158
        ];
159
160
        if (! in_array($order['CURRENCY'], $currency_arr)) {
161
            throw InvalidConfiguration::generatePaymentFormOrderInvalidCurrency($order['CURRENCY']);
162
        }
163
    }
164
165
    /**
166
     * Generate html forms from view with payment buttons
167
     * Note: you can customise the view via artisan:publish.
168
     * @param $order
169
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
170
     */
171
    public function generatePaymentForm($payment_amount, $payment_no, $user_email, $item_name, $currency)
172
    {
173
        $order = $this->generateUnitPayOrderWithRequiredFields($payment_amount, $payment_no, $user_email, $item_name, $currency);
174
175
        $this->requiredOrderParamsCheck($order);
176
177
        $payment_fields['LOCALE'] = config('unitpay.locale', 'ru');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$payment_fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $payment_fields = 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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

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.

Loading history...
178
        $payment_fields['PUB_KEY'] = config('unitpay.UNITPAY_PUBLIC_KEY');
179
        $payment_fields['PAYMENT_AMOUNT'] = $order['PAYMENT_AMOUNT'];
180
        $payment_fields['PAYMENT_NO'] = $order['PAYMENT_NO'];
181
        $payment_fields['USER_EMAIL'] = $order['USER_EMAIL'];
182
        $payment_fields['ITEM_NAME'] = $order['ITEM_NAME'];
183
        $payment_fields['CURRENCY'] = $order['CURRENCY'];
184
185
        $payment_fields['SIGN'] = $this->getFormSignature(
186
            $payment_fields['PAYMENT_NO'],
187
            $payment_fields['CURRENCY'],
188
            $payment_fields['ITEM_NAME'],
189
            $payment_fields['PAYMENT_AMOUNT'],
190
            config('unitpay.UNITPAY_SECRET_KEY')
191
        );
192
193
        return view('unitpay::payment_form', compact('payment_fields'));
194
    }
195
196
    /**
197
     * Validate request params from UnitPay gate.
198
     * @param Request $request
199
     * @return bool
200
     */
201
    public function validate(Request $request)
202
    {
203
        $validator = Validator::make($request->all(), [
204
            'method' => 'required|in:check,pay,error',
205
            'params.account' => 'required',
206
            'params.date' => 'required',
207
            'params.payerSum' => 'required',
208
            'params.payerCurrency' => 'required',
209
            'params.signature' => 'required',
210
            'params.orderSum' => 'required',
211
            'params.orderCurrency' => 'required',
212
            'params.unitpayId' => 'required',
213
        ]);
214
215
        if ($validator->fails()) {
216
            return false;
217
        }
218
219
        return true;
220
    }
221
222
    /**
223
     * Validate request signature from UnitPay gate.
224
     * @param Request $request
225
     * @return bool
226
     */
227
    public function validateSignature(Request $request)
228
    {
229
        $sign = $this->getSignature($request->get('method'), $request->get('params'), config('unitpay.UNITPAY_SECRET_KEY'));
230
231
        if ($request->input('params.signature') != $sign) {
232
            return false;
233
        }
234
235
        return true;
236
    }
237
238
    /**
239
     * Validate ip, request params and signature from UnitPay gate.
240
     * @param Request $request
241
     * @return bool
242
     */
243
    public function validateOrderRequestFromGate(Request $request)
244
    {
245
        if (! $this->AllowIP($request->ip()) || ! $this->validate($request) || ! $this->validateSignature($request)) {
246
            $this->eventFillAndSend('unitpay.error', 'validateOrderRequestFromGate', $request);
247
248
            return false;
249
        }
250
251
        return true;
252
    }
253
254
    /**
255
     * Validate the required attributes of the found order.
256
     * @param Request $request
257
     * @param $order
258
     * @return bool
259
     */
260
    public function validateSearchOrderRequiredAttributes(Request $request, $order)
261
    {
262
        if (! $order) {
263
            $this->eventFillAndSend('unitpay.error', 'orderNotFound', $request);
264
265
            return false;
266
        }
267
268
        // check required found order attributes
269
        $attr = ['UNITPAY_orderStatus', 'UNITPAY_orderSum', 'UNITPAY_orderCurrency'];
270
271
        foreach ($attr as $k => $value) {
272
            if (! $order->getAttribute($value)) {
273
                $this->eventFillAndSend('unitpay.error', $value.'Invalid', $request);
274
275
                return false;
276
            }
277
        }
278
279
        // compare order attributes vs request params
280
        $attr = ['UNITPAY_orderSum', 'UNITPAY_orderCurrency'];
281
        foreach ($attr as $k => $value) {
282
            if ($order->getAttribute($value) != $request->input('params.'.str_replace('UNITPAY_', '', $value))) {
283
                $this->eventFillAndSend('unitpay.error', $value.'Invalid', $request);
284
285
                return false;
286
            }
287
        }
288
289
        return true;
290
    }
291
292
    /**
293
     * Call SearchOrderFilter and check return order params.
294
     * @param Request $request
295
     * @return bool
296
     * @throws InvalidConfiguration
297
     */
298
    public function callFilterSearchOrder(Request $request)
299
    {
300
        $callable = config('unitpay.searchOrderFilter');
301
302
        if (! is_callable($callable)) {
303
            throw InvalidConfiguration::searchOrderFilterInvalid();
304
        }
305
306
        /*
307
         *  SearchOrderFilter
308
         *  Search order in the database and return order details
309
         *  Must return array with:
310
         *
311
         *  orderStatus
312
         *  orderCurrency
313
         *  orderSum
314
         */
315
316
        $order = $callable($request, $request->input('params.account'));
317
318
        if (! $this->validateSearchOrderRequiredAttributes($request, $order)) {
319
            return false;
320
        }
321
322
        return $order;
323
    }
324
325
    /**
326
     * Call PaidOrderFilter if order not paid.
327
     * @param Request $request
328
     * @param $order
329
     * @return mixed
330
     * @throws InvalidConfiguration
331
     */
332
    public function callFilterPaidOrder(Request $request, $order)
333
    {
334
        $callable = config('unitpay.paidOrderFilter');
335
336
        if (! is_callable($callable)) {
337
            throw InvalidConfiguration::orderPaidFilterInvalid();
338
        }
339
340
        // unset the custom order attributes for Eloquent support
341
        unset($order['UNITPAY_orderSum'], $order['UNITPAY_orderCurrency'], $order['UNITPAY_orderStatus']);
342
343
        // Run PaidOrderFilter callback
344
        return $callable($request, $order);
345
    }
346
347
    /**
348
     * Run UnitPay::payOrderFromGate($request) when receive request from UnitPay gate.
349
     * @param Request $request
350
     * @return bool
351
     */
352
    public function payOrderFromGate(Request $request)
353
    {
354
        // Validate request params from UnitPay server.
355
        if (! $this->validateOrderRequestFromGate($request)) {
356
            return $this->responseError('validateOrderRequestFromGate');
357
        }
358
359
        // Search and return order
360
        $order = $this->callFilterSearchOrder($request);
361
362
        if (! $order) {
363
            return $this->responseError('searchOrderFilter');
364
        }
365
366
        // Return success response for check and error methods
367
        if (in_array($request->get('method'), ['check', 'error'])) {
368
            $this->eventFillAndSend('unitpay.info', 'payOrderFromGate method = '.$request->get('method'), $request);
369
370
            return $this->responseOK('OK');
371
        }
372
373
        // If method unknown then return error
374
        if ($request->get('method') != 'pay') {
375
            return $this->responseError('Invalid request');
376
        }
377
378
        // If method pay and current order status is paid
379
        // return success response and notify info
380
        if (mb_strtolower($order->UNITPAY_orderStatus) === 'paid') {
381
            $this->eventFillAndSend('unitpay.info', 'order already paid', $request);
382
383
            return $this->responseOK('OK');
384
        }
385
386
        // Current order is paid in UnitPay and not paid in database
387
388
        $this->eventFillAndSend('unitpay.success', 'paid order', $request);
389
390
        // PaidOrderFilter - update order into DB as paid & other actions
391
        // if return false then error
392
        if (! $this->callFilterPaidOrder($request, $order)) {
393
            $this->eventFillAndSend('unitpay.error', 'callFilterPaidOrder', $request);
394
395
            return $this->responseError('callFilterPaidOrder');
396
        }
397
398
        // Order is paid in UnitPay and updated in database
399
        return $this->responseOK('OK');
400
    }
401
}
402