Issues (86)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  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.
  Header Injection
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/Trucker/Resource/Model.php (13 issues)

1
<?php
2
3
namespace Trucker\Resource;
4
5
use Illuminate\Container\Container;
6
use Trucker\Facades\AuthFactory;
7
use Trucker\Facades\Collection;
8
use Trucker\Facades\Config;
9
use Trucker\Facades\ErrorHandlerFactory;
10
use Trucker\Facades\Instance;
11
use Trucker\Facades\RequestFactory;
12
use Trucker\Facades\ResponseInterpreterFactory;
13
use Trucker\Facades\UrlGenerator;
14
use Trucker\Finders\Conditions\QueryConditionInterface;
15
use Trucker\Finders\Conditions\QueryResultOrderInterface;
16
17
/**
18
 * Base class for interacting with a remote API.
19
 *
20
 * @author Alessandro Manno <[email protected]>
21
 */
22
class Model
23
{
24
    /**
25
     * The IoC Container.
26
     *
27
     * @var Container
28
     */
29
    protected $app;
30
31
    /**
32
     * The name of the resource which is used to determine
33
     * the resource URI through the use of reflection.  By default
34
     * if this is not set the class name will be used.
35
     *
36
     * @var string
37
     */
38
    protected $resourceName;
39
40
    /**
41
     * Property to overwrite the getURI()
42
     * function with a static value of what remote API URI path
43
     * to hit.
44
     *
45
     * @var string
46
     */
47
    protected $uri;
48
49
    /**
50
     * Property to hold the data about entities for which this
51
     * resource is nested beneath.  For example if this entity was
52
     * 'Employee' which was a nested resource under a 'Company' and
53
     * the instance URI should be /companies/:company_id/employees/:id
54
     * then you would assign this string with 'Company:company_id'.
55
     * Doing this will allow you to pass in ':company_id' as an option
56
     * to the URI creation functions and ':company_id' will be replaced
57
     * with the value passed.
58
     *
59
     * Alternativley you could set the value to something like 'Company:100'.
60
     * You could do this before a call like:
61
     *
62
     * <code>
63
     * $e = new Employee;
64
     * $e->nestedUnder = 'Company:100';
65
     * $found = Employee::find(1, [], $e);
66
     * //this would generate /companies/100/employees/1
67
     * </code>
68
     *
69
     *
70
     * This value can be nested as a comma separated string as well.
71
     * So you could set something like
72
     * "Company:company_id,Employee:employee_id,Preference:pref_id"
73
     * which would generate
74
     * /companies/:company_id/employees/:employee_id/preferences/:pref_id
75
     *
76
     * @var string
77
     */
78
    public $nestedUnder;
79
80
    /**
81
     * Array of instance values.
82
     *
83
     * @var array
84
     */
85
    protected $properties = [];
86
87
    /**
88
     * Remote resource's primary key property.
89
     *
90
     * @var string
91
     */
92
    protected $identityProperty;
93
94
    /**
95
     * Var to hold instance errors.
96
     *
97
     * @var array
98
     */
99
    protected $errors = [];
100
101
    /**
102
     * Comma separated list of properties that can't
103
     * be set via mass assignment.
104
     *
105
     * @var string
106
     */
107
    protected $guarded = '';
108
109
    /**
110
     * Comma separated list of properties that will take
111
     * a file path that should be read in and sent
112
     * with any API request.
113
     *
114
     * @var string
115
     */
116
    protected $fileFields = '';
117
118
    /**
119
     * Comma separated list of properties that may be in
120
     * a GET request but should not be added to a create or
121
     * update request.
122
     *
123
     * @var string
124
     */
125
    protected $readOnlyFields = '';
126
127
    /**
128
     * Array of files that were temporarily written for a request
129
     * that should be removed after the request is done.
130
     *
131
     * @var array
132
     */
133
    private $postRequestCleanUp = [];
134
135
    /**
136
     * Filesystem location that temporary files could be
137
     * written to if needed.
138
     *
139
     * @var string
140
     */
141
    protected $scratchDiskLocation;
142
143
    /**
144
     * Portion of a property name that would indicate
145
     * that the value would be Base64 encoded when the
146
     * property is set.
147
     *
148
     * @var string
149
     */
150
    protected $base64Indicator;
151
152
    /**
153
     * Constructor used to popuplate the instance with
154
     * attribute values.
155
     *
156
     * @param array $attributes Associative array of property names and values
157
     */
158 42
    public function __construct(array $attributes = [])
159
    {
160 42
        $this->fill($attributes);
161 42
    }
162
163
    /**
164
     * Create a new instance of the given model.
165
     *
166
     * @param array $attributes
167
     *
168
     * @return \Trucker\Resource\Model
169
     */
170 13
    public function newInstance(array $attributes = [])
171
    {
172
        // This method just provides a convenient way for us to generate fresh model
173
        // instances of this current model. It is particularly useful during the
174
        // hydration of new objects.
175 13
        $model = new static();
176
177 13
        $model->fill((array) $attributes);
178
179 13
        return $model;
180
    }
181
182
    /**
183
     * Magic getter function for accessing instance properties.
184
     *
185
     * @param string $key Property name
186
     *
187
     * @return mixed The value stored in the property
188
     */
189 14
    public function __get($key)
190
    {
191 14
        if (array_key_exists($key, $this->properties)) {
192 14
            return $this->properties[$key];
193
        }
194
195 1
        return null;
196
    }
197
198
    /**
199
     * Magic setter function for setting instance properties.
200
     *
201
     * @param string $property Property name
202
     * @param mixed  $value    The value to store for the property
203
     */
204 30
    public function __set($property, $value)
205
    {
206
        //if property contains '_base64'
207 30
        if (!(false === stripos($property, $this->getBase64Indicator()))) {
208
            //if the property IS a file field
209 1
            $fileProperty = str_replace($this->getBase64Indicator(), '', $property);
210 1
            if (in_array($fileProperty, $this->getFileFields(), true)) {
211 1
                $this->handleBase64File($fileProperty, $value);
212
            }//end if file field
213
        } else {
214 30
            $this->properties[$property] = $value;
215
        }
216 30
    }
217
218
    //end __set
219
220
    /**
221
     * Magic unsetter function for unsetting an instance property.
222
     *
223
     * @param string $property Property name
224
     */
225 1
    public function __unset($property)
226
    {
227 1
        if (array_key_exists($property, $this->properties)) {
228 1
            unset($this->properties[$property]);
229
        }
230 1
    }
231
232
    //end __unset
233
234
    /**
235
     * Getter function to access the
236
     * underlying attributes array for the
237
     * entity.
238
     *
239
     * @return array
240
     */
241 15
    public function attributes()
242
    {
243 15
        return $this->properties;
244
    }
245
246
    /**
247
     * Function to return any errors that
248
     * may have prevented a save.
249
     *
250
     * @return array
251
     */
252 7
    public function errors()
253
    {
254 7
        return $this->errors;
255
    }
256
257
    /**
258
     * Function to fill an instance's properties from an
259
     * array of keys and values.
260
     *
261
     * @param array $attributes Associative array of properties and values
262
     */
263 42
    public function fill(array $attributes = [])
264
    {
265 42
        $guarded = $this->getGuardedAttributes();
266
267 42
        foreach ($attributes as $property => $value) {
268 19
            if (!in_array($property, $guarded, true)) {
269
                //get the fields on the entity that are files
270 19
                $fileFields = $this->getFileFields();
271
272
                //if property contains base64 indicator
273 19
                if (!(false === stripos($property, $this->getBase64Indicator()))) {
274
                    //get a list of file properties w/o the base64 indicator
275 1
                    $fileProperty = str_replace($this->getBase64Indicator(), '', $property);
276
277
                    //if the property IS a file field, handle appropriatley
278 1
                    if (in_array($fileProperty, $fileFields, true)) {
279 1
                        $this->handleBase64File($fileProperty, $value);
280
                    }//end if file field
281
                } else {
282
                    //handle as normal property, but file fields can't be mass assigned
283 18
                    if (!in_array($property, $fileFields, true)) {
284 19
                        $this->properties[$property] = $value;
285
                    }
286
                }//end if-else base64
287
            }//end if not guarded
288
        }//end foreach
289 42
    }
290
291
    /**
292
     * Function to return an array of properties that should not
293
     * be set via mass assignment.
294
     *
295
     * @return array
296
     */
297 42
    public function getGuardedAttributes()
298
    {
299 42
        $attrs = array_map('trim', explode(',', $this->guarded));
300
301
        //the identityProperty should always be guarded
302 42
        if (!in_array($this->getIdentityProperty(), $attrs, true)) {
303 42
            $attrs[] = $this->getIdentityProperty();
304
        }
305
306 42
        return $attrs;
307
    }
308
309
    /**
310
     * Function to return an array of properties that will
311
     * accept a file path.
312
     *
313
     * @return array
314
     */
315 20
    public function getFileFields()
316
    {
317 20
        $attrs = array_map('trim', explode(',', $this->fileFields));
318
319 20
        return array_filter($attrs);
320
    }
321
322
    /**
323
     * Function to take base64 encoded image and write it to a
324
     * temp file, then add that file to the property list to get
325
     * added to a request.
326
     *
327
     * @param string $property Entity attribute
328
     * @param string $value    Base64 encoded string
329
     */
330 1
    protected function handleBase64File($property, $value)
331
    {
332 1
        $image = base64_decode($value);
333 1
        $imgData = \getimagesizefromstring($image);
0 ignored issues
show
It seems like $image can also be of type false; however, parameter $imagedata of getimagesizefromstring() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

333
        $imgData = \getimagesizefromstring(/** @scrutinizer ignore-type */ $image);
Loading history...
334 1
        $mimeExp = explode('/', $imgData['mime']);
335 1
        $ext = end($mimeExp);
336 1
        $output_file = implode(
337 1
            DIRECTORY_SEPARATOR,
338 1
            [$this->getScratchDiskLocation(), uniqid("tmp_{$property}_", true) . ".$ext"]
339
        );
340 1
        $f = fopen($output_file, 'wb');
341 1
        fwrite($f, $image);
0 ignored issues
show
It seems like $f can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
        fwrite(/** @scrutinizer ignore-type */ $f, $image);
Loading history...
It seems like $image can also be of type false; however, parameter $string of fwrite() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
        fwrite($f, /** @scrutinizer ignore-type */ $image);
Loading history...
342 1
        fclose($f);
0 ignored issues
show
It seems like $f can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

342
        fclose(/** @scrutinizer ignore-type */ $f);
Loading history...
343
344 1
        $this->postRequestCleanUp[] = $output_file;
345 1
        $this->{$property} = $output_file;
346 1
    }
347
348
    //end handleBase64File
349
350
    /**
351
     * Function to get the instance ID, returns false if there
352
     * is not one.
353
     *
354
     * @return mixed
355
     */
356 13
    public function getId()
357
    {
358 13
        if (array_key_exists($this->getIdentityProperty(), $this->properties)) {
359 11
            return $this->properties[$this->getIdentityProperty()];
360
        }
361
362 4
        return false;
363
    }
364
365
    /**
366
     * Getter function to return the identity property.
367
     *
368
     * @return string
369
     */
370 42
    public function getIdentityProperty()
371
    {
372 42
        return $this->identityProperty ?: Config::get('resource.identity_property');
0 ignored issues
show
The method get() does not exist on Trucker\Facades\Config. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

372
        return $this->identityProperty ?: Config::/** @scrutinizer ignore-call */ get('resource.identity_property');
Loading history...
373
    }
374
375
    /**
376
     * Getter function to return the scratch disk location.
377
     *
378
     * @return string
379
     */
380 1
    public function getScratchDiskLocation()
381
    {
382 1
        return $this->scratchDiskLocation ?: Config::get('resource.scratch_disk_location');
383
    }
384
385
    /**
386
     * Getter function to return base64 param indicator.
387
     *
388
     * @return string
389
     */
390 26
    public function getBase64Indicator()
391
    {
392 26
        return $this->base64Indicator ?: Config::get('resource.base_64_property_indication');
393
    }
394
395
    /**
396
     * Function to return an array of property names
397
     * that are read only.
398
     *
399
     * @return array
400
     */
401 10
    public function getReadOnlyFields()
402
    {
403 10
        $cantSet = array_map('trim', explode(',', $this->readOnlyFields));
404
405 10
        return $cantSet;
406
    }
407
408
    /**
409
     * Function to get an associative array of fields
410
     * with their values that are NOT read only.
411
     *
412
     * @return array
413
     */
414 1
    public function getMutableFields()
415
    {
416 1
        $cantSet = $this->getReadOnlyFields();
417
418 1
        $mutableFields = [];
419
420
        //set the property attributes
421 1
        foreach ($this->properties as $key => $value) {
422 1
            if (!in_array($key, $cantSet, true)) {
423 1
                $mutableFields[$key] = $value;
424
            }
425
        }
426
427 1
        return $mutableFields;
428
    }
429
430
    /**
431
     * Function to interpret the URI resource name based on the class called.
432
     * Generally this would be the name of the class.
433
     *
434
     * @return string The sub name of the resource
435
     */
436 26
    public function getResourceName()
437
    {
438 26
        if (isset($this->resourceName)) {
439 5
            return $this->resourceName;
440
        }
441
442 26
        $full_class_arr = explode('\\', get_called_class());
443 26
        $klass = end($full_class_arr);
444 26
        $this->resourceName = $klass;
445
446 26
        return $klass;
447
    }
448
449
    /**
450
     * Getter function to return a URI
451
     * that has been manually set.
452
     *
453
     * @return string
454
     */
455 25
    public function getURI()
456
    {
457 25
        return $this->uri ?: null;
458
    }
459
460
    /**
461
     * Function to find an instance of an Entity record.
462
     *
463
     * @param int   $id        The primary identifier value for the record
464
     * @param array $getParams Array of GET parameters to pass
465
     * @param Model $instance  An instance to use for interpreting url values
466
     *
467
     * @return Model An instance of the entity requested
468
     */
469 2
    public static function find($id, array $getParams = [], self $instance = null)
470
    {
471 2
        $m = $instance ?: new static();
472
473 2
        return Instance::fetch($m, $id, $getParams);
0 ignored issues
show
The method fetch() does not exist on Trucker\Facades\Instance. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

473
        return Instance::/** @scrutinizer ignore-call */ fetch($m, $id, $getParams);
Loading history...
474
    }
475
476
    /**
477
     * Function to find a collection of Entity records from the remote api.
478
     *
479
     * @param QueryConditionInterface   $condition   query conditions
480
     * @param QueryResultOrderInterface $resultOrder result ordering info
481
     * @param array                     $getParams   additional GET params
482
     *
483
     * @return Collection
484
     */
485 4
    public static function all(
486
        QueryConditionInterface $condition = null,
487
        QueryResultOrderInterface $resultOrder = null,
488
        array $getParams = []
489
    ) {
490 4
        return Collection::fetch(new static(), $condition, $resultOrder, $getParams);
0 ignored issues
show
The method fetch() does not exist on Trucker\Facades\Collection. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

490
        return Collection::/** @scrutinizer ignore-call */ fetch(new static(), $condition, $resultOrder, $getParams);
Loading history...
491
    }
492
493
    /**
494
     * Function to handle persistance of the entity across the
495
     * remote API.  Function will handle either a CREATE or UPDATE.
496
     *
497
     * @return bool Success of the save operation
498
     */
499 8
    public function save()
500
    {
501
        //get a request object
502 8
        $request = RequestFactory::build();
0 ignored issues
show
The method build() does not exist on Trucker\Facades\RequestFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

502
        /** @scrutinizer ignore-call */ 
503
        $request = RequestFactory::build();
Loading history...
503
504 8
        if (false === $this->getId()) {
505
            //make a CREATE request
506 4
            $request->createRequest(
507 4
                Config::get('request.base_uri'),
508 4
                UrlGenerator::getCreateUri($this),
0 ignored issues
show
The method getCreateUri() does not exist on Trucker\Facades\UrlGenerator. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

508
                UrlGenerator::/** @scrutinizer ignore-call */ 
509
                              getCreateUri($this),
Loading history...
509 4
                'POST',
510 4
                [], //no extra headers
511 4
                Config::get('request.http_method_param')
512
            );
513
        } else {
514
            //make an UPDATE request
515 4
            $request->createRequest(
516 4
                Config::get('request.base_uri'),
517 4
                UrlGenerator::getDeleteUri(
0 ignored issues
show
The method getDeleteUri() does not exist on Trucker\Facades\UrlGenerator. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

517
                UrlGenerator::/** @scrutinizer ignore-call */ 
518
                              getDeleteUri(
Loading history...
518 4
                    $this,
519 4
                    [':' . $this->getIdentityProperty() => $this->getId()]
520
                ),
521 4
                'PUT',
522 4
                [], //no extra headers
523 4
                Config::get('request.http_method_param')
524
            );
525
        }
526
527
        //add auth if it is needed
528 8
        if ($auth = AuthFactory::build()) {
0 ignored issues
show
The method build() does not exist on Trucker\Facades\AuthFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

528
        if ($auth = AuthFactory::/** @scrutinizer ignore-call */ build()) {
Loading history...
529
            $request->authenticate($auth);
530
        }
531
532
        //set the property attributes on the request
533 8
        $request->setModelProperties($this);
534
535
        //actually send the request
536 8
        $response = $request->sendRequest();
537
538
        //handle clean response with errors
539 8
        if (ResponseInterpreterFactory::build()->invalid($response)) {
0 ignored issues
show
The method build() does not exist on Trucker\Facades\ResponseInterpreterFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

539
        if (ResponseInterpreterFactory::/** @scrutinizer ignore-call */ build()->invalid($response)) {
Loading history...
540
            //get the errors and set them to our local collection
541 4
            $this->errors = ErrorHandlerFactory::build()->parseErrors($response);
0 ignored issues
show
The method build() does not exist on Trucker\Facades\ErrorHandlerFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

541
            $this->errors = ErrorHandlerFactory::/** @scrutinizer ignore-call */ build()->parseErrors($response);
Loading history...
542
543
            //do any needed cleanup
544 4
            $this->doPostRequestCleanUp();
545
546 4
            return false;
547
        }//end if
548
549
        //get the response and inflate from that
550 4
        $data = $response->parseResponseToData();
551 4
        $this->fill($data);
552
553
        //inflate the ID property that should be guarded
554
        //and thus not fillable
555 4
        $id = $this->getIdentityProperty();
556 4
        if (array_key_exists($id, $data)) {
557 4
            $this->{$id} = $data[$id];
558
        }
559
560 4
        $this->doPostRequestCleanUp();
561
562 4
        return true;
563
    }
564
565
    /**
566
     * Function to delete an existing entity.
567
     *
568
     * @return bool Success of the delete operation
569
     */
570 4
    public function destroy()
571
    {
572
        //get a request object
573 4
        $request = RequestFactory::build();
574
575
        //init the request
576 4
        $request->createRequest(
577 4
            Config::get('request.base_uri'),
578 4
            UrlGenerator::getDeleteUri(
579 4
                $this,
580 4
                [':' . $this->getIdentityProperty() => $this->getId()]
581
            ),
582 4
            'DELETE',
583 4
            [], //no extra headers
584 4
            Config::get('request.http_method_param')
585
        );
586
587
        //add auth if it is needed
588 4
        if ($auth = AuthFactory::build()) {
589
            $request->authenticate($auth);
590
        }
591
592
        //actually send the request
593 4
        $response = $request->sendRequest();
594
595
        //clean up anything no longer needed
596 4
        $this->doPostRequestCleanUp();
597
598 4
        $interpreter = ResponseInterpreterFactory::build();
599
600
        //handle clean response with errors
601 4
        if ($interpreter->success($response)) {
602 2
            return true;
603
        }
604
605 2
        if ($interpreter->invalid($response)) {
606
            //get the errors and set them to our local collection
607 2
            $this->errors = ErrorHandlerFactory::build()->parseErrors($response);
608
        }//end if-else
609
610 2
        return false;
611
    }
612
613
    /**
614
     * Function to clean up any temp files written for a request.
615
     */
616 13
    protected function doPostRequestCleanUp()
617
    {
618 13
        while (count($this->postRequestCleanUp) > 0) {
619 1
            $f = array_pop($this->postRequestCleanUp);
620 1
            if (file_exists($f)) {
621 1
                unlink($f);
622
            }
623
        }
624 13
    }
625
}
626