Completed
Push — master ( 91de28...8580e8 )
by Tomas
02:47
created

ApiPresenter::processParams()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 5
Ratio 50 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 5
loc 10
ccs 0
cts 10
cp 0
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Tomaj\NetteApi\Presenters;
4
5
use Nette\Application\Responses\JsonResponse;
6
use Nette\Application\UI\Presenter;
7
use Nette\Http\Response;
8
use Tomaj\NetteApi\ApiDecider;
9
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
10
use Tomaj\NetteApi\Handlers\ApiHandlerInterface;
11
use Tomaj\NetteApi\Logger\ApiLoggerInterface;
12
use Tomaj\NetteApi\Misc\IpDetectorInterface;
13
use Tomaj\NetteApi\Params\ParamsProcessor;
14
use Tomaj\NetteApi\Response\JsonApiResponse;
15
use Exception;
16
17
/**
18
 * @property-read \Nette\DI\Container $context
19
 */
20
class ApiPresenter extends Presenter
21
{
22
    /**
23
     * @var  ApiDecider @inject
24
     */
25
    public $apiDecider;
26
27
    /**
28
     * @var  IpDetectorInterface @inject
29
     */
30
    public $ipDetector;
31
32
    /**
33
     * CORS header settings
34
     *
35
     * Available values:
36
     *   'auto'  - send back header Access-Control-Allow-Origin with domain that made request
37
     *   '*'     - send header with '*' - this will workf fine if you dont need to send cookies via ajax calls to api
38
     *             with jquery $.ajax with xhrFields: { withCredentials: true } settings
39
     *   'off'   - will not send any CORS header
40
     *   other   - any other value will be send in Access-Control-Allow-Origin header
41
     *
42
     * @var string
43
     */
44
    protected $corsHeader = '*';
45
46
    /**
47
     * Presenter startup method
48
     *
49
     * @return void
50
     */
51
    public function startup()
52
    {
53
        parent::startup();
54
        $this->autoCanonicalize = false;
55
    }
56
57
    /**
58
     * Set cors header
59
     *
60
     * See description to property $corsHeader for valid inputs
61
     *
62
     * @param string $corsHeader
63
     */
64
    public function setCorsHeader($corsHeader)
65
    {
66
        $this->corsHeader = $corsHeader;
67
    }
68
69
    /**
70
     * Nette render default method
71
     *
72
     * @return void
73
     */
74
    public function renderDefault()
75
    {
76
        $start = microtime(true);
77
78
        $this->sendCorsHeaders();
79
80
        $hand = $this->getHandler();
81
        $handler = $hand['handler'];
82
        $authorization = $hand['authorization'];
83
84
        if ($this->checkAuth($authorization) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
85
            return;
86
        }
87
88
        $params = $this->processParams($handler);
89
        if ($params === false) {
90
            return;
91
        }
92
93
        try {
94
            $response = $handler->handle($params);
95
            $code = $response->getCode();
96
        } catch (Exception $exception) {
97
            $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
98
            $code = $response->getCode();
99
        }
100
101
        $end = microtime(true);
102
103
        if ($this->context->hasService('apiLogger')) {
104
            $this->logRequest($this->context->getService('apiLogger'), $code, $end - $start);
105
        }
106
107
        // output to nette
108
        $this->getHttpResponse()->setCode($code);
109
        $this->sendResponse($response);
110
    }
111
112
    /**
113
     * Get handler information triplet (endpoint, handler, authorization)
114
     *
115
     * @return array
116
     */
117
    private function getHandler()
118
    {
119
        return $this->apiDecider->getApiHandler(
120
            $this->getRequest()->getMethod(),
121
            $this->params['version'],
122
            $this->params['package'],
123
            $this->params['apiAction']
124
        );
125
    }
126
127
    /**
128
     * Check authorization
129
     *
130
     * @param ApiAuthorizationInterface  $authorization
131
     *
132
     * @return bool
133
     */
134
    private function checkAuth(ApiAuthorizationInterface $authorization)
135
    {
136 View Code Duplication
        if (!$authorization->authorized()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
            $this->getHttpResponse()->setCode(Response::S403_FORBIDDEN);
138
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]));
139
            return false;
140
        }
141
        return true;
142
    }
143
144
    /**
145
     * Process input parameters
146
     *
147
     * @param ApiHandlerInterface   $handler
148
     *
149
     * @return array|bool
150
     */
151
    private function processParams(ApiHandlerInterface $handler)
152
    {
153
        $paramsProcessor = new ParamsProcessor($handler->params());
154 View Code Duplication
        if ($paramsProcessor->isError()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
156
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
157
            return false;
158
        }
159
        return $paramsProcessor->getValues();
160
    }
161
162
    /**
163
     * Log request
164
     *
165
     * @param ApiLoggerInterface  $logger
166
     * @param integer             $code
167
     * @param double              $elapsed
168
     *
169
     * @return void
170
     */
171
    private function logRequest(ApiLoggerInterface $logger, $code, $elapsed)
172
    {
173
        $headers = [];
174
        if (function_exists('getallheaders')) {
175
            $headers = getallheaders();
176
        }
177
178
        $requestHeaders = '';
179
        foreach ($headers as $key => $value) {
180
            $requestHeaders .= "$key: $value\n";
181
        }
182
183
        $logger->log(
184
            $code,
185
            $this->getRequest()->getMethod(),
186
            $requestHeaders,
187
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
188
            $this->ipDetector->getRequestIp(),
189
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
190
            ($elapsed) * 1000
191
        );
192
    }
193
194
    protected function sendCorsHeaders()
195
    {
196
        if ($this->corsHeader == 'auto') {
197
            $domain = $this->getRequestDomain();
198
            if ($domain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $domain of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
199
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
200
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
201
            }
202
            return;
203
        }
204
205
        if ($this->corsHeader == '*') {
206
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
207
            return;
208
        }
209
210
        if ($this->corsHeader != 'off') {
211
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
212
        }
213
    }
214
215
    private function getRequestDomain()
216
    {
217
        if (filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
218
            $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
219
            if (isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host'])) {
220
                return $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
221
            }
222
        }
223
        return null;
224
    }
225
}
226