Issues (15)

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/SodaDataset.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
3
/**
4
 * This file contains the SodaDataset class
5
 *
6
 * @copyright 2015 Vladimir Jimenez
7
 * @license   https://github.com/allejo/PhpSoda/blob/master/LICENSE.md MIT
8
 */
9
10
namespace allejo\Socrata;
11
12
use allejo\Socrata\Converters\Converter;
13
use allejo\Socrata\Exceptions\InvalidResourceException;
14
use allejo\Socrata\Utilities\StringUtilities;
15
use allejo\Socrata\Utilities\UrlQuery;
16
17
/**
18
 * An object provided to interact with a Socrata dataset directly. Provides functionality for fetching the dataset, an
19
 * individual row, or updating/replacing a dataset.
20
 *
21
 * @package allejo\Socrata
22
 * @since   0.1.0
23
 */
24
class SodaDataset
25
{
26
    /**
27
     * The client with all the authentication and configuration set
28
     *
29
     * @var SodaClient
30
     */
31
    private $sodaClient;
32
33
    /**
34
     * The object used to make URL jobs for common requests
35
     *
36
     * @var UrlQuery
37
     */
38
    private $urlQuery;
39
40
    /**
41
     * The 4x4 resource ID of a dataset
42
     *
43
     * @var string
44
     */
45
    private $resourceId;
46
47
    /**
48
     * The API version of the dataset being worked with
49
     *
50
     * @var int
51
     */
52
    private $apiVersion;
53
54
    /**
55
     * The API's cached metadata
56
     *
57
     * @var array
58
     */
59
    private $metadata;
60
61
    /**
62
     * Create an object for interacting with a Socrata dataset
63
     *
64
     * @param  SodaClient $sodaClient The SodaClient with all of the authentication information and settings for access
65
     * @param  string     $resourceID The 4x4 resource ID of the dataset that will be referenced
66
     *
67
     * @throws InvalidResourceException If the given resource ID does not match the pattern of a resource ID
68
     *
69
     * @since 0.1.0
70
     */
71
    public function __construct ($sodaClient, $resourceID)
72
    {
73
        StringUtilities::validateResourceID($resourceID);
74
75
        if (!($sodaClient instanceof SodaClient))
76
        {
77
            throw new \InvalidArgumentException("The first variable is expected to be a SodaClient object");
78
        }
79
80
        $this->apiVersion = 0;
81
        $this->sodaClient = $sodaClient;
82
        $this->resourceId = $resourceID;
83
        $this->urlQuery   = new UrlQuery($this->buildResourceUrl(), $this->sodaClient->getToken(), $this->sodaClient->getEmail(), $this->sodaClient->getPassword());
84
85
        $this->urlQuery->setOAuth2Token($this->sodaClient->getOAuth2Token());
86
    }
87
88
    /**
89
     * Get the API version this dataset is using
90
     *
91
     * @since  0.1.0
92
     *
93
     * @return double The API version number
94
     */
95
    public function getApiVersion ()
96
    {
97
        // If we don't have the API version set, send a dummy query with limit 0 since we only care about the headers
98
        if ($this->apiVersion == 0)
99
        {
100
            $soql = new SoqlQuery();
101
            $soql->limit(0);
102
103
            // When we fetch a dataset, the API version is stored
104
            $this->getDataset($soql);
105
        }
106
107
        return $this->apiVersion;
108
    }
109
110
    /**
111
     * Get the metadata of a dataset
112
     *
113
     * @param bool $forceFetch Set to true if the cached metadata for the dataset is outdata or needs to be refreshed
114
     *
115
     * @see    SodaClient::enableAssociativeArrays()
116
     * @see    SodaClient::disableAssociativeArrays()
117
     *
118
     * @since  0.1.0
119
     *
120
     * @return array The metadata as a PHP array. The array will contain associative arrays or stdClass objects from
121
     *               the decoded JSON received from the data set.
122
     */
123
    public function getMetadata ($forceFetch = false)
124
    {
125
        if (empty($this->metadata) || $forceFetch)
126
        {
127
            $metadataUrlQuery = new UrlQuery($this->buildViewUrl(), $this->sodaClient->getToken(), $this->sodaClient->getEmail(), $this->sodaClient->getPassword());
128
            $metadataUrlQuery->setOAuth2Token($this->sodaClient->getOAuth2Token());
129
130
            $this->metadata = $metadataUrlQuery->sendGet("", $this->sodaClient->associativeArrayEnabled());
131
        }
132
133
        return $this->metadata;
134
    }
135
136
    /**
137
     * Fetch a dataset based on a resource ID.
138
     *
139
     * @param  string|SoqlQuery $filterOrSoqlQuery A simple filter or a SoqlQuery to filter the results
140
     *
141
     * @see    SodaClient::enableAssociativeArrays()
142
     * @see    SodaClient::disableAssociativeArrays()
143
     *
144
     * @since  0.1.0
145
     *
146
     * @return array The data set as a PHP array. The array will contain associative arrays or stdClass objects from
147
     *               the decoded JSON received from the data set.
148
     */
149
    public function getDataset ($filterOrSoqlQuery = "")
150
    {
151
        $headers = array();
152
153
        if (!($filterOrSoqlQuery instanceof SoqlQuery) && StringUtilities::isNullOrEmpty($filterOrSoqlQuery))
154
        {
155
            $filterOrSoqlQuery = new SoqlQuery();
156
        }
157
158
        $dataset = $this->urlQuery->sendGet($filterOrSoqlQuery, $this->sodaClient->associativeArrayEnabled(), $headers);
159
160
        $this->setApiVersion($headers);
161
162
        return $dataset;
163
    }
164
165
    /**
166
     * Delete an individual row based on their row identifier. For deleting more than a single row, use an upsert
167
     * instead.
168
     *
169
     * @param  int|string $rowID The row identifier of the row to fetch; if no identifier is set for the dataset, the
170
     *                           internal row identifier should be used
171
     *
172
     * @link   http://dev.socrata.com/publishers/direct-row-manipulation.html#deleting-a-row Deleting a Row
173
     *
174
     * @see    SodaClient::enableAssociativeArrays()
175
     * @see    SodaClient::disableAssociativeArrays()
176
     * @see    upsert()
177
     *
178
     * @since  0.1.2
179
     *
180
     * @return mixed An object with information about the deletion. The array will contain associative arrays or
181
     *               stdClass objects from the decoded JSON received from the data set.
182
     */
183
    public function deleteRow ($rowID)
184
    {
185
        return $this->individualRow($rowID, "delete");
186
    }
187
188
    /**
189
     * Fetch an individual row from a dataset.
190
     *
191
     * @param  int|string $rowID The row identifier of the row to fetch; if no identifier is set for the dataset, the
192
     *                           internal row identifier should be used
193
     *
194
     * @link   http://dev.socrata.com/publishers/direct-row-manipulation.html#retrieving-an-individual-row  Retrieving
195
     *         An Individual Row
196
     *
197
     * @see    SodaClient::enableAssociativeArrays()
198
     * @see    SodaClient::disableAssociativeArrays()
199
     *
200
     * @since  0.1.2
201
     *
202
     * @return array The data set as a PHP array. The array will contain associative arrays or stdClass objects from
203
     *               the decoded JSON received from the data set.
204
     */
205
    public function getRow ($rowID)
206
    {
207
        return $this->individualRow($rowID, "get");
208
    }
209
210
    /**
211
     * Replace the entire dataset with the new payload provided
212
     *
213
     * Data will always be transmitted as JSON to Socrata even though different forms are accepted. In order to pass
214
     * other forms of data, you must use a Converter class that has a `toJson()` method, such as the CsvConverter.
215
     *
216
     * @param  array|Converter|JSON $payload  The data that will be upserted to the Socrata dataset as a PHP array, an
217
     *                                        instance of a Converter child class, or a JSON string
218
     *
219
     * @link   http://dev.socrata.com/publishers/replace.html Replacing a dataset with Replace
220
     *
221
     * @see    Converter
222
     * @see    CsvConverter
223
     *
224
     * @since  0.1.0
225
     *
226
     * @return mixed
227
     */
228
    public function replace ($payload)
229
    {
230
        $upsertData = $this->handleJson($payload);
231
232
        return $this->urlQuery->sendPut($upsertData, $this->sodaClient->associativeArrayEnabled());
233
    }
234
235
    /**
236
     * Create, update, and delete rows in a single operation, using their row identifiers.
237
     *
238
     * Data will always be transmitted as JSON to Socrata even though different forms are accepted. In order to pass
239
     * other forms of data, you must use a Converter class that has a `toJson()` method, such as the CsvConverter.
240
     *
241
     * @param  array|Converter|JSON $payload  The data that will be upserted to the Socrata dataset as a PHP array, an
242
     *                                        instance of a Converter child class, or a JSON string
243
     *
244
     * @link   http://dev.socrata.com/publishers/upsert.html Updating Rows in Bulk with Upsert
245
     *
246
     * @see    Converter
247
     * @see    CsvConverter
248
     *
249
     * @since  0.1.0
250
     *
251
     * @return mixed
252
     */
253
    public function upsert ($payload)
254
    {
255
        $upsertData = $this->handleJson($payload);
256
257
        return $this->urlQuery->sendPost($upsertData, $this->sodaClient->associativeArrayEnabled());
258
    }
259
260
    /**
261
     * Build the API URL that will be used to access the dataset
262
     *
263
     * @return string The apt API URL
264
     */
265
    private function buildResourceUrl ()
266
    {
267
        return $this->buildApiUrl("resource");
268
    }
269
270
    /**
271
     * Build the API URL that will be used to access the metadata for the dataset
272
     *
273
     * @return string The apt API URL
274
     */
275
    private function buildViewUrl ()
276
    {
277
        return $this->buildApiUrl("views");
278
    }
279
280
    /**
281
     * Build the URL that will be used to access the API for the respective action
282
     *
283
     * @param  string      $location    The location of where to get information from
284
     * @param  string|null $identifier  The part of the URL that will end with .json. This will either be the resource
285
     *                                  ID or it will be a row ID prepended with the resource ID
286
     *
287
     * @return string The API URL
288
     */
289
    private function buildApiUrl ($location, $identifier = NULL)
290
    {
291
        if ($identifier === NULL)
292
        {
293
            $identifier = $this->resourceId;
294
        }
295
296
        return sprintf("%s://%s/%s/%s.json", UrlQuery::DEFAULT_PROTOCOL, $this->sodaClient->getDomain(), $location, $identifier);
297
    }
298
299
    /**
300
     * Handle different forms of data to be returned in JSON format so it can be sent to Socrata.
301
     *
302
     * Data will always be transmitted as JSON to Socrata even though different forms are accepted.
303
     *
304
     * @param  array|Converter|JSON $payload  The data that will be upserted to the Socrata dataset as a PHP array, an
305
     *                                        instance of a Converter child class, or a JSON string
306
     *
307
     * @return string A JSON encoded string available to be used for UrlQuery requsts
0 ignored issues
show
Should the return type not be array|Converter|JSON|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
308
     */
309
    private function handleJson ($payload)
310
    {
311
        $uploadData = $payload;
312
313
        if (is_array($payload))
314
        {
315
            $uploadData = json_encode($payload);
316
        }
317
        else if ($payload instanceof Converter)
318
        {
319
            $uploadData = $payload->toJson();
320
        }
321
        else if (!StringUtilities::isJson($payload))
322
        {
323
            throw new \InvalidArgumentException("The given data is not valid JSON");
324
        }
325
326
        return $uploadData;
327
    }
328
329
    /**
330
     * Interact with an individual row. Either to retrieve it or to delete it; both actions use the same API endpoint
331
     * with the exception of what type of request is sent.
332
     *
333
     * @param  string $rowID  The 4x4 resource ID of the dataset to work with
334
     * @param  string $method Either `get` or `delete`
335
     *
336
     * @return mixed
337
     */
338
    private function individualRow ($rowID, $method)
339
    {
340
        $headers = array();
341
342
        // For a single row, the format is the `resourceID/rowID.json`, so we'll use that as the "location" of the Api URL
343
        $apiEndPoint = $this->buildApiUrl("resource", $this->resourceId . "/" . $rowID);
344
345
        $urlQuery = new UrlQuery($apiEndPoint, $this->sodaClient->getToken(), $this->sodaClient->getEmail(), $this->sodaClient->getPassword());
346
        $urlQuery->setOAuth2Token($this->sodaClient->getOAuth2Token());
347
348
        $result = $this->sendIndividualRequest($urlQuery, $method, $this->sodaClient->associativeArrayEnabled(), $headers);
349
350
        $this->setApiVersion($headers);
351
352
        return $result;
353
    }
354
355
    /**
356
     * Send the appropriate request header based on the method that's required
357
     *
358
     * @param UrlQuery $urlQuery          The object for the API endpoint
359
     * @param string   $method            Either `get` or `delete`
360
     * @param bool     $associativeArrays Whether or not to return the information as an associative array
361
     * @param array    $headers           An array with the cURL headers received
362
     *
363
     * @return mixed
364
     */
365
    private function sendIndividualRequest ($urlQuery, $method, $associativeArrays, &$headers)
366
    {
367
        if ($method === "get")
368
        {
369
            return $urlQuery->sendGet("", $associativeArrays, $headers);
370
        }
371
        else if ($method === "delete")
372
        {
373
            return $urlQuery->sendDelete($associativeArrays, $headers);
374
        }
375
376
        throw new \InvalidArgumentException("Invalid request method");
377
    }
378
379
    /**
380
     * Determine and save the API version if it does not exist for easy access later
381
     *
382
     * @param array $headers An array with the cURL headers received
383
     */
384
    private function setApiVersion ($headers)
385
    {
386
        // Only set the API version number if it hasn't been set yet
387
        if ($this->apiVersion == 0)
388
        {
389
            $this->apiVersion = $this->parseApiVersion($headers);
390
        }
391
    }
392
393
    /**
394
     * Determine the version number of the API this dataset is using
395
     *
396
     * @param  array  $responseHeaders An array with the cURL headers received
397
     *
398
     * @return double The Socrata API version number this dataset uses
399
     */
400
    private function parseApiVersion ($responseHeaders)
401
    {
402
        // A header that's unique to the legacy API
403
        if (array_key_exists('X-SODA2-Legacy-Types', $responseHeaders) && $responseHeaders['X-SODA2-Legacy-Types'])
404
        {
405
            return 1;
406
        }
407
408
        // A header that's unique to the new API
409
        if (array_key_exists('X-SODA2-Truth-Last-Modified', $responseHeaders))
410
        {
411
            if (empty($this->metadata))
412
            {
413
                $this->getMetadata();
414
            }
415
416
            if ($this->metadata['newBackend'])
417
            {
418
                return 2.1;
419
            }
420
421
            return 2;
422
        }
423
424
        return 0;
425
    }
426
}
427