Issues (22)

Security Analysis    no request data  

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/Client.php (1 issue)

Labels
Severity

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
/*
4
 * This file is part of the slince/shopify-api-php
5
 *
6
 * (c) Slince <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Slince\Shopify;
13
14
use GuzzleHttp\Utils;
15
use GuzzleHttp\Exception\GuzzleException;
16
use GuzzleHttp\Psr7\Request;
17
use Slince\Di\Container;
18
use GuzzleHttp\Client as HttpClient;
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseInterface;
21
use Slince\Shopify\Common\Manager\ManagerInterface;
22
use Slince\Shopify\Exception\InvalidArgumentException;
23
use GuzzleHttp\Exception\RequestException;
24
use Slince\Shopify\Exception\ClientException;
25
use Slince\Shopify\Exception\RuntimeException;
26
use Slince\Shopify\Hydrator\Hydrator;
27
28
/**
29
 * @method Manager\Article\ArticleManagerInterface getArticleManager
30
 * @method Manager\Asset\AssetManagerInterface getAssetManager
31
 * @method Manager\Blog\BlogManagerInterface getBlogManager
32
 * @method Manager\CarrierService\CarrierServiceManagerInterface getCarrierServiceManager
33
 * @method Manager\Collect\CollectManagerInterface getCollectManager
34
 * @method Manager\Comment\CommentManagerInterface getCommentManager
35
 * @method Manager\Country\CountryManagerInterface getCountryManager
36
 * @method Manager\CustomCollection\CustomCollectionManagerInterface getCustomCollectionManager
37
 * @method Manager\Customer\CustomerManagerInterface getCustomerManager
38
 * @method Manager\CustomerAddress\AddressManagerInterface getCustomerAddressManager
39
 * @method Manager\CustomerSavedSearch\CustomerSavedSearchManagerInterface getCustomerSavedSearchManager
40
 * @method Manager\DiscountCode\DiscountCodeManagerInterface getDiscountCodeManager
41
 * @method Manager\DraftOrder\DraftOrderManagerInterface getDraftOrderManager
42
 * @method Manager\Fulfillment\FulfillmentManagerInterface getFulfillmentManager
43
 * @method Manager\FulfillmentService\FulfillmentServiceManagerInterface getFulfillmentServiceManager
44
 * @method Manager\InventoryItem\InventoryItemManagerInterface getInventoryItemManager
45
 * @method Manager\InventoryLevel\InventoryLevelManagerInterface getInventoryLevelManager
46
 * @method Manager\Location\LocationManagerInterface getLocationManager
47
 * @method Manager\Order\OrderManagerInterface getOrderManager
48
 * @method Manager\OrderRisk\RiskManagerInterface getOrderRiskManager
49
 * @method Manager\Page\PageManagerInterface getPageManager
50
 * @method Manager\Policy\PolicyManagerInterface getPolicyManager
51
 * @method Manager\PriceRule\PriceRuleManagerInterface getPriceRuleManager
52
 * @method Manager\Product\ProductManagerInterface getProductManager
53
 * @method Manager\ProductImage\ImageManagerInterface getProductImageManager
54
 * @method Manager\ProductVariant\VariantManagerInterface getProductVariantManager
55
 * @method Manager\Province\ProvinceManagerInterface getProvinceManager
56
 * @method Manager\RecurringApplicationCharge\RecurringApplicationChargeManagerInterface getRecurringApplicationChargeManager
57
 * @method Manager\Redirect\RedirectManagerInterface getRedirectManager
58
 * @method Manager\Refund\RefundManagerInterface getRefundManager
59
 * @method Manager\ShippingZone\ShippingZoneManagerInterface getShippingZoneManager
60
 * @method Manager\Shop\ShopManagerInterface getShopManager
61
 * @method Manager\SmartCollection\SmartCollectionManager getSmartCollectionManager
62
 * @method Manager\Theme\ThemeManagerInterface getThemeManager
63
 * @method Manager\Transaction\TransactionManagerInterface getTransactionManager
64
 * @method Manager\Webhook\WebhookManagerInterface getWebhookManager
65
 */
