Issues (9)

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/Context/WebApiContext.php (7 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
/*
4
 * This file is part of the Behat WebApiExtension.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Behat\WebApiExtension\Context;
12
13
use Behat\Gherkin\Node\PyStringNode;
14
use Behat\Gherkin\Node\TableNode;
15
use GuzzleHttp\ClientInterface;
16
use GuzzleHttp\Exception\RequestException;
17
use GuzzleHttp\Psr7\Request;
18
use PHPUnit_Framework_Assert as Assertions;
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseInterface;
21
22
/**
23
 * Provides web API description definitions.
24
 *
25
 * @author Konstantin Kudryashov <[email protected]>
26
 */
27
class WebApiContext implements ApiClientAwareContext
28
{
29
    /**
30
     * @var string
31
     */
32
    private $authorization;
33
34
    /**
35
     * @var ClientInterface
36
     */
37
    private $client;
38
39
    /**
40
     * @var array
41
     */
42
    private $headers = array();
43
44
    /**
45
     * @var \GuzzleHttp\Message\RequestInterface|RequestInterface
46
     */
47
    private $request;
48
49
    /**
50
     * @var \GuzzleHttp\Message\ResponseInterface|ResponseInterface
51
     */
52
    private $response;
53
54
    private $placeHolders = array();
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function setClient(ClientInterface $client)
60
    {
61
        $this->client = $client;
62
    }
63
64
    /**
65
     * Adds Basic Authentication header to next request.
66
     *
67
     * @param string $username
68
     * @param string $password
69
     *
70
     * @Given /^I am authenticating as "([^"]*)" with "([^"]*)" password$/
71
     */
72
    public function iAmAuthenticatingAs($username, $password)
73
    {
74
        $this->removeHeader('Authorization');
75
        $this->authorization = base64_encode($username . ':' . $password);
76
        $this->addHeader('Authorization', 'Basic ' . $this->authorization);
77
    }
78
79
    /**
80
     * Sets a HTTP Header.
81
     *
82
     * @param string $name  header name
83
     * @param string $value header value
84
     *
85
     * @Given /^I set header "([^"]*)" with value "([^"]*)"$/
86
     */
87
    public function iSetHeaderWithValue($name, $value)
88
    {
89
        $this->addHeader($name, $value);
90
    }
91
92
    /**
93
     * Sends HTTP request to specific relative URL.
94
     *
95
     * @param string $method request method
96
     * @param string $url    relative url
97
     *
98
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)"$/
99
     */
100
    public function iSendARequest($method, $url)
101
    {
102
        $url = $this->prepareUrl($url);
103
104 View Code Duplication
        if (version_compare(ClientInterface::VERSION, '6.0', '>=')) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
            $this->request = new Request($method, $url, $this->headers);
106
        } else {
107
            $this->request = $this->getClient()->createRequest($method, $url);
0 ignored issues
show
The method createRequest() does not exist on GuzzleHttp\ClientInterface. Did you maybe mean request()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
108
            if (!empty($this->headers)) {
109
                $this->request->addHeaders($this->headers);
110
            }
111
        }
112
113
        $this->sendRequest();
114
    }
115
116
    /**
117
     * Sends HTTP request to specific URL with field values from Table.
118
     *
119
     * @param string    $method request method
120
     * @param string    $url    relative url
121
     * @param TableNode $post   table of post values
122
     *
123
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with values:$/
124
     */
125
    public function iSendARequestWithValues($method, $url, TableNode $post)
126
    {
127
        $url = $this->prepareUrl($url);
128
        $fields = array();
129
130
        foreach ($post->getRowsHash() as $key => $val) {
131
            $fields[$key] = $this->replacePlaceHolder($val);
132
        }
133
134
        $bodyOption = array(
135
          'body' => json_encode($fields),
136
        );
137
138 View Code Duplication
        if (version_compare(ClientInterface::VERSION, '6.0', '>=')) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
            $this->request = new Request($method, $url, $this->headers, $bodyOption['body']);
140
        } else {
141
            $this->request = $this->getClient()->createRequest($method, $url, $bodyOption);
0 ignored issues
show
The method createRequest() does not exist on GuzzleHttp\ClientInterface. Did you maybe mean request()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
142
            if (!empty($this->headers)) {
143
                $this->request->addHeaders($this->headers);
144
            }
145
        }
146
147
        $this->sendRequest();
148
    }
