Issues (197)

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/Schema/Schema.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 declare (strict_types = 1);
2
3
namespace Limoncello\Flute\Schema;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Contracts\Application\ModelInterface;
22
use Limoncello\Contracts\Data\ModelSchemaInfoInterface;
23
use Limoncello\Contracts\Data\RelationshipTypes;
24
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
25
use Limoncello\Flute\Contracts\Schema\JsonSchemasInterface;
26
use Limoncello\Flute\Contracts\Schema\SchemaInterface;
27
use Limoncello\Flute\Contracts\Validation\JsonApiQueryParserInterface;
28
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
29
use Neomerx\JsonApi\Contracts\Schema\DocumentInterface;
30
use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
31
use Neomerx\JsonApi\Schema\BaseSchema;
32
use Neomerx\JsonApi\Schema\Identifier;
33
use function array_key_exists;
34
use function assert;
35
use function http_build_query;
36
use function property_exists;
37
38
/**
39
 * @package Limoncello\Flute
40
 */
41
abstract class Schema extends BaseSchema implements SchemaInterface
42
{
43
    /**
44
     * @var ModelSchemaInfoInterface
45
     */
46
    private $modelSchemas;
47
48
    /**
49
     * @var JsonSchemasInterface
50
     */
51
    private $jsonSchemas;
52
53
    /**
54
     * @var array|null
55
     */
56
    private $attributesMapping;
57
58
    /**
59
     * @var array|null
60
     */
61
    private $relationshipsMapping;
62
63
    /**
64 28
     * @param FactoryInterface         $factory
65
     * @param JsonSchemasInterface     $jsonSchemas
66
     * @param ModelSchemaInfoInterface $modelSchemas
67
     */
68
    public function __construct(
69 28
        FactoryInterface $factory,
70 28
        JsonSchemasInterface $jsonSchemas,
71
        ModelSchemaInfoInterface $modelSchemas
72 28
    ) {
73
        assert(empty(static::TYPE) === false);
74 28
        assert(empty(static::MODEL) === false);
75 28
76
        parent::__construct($factory);
77 28
78 28
        $this->modelSchemas = $modelSchemas;
79
        $this->jsonSchemas  = $jsonSchemas;
80
81
        $this->attributesMapping    = null;
82
        $this->relationshipsMapping = null;
83
    }
84 19
85
    /**
86 19
     * @inheritdoc
87
     */
88
    public function getType(): string
89
    {
90
        return static::TYPE;
91
    }
92 14
93
    /**
94 14
     * @inheritdoc
95
     */
96
    public static function getAttributeMapping(string $jsonName): string
97
    {
98
        return static::getMappings()[static::SCHEMA_ATTRIBUTES][$jsonName];
99
    }
100 12
101
    /**
102 12
     * @inheritdoc
103
     */
104
    public static function getRelationshipMapping(string $jsonName): string
105
    {
106
        return static::getMappings()[static::SCHEMA_RELATIONSHIPS][$jsonName];
107
    }
108 21
109
    /**
110 21
     * @inheritdoc
111
     */
112
    public static function hasAttributeMapping(string $jsonName): bool
113 21
    {
114 21
        $mappings = static::getMappings();
115
116
        return
117
            array_key_exists(static::SCHEMA_ATTRIBUTES, $mappings) === true &&
118
            array_key_exists($jsonName, $mappings[static::SCHEMA_ATTRIBUTES]) === true;
119
    }
120 15
121
    /**
122 15
     * @inheritdoc
123
     */
124
    public static function hasRelationshipMapping(string $jsonName): bool
125 15
    {
126 15
        $mappings = static::getMappings();
127
128
        return
129
            array_key_exists(static::SCHEMA_RELATIONSHIPS, $mappings) === true &&
130
            array_key_exists($jsonName, $mappings[static::SCHEMA_RELATIONSHIPS]) === true;
131
    }
132 16
133
    /**
134 16
     * @inheritdoc
135 16
     */
136 16
    public function getAttributes($model): iterable
137
    {
138
        foreach ($this->getAttributesMapping() as $jsonAttrName => $modelAttrName) {
139
            if ($this->hasProperty($model, $modelAttrName) === true) {
140
                yield $jsonAttrName => $this->getProperty($model, $modelAttrName);
141
            }
142
        }
143
    }
144
145
    /**
146 19
     * @inheritdoc
147
     *
148 19
     * @SuppressWarnings(PHPMD.ElseExpression)
149
     */
150 19
    public function getRelationships($model): iterable
151
    {
152 19
        assert($model instanceof ModelInterface);
153 4
154 4
        foreach ($this->getRelationshipsMapping() as $jsonRelName => [$modelRelName, $belongsToFkName, $reverseType]) {
155 4
            // if model has relationship data then use it
156 4
            if ($this->hasProperty($model, $modelRelName) === true) {
157
                yield $jsonRelName => $this->createRelationshipRepresentationFromData(
158 4
                    $model,
159
                    $modelRelName,
0 ignored issues
show
The variable $modelRelName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
160
                    $jsonRelName
161
                );
162 18
                continue;
163 15
            }
164 15
165 15
            // if relationship is `belongs-to` and has that ID we can add relationship as identifier
166
            if ($belongsToFkName !== null && $this->hasProperty($model, $belongsToFkName) === true) {
167
                $reverseIndex = $this->getProperty($model, $belongsToFkName);
0 ignored issues
show
The variable $belongsToFkName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
168 15
                $identifier   = $reverseIndex === null ?
169 15
                    null : new Identifier((string)$reverseIndex, $reverseType, false, null);
0 ignored issues
show
The variable $reverseType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
170
171 15
                yield $jsonRelName => [
172
                    static::RELATIONSHIP_DATA       => $identifier,
173
                    static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName),
174
                ];
175 18
                continue;
176
            }
177
178
            // if we are here it's nothing left but show relationship as a link
179
            yield $jsonRelName => [static::RELATIONSHIP_LINKS_SELF => true];
180
        }
181
    }
