Issues (24)

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

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
namespace Loevgaard\Dandomain\Api;
3
4
use Assert\Assert;
5
use GuzzleHttp\Client;
6
use GuzzleHttp\ClientInterface;
7
use GuzzleHttp\Exception\GuzzleException;
8
use GuzzleHttp\Psr7\Response;
9
use GuzzleHttp\RequestOptions;
10
use Psr\Http\Message\ResponseInterface;
11
use Symfony\Component\OptionsResolver\OptionsResolver;
12
13
/**
14
 * @property Endpoint\Customer $customer
15
 * @property Endpoint\Discount $discount
16
 * @property Endpoint\Order $order
17
 * @property Endpoint\Product $product
18
 * @property Endpoint\ProductData $productData
19
 * @property Endpoint\ProductTag $productTag
20
 * @property Endpoint\RelatedData $relatedData
21
 * @property Endpoint\Settings $settings
22
 * @property Endpoint\Plugin $plugin
23
 */
24
class Api
25
{
26
    /**
27
     * Example: http://www.example.com
28
     *
29
     * @var string
30
     */
31
    protected $host;
32
33
    /**
34
     * The API key from your Dandomain admin
35
     *
36
     * @var string
37
     */
38
    protected $apiKey;
39
40
    /**
41
     * This is the HTTP client
42
     *
43
     * @var ClientInterface
44
     */
45
    protected $client;
46
47
    /**
48
     * This is the last response
49
     *
50
     * @var Response
51
     */
52
    protected $response;
53
54
    /**
55
     * These are the options used for the next request
56
     * After that request, these options are reset
57
     *
58
     * @var array
59
     */
60
    protected $options;
61
62
    /**
63
     * These are default options used for every request
64
     *
65
     * @var array
66
     */
67
    protected $defaultOptions;
68
69
    /**
70
     * These are the resolved options used in the last request
71
     *
72
     * @var array
73
     */
74
    protected $lastOptions;
75
76
    /*
77
     * These are endpoints in the Dandomain API
78
     */
79
    /**
80
     * @var Endpoint\Customer
81
     */
82
    protected $customer;
83
84
    /**
85
     * @var Endpoint\Discount
86
     */
87
    protected $discount;
88
89
    /**
90
     * @var Endpoint\Order;
91
     */
92
    protected $order;
93
94
    /**
95
     * @var Endpoint\Product;
96
     */
97
    protected $product;
98
99
    /**
100
     * @var Endpoint\ProductData;
101
     */
102
    protected $productData;
103
104
    /**
105
     * @var Endpoint\ProductTag;
106
     */
107
    protected $productTag;
108
109
    /**
110
     * @var Endpoint\RelatedData;
111
     */
112
    protected $relatedData;
113
114
    /**
115
     * @var Endpoint\Settings;
116
     */
117
    protected $settings;
118
    
119
    /**
120
     * @var Endpoint\Plugin;
121
     */
122
    protected $plugin;
123
124 4
    public function __construct(string $host, string $apiKey, array $defaultOptions = [])
125
    {
126 4
        $host = rtrim($host, '/');
127
128 4
        Assert::that($host)->url('`$host` is not a valid URL');
129 4
        Assert::that($apiKey)->length(36, '`$apiKey` is not a valid api key. It must be 36 characters');
130
131 4
        $this->host = $host;
132 4
        $this->apiKey = $apiKey;
133 4
        $this->options = [];
134 4
        $this->defaultOptions = $defaultOptions;
135 4
    }
136
137
    /**
138
     * This ensures lazy loading of the endpoint classes
139
     *
140
     * @param string $name
141
     * @return Endpoint\Endpoint
142
     */
143 4
    public function __get($name)
144
    {
145 4
        if (!property_exists(self::class, $name)) {
146
            throw new \InvalidArgumentException('The property `'.$name.'` does not exist on `'.self::class.'`');
147
        }
148
149 4
        if (!$this->{$name}) {
150 4
            $className = 'Loevgaard\\Dandomain\\Api\\Endpoint\\'.ucfirst($name);
151
152 4
            if (!class_exists($className)) {
153
                throw new \InvalidArgumentException('Class `'.$className.'` does not exist or could not be autoloaded');
154
            }
155
156 4
            $this->{$name} = new $className($this);
157
        }
158
159 4
        return $this->{$name};
160
    }
161
162
    /**
163
     * Will always return a JSON result contrary to Dandomains API
164
     * Errors are formatted as described here: http://jsonapi.org/format/#errors
165
     *
166
     * @param string $method
167
     * @param string $uri
168
     * @param array|\stdClass $body The body is sent as JSON
169
     * @param array $options
170
     * @return mixed
171
     */
172 2
    public function doRequest(string $method, string $uri, $body = null, array $options = [])
173
    {
174 2
        $parsedResponse = [];
175
176
        try {
177
            // merge all options
178
            // the priority is
179
            // 1. options supplied by the user
180
            // 2. options supplied by the method calling
181
            // 3. the default options
182 2
            $options = $this->resolveOptions($this->defaultOptions, $options, $this->options);
183
184 2
            if (!empty($body)) {
185
                $body = $this->objectToArray($body);
186
                Assert::that($body)->notEmpty('The body of the request cannot be empty');
187
188
                // the body will always override any other data sent
189
                $options['json'] = $body;
190
            }
191
192
            // save the resolved options
193 2
            $this->lastOptions = $options;
194
195
            // replace the {KEY} placeholder with the api key
196 2
            $url = $this->host . str_replace('{KEY}', $this->apiKey, $uri);
197
198
            // do request
199 2
            $this->response = $this->getClient()->request($method, $url, $options);
200
201
            // parse response and create error object if the json decode throws an exception
202
            try {
203 2
                $parsedResponse = \GuzzleHttp\json_decode((string)$this->response->getBody(), true);
204
            } catch (\InvalidArgumentException $e) {
205
                $parsedResponse['errors'][] = [
206
                    'status' => $this->response->getStatusCode(),
207
                    'title' => 'JSON parse error',
208
                    'detail' => $e->getMessage()
209
                ];
210
            }
211
212 2
            $statusCode = $this->response->getStatusCode();
213 2
            if ($statusCode > 299 || $statusCode < 200) {
214
                if (!is_array($parsedResponse)) {
215
                    $parsedResponse = [];
216
                }
217
                $parsedResponse['errors'] = [];
218
                $parsedResponse['errors'][] = [
219
                    'status' => $this->response->getStatusCode(),
220 2
                    'detail' => 'See Api::$response for details'
221
                ];
222
            }
223
        } catch (GuzzleException $e) {
224
            $parsedResponse['errors'] = [];
225
            $parsedResponse['errors'][] = [
226
                'title' => 'Unexpected error',
227
                'detail' => $e->getMessage()
228
            ];
229 2
        } finally {
230
            // reset request options
231 2
            $this->options = [];
232
        }
233
234 2
        return $parsedResponse;
235
    }
236
237
    /**
238
     * @return ClientInterface
239
     */
240 2
    public function getClient() : ClientInterface
241
    {
242 2
        if (!$this->client) {
243
            $this->client = new Client();
244
        }
245
246 2
        return $this->client;
247
    }
248
249
    /**
250
     * @param ClientInterface $client
251
     * @return Api
252
     */
253 2
    public function setClient(ClientInterface $client) : Api
254
    {
255 2
        $this->client = $client;
256 2
        return $this;
257
    }
258
259
    /**
260
     * Returns the latest response
261
     *
262
     * @return ResponseInterface
263
     */
264
    public function getResponse() : ResponseInterface
265
    {
266
        return $this->response;
267
    }
268
269
    /**
270
     * Sets request options for the next request
271
     *
272
     * @param array $options
273
     * @return Api
274
     */
275
    public function setOptions(array $options) : Api
276
    {
277
        $this->options = $options;
278
        return $this;
279
    }
280
281
    /**
282
     * Sets default request options
283
     *
284
     * @param array $defaultOptions
285
     * @return Api
286
     */
287 1
    public function setDefaultOptions(array $defaultOptions) : Api
288
    {
289 1
        $this->defaultOptions = $defaultOptions;
290 1
        return $this;
291
    }
292
293
    /**
294
     * @return array
295
     */
296 1
    public function getLastOptions(): array
297
    {
298 1
        return $this->lastOptions;
299
    }
300
301
    /**
302
     * Helper method to convert a \stdClass into an array
303
     *
304
     * @param $obj
305
     * @return array
306
     */
307
    protected function objectToArray($obj) : array
308
    {
309
        if ($obj instanceof \stdClass) {
310
            $obj = json_decode(json_encode($obj), true);
311
        }
312
313
        return (array)$obj;
314
    }
315
316 2
    protected function configureOptions(OptionsResolver $resolver)
317
    {
318 2
        $refl = new \ReflectionClass(RequestOptions::class);
319
320 2
        $resolver->setDefined(array_values($refl->getConstants()));
0 ignored issues
show
array_values($refl->getConstants()) is of type array<integer,integer|double|string|boolean>, but the function expects a string|array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
321
322 2
        $resolver->setDefaults([
323 2
            RequestOptions::HEADERS => [
324
                'Accept' => 'application/json',
325
            ],
326 2
            RequestOptions::CONNECT_TIMEOUT => 15,
327 2
            RequestOptions::TIMEOUT => 60,
328 2
            RequestOptions::HTTP_ERRORS => false
329
        ]);
330 2
    }
331
332 2
    protected function resolveOptions(array ...$options) : array
333
    {
334 2
        $computedOptions = [];
335
336 2
        foreach ($options as $arr) {
337 2
            $computedOptions = array_replace_recursive($computedOptions, $arr);
338
        }
339
340 2
        $resolver = new OptionsResolver();
341 2
        $this->configureOptions($resolver);
342 2
        $options = $resolver->resolve($computedOptions);
343
344 2
        return $options;
345
    }
346
}
347