149
150
    /**
151
     * Sends HTTP request to specific URL with raw body from PyString.
152
     *
153
     * @param string       $method request method
154
     * @param string       $url    relative url
155
     * @param PyStringNode $string request body
156
     *
157
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with body:$/
158
     */
159
    public function iSendARequestWithBody($method, $url, PyStringNode $string)
160
    {
161
        $url = $this->prepareUrl($url);
162
        $string = $this->replacePlaceHolder(trim($string));
163
164
        if (version_compare(ClientInterface::VERSION, '6.0', '>=')) {
165
            $this->request = new Request($method, $url, $this->headers, $string);
166
        } else {
167
            $this->request = $this->getClient()->createRequest(
0 ignored issues
show
The method createRequest() does not exist on GuzzleHttp\ClientInterface. Did you maybe mean request()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
168
                $method,
169
                $url,
170
                array(
171
                    'headers' => $this->getHeaders(),
172
                    'body' => $string,
173
                )
174
            );
175
        }
176
177
        $this->sendRequest();
178
    }
179
180
    /**
181
     * Sends HTTP request to specific URL with form data from PyString.
182
     *
183
     * @param string       $method request method
184
     * @param string       $url    relative url
185
     * @param PyStringNode $body   request body
186
     *
187
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with form data:$/
188
     */
189
    public function iSendARequestWithFormData($method, $url, PyStringNode $body)
190
    {
191
        $url = $this->prepareUrl($url);
192
        $body = $this->replacePlaceHolder(trim($body));
193
194
        $fields = array();
195
        parse_str(implode('&', explode("\n", $body)), $fields);
196
197
        if (version_compare(ClientInterface::VERSION, '6.0', '>=')) {
198
            $this->request = new Request($method, $url, ['Content-Type' => 'application/x-www-form-urlencoded'], http_build_query($fields, null, '&'));
199
        } else {
200
            $this->request = $this->getClient()->createRequest($method, $url);
0 ignored issues
show
The method createRequest() does not exist on GuzzleHttp\ClientInterface. Did you maybe mean request()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
201
            /** @var \GuzzleHttp\Post\PostBodyInterface $requestBody */
202
            $requestBody = $this->request->getBody();
203
            foreach ($fields as $key => $value) {
0 ignored issues
show
The expression $fields of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
204
                $requestBody->setField($key, $value);
205
            }
206
        }
207
208
        $this->sendRequest();
209
    }
210
211
    /**
212
     * Checks that response has specific status code.
213
     *
214
     * @param string $code status code
215
     *
216
     * @Then /^(?:the )?response code should be (\d+)$/
217
     */
218
    public function theResponseCodeShouldBe($code)
219
    {
220
        $expected = intval($code);
221
        $actual = intval($this->response->getStatusCode());
222
        Assertions::assertSame($expected, $actual);
223
    }
224
225
    /**
226
     * Checks that response body contains specific text.
227
     *
228
     * @param string $text
229
     *
230
     * @Then /^(?:the )?response should contain "([^"]*)"$/
231
     */
232
    public function theResponseShouldContain($text)
233
    {
234
        $expectedRegexp = '/' . preg_quote($text) . '/i';
235
        $actual = (string) $this->response->getBody();
236
        Assertions::assertRegExp($expectedRegexp, $actual);
237
    }
238
239
    /**
240
     * Checks that response body doesn't contains specific text.
241
     *
242
     * @param string $text
243
     *
244
     * @Then /^(?:the )?response should not contain "([^"]*)"$/
245
     */
246
    public function theResponseShouldNotContain($text)
247
    {
248
        $expectedRegexp = '/' . preg_quote($text) . '/';
249
        $actual = (string) $this->response->getBody();
250
        Assertions::assertNotRegExp($expectedRegexp, $actual);
251
    }