182 17
183
    /**
184 17
     * @inheritdoc
185
     */
186
    public function isAddSelfLinkInRelationshipWithData(string $relationshipName): bool
187
    {
188
        return false;
189
    }
190 19
191
    /**
192 19
     * @return ModelSchemaInfoInterface
193
     */
194
    protected function getModelSchemas(): ModelSchemaInfoInterface
195
    {
196
        return $this->modelSchemas;
197
    }
198 17
199
    /**
200 17
     * @return JsonSchemasInterface
201
     */
202
    protected function getJsonSchemas(): JsonSchemasInterface
203
    {
204
        return $this->jsonSchemas;
205
    }
206
207
    /**
208
     * @param ModelInterface $model
209
     * @param string         $modelRelName
210
     * @param string         $jsonRelName
211
     *
212 4
     * @return array
213
     *
214
     * @SuppressWarnings(PHPMD.ElseExpression)
215
     */
216
    protected function createRelationshipRepresentationFromData(
217 4
        ModelInterface $model,
218 4
        string $modelRelName,
219 4
        string $jsonRelName
220
    ): array {
221 4
        assert($this->hasProperty($model, $modelRelName) === true);
222
        $relationshipData = $this->getProperty($model, $modelRelName);
223 4
        $isPaginatedData  = $relationshipData instanceof PaginatedDataInterface;
224 2
225
        $description = [static::RELATIONSHIP_LINKS_SELF => $this->isAddSelfLinkInRelationshipWithData($jsonRelName)];
226 2
227
        if ($isPaginatedData === false) {
228
            $description[static::RELATIONSHIP_DATA] = $relationshipData;
229 3
230
            return $description;
231 3
        }
232
233 3
        assert($relationshipData instanceof PaginatedDataInterface);
234 2
235
        $description[static::RELATIONSHIP_DATA] = $relationshipData->getData();
236
237
        if ($relationshipData->hasMoreItems() === false) {
238 2
            return $description;
239 2
        }
240 2
241
        // if we are here then relationship contains paginated data, so we have to add pagination links
242
        $offset    = $relationshipData->getOffset();
243 2
        $limit     = $relationshipData->getLimit();
244 2
        $urlPrefix = $this->getRelationshipSelfSubUrl($model, $jsonRelName) . '?';
245
        $buildLink = function (int $offset, int $limit) use ($urlPrefix) : LinkInterface {
246
            $paramsWithPaging = [
247 2
                JsonApiQueryParserInterface::PARAM_PAGING_OFFSET => $offset,
248
                JsonApiQueryParserInterface::PARAM_PAGING_LIMIT  => $limit,
249 2
            ];
250 2
251
            $subUrl = $urlPrefix . http_build_query($paramsWithPaging);
252 2
253 2
            return $this->getFactory()->createLink(true, $subUrl, false);
254 2
        };
255 1
256 1
        $nextOffset = $offset + $limit;
257
        $nextLimit  = $limit;
258
        if ($offset <= 0) {
259 1
            $description[static::RELATIONSHIP_LINKS] = [
260 1
                DocumentInterface::KEYWORD_NEXT => $buildLink($nextOffset, $nextLimit),
261
            ];
262 1
        } else {
263 1
            $prevOffset = $offset - $limit;
264
            if ($prevOffset < 0) {
265 1
                // set offset 0 and decrease limit
266
                $prevLimit  = $limit + $prevOffset;
267 1
                $prevOffset = 0;
268 1
            } else {
269 1
                $prevLimit = $limit;
270
            }
271
            $description[static::RELATIONSHIP_LINKS] = [
272
                DocumentInterface::KEYWORD_PREV => $buildLink($prevOffset, $prevLimit),
273 2
                DocumentInterface::KEYWORD_NEXT => $buildLink($nextOffset, $nextLimit),
274
            ];
275
        }
276
277
        return $description;
278
    }
