Issues (15)

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.

Controller/AbstractRestResourceController.php (3 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
namespace Dontdrinkandroot\RestBundle\Controller;
4
5
use Doctrine\ORM\Tools\Pagination\Paginator;
6
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
8
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
9
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
10
use Dontdrinkandroot\RestBundle\Metadata\RestMetadataFactory;
11
use Dontdrinkandroot\RestBundle\Serializer\RestDenormalizer;
12
use Dontdrinkandroot\RestBundle\Serializer\RestNormalizer;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
19
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
20
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
21
use Symfony\Component\Serializer\SerializerInterface;
22
use Symfony\Component\Validator\ConstraintViolationInterface;
23
use Symfony\Component\Validator\ConstraintViolationListInterface;
24
use Symfony\Component\Validator\Validator\ValidatorInterface;
25
26
/**
27
 * @author Philip Washington Sorst <[email protected]>
28
 */
29
abstract class AbstractRestResourceController implements RestResourceControllerInterface
30
{
31
    /**
32
     * @var ValidatorInterface
33
     */
34
    private $validator;
35
36
    /**
37
     * @var RequestStack
38
     */
39
    private $requestStack;
40
41
    /**
42
     * @var RestMetadataFactory
43
     */
44
    private $metadataFactory;
45
46
    /**
47
     * @var PropertyAccessorInterface
48
     */
49
    private $propertyAccessor;
50
51
    /**
52
     * @var AuthorizationCheckerInterface
53
     */
54
    private $authorizationChecker;
55
56
    /**
57
     * @var SerializerInterface
58
     */
59
    private $serializer;
60
61 82
    public function __construct(
62
        ValidatorInterface $validator,
63
        RequestStack $requestStack,
64
        RestMetadataFactory $metadataFactory,
65
        PropertyAccessorInterface $propertyAccessor,
66
        SerializerInterface $serializer
67
    ) {
68 82
        $this->validator = $validator;
69 82
        $this->requestStack = $requestStack;
70 82
        $this->metadataFactory = $metadataFactory;
71 82
        $this->propertyAccessor = $propertyAccessor;
72 82
        $this->serializer = $serializer;
73 82
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 10
    public function listAction(Request $request)
79
    {
80 10
        $page = $request->query->get('page', 1);
81 10
        $perPage = $request->query->get('perPage', 50);
82
83 10
        $this->assertMethodGranted(Method::LIST);
84
85 6
        $listResult = $this->listEntities($page, $perPage);
86
87 6
        $response = new JsonResponse();
88
89 6
        if ($listResult instanceof Paginator) {
90 6
            $entities = iterator_to_array($listResult->getIterator());
91 6
            $total = $listResult->count();
92 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
93
        } else {
94
            $entities = $listResult;
95
        }
96
97 6
        $json = $this->getSerializer()->serialize(
98 6
            $entities,
99 6
            'json',
100
            [
101 6
                RestNormalizer::DDR_REST_INCLUDES => $this->parseIncludes($request),
102 6
                RestNormalizer::DDR_REST_DEPTH    => 0,
103 6
                RestNormalizer::DDR_REST_PATH     => ''
104
            ]
105
        );
106 6
        $response->setJson($json);
107
108 6
        return $response;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 14
    public function postAction(Request $request)
115
    {
116 14
        $this->assertMethodGranted(Method::POST);
117
118 12
        $entity = $this->serializer->deserialize(
119 12
            $request->getContent(),
120 12
            $this->getEntityClass(),
121 12
            'json',
122 12
            [RestDenormalizer::DDR_REST_METHOD => Method::POST]
123
        );
124 12
        $entity = $this->postProcessPostedEntity($entity);
125
126 12
        $errors = $this->getValidator()->validate($entity);
127 12
        if ($errors->count() > 0) {
128 2
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
129
        }
130
131 10
        $entity = $this->createEntity($entity);
132
133 10
        $response = new JsonResponse(null, Response::HTTP_CREATED);
134
135 10
        $json = $this->getSerializer()->serialize(
136 10
            $entity,
137 10
            'json',
138
            [
139 10
                RestNormalizer::DDR_REST_INCLUDES => $this->parseIncludes($request),
140 10
                RestNormalizer::DDR_REST_DEPTH    => 0,
141 10
                RestNormalizer::DDR_REST_PATH     => ''
142
            ]
143
        );
144 10
        $response->setJson($json);
145
146 10
        return $response;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 32
    public function getAction(Request $request, $id)
153
    {
154 32
        $entity = $this->fetchEntity($id);
155 30
        $this->assertMethodGranted(Method::GET, $entity);
156
157 28
        $response = new JsonResponse();
158 28
        $json = $this->getSerializer()->serialize(
159 28
            $entity,
160 28
            'json',
161
            [
162 28
                RestNormalizer::DDR_REST_INCLUDES => $this->parseIncludes($request),
163 28
                RestNormalizer::DDR_REST_DEPTH    => 0,
164 28
                RestNormalizer::DDR_REST_PATH     => ''
165
            ]
166
        );
167 28
        $response->setJson($json);
168
169 28
        return $response;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 12
    public function putAction(Request $request, $id)
176
    {
177 12
        $entity = $this->fetchEntity($id);
178 12
        $this->assertMethodGranted(Method::PUT, $entity);
179
180 10
        $entity = $this->serializer->deserialize(
181 10
            $request->getContent(),
182 10
            $this->getEntityClass(),
183 10
            'json',
184 10
            [RestDenormalizer::DDR_REST_METHOD => Method::PUT, RestDenormalizer::DDR_REST_ENTITY => $entity]
185
        );
186 10
        $entity = $this->postProcessPuttedEntity($entity);
187
188 10
        $errors = $this->getValidator()->validate($entity);
189 10
        if ($errors->count() > 0) {
190
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
191
        }
192
193 10
        $entity = $this->updateEntity($entity);
194
195 10
        $response = new JsonResponse();
196
197 10
        $json = $this->getSerializer()->serialize(
198 10
            $entity,
199 10
            'json',
200
            [
201 10
                RestNormalizer::DDR_REST_INCLUDES => $this->parseIncludes($request),
202 10
                RestNormalizer::DDR_REST_DEPTH    => 0,
203 10
                RestNormalizer::DDR_REST_PATH     => ''
204
            ]
205
        );
206 10
        $response->setJson($json);
207
208 10
        return $response;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214 4
    public function deleteAction(Request $request, $id)
215
    {
216 4
        $entity = $this->fetchEntity($id);
217 4
        $this->assertMethodGranted(Method::DELETE, $entity);
218 2
        $this->removeEntity($entity);
219
220 2
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226 6
    public function listSubresourceAction(Request $request, $id, string $subresource)
227
    {
228 6
        $page = $request->query->get('page', 1);
229 6
        $perPage = $request->query->get('perPage', 50);
230
231 6
        $entity = $this->fetchEntity($id);
232 6
        $this->assertSubResourceMethodGranted(Method::LIST, $entity, $subresource);
233
234 6
        $listResult = $this->listSubresource($entity, $subresource, $page, $perPage);
235
236 6
        $response = new JsonResponse();
237
238 6
        if ($listResult instanceof Paginator) {
239 6
            $entities = iterator_to_array($listResult->getIterator());
240 6
            $total = $listResult->count();
241 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
242
        } else {
243
            $entities = $listResult;
244
        }
245
246 6
        $json = $this->getSerializer()->serialize(
247 6
            $entities,
248 6
            'json',
249
            [
250 6
                RestNormalizer::DDR_REST_INCLUDES => $this->parseIncludes($request),
251 6
                RestNormalizer::DDR_REST_DEPTH    => 0,
252 6
                RestNormalizer::DDR_REST_PATH     => ''
253
            ]
254
        );
255 6
        $response->setJson($json);
256
257 6
        return $response;
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 6
    public function postSubresourceAction(Request $request, $id, string $subresource)
264
    {
265 6
        $parent = $this->fetchEntity($id);
266 6
        $this->assertSubResourceMethodGranted(Method::POST, $parent, $subresource);
267
268 4
        $entity = $this->getSubresourcePostedEntity($request, $subresource);
269
270 4
        $entity = $this->buildAssociation($parent, $subresource, $entity);
271 4
        $entity = $this->postProcessSubResourcePostedEntity($parent, $subresource, $entity);
272
273 4
        $errors = $this->getValidator()->validate($entity);
274
275 4
        if ($errors->count() > 0) {
276
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
277
        }
278
279 4
        $entity = $this->createAssociation($entity);
280
281 4
        $response = new JsonResponse(null, Response::HTTP_CREATED);
282 4
        $json = $this->getSerializer()->serialize(
283 4
            $entity,
284 4
            'json',
285
            [
286 4
                RestNormalizer::DDR_REST_INCLUDES => $this->parseIncludes($request),
287 4
                RestNormalizer::DDR_REST_DEPTH    => 0,
288 4
                RestNormalizer::DDR_REST_PATH     => ''
289
            ]
290
        );
291 4
        $response->setJson($json);
292
293 4
        return $response;
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299 12
    public function putSubresourceAction(Request $request, $id, string $subresource, $subId)
300
    {
301 12
        $parent = $this->fetchEntity($id);
302 12
        $this->assertSubResourceMethodGranted(Method::PUT, $parent, $subresource);
303 12
        $this->addAssociation($parent, $subresource, $subId);
304
305 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311 12
    public function deleteSubresourceAction(Request $request, $id, string $subresource, $subId = null)
312
    {
313 12
        $parent = $this->fetchEntity($id);
314 12
        $this->assertSubResourceMethodGranted(Method::DELETE, $parent, $subresource);
315 12
        $this->removeAssociation($parent, $subresource, $subId);
316
317 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
318
    }
319
320
    /**
321
     * @param object $entity
322
     *
323
     * @return object
324
     */
325 12
    protected function postProcessPostedEntity($entity)
326
    {
327 12
        return $entity;
328
    }
329
330
    /**
331
     * @param object $entity
332
     *
333
     * @return object
334
     */
335 10
    protected function postProcessPuttedEntity($entity)
336
    {
337 10
        return $entity;
338
    }
339
340
    /**
341
     * @param object $parent
342
     * @param string $subresource
343
     * @param object $entity
344
     *
345
     * @return object
346
     */
347 4
    protected function postProcessSubResourcePostedEntity($parent, $subresource, $entity)
0 ignored issues
show
The parameter $parent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $subresource is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
348
    {
349 4
        return $entity;
350
    }
351
352 82
    protected function getEntityClass()
353
    {
354 82
        return $this->getCurrentRequest()->attributes->get('_entityClass');
355
    }
356
357 4
    protected function getSubResourceEntityClass($subresource)
358
    {
359
        /** @var PropertyMetadata $propertyMetadata */
360 4
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
361
362 4
        return $propertyMetadata->getType();
363
    }
364
365 82
    protected function getCurrentRequest()
366
    {
367 82
        return $this->getRequestStack()->getCurrentRequest();
368
    }
369
370 70
    protected function assertMethodGranted(string $methodName, $entity = null)
371
    {
372 70
        $method = $this->getClassMetadata()->getMethod($methodName);
373 70
        if ($method !== null && null !== $right = $method->right) {
374 36
            $this->assertRightGranted($right, $entity);
375
        }
376 58
    }
377
378
    /**
379
     * @param string $methodName
380
     * @param object $entity
381
     * @param string $subresource
382
     */
383 32
    protected function assertSubResourceMethodGranted($methodName, $entity, string $subresource): void
384
    {
385 32
        $classMetadata = $this->getClassMetadata();
386
        /** @var PropertyMetadata $propertyMetadata */
387 32
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
388 32
        $method = $propertyMetadata->getMethod($methodName);
389 32
        if (null !== $right = $method->right) {
390 16
            $this->assertRightGranted($right, $entity);
391
        }
392 30
    }
393
394
    /**
395
     * @return ClassMetadata
396
     */
397 82
    protected function getClassMetadata()
398
    {
399 82
        $metaDataFactory = $this->getMetadataFactory();
400
        /** @var ClassMetadata $classMetaData */
401 82
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
402
403 82
        return $classMetaData;
404
    }
405
406
    protected function resolveSubject($entity, $propertyPath)
407
    {
408
        if ('this' === $propertyPath) {
409
            return $entity;
410
        }
411
        $propertyAccessor = $this->getPropertyAccessor();
412
413
        return $propertyAccessor->getValue($entity, $propertyPath);
414
    }
415
416
    /**
417
     * @param Right  $right
418
     * @param object $entity
419
     */
420 48
    protected function assertRightGranted(Right $right, $entity = null)
421
    {
422 48
        $propertyPath = $right->propertyPath;
423 48
        if (null === $propertyPath || null == $entity) {
424 48
            $this->denyAccessUnlessGranted($right->attributes);
425
        } else {
426
            $subject = $this->resolveSubject($entity, $propertyPath);
427
            $this->denyAccessUnlessGranted($right->attributes, $subject);
428
        }
429 34
    }
430
431 64
    protected function parseIncludes(Request $request)
432
    {
433 64
        $defaultIncludes = $request->attributes->get('_defaultincludes');
434 64
        if (null == $defaultIncludes) {
435 30
            $defaultIncludes = [];
436
        }
437
438 64
        $includeString = $request->query->get('include');
439 64
        if (empty($includeString)) {
440 50
            $includes = [];
441
        } else {
442 16
            $includes = explode(',', $includeString);
443
        }
444
445 64
        return array_merge($defaultIncludes, $includes);
446
    }
447
448 48
    protected function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.')
449
    {
450 48
        $authorizationChecker = $this->getAuthorizationChecker();
451 48
        if (null === $authorizationChecker) {
452
            throw new AccessDeniedException('No authorization checker configured');
453
        }
454
455 48
        if (!$authorizationChecker->isGranted($attributes, $object)) {
456 14
            throw new AccessDeniedException($message);
457
        }
458 34
    }
459
460 2
    protected function parseConstraintViolations(ConstraintViolationListInterface $errors)
461
    {
462 2
        $data = [];
463
        /** @var ConstraintViolationInterface $error */
464 2
        foreach ($errors as $error) {
465 2
            $data[] = [
466 2
                'propertyPath' => $error->getPropertyPath(),
467 2
                'message'      => $error->getMessage(),
468 2
                'value'        => $error->getInvalidValue()
469
            ];
470
        }
471
472 2
        return $data;
473
    }
474
475 12
    protected function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
476
    {
477 12
        $response->headers->add(
478
            [
479 12
                'x-pagination-current-page' => $page,
480 12
                'x-pagination-per-page'     => $perPage,
481 12
                'x-pagination-total'        => $total,
482 12
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
483
            ]
484
        );
485 12
    }
486
487 26
    protected function getValidator()
488
    {
489 26
        return $this->validator;
490
    }
491
492 82
    protected function getRequestStack()
493
    {
494 82
        return $this->requestStack;
495
    }
496
497 82
    protected function getMetadataFactory()
498
    {
499 82
        return $this->metadataFactory;
500
    }
501
502
    protected function getPropertyAccessor()
503
    {
504
        return $this->propertyAccessor;
505
    }
506
507 48
    protected function getAuthorizationChecker(): ?AuthorizationCheckerInterface
508
    {
509 48
        return $this->authorizationChecker;
510
    }
511
512 64
    protected function getSerializer(): SerializerInterface
513
    {
514 64
        return $this->serializer;
515
    }
516
517
    /**
518
     * @param AuthorizationCheckerInterface $authorizationChecker
519
     */
520 78
    public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker): void
521
    {
522 78
        $this->authorizationChecker = $authorizationChecker;
523 78
    }
524
525
    /**
526
     * @param int $page
527
     * @param int $perPage
528
     *
529
     * @return Paginator|array
530
     */
531
    abstract protected function listEntities(int $page = 1, int $perPage = 50);
532
533
    /**
534
     * @param int|string $id
535
     *
536
     * @return object
537
     *
538
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
539
     */
540
    abstract protected function fetchEntity($id);
541
542
    /**
543
     * @param object $entity
544
     *
545
     * @return object
546
     */
547
    abstract protected function createEntity($entity);
548
549
    /**
550
     * @param object $entity
551
     *
552
     * @return object
553
     *
554
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
555
     */
556
    abstract protected function updateEntity($entity);
557
558
    /**
559
     * @param $entity
560
     *
561
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
562
     */
563
    abstract protected function removeEntity($entity);
564
565
    /**
566
     * @param object $entity
567
     * @param string $subresource
568
     * @param int    $page
569
     * @param int    $perPage
570
     *
571
     * @return Paginator|array
572
     */
573
    abstract protected function listSubresource($entity, string $subresource, int $page = 1, int $perPage = 50);
574
575
    /**
576
     * @param object $parent
577
     * @param string $subresource
578
     *
579
     * @return object
580
     */
581
    abstract protected function buildAssociation($parent, string $subresource, $entity);
582
583
    /**
584
     * @param object $associatedEntity
585
     *
586
     * @return object
587
     */
588
    abstract protected function createAssociation($associatedEntity);
589
590
    /**
591
     * @param object     $parent
592
     * @param string     $subresource
593
     * @param int|string $subId
594
     *
595
     * @return object
596
     */
597
    abstract protected function addAssociation($parent, string $subresource, $subId);
598
599
    /**
600
     * @param object          $parent
601
     * @param string          $subresource
602
     * @param int|string|null $subId
603
     *
604
     * @return mixed
605
     */
606
    abstract protected function removeAssociation($parent, string $subresource, $subId = null);
607
608
    /**
609
     * @param Request $request
610
     * @param string  $subresource
611
     *
612
     * @return mixed
613
     */
614 4
    protected function getSubresourcePostedEntity(Request $request, string $subresource)
615
    {
616 4
        $content = null;
0 ignored issues
show
$content is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
617 4
        $requestParameters = $request->request->all();
618 4
        if (!empty($requestParameters)) {
619 2
            $content = json_encode($requestParameters);
620
        } else {
621 2
            $content = $request->getContent();
622
        }
623
624 4
        if (null === $content || '' === $content) {
625
            $content = '{}';
626
        }
627
628 4
        $entity = $this->serializer->deserialize(
629 4
            $content,
630 4
            $this->getSubResourceEntityClass($subresource),
631 4
            'json',
632 4
            [RestDenormalizer::DDR_REST_METHOD => Method::POST]
633
        );
634
635 4
        return $entity;
636
    }
637
}
638