Issues (126)

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.

src/Http/Rescuer.php (6 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
declare(strict_types=1);
3
/**
4
 * Minotaur
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
 * use this file except in compliance with the License. You may obtain a copy of
8
 * the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 * License for the specific language governing permissions and limitations under
16
 * the License.
17
 *
18
 * @copyright 2015-2017 Appertly
19
 * @license   Apache-2.0
20
 */
21
namespace Minotaur\Http;
22
23
use Psr\Http\Message\ServerRequestInterface as Request;
24
use Psr\Http\Message\ResponseInterface as Response;
25
use Caridea\Http\ProblemDetails;
26
27
/**
28
 * A pretty basic contingency plan.
29
 *
30
 * Under normal circumstances, this class will simply return the response given
31
 * by the `$next` function. In the event that an Exception occurred in the
32
 * `$next` function, this class will craft a new response containing details
33
 * about the error itself.
34
 */
35
class Rescuer implements \Minotaur\Route\Plugin
36
{
37
    /**
38
     * @var bool Whether to include exception information in responses
39
     */
40
    protected $debug;
41
    /**
42
     * @var string The class name of the XHP to render
43
     */
44
    protected $xhpClass;
45
46
    /**
47
     * Convenient map of HTTP status codes to human-readable explanations
48
     */
49
    protected const MESSAGES = [
50
        403 => "You are not allowed to perform this action.",
51
        404 => "We don't have anything at this URL. Double-check the URL you requested.",
52
        405 => "You can't use that HTTP method for this URL. Check the Allow response header for the ones you can.",
53
        406 => "We don't have any content available in the MIME type you specified in your Accept header. Try specifying additional MIME types.",
54
        422 => "There was a problem with the data you submitted. Review the messages for each field and try again.",
55
        423 => "This data is locked. You have permission, but it is no longer allowed to be changed.",
56
        500 => "It looks like we have a problem on our end! Our staff has been notified. Please try again later."
57
    ];
58
59
    /**
60
     * Creates a new Contingency.
61
     *
62
     * The following options are available:
63
     * * `debug` – Whether to include exception stack trace information (*should be `false` in production!*). Defaults to `false`.
64
     * * `xhpClass` – The class name of XHP to render (must be `xhp_class_name` format). Defaults to `xhp__labrys__error_page`.
65
     *
66
     * @param array<string,mixed> $options The options
67
     */
68 10
    public function __construct(array $options = [])
69
    {
70 10
        $this->debug = (bool)($options['debug'] ?? false);
71 10
        $c = (string)($options['xhpClass'] ?? 'labrys_error_page');
72 10
        if (!is_subclass_of($c, \Minotaur\Tags\Primitive::class)) {
0 ignored issues
show
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Minotaur\Tags\Primitive::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
73
            throw new \InvalidArgumentException("Class given in xhpClass option '$c' does not extend \Minotaur\Tags\Node");
74
        }
75 10
        $this->xhpClass = $c;
76 10
    }
77
78
    /**
79
     * Gets the plugin priority; larger means first.
80
     *
81
     * @return - The plugin priority
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
82
     */
83
    public function getPriority(): int
84
    {
85
        return PHP_INT_MAX;
86
    }
87
88
    /**
89
     * Middleware request–response handling.
90
     *
91
     * @param $request - The server request
92
     * @param $response - The response
93
     * @param callable $next - The next layer. (function (Request,Response): Response)
94
     * @return - The response
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
95
     */
96 10
    public function __invoke(Request $request, Response $response, callable $next): Response
97
    {
98
        try {
99 10
            return $next($request, $response);
100 10
        } catch (\Exception $e) {
101 10
            return $this->process($request, $response, $e);
102
        }
103
    }
104
105
    /**
106
     * Handles an exception.
107
     *
108
     * This is your chance for logging, changing the HTTP status header, and
109
     * rendering some kind of message for the client.
110
     *
111
     * @param $request - The request
112
     * @param $response - The response
113
     * @param $e - The exception to process
114
     * @return - The new response
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
115
     */
116 10
    public function process(Request $request, Response $response, \Exception $e) : Response
117
    {
118 10
        if ($e instanceof \Minotaur\Route\Exception\Unroutable) {
119 3
            $response = $response->withStatus($e->getCode(), $e->getMessage());
120 3
            foreach ($e->getHeaders() as $k => $v) {
121 3
                $response = $response->withHeader($k, $v);
122
            }
123 7
        } elseif ($e instanceof \Caridea\Acl\Exception\Forbidden) {
124 1
            $response = $response->withStatus(403, 'Forbidden');
125 6
        } elseif ($e instanceof \Caridea\Dao\Exception\Unretrievable ||
126 6
            $e instanceof \Caridea\Acl\Exception\Unloadable) {
127 1
            $response = $response->withStatus(404, 'Not Found');
128 5
        } elseif ($e instanceof \Caridea\Dao\Exception\Conflicting ||
129 5
            $e instanceof \Caridea\Dao\Exception\Duplicative) {
130 2
            $response = $response->withStatus(409, 'Conflict');
131 3
        } elseif ($e instanceof \Caridea\Validate\Exception\Invalid) {
132 1
            $response = $response->withStatus(422, 'Unprocessable Entity');
133 2
        } elseif ($e instanceof \Caridea\Dao\Exception\Locked) {
134 1
            $response = $response->withStatus(423, 'Locked');
135
        } else {
136 1
            $response = $response->withStatus(500, 'Internal Server Error');
137
        }
138 10
        $values = $this->getValues($request, $e);
139 10
        $types = new \Caridea\Http\AcceptTypes($request->getServerParams());
140 10
        switch ($types->preferred(['application/json', ProblemDetails::MIME_TYPE_JSON, 'text/html'])) {
141
            /* HH_IGNORE_ERROR[4110]: Not sure why hh_client has a problem with this */
142 10
            case ProblemDetails::MIME_TYPE_JSON:
0 ignored issues
show
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
143
            /* HH_IGNORE_ERROR[4110]: Not sure why hh_client has a problem with this */
144 10
            case 'application/json':
145 8
                $response = $response->withHeader('Content-Type', ProblemDetails::MIME_TYPE_JSON);
146 8
                $response->getBody()->write((string)$this->renderJson($values));
147 8
                break;
148
            default:
149 2
                $response = $response->withHeader('Content-Type', 'text/html');
150 2
                $response->getBody()->write((string)$this->renderHtml($values));
151
        }
152 10
        return $response;
153
    }
154
155
    /**
156
     * Assembles the values from the Request and Exception.
157
     *
158
     * @param $request - The request
159
     * @param $e - The Exception
160
     * @return array<string,mixed> The assembled values
161
     */
162 10
    protected function getValues(Request $request, \Exception $e): array
0 ignored issues
show
The parameter $request 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...
163
    {
164 10
        $values = [];
165 10
        $extra = ['success' => false];
166 10
        if ($this->debug) {
167 10
            $extra['exception'] = $this->getStackTrace($e);
168
        }
169 10
        if ($e instanceof \Minotaur\Route\Exception\Unroutable) {
170 3
            $code = $e->getCode();
171 3
            $values['title'] = $e->getMessage();
172 3
            $values['status'] = $code;
173 3
            $values['detail'] = self::MESSAGES[$code];
174 7
        } elseif ($e instanceof \Caridea\Acl\Exception\Forbidden) {
175 1
            $values['title'] = 'Access Denied';
176 1
            $values['status'] = 403;
177 1
            $values['detail'] = self::MESSAGES[403];
178 6
        } elseif ($e instanceof \Caridea\Dao\Exception\Unretrievable ||
179 6
            $e instanceof \Caridea\Acl\Exception\Unloadable) {
180 1
            $values['title'] = 'Resource Not Found';
181 1
            $values['status'] = 404;
182 1
            $values['detail'] = self::MESSAGES[404];
183 5
        } elseif ($e instanceof \Caridea\Dao\Exception\Duplicative) {
184 1
            $values['title'] = 'Constraint Violation';
185 1
            $values['status'] = 409;
186 1
            $values['detail'] = 'The data you submitted violates unique constraints. Most likely, this is a result of an existing record with similar data. Double-check existing records and try again.';
187 4
        } elseif ($e instanceof \Caridea\Dao\Exception\Conflicting) {
188 1
            $values['title'] = 'Concurrent Modification';
189 1
            $values['status'] = 409;
190 1
            $values['detail'] = 'Someone else saved changes to this same data while you were editing. Try your request again using the latest copy of the record.';
191 3
        } elseif ($e instanceof \Caridea\Validate\Exception\Invalid) {
192 1
            $values['title'] = 'Data Validation Failure';
193 1
            $values['status'] = 422;
194 1
            $values['detail'] = self::MESSAGES[422];
195 1
            $errors = [];
196 1
            foreach ($e->getErrors() as $field => $code) {
197 1
                $errors[] = ['field' => $field, 'code' => $code];
198
            }
199 1
            $extra['errors'] = $errors;
200 2
        } elseif ($e instanceof \Caridea\Dao\Exception\Locked) {
201 1
            $values['title'] = 'Resource Locked';
202 1
            $values['status'] = 423;
203 1
            $values['detail'] = self::MESSAGES[423];
204
        } else {
205 1
            $values['title'] = 'Internal Server Error';
206 1
            $values['status'] = 500;
207 1
            $values['detail'] = self::MESSAGES[500];
208
        }
209 10
        $values['extra'] = $extra;
210 10
        return $values;
211
    }
212
213
    /**
214
     * Gets an exception stack trace as a string, including nested exceptions.
215
     *
216
     * @param $e - The exception
217
     * @return array<string,string> The full stack trace
218
     */
219 10
    private function getStackTrace(\Exception $e): array
220
    {
221
        $details = [
222 10
            'class' => get_class($e),
223 10
            'message' => $e->getMessage(),
224 10
            'stack' => $e->getTraceAsString()
225
        ];
226 10
        if ($e->getPrevious() !== null) {
227 1
            $details['previous'] = $this->getStackTrace($e->getPrevious());
228
        }
229 10
        return $details;
230
    }
231
232
    /**
233
     * Returns the ProblemDetails to render.
234
     *
235
     * @param array<string,mixed> $values The values
236
     * @return ProblemDetails The JSON response
237
     */
238 8
    protected function renderJson(array $values): ProblemDetails
239
    {
240 8
        $extra = $values['extra'] ?? [];
241 8
        return new ProblemDetails(
242 8
            null,
243 8
            (string) $values['title'],
244 8
            (int) $values['status'],
245 8
            (string) $values['detail'],
246 8
            null,
247 8
            $extra
248
        );
249
    }
250
251
    /**
252
     * Returns the HTML to render.
253
     *
254
     * @param array<string,mixed> $values The values
255
     * @return \Minotaur\Tags\Node The HTML response
256
     */
257 2
    protected function renderHtml(array $values): \Minotaur\Tags\Node
258
    {
259 2
        return \Minotaur\Tags\fcomposited($this->xhpClass, ['values' => $values]);
260
    }
261
}
262