279 16
280
    /**
281 16
     * @param ModelInterface $model
282 10
     * @param string         $name
283
     *
284
     * @return bool
285 16
     */
286
    protected function hasProperty(ModelInterface $model, string $name): bool
287
    {
288 16
        $hasRelationship = property_exists($model, $name) || isset($model->{$name});
289
290 16
        return $hasRelationship;
291
    }
292 16
293
    /**
294
     * @param ModelInterface $model
295
     * @param string         $name
296
     *
297
     * @return mixed
298 19
     */
299
    protected function getProperty(ModelInterface $model, string $name)
300 19
    {
301 12
        assert($this->hasProperty($model, $name));
302
303
        return $model->{$name};
304 19
    }
305 19
306 19
    /**
307 19
     * @return array
308
     */
309 19
    private function getAttributesMapping(): array
310 19
    {
311 17
        if ($this->attributesMapping !== null) {
312 17
            return $this->attributesMapping;
313 17
        }
314 17
315
        $attributesMapping = static::getMappings()[static::SCHEMA_ATTRIBUTES] ?? [];
316
317 19
        // `id` is a `special` attribute and cannot be included in JSON API resource
318
        unset($attributesMapping[static::RESOURCE_ID]);
319
320 19
        $this->attributesMapping = $attributesMapping;
321
322 19
        return $this->attributesMapping;
323
    }
324
325
    /**
326
     * @return array
327
     */
328
    private function getRelationshipsMapping(): array
329
    {
330
        if ($this->relationshipsMapping !== null) {
331 19
            return $this->relationshipsMapping;
332
        }
333 19
334
        $relationshipsMapping = [];
335 19
        foreach (static::getMappings()[static::SCHEMA_RELATIONSHIPS] ?? [] as $jsonRelName => $modelRelName) {
336
            $belongsToFkName = null;
337
            $reverseJsonType = null;
338
339
            $relType = $this->getModelSchemas()->getRelationshipType(static::MODEL, $modelRelName);
340
            if ($relType === RelationshipTypes::BELONGS_TO) {
341
                $belongsToFkName = $this->getModelSchemas()->getForeignKey(static::MODEL, $modelRelName);
342
                $reverseSchema   = $this->getJsonSchemas()
343
                    ->getRelationshipSchema(static::class, $jsonRelName);
344 2
                $reverseJsonType = $reverseSchema->getType();
345
            }
346 2
347
            $relationshipsMapping[$jsonRelName] = [$modelRelName, $belongsToFkName, $reverseJsonType];
348
        }
349
350
        $this->relationshipsMapping = $relationshipsMapping;
351
352
        return $this->relationshipsMapping;
353
    }
354
355
    /**
356
     * @param ModelInterface $model
357
     * @param string         $jsonRelName
358
     *
359
     * @return string
360
     */
361
    private function getRelationshipSelfSubUrl(ModelInterface $model, string $jsonRelName): string
362
    {
363
        return $this->getSelfSubUrl($model) . '/' . DocumentInterface::KEYWORD_RELATIONSHIPS . '/' . $jsonRelName;
364
    }
365
}
366