Completed
Pull Request — master (#61)
by Michal
03:22
created

ApiPresenter::sendCorsHeaders()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 9.664

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 6
cts 14
cp 0.4286
rs 9.2568
c 0
b 0
f 0
cc 5
nc 5
nop 0
crap 9.664
1
<?php
2
3
namespace Tomaj\NetteApi\Presenters;
4
5
use Exception;
6
use Nette\Application\Responses\JsonResponse;
7
use Nette\Application\UI\Presenter;
8
use Nette\DI\Container;
9
use Nette\Http\Response;
10
use Tomaj\NetteApi\ApiDecider;
11
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
12
use Tomaj\NetteApi\Handlers\ApiHandlerInterface;
13
use Tomaj\NetteApi\Api;
14
use Tomaj\NetteApi\Logger\ApiLoggerInterface;
15
use Tomaj\NetteApi\Params\ParamsProcessor;
16
use Tomaj\NetteApi\Response\JsonApiResponse;
17
use Tracy\Debugger;
18
19
/**
20
 * @property-read Container $context
21
 */
22
class ApiPresenter extends Presenter
23
{
24
    /**
25
     * @var  ApiDecider @inject
26
     */
27
    public $apiDecider;
28
29
    /**
30
     * CORS header settings
31
     *
32
     * Available values:
33
     *   'auto'  - send back header Access-Control-Allow-Origin with domain that made request
34
     *   '*'     - send header with '*' - this will workf fine if you dont need to send cookies via ajax calls to api
35
     *             with jquery $.ajax with xhrFields: { withCredentials: true } settings
36
     *   'off'   - will not send any CORS header
37
     *   other   - any other value will be send in Access-Control-Allow-Origin header
38
     *
39
     * @var string
40
     */
41
    protected $corsHeader = '*';
42
43
    /**
44
     * Presenter startup method
45
     *
46
     * @return void
47
     */
48 9
    public function startup()
49
    {
50 9
        parent::startup();
51 9
        $this->autoCanonicalize = false;
52 9
    }
53
54
    /**
55
     * Set cors header
56
     *
57
     * See description to property $corsHeader for valid inputs
58
     *
59
     * @param string $corsHeader
60
     */
61
    public function setCorsHeader($corsHeader)
62
    {
63
        $this->corsHeader = $corsHeader;
64
    }
65
66
    /**
67
     * Nette render default method
68
     *
69
     * @return void
70
     */
71 9
    public function renderDefault()
72
    {
73 9
        $start = microtime(true);
74
75 9
        $this->sendCorsHeaders();
76
77 9
        $api = $this->getApi();
78 9
        $handler = $api->getHandler();
79 9
        $authorization = $api->getAuthorization();
80
81 9
        if ($this->checkAuth($authorization) === false) {
82
            return;
83
        }
84
85 6
        $params = $this->processParams($handler);
86 3
        if ($params === false) {
87
            return;
88
        }
89
90
        try {
91 3
            $response = $handler->handle($params);
92 3
            $code = $response->getCode();
93
        } catch (Exception $exception) {
94
            if (Debugger::isEnabled()) {
95
                $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]);
96
            } else {
97
                $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
98
            }
99
            $code = $response->getCode();
100
            Debugger::log($exception, Debugger::EXCEPTION);
101
        }
102
103 3
        $end = microtime(true);
104
105 3
        if ($this->context->findByType('Tomaj\NetteApi\Logger\ApiLoggerInterface')) {
106
            $this->logRequest($this->context->getByType('Tomaj\NetteApi\Logger\ApiLoggerInterface'), $code, $end - $start);
107
        }
108
109
        // output to nette
110 3
        $this->getHttpResponse()->setCode($code);
111 3
        $this->sendResponse($response);
112
    }
113
114
    /**
115
     * Get handler settings (endpoint, handler, authorization)
116
     *
117
     * @return Api
118
     */
119 9
    private function getApi()
120
    {
121 9
        return $this->apiDecider->getApi(
122 9
            $this->getRequest()->getMethod(),
123 9
            $this->params['version'],
124 9
            $this->params['package'],
125 9
            $this->params['apiAction']
126
        );
127
    }
128
129
    /**
130
     * Check authorization
131
     *
132
     * @param ApiAuthorizationInterface  $authorization
133
     *
134
     * @return bool
135
     */
136 9
    private function checkAuth(ApiAuthorizationInterface $authorization)
137
    {
138 9 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 6
        return true;
144
    }
145
146
    /**
147
     * Process input parameters
148
     *
149
     * @param ApiHandlerInterface   $handler
150
     *
151
     * @return array|bool
152
     */
153 6
    private function processParams(ApiHandlerInterface $handler)
154
    {
155 6
        $paramsProcessor = new ParamsProcessor($handler->params());
156 6 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 3
        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
        } elseif (isset($_SERVER)) {
179
            foreach ($_SERVER as $name => $value) {
180
                if (substr($name, 0, 5) == 'HTTP_') {
181
                    $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
182
                    $headers[$key] = $value;
183
                }
184
            }
185
        }
186
187
        $requestHeaders = '';
188
        foreach ($headers as $key => $value) {
189
            $requestHeaders .= "$key: $value\n";
190
        }
191
192
        $ipDetector = $this->context->getByType('Tomaj\NetteApi\Misc\IpDetectorInterface');
193
        $logger->log(
194
            $code,
195
            $this->getRequest()->getMethod(),
196
            $requestHeaders,
197
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
198
            $ipDetector->getRequestIp(),
199
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
200
            ($elapsed) * 1000
201
        );
202
    }
203
204 9
    protected function sendCorsHeaders()
205
    {
206 9
        $this->getHttpResponse()->addHeader('Access-Control-Allow-Methods', 'POST, DELETE, PUT, GET, OPTIONS');
207
208 9
        if ($this->corsHeader == 'auto') {
209
            $domain = $this->getRequestDomain();
210
            if ($domain !== false) {
211
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
212
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
213
            }
214
            return;
215
        }
216
217 9
        if ($this->corsHeader == '*') {
218 9
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
219 9
            return;
220
        }
221
222
        if ($this->corsHeader != 'off') {
223
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
224
        }
225
    }
226
227
    private function getRequestDomain()
228
    {
229
        if (!filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
230
            return false;
231
        }
232
        $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
233
        if (!(isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host']))) {
234
            return false;
235
        }
236
        $url = $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
237
        if (isset($refererParsedUrl['port']) && $refererParsedUrl['port'] !== 80) {
238
            $url .= ':' . $refererParsedUrl['port'];
239
        }
240
        return $url;
241
    }
242
}
243