Issues (5)

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 (3 issues)

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
namespace Anorgan\Onfleet;
4
5
use GuzzleHttp\Client as Guzzle;
6
use GuzzleHttp\ClientInterface;
7
use GuzzleHttp\Exception\ClientException;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\UriInterface;
10
use RuntimeException;
11
12
/**
13
 * Class Client
14
 * @package Anorgan\Onfleet
15
 */
16
class Client extends Guzzle
17
{
18
    const BASE_URL = 'https://onfleet.com/api/v2/';
19
    const VERSION  = 'v2';
20
21
    /**
22
     * Client constructor.
23
     * @param string $username
24
     * @param array $config
25
     */
26
    public function __construct($username, array $config = [])
27
    {
28
        $baseUriKey = version_compare(ClientInterface::VERSION, '6') === 1 ? 'base_uri' : 'base_url';
29
        if (!isset($config[$baseUriKey])) {
30
            $config[$baseUriKey] = static::BASE_URL;
31
        }
32
        $config['auth'] = [$username, null];
33
34
        parent::__construct($config);
35
    }
36
37
    /**
38
     * @param string|UriInterface $uri
39
     * @param array $options
40
     * @throws \Exception
41
     * @return ResponseInterface|Response
42
     */
43
    public function post($uri, array $options = [])
44
    {
45
        try {
46
            $response = parent::post($uri, $options);
47
            return Response::fromResponse($response);
48
        } catch (ClientException $e) {
49
            $error   = Response::fromResponse($e->getResponse())->json(true);
0 ignored issues
show
It seems like $e->getResponse() can be null; however, fromResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
50
            $message = $error['message']['message'];
51
            if (isset($error['message']['cause'])) {
52
                if (is_array($error['message']['cause'])) {
53
                    $message .= ' '.implode(', ', $error['message']['cause']);
54
                } else {
55
                    $message .= ' '.$error['message']['cause'];
56
                }
57
            }
58
            throw new RuntimeException('Error while calling post on '.$uri.': '.$message);
59
        }
60
    }
61
62
    /**
63
     * @param string|UriInterface $uri
64
     * @param array $options
65
     * @throws \GuzzleHttp\Exception\ClientException
66
     * @return null|\Psr\Http\Message\ResponseInterface|Response
67
     */
68
    public function get($uri, array $options = [])
69
    {
70
        try {
71
            return Response::fromResponse(parent::get($uri, $options));
72
        } catch (ClientException $e) {
73
            if ((int) $e->getCode() === 404) {
74
                return null;
75
            }
76
            throw $e;
77
        }
78
    }
79
80
    /**
81
     * @return Organization
82
     */
83
    public function getMyOrganization(): Organization
84
    {
85
        $response = $this->get('organization');
86
        return Organization::fromJson($response->json(), $this);
87
    }
88
89
    /**
90
     * Get delegatee organization
91
     *
92
     * @param string $id
93
     * @return Organization
94
     */
95
    public function getOrganization($id): Organization
96
    {
97
        $response = $this->get('organizations/'.$id);
98
        return Organization::fromJson($response->json(), $this);
99
    }
100
101
    /**
102
     * @param array $data {
103
     * @var string $name The administrator’s complete name.
104
     * @var string $email The administrator’s email address.
105
     * @var string $phone Optional. The administrator’s phone number.
106
     * @var boolean $isReadOnly Optional. Whether this administrator can perform write operations.
107
     * }
108
     * @return Administrator
109
     */
110
    public function createAdministrator(array $data): Administrator
111
    {
112
        $response = $this->post('admins', ['json' => $data]);
113
        return Administrator::fromJson($response->json(), $this);
114
    }
115
116
    /**
117
     * @return Administrator[]
118
     */
119
    public function getAdministrators(): array
120
    {
121
        $response = $this->get('admins');
122
123
        $administrators = [];
124
        foreach ($response->json() as $administratorData) {
125
            $administrators[] = Administrator::fromJson($administratorData, $this);
126
        }
127
128
        return $administrators;
129
    }
130
131
    /**
132
     * @param array $data {
133
     * @var string $name The worker’s complete name.
134
     * @var string $phone A valid phone number as per the worker’s organization’s country.
135
     * @var string|array $teams One or more team IDs of which the worker is a member.
136
     * @var object $vehicle Optional. The worker’s vehicle; providing no vehicle details is equivalent to the
137
     *                              worker being on foot.
138
     * @var string $type The vehicle’s type, must be one of CAR, MOTORCYCLE, BICYCLE or TRUCK.
139
     * @var string $description Optional. The vehicle’s make, model, year, or any other relevant identifying details.
140
     * @var string $licensePlate Optional. The vehicle’s license plate number.
141
     * @var string $color Optional. The vehicle's color.
142
     * @var integer $capacity Optional. The maximum number of units this worker can carry, for route optimization purposes.
143
     * }
144
     * @return Worker
145
     */
146
    public function createWorker(array $data): Worker
147
    {
148
        $response = $this->post('workers', ['json' => $data]);
149
        return Worker::fromJson($response->json(), $this);
150
    }
151
152
    /**
153
     * @param string $filter Optional. A comma-separated list of fields to return, if all are not desired. For example, name, location.
154
     * @param string $teams Optional. A comma-separated list of the team IDs that workers must be part of.
155
     * @param string $states Optional. A comma-separated list of worker states, where
156
     *                       0 is off-duty, 1 is idle (on-duty, no active task) and 2 is active (on-duty, active task).
157
     * @return Worker[]
158
     */
159
    public function getWorkers($filter = null, $teams = null, $states = null): array
160
    {
161
        $query = array_filter([
162
            'filter' => $filter,
163
            'teams'  => $teams,
164
            'states' => $states,
165
        ]);
166
        $response = $this->get('workers', compact('query'));
167
168
        $workers = [];
169
        foreach ($response->json() as $workerData) {
170
            $workers[] = Worker::fromJson($workerData, $this);
171
        }
172
173
        return $workers;
174
    }
175
176
    /**
177
     * @param string $id
178
     * @param string $filter Optional. A comma-separated list of fields to return, if all are not desired.
179
     *                        For example: "name, location".
180
     * @param bool $analytics Basic worker duty event, traveled distance (meters) and time analytics are optionally
181
     *                        available by specifying the query parameter analytics as true.
182
     * @return Worker
183
     *
184
     * @todo: Add "from" and "to" timestamps if analytics is true
185
     */
186
    public function getWorker($id, $filter = null, $analytics = false): Worker
187
    {
188
        $query = array_filter([
189
            'filter'    => $filter,
190
            'analytics' => $analytics ? 'true' : 'false',
191
        ]);
192
        $response = $this->get('workers/'.$id, compact('query'));
193
194
        return Worker::fromJson($response->json(), $this);
195
    }
196
197
    /**
198
     * @return Hub[]
199
     */
200
    public function getHubs(): array
201
    {
202
        $response = $this->get('hubs');
203
204
        $hubs = [];
205
        foreach ($response->json() as $hubData) {
206
            $hubs[] = Hub::fromJson($hubData, $this);
207
        }
208
209
        return $hubs;
210
    }
211
212
    /**
213
     * @param array $data {
214
     * @var string $name A unique name for the team.
215
     * @var array $workers An array of worker IDs.
216
     * @var array $managers An array of managing administrator IDs.
217
     * @var string $hub Optional. The ID of the team's hub.
218
     * }
219
     *
220
     * @return Team
221
     */
222
    public function createTeam(array $data): Team
223
    {
224
        $response = $this->post('teams', ['json' => $data]);
225
        return Team::fromJson($response->json(), $this);
226
    }
227
228
    /**
229
     * @return Team[]
230
     */
231
    public function getTeams(): array
232
    {
233
        $response = $this->get('teams');
234
235
        $teams = [];
236
        foreach ($response->json() as $teamData) {
237
            $teams[] = Team::fromJson($teamData, $this);
238
        }
239
240
        return $teams;
241
    }
242
243
    /**
244
     * @param string $id
245
     * @return Team
246
     */
247
    public function getTeam($id): Team
248
    {
249
        $response = $this->get('teams/'.$id);
250
        return Team::fromJson($response->json(), $this);
251
    }
252
253
    /**
254
     * @param array $data {
255
     * @var array $address The destination’s street address details. {
256
     * @var string $name Optional. A name associated with this address, e.g., "Transamerica Pyramid".
257
     * @var string $number The number component of this address, it may also contain letters.
258
     * @var string $street The name of the street.
259
     * @var string $apartment Optional. The suite or apartment number, or any additional relevant information.
260
     * @var string $city The name of the municipality.
261
     * @var string $state Optional. The name of the state, province or jurisdiction.
262
     * @var string $postalCode Optional. The postal or zip code.
263
     * @var string $country The name of the country.
264
     * @var string $unparsed Optional. A complete address specified in a single, unparsed string where the
265
     *                                 various elements are separated by commas. If present, all other address
266
     *                                 properties will be ignored (with the exception of name and apartment).
267
     *                                 In some countries, you may skip most address details (like city or state)
268
     *                                 if you provide a valid postalCode: for example,
269
     *                                 543 Howard St, 94105, USA will be geocoded correctly.
270
     *     }
271
     * @var string $notes Optional. Note that goes with this destination, e.g. "Please call before"
272
     * @var array $location Optional. The [ longitude, latitude ] geographic coordinates. If missing, the API will
273
     *                          geocode based on the address details provided. Note that geocoding may slightly modify
274
     *                          the format of the address properties. address.unparsed cannot be provided if you are
275
     *                          also including a location.
276
     * }
277
     * @return Destination
278
     */
279
    public function createDestination(array $data): Destination
280
    {
281
        $response = $this->post('destinations', ['json' => $data]);
282
        return Destination::fromJson($response->json(), $this);
283
    }
284
285
    /**
286
     * @param string $id
287
     * @return Destination
288
     */
289
    public function getDestination($id): Destination
290
    {
291
        $response = $this->get('destinations/'.$id);
292
        return Destination::fromJson($response->json(), $this);
293
    }
294
295
    /**
296
     * @param array $data {
297
     * @var string $name The recipient’s complete name.
298
     * @var string $phone A unique, valid phone number as per the recipient’s organization’s country.
299
     * @var string $notes Optional. Notes for this recipient: these are global notes that should not be
300
     *                          task- or destination-specific.
301
     *                          For example, "Customer since June 2012, does not drink non-specialty coffee".
302
     * @var boolean $skipSMSNotifications Optional. Whether this recipient has requested to not receive SMS
303
     *                          notifications. Defaults to false if not provided.
304
     * @var boolean $skipPhoneNumberValidation Optional. Whether to skip validation of this recipient's phone
305
     *                          number. An E.164-like number is still required (must start with +), however the API
306
     *                          will not enforce any country-specific validation rules.
307
     * }
308
     * @return Recipient
309
     */
310
    public function createRecipient(array $data): Recipient
311
    {
312
        $response = $this->post('recipients', ['json' => $data]);
313
        return Recipient::fromJson($response->json(), $this);
314
    }
315
316
    /**
317
     * @param string $id
318
     * @return Recipient
319
     */
320
    public function getRecipient($id): Recipient
321
    {
322
        $response = $this->get('recipients/'.$id);
323
        return Recipient::fromJson($response->json(), $this);
324
    }
325
326
    /**
327
     * @param string $name
328
     * @return Recipient|null
329
     */
330
    public function getRecipientByName($name)
331
    {
332
        $name     = str_replace('+', '%20', urlencode(strtolower($name)));
333
        $response = $this->get('recipients/name/'.$name);
334
335
        if (null === $response) {
336
            return null;
337
        }
338
339
        return Recipient::fromJson($response->json(), $this);
0 ignored issues
show
It seems like $response->json() targeting Anorgan\Onfleet\Response::json() can also be of type array or null; however, Anorgan\Onfleet\Entity::fromJson() does only seem to accept object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
340
    }
341
342
    /**
343
     * @param string $phone
344
     * @return Recipient|null
345
     */
346
    public function getRecipientByPhone($phone)
347
    {
348
        $phone    = preg_replace('/[^\d]/', '', $phone);
349
        $response = $this->get('recipients/phone/+'.$phone);
350
351
        if (null === $response) {
352
            return null;
353
        }
354
355
        return Recipient::fromJson($response->json(), $this);
0 ignored issues
show
It seems like $response->json() targeting Anorgan\Onfleet\Response::json() can also be of type array or null; however, Anorgan\Onfleet\Entity::fromJson() does only seem to accept object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
356
    }
357
358
    /**
359
     * @see Task::createAutoAssignedArray
360
     * @param array $data
361
     * @return Task
362
     */
363
    public function createTask(array $data): Task
364
    {
365
        $response = $this->post('tasks', ['json' => $data]);
366
        return Task::fromJson($response->json(), $this);
367
    }
368
369
    /**
370
     * @param int|\DateTime $from The starting time of the range. Tasks created or completed at or after this time will be included.
371
     *                                 Millisecond precision int or DateTime
372
     * @param int|\DateTime $to Optional. If missing, defaults to the current time. The ending time of the range.
373
     *                                 Tasks created or completed before this time will be included.
374
     *                                 Millisecond precision int or DateTime
375
     * @param string $lastId Optional. Used to walk the paginated response, if there is one. Tasks created after this ID
376
     *                       will be returned, up to the per-query limit of 64.
377
     * @return Task[]
378
     */
379
    public function getTasks($from, $to = null, &$lastId = null): array
380
    {
381
        if ($from instanceof \DateTime) {
382
            $from = $from->getTimestamp() * 1000;
383
        }
384
        if ($to instanceof \DateTime) {
385
            $to = $to->getTimestamp() * 1000;
386
        }
387
        $query = array_filter([
388
            'from'   => $from,
389
            'to'     => $to,
390
            'lastId' => $lastId,
391
        ]);
392
        $response = $this->get('tasks/all', compact('query'));
393
394
        $tasks  = [];
395
        $json   = $response->json();
396
        $lastId = isset($json->lastId) ? $json->lastId : false;
397
        foreach ($json->tasks as $taskData) {
398
            $tasks[] = Task::fromJson($taskData, $this);
399
        }
400
401
        return $tasks;
402
    }
403
404
    /**
405
     * @param string $id
406
     * @return Task
407
     */
408
    public function getTask($id): Task
409
    {
410
        $response = $this->get('tasks/'.$id);
411
        return Task::fromJson($response->json(), $this);
412
    }
413
414
    /**
415
     * @param string $shortId
416
     * @return Task
417
     */
418
    public function getTaskByShortId($shortId): Task
419
    {
420
        $response = $this->get('tasks/shortId/'.$shortId);
421
        return Task::fromJson($response->json(), $this);
422
    }
423
424
    /**
425
     * Replace all organization's tasks.
426
     *
427
     * @param array $taskIds
428
     * @param string $organizationId
429
     */
430
    public function setOrganizationTasks(array $taskIds, $organizationId)
431
    {
432
        $this->setContainerTasks('organizations', $organizationId, $taskIds);
433
    }
434
435
    /**
436
     * @param array $taskIds
437
     * @param string $organizationId
438
     */
439
    public function addTasksToOrganization(array $taskIds, $organizationId)
440
    {
441
        $this->setContainerTasks('organizations', $organizationId, $taskIds, -1);
442
    }
443
444
    /**
445
     * Replace all team's tasks.
446
     *
447
     * @param array $taskIds
448
     * @param string $teamId
449
     */
450
    public function setTeamTasks(array $taskIds, $teamId)
451
    {
452
        $this->setContainerTasks('teams', $teamId, $taskIds);
453
    }
454
455
    /**
456
     * @param array $taskIds
457
     * @param string $teamId
458
     */
459
    public function addTasksToTeam(array $taskIds, $teamId)
460
    {
461
        $this->setContainerTasks('teams', $teamId, $taskIds, -1);
462
    }
463
464
    /**
465
     * Replace all worker's tasks.
466
     *
467
     * @param array $taskIds
468
     * @param string $workerId
469
     */
470
    public function setWorkerTasks(array $taskIds, $workerId)
471
    {
472
        $this->setContainerTasks('workers', $workerId, $taskIds);
473
    }
474
475
    /**
476
     * @param array $taskIds
477
     * @param string $workerId
478
     */
479
    public function addTasksToWorker(array $taskIds, $workerId)
480
    {
481
        $this->setContainerTasks('workers', $workerId, $taskIds, -1);
482
    }
483
484
    /**
485
     * @param string $url
486
     * @param int $triggerId
487
     * @return Webhook
488
     */
489
    public function createWebhook($url, $triggerId): Webhook
490
    {
491
        $response = $this->post('webhooks', [
492
            'json' => [
493
                'url'     => $url,
494
                'trigger' => $triggerId,
495
            ]
496
        ]);
497
498
        return Webhook::fromJson($response->json(), $this);
499
    }
500
501
    /**
502
     * @return Webhook[]
503
     */
504
    public function getWebhooks(): array
505
    {
506
        $response = $this->get('webhooks');
507
508
        $webhooks = [];
509
        foreach ($response->json() as $webhookData) {
510
            $webhooks[] = Webhook::fromJson($webhookData, $this);
511
        }
512
513
        return $webhooks;
514
    }
515
516
    /**
517
     * @param string $containerEndpoint "organizations", "workers" or "teams"
518
     * @param string $targetId ID of organization, worker or team.
519
     * @param array $taskIds Array of task IDs.
520
     * @param int $position Insert tasks at a given index. To append to the end, use -1, to prepend, use 0.
521
     * @throws \InvalidArgumentException
522
     */
523
    private function setContainerTasks($containerEndpoint, $targetId, array $taskIds, $position = null)
524
    {
525
        if (null !== $position) {
526
            if (!is_numeric($position)) {
527
                throw new \InvalidArgumentException('Position argument should be numeric, -1 for appending, 0 to prepend');
528
            }
529
530
            array_unshift($taskIds, $position);
531
        }
532
533
        $this->put('containers/'.$containerEndpoint.'/'.$targetId, [
534
            'json' => [
535
                'tasks' => $taskIds
536
            ]
537
        ]);
538
    }
539
}
540