66
class Client
67
{
68
    const NAME = 'SlinceShopifyClient';
69
    const VERSION = '2.4.0';
70
71
    /**
72
     * @var HttpClient
73
     */
74
    protected $httpClient;
75
76
    /**
77
     * @var Container
78
     */
79
    protected $container;
80
81
    /**
82
     * @var CredentialInterface
83
     */
84
    protected $credential;
85
86
    /**
87
     * The shop.
88
     *
89
     * @var string
90
     */
91
    protected $shop;
92
93
    /**
94
     * @var string
95
     */
96
    protected $apiVersion = '2019-10';
97
98
    /**
99
     * @var ResponseInterface
100
     */
101
    protected $lastResponse;
102
103
    /**
104
     * Array of services classes.
105
     *
106
     * @var array
107
     */
108
    public $serviceClass = [
109
        Manager\Article\ArticleManager::class,
110
        Manager\Asset\AssetManager::class,
111
        Manager\Blog\BlogManager::class,
112
        Manager\CarrierService\CarrierServiceManager::class,
113
        Manager\Collect\CollectManager::class,
114
        Manager\Comment\CommentManager::class,
115
        Manager\Country\CountryManager::class,
116
        Manager\CustomCollection\CustomCollectionManager::class,
117
        Manager\Customer\CustomerManager::class,
118
        Manager\CustomerAddress\AddressManager::class,
119
        Manager\CustomerSavedSearch\CustomerSavedSearchManager::class,
120
        Manager\DiscountCode\DiscountCodeManager::class,
121
        Manager\DraftOrder\DraftOrderManager::class,
122
        Manager\Fulfillment\FulfillmentManager::class,
123
        Manager\FulfillmentService\FulfillmentServiceManager::class,
124
        Manager\InventoryItem\InventoryItemManager::class,
125
        Manager\InventoryLevel\InventoryLevelManager::class,
126
        Manager\Location\LocationManager::class,
127
        Manager\Order\OrderManager::class,
128
        Manager\OrderRisk\RiskManager::class,
129
        Manager\Page\PageManager::class,
130
        Manager\Policy\PolicyManager::class,
131
        Manager\PriceRule\PriceRuleManager::class,
132
        Manager\Product\ProductManager::class,
133
        Manager\ProductImage\ImageManager::class,
134
        Manager\ProductVariant\VariantManager::class,
135
        Manager\Province\ProvinceManager::class,
136
        Manager\RecurringApplicationCharge\RecurringApplicationChargeManager::class,
137
        Manager\Redirect\RedirectManager::class,
138
        Manager\Refund\RefundManager::class,
139
        Manager\ScriptTag\ScriptTagManager::class,
140
        Manager\ShippingZone\ShippingZoneManager::class,
141
        Manager\Shop\ShopManager::class,
142
        Manager\SmartCollection\SmartCollectionManager::class,
143
        Manager\Theme\ThemeManager::class,
144
        Manager\Transaction\TransactionManager::class,
145
        Manager\Webhook\WebhookManager::class,
146
    ];
147
148
    protected $metaDirs = [
149
        'Slince\Shopify' => __DIR__.'/../config/serializer'
150
    ];
151
152
    /**
153
     * Whether delay the next request.
154
     *
155
     * @var bool
156
     */
157
    protected static $delayNextRequest = false;
158
159
    /**
160
     * @var string
161
     */
162
    protected $metaCacheDir;
163
164
    /**
165
     * @var Hydrator
166
     */
167
    protected $hydrator;
168
169
    public function __construct(CredentialInterface $credential, $shop, array $options = [])
170
    {
171
        $this->container = new Container();
172
        $this->container->register($this);
173
        $this->credential = $credential;
174
        $this->setShop($shop);
175
        $this->applyOptions($options);
176
        $this->initializeBaseServices();
177
    }
178
179
    public function __call($name, $arguments)
180
    {
181
        if ('Manager' === substr($name, -7)) {
182
            $serviceName = substr($name, 3, -7);
183
184
            return $this->container->get(Inflector::tableize(Inflector::pluralize($serviceName)));
185
        }
186
        throw new \InvalidArgumentException(sprintf('The method "%s" is not exists', $name));
187
    }
188
189
    /**
190
     * Gets the credential.
191
     *
192
     * @return CredentialInterface
193
     */
194
    public function getCredential()
195
    {
196
        return $this->credential;
197
    }
198
199
    /**
200
     * sets the shop name for the client.
201
     *
202
     * @param string $shop
203
     */
204
    public function setShop($shop)
205
    {
206
        if (!preg_match('/^[a-zA-Z0-9\-]{3,100}\.myshopify\.(?:com|io)$/', $shop)) {
207
            throw new InvalidArgumentException(
208
                'Shop name should be 3-100 letters, numbers, or hyphens e.g. your-store.myshopify.com'
209
            );
210
        }
211
        $this->shop = $shop;
212
    }
213
214
    /**
215
     * Gets the shop.
216
     *
217
     * @return string
218
     */
219
    public function getShop()
220
    {
221
        return $this->shop;
222
    }
223
224
    /**
225
     * Sets the http client for the client.
226
     *
227
     * @param HttpClient $httpClient
228
     */
229
    public function setHttpClient($httpClient)
230
    {
231
        $this->httpClient = $httpClient;
232
    }
233
234
    /**
235
     * Gets the http client.
236
     *
237
     * @return HttpClient
238
     */
239
    public function getHttpClient()
240
    {
241
        if ($this->httpClient) {
242
            return $this->httpClient;
243
        }
244
        return $this->httpClient = new HttpClient([
245
            'verify' => false,
246
        ]);
247
    }
248
249
    /**
250
     * Perform a GET request.
251
     *
252
     * @param string $resource
253
     * @param array  $query
254
     *
255
     * @return array
256
     */
257
    public function get($resource, $query = [])
258
    {
259
        return $this->doRequest('GET', $resource, [
260
            'query' => $query,
261
        ]);
262
    }
263
264
    /**
265
     * Perform a POST request.
266
     *
267
     * @param string $resource
268
     * @param array  $data
269
     * @param array  $query
270
     *
271
     * @return array
272
     */
273
    public function post($resource, $data, $query = [])
274
    {
275
        return $this->doRequest('POST', $resource, [
276
            'query' => $query,
277
            'json' => $data,
278
        ]);
279
    }
280
281
    /**
282
     * Perform a PUT request.
283
     *
284
     * @param string $resource
285
     * @param array  $data
286
     * @param array  $query
287
     *
288
     * @return array
289
     */
290
    public function put($resource, $data, $query = [])
291
    {
292
        return $this->doRequest('PUT', $resource, [
293
            'query' => $query,
294
            'json' => $data,
295
        ]);
296
    }
297
298
    /**
299
     * Perform a DELETE request.
300
     *
301
     * @param string $resource
302
     * @param array  $query
303
     */
304
    public function delete($resource, $query = [])
305
    {
306
        $this->doRequest('DELETE', $resource, [
307
            'query' => $query
308
        ]);
309
    }
310
311
    /**
312
     * Send an HTTP request
313
     *
314
     * @param string $method
315
     * @param string $resource
316
     * @param array $options
317
     * @return array
318
     */
319
    protected function doRequest($method, $resource, $options = [])
320
    {
321
        $request = new Request($method, $this->buildUrl($resource), [
322
            'Content-Type' => 'application/json',
323
        ]);
324
        $response = $this->sendRequest($request, $options);
325
        $body = $response->getBody();
326
327
        return $body->getSize()
328
            ? Utils::jsonDecode($body, true)
329
            : [];
330
    }
331
332
    /**
333
     * Send a request.
334
     *
335
     * @param RequestInterface $request
336
     * @param array $options
337
     *
338
     * @return ResponseInterface
339
     * @throws GuzzleException
340
     * @codeCoverageIgnore
341
     */
342
    public function sendRequest(RequestInterface $request, array $options = [])
343
    {
344
        if (static::$delayNextRequest) {
345
            usleep(1000000 * rand(3, 10));
346
        }
347
        $request = $request->withHeader('User-Agent', static::NAME . '/' . static::VERSION);
348
        $request = $this->credential->applyToRequest($request);
349
        try {
350
            $response = $this->getHttpClient()->send($request, $options);
351
            $this->lastResponse = $response;
352
        } catch (RequestException $exception) {
353
            $exception = new ClientException($request, $exception->getResponse(), $exception->getMessage(), $exception->getCode());
354
            throw $exception;
355
        }
356
        list($callsMade, $callsLimit) = explode('/', $response->getHeaderLine('http_x_shopify_shop_api_call_limit'));
357
        static::$delayNextRequest = $callsMade / $callsLimit >= 0.8;
358
        return $response;
359
    }
360
361
    /**
362
     * Gets the latest http response.
363
     *
364
     * @return ResponseInterface
365
     */
366
    public function getLastResponse()
367
    {
368
        return $this->lastResponse;
369
    }
370
371
    /**
372
     * Builds an url by given resource name.
373
     *
374
     * @param string $resource
375
     *
376
     * @return string
377
     */
378
    protected function buildUrl($resource)
379
    {
380
        return sprintf('https://%s/admin/api/%s/%s.json', $this->shop, $this->apiVersion, $resource);
381
    }
382
383
    /**
384
     * Applies the array of request options to the client.
385
     *
386
     * @param array $options
387
     */
388
    protected function applyOptions(array $options)
389
    {
390
        isset($options['httpClient']) && $this->httpClient = $options['httpClient'];
391
        if (!isset($options['metaCacheDir'])) {
392
            throw new InvalidArgumentException('You must provide option "metaCacheDir"');
393
        }
394
        $this->metaCacheDir = $options['metaCacheDir'];
395
        if (isset($options['apiVersion'])) {
396
            if (!preg_match('/^[0-9]{4}-[0-9]{2}$|^unstable$/', $options['apiVersion'])) {
397
                throw new InvalidArgumentException('Version string must be of YYYY-MM or unstable');
398
            }
399
            $this->apiVersion = $options['apiVersion'];
400
        }
401
    }
402
403
    /**
404
     * Gets the hydrator instance.
405
     *
406
     * @return Hydrator
407
     */
408
    public function getHydrator()
409
    {
410
        if ($this->hydrator) {
411
            return $this->hydrator;
412
        }
413
        return $this->hydrator = new Hydrator($this->metaCacheDir, $this->metaDirs);
414
    }
415
416
    /**
417
     * Add a custom meta dir.
418
     *
419
     * @param string $namespace
420
     * @param string $path
421
     * @throws RuntimeException
422
     */
423
    public function addMetaDir($namespace, $path)
424
    {
425
        if ($this->hydrator) {
426
            throw new RuntimeException(sprintf('The hydrator has been built, you should add meta dir before getting manager.'));
427
        }
428
        $this->metaDirs[$namespace] = $path;
429
    }
430
431
    /**
432
     * Add a custom service class.
433
     *
434
     * @param string $serviceClass
435
     * @throws InvalidArgumentException
436
     */
437
    public function addServiceClass($serviceClass)
438
    {
439
        if (!is_subclass_of($serviceClass, ManagerInterface::class)) {
0 ignored issues
show
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Slince\Shopify\Common\M...ManagerInterface::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
440
            throw new InvalidArgumentException(sprintf('The service class "%s" should implement "ManagerInterface"', $serviceClass));
441
        }
442
        $this->serviceClass[] = $serviceClass;
443
        $this->container->register($serviceClass::getServiceName(), $serviceClass);
444
    }
445
446
    /**
447
     * Initialize base services.
448
     */
449
    protected function initializeBaseServices()
450
    {
451
        foreach ($this->serviceClass as $serviceClass) {
452
            $this->container->register($serviceClass::getServiceName(), $serviceClass);
453
        }
454
    }
455
}
456