252
253
    /**
254
     * Checks that response body contains JSON from PyString.
255
     *
256
     * Do not check that the response body /only/ contains the JSON from PyString,
257
     *
258
     * @param PyStringNode $jsonString
259
     *
260
     * @throws \RuntimeException
261
     *
262
     * @Then /^(?:the )?response should contain json:$/
263
     */
264
    public function theResponseShouldContainJson(PyStringNode $jsonString)
265
    {
266
        $etalon = json_decode($this->replacePlaceHolder($jsonString->getRaw()), true);
267
        $actual = json_decode($this->response->getBody(), true);
268
269
        if (null === $etalon) {
270
            throw new \RuntimeException(
271
              "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw())
272
            );
273
        }
274
275
        if (null === $actual) {
276
            throw new \RuntimeException(
277
              "Can not convert actual to json:\n" . $this->replacePlaceHolder((string) $this->response->getBody())
278
            );
279
        }
280
281
        Assertions::assertGreaterThanOrEqual(count($etalon), count($actual));
282
        foreach ($etalon as $key => $needle) {
283
            Assertions::assertArrayHasKey($key, $actual);
284
            Assertions::assertEquals($etalon[$key], $actual[$key]);
285
        }
286
    }
287
288
    /**
289
     * Prints last response body.
290
     *
291
     * @Then print response
292
     */
293
    public function printResponse()
294
    {
295
        $request = $this->request;
296
        $response = $this->response;
297
298
        echo sprintf(
299
            "%s %s => %d:\n%s",
300
            $request->getMethod(),
301
            (string) ($request instanceof RequestInterface ? $request->getUri() : $request->getUrl()),
302
            $response->getStatusCode(),
303
            (string) $response->getBody()
304
        );
305
    }
306
307
    /**
308
     * Prepare URL by replacing placeholders and trimming slashes.
309
     *
310
     * @param string $url
311
     *
312
     * @return string
313
     */
314
    private function prepareUrl($url)
315
    {
316
        return ltrim($this->replacePlaceHolder($url), '/');
317
    }
318
319
    /**
320
     * Sets place holder for replacement.
321
     *
322
     * you can specify placeholders, which will
323
     * be replaced in URL, request or response body.
324
     *
325
     * @param string $key   token name
326
     * @param string $value replace value
327
     */
328
    public function setPlaceHolder($key, $value)
329
    {
330
        $this->placeHolders[$key] = $value;
331
    }
332
333
    /**
334
     * Replaces placeholders in provided text.
335
     *
336
     * @param string $string
337
     *
338
     * @return string
339
     */
340
    protected function replacePlaceHolder($string)
341
    {
342
        foreach ($this->placeHolders as $key => $val) {
343
            $string = str_replace($key, $val, $string);
344
        }
345
346
        return $string;
347
    }
348
349
    /**
350
     * Returns headers, that will be used to send requests.
351
     *
352
     * @return array
353
     */
354
    protected function getHeaders()
355
    {
356
        return $this->headers;
357
    }
358
359
    /**
360
     * Adds header
361
     *
362
     * @param string $name
363
     * @param string $value
364
     */
365
    protected function addHeader($name, $value)
366
    {
367
        if (isset($this->headers[$name])) {
368
            if (!is_array($this->headers[$name])) {
369
                $this->headers[$name] = array($this->headers[$name]);
370
            }
371
372
            $this->headers[$name][] = $value;
373
        } else {
374
            $this->headers[$name] = $value;
375
        }
376
    }
377
378
    /**
379
     * Removes a header identified by $headerName
380
     *
381
     * @param string $headerName
382
     */
383
    protected function removeHeader($headerName)
384
    {
385
        if (array_key_exists($headerName, $this->headers)) {
386
            unset($this->headers[$headerName]);
387
        }
388
    }
389
390
    private function sendRequest()
391
    {
392
        try {
393
            $this->response = $this->getClient()->send($this->request);
394
        } catch (RequestException $e) {
395
            $this->response = $e->getResponse();
396
397
            if (null === $this->response) {
398
                throw $e;
399
            }
400
        }
401
    }
402
403
    private function getClient()
404
    {
405
        if (null === $this->client) {
406
            throw new \RuntimeException('Client has not been set in WebApiContext');
407
        }
408
409
        return $this->client;
410
    }
411
}
412