Completed
Push — master ( fcda7e...e0250b )
by Tomas
02:45
created

ApiPresenter::logRequest()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 22
ccs 0
cts 18
cp 0
rs 9.2
cc 3
eloc 15
nc 4
nop 3
crap 12
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 Tracy\Debugger;
16
use Exception;
17
18
/**
19
 * @property-read \Nette\DI\Container $context
20
 */
21
class ApiPresenter extends Presenter
22
{
23
    /**
24
     * @var  ApiDecider @inject
25
     */
26
    public $apiDecider;
27
28
    /**
29
     * @var  IpDetectorInterface @inject
30
     */
31
    public $ipDetector;
32
33
    /**
34
     * CORS header settings
35
     *
36
     * Available values:
37
     *   'auto'  - send back header Access-Control-Allow-Origin with domain that made request
38
     *   '*'     - send header with '*' - this will workf fine if you dont need to send cookies via ajax calls to api
39
     *             with jquery $.ajax with xhrFields: { withCredentials: true } settings
40
     *   'off'   - will not send any CORS header
41
     *   other   - any other value will be send in Access-Control-Allow-Origin header
42
     *
43
     * @var string
44
     */
45
    protected $corsHeader = '*';
46
47
    /**
48
     * Presenter startup method
49
     *
50
     * @return void
51
     */
52 6
    public function startup()
53
    {
54 6
        parent::startup();
55 6
        $this->autoCanonicalize = false;
56 6
    }
57
58
    /**
59
     * Set cors header
60
     *
61
     * See description to property $corsHeader for valid inputs
62
     *
63
     * @param string $corsHeader
64
     */
65
    public function setCorsHeader($corsHeader)
66
    {
67
        $this->corsHeader = $corsHeader;
68
    }
69
70
    /**
71
     * Nette render default method
72
     *
73
     * @return void
74
     */
75 6
    public function renderDefault()
76
    {
77 6
        $start = microtime(true);
78
79 6
        $this->sendCorsHeaders();
80
81 6
        $hand = $this->getHandler();
82 6
        $handler = $hand['handler'];
83 6
        $authorization = $hand['authorization'];
84
85 6
        if ($this->checkAuth($authorization) === false) {
86
            return;
87
        }
88
89 3
        $params = $this->processParams($handler);
90
        if ($params === false) {
91
            return;
92
        }
93
94
        try {
95
            $response = $handler->handle($params);
96
            $code = $response->getCode();
97
        } catch (Exception $exception) {
98
            $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
99
            $code = $response->getCode();
100
            Debugger::log($exception, Debugger::EXCEPTION);
101
        }
102
103
        $end = microtime(true);
104
105
        if ($this->context->hasService('apiLogger')) {
106
            $this->logRequest($this->context->getService('apiLogger'), $code, $end - $start);
107
        }
108
109
        // output to nette
110
        $this->getHttpResponse()->setCode($code);
111
        $this->sendResponse($response);
112
    }
113
114
    /**
115
     * Get handler information triplet (endpoint, handler, authorization)
116
     *
117
     * @return array
118
     */
119 6
    private function getHandler()
120
    {
121 6
        return $this->apiDecider->getApiHandler(
122 6
            $this->getRequest()->getMethod(),
123 6
            $this->params['version'],
124 6
            $this->params['package'],
125 6
            $this->params['apiAction']
126 6
        );
127
    }
128
129
    /**
130
     * Check authorization
131
     *
132
     * @param ApiAuthorizationInterface  $authorization
133
     *
134
     * @return bool
135
     */
136 6
    private function checkAuth(ApiAuthorizationInterface $authorization)
137
    {
138 6 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...
139 3
            $this->getHttpResponse()->setCode(Response::S403_FORBIDDEN);
140 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]));
141
            return false;
142
        }
143 3
        return true;
144
    }
145
146
    /**
147
     * Process input parameters
148
     *
149
     * @param ApiHandlerInterface   $handler
150
     *
151
     * @return array|bool
152
     */
153 3
    private function processParams(ApiHandlerInterface $handler)
154
    {
155 3
        $paramsProcessor = new ParamsProcessor($handler->params());
156 3 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...
157 3
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
158 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
159
            return false;
160
        }
161
        return $paramsProcessor->getValues();
162
    }
163
164
    /**
165
     * Log request
166
     *
167
     * @param ApiLoggerInterface  $logger
168
     * @param integer             $code
169
     * @param double              $elapsed
170
     *
171
     * @return void
172
     */
173
    private function logRequest(ApiLoggerInterface $logger, $code, $elapsed)
174
    {
175
        $headers = [];
176
        if (function_exists('getallheaders')) {
177
            $headers = getallheaders();
178
        }
179
180
        $requestHeaders = '';
181
        foreach ($headers as $key => $value) {
182
            $requestHeaders .= "$key: $value\n";
183
        }
184
185
        $logger->log(
186
            $code,
187
            $this->getRequest()->getMethod(),
188
            $requestHeaders,
189
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
190
            $this->ipDetector->getRequestIp(),
191
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
192
            ($elapsed) * 1000
193
        );
194
    }
195
196 6
    protected function sendCorsHeaders()
197
    {
198 6
        if ($this->corsHeader == 'auto') {
199
            $domain = $this->getRequestDomain();
200
            if ($domain !== false) {
201
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
202
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
203
            }
204
            return;
205
        }
206
207 6
        if ($this->corsHeader == '*') {
208 6
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
209 6
            return;
210
        }
211
212
        if ($this->corsHeader != 'off') {
213
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
214
        }
215
    }
216
217
    private function getRequestDomain()
218
    {
219
        if (filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
220
            $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
221
            if (isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host'])) {
222
                $url = $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
223
                if (isset($refererParsedUrl['port']) && $refererParsedUrl['port'] !== 80) {
224
                    $url .= ':' . $refererParsedUrl['port'];
225
                }
226
                return $url;
227
            }
228
        }
229
        return false;
230
    }
231
}
232