Completed
Push — master ( f6ca49...67c60c )
by Tomas
04:33
created

ApiPresenter::renderDefault()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5.7044

Importance

Changes 5
Bugs 0 Features 2
Metric Value
c 5
b 0
f 2
dl 0
loc 37
ccs 16
cts 23
cp 0.6957
rs 8.439
cc 5
eloc 22
nc 8
nop 0
crap 5.7044
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 3
    public function startup()
52
    {
53 3
        parent::startup();
54 3
        $this->autoCanonicalize = false;
55 3
    }
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 3
    public function renderDefault()
75
    {
76 3
        $start = microtime(true);
77
78 3
        $this->sendCorsHeaders();
79
80 3
        $hand = $this->getHandler();
81 3
        $handler = $hand['handler'];
82 3
        $authorization = $hand['authorization'];
83
84 3
        if ($this->checkAuth($authorization) === false) {
85
            return;
86
        }
87
88 3
        $params = $this->processParams($handler);
89 3
        if ($params === false) {
90
            return;
91
        }
92
93
        try {
94 3
            $response = $handler->handle($params);
95 3
            $code = $response->getCode();
96 3
        } catch (Exception $exception) {
97
            $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
98
            $code = $response->getCode();
99
        }
100
101 3
        $end = microtime(true);
102
103 3
        if ($this->context->hasService('apiLogger')) {
104
            $this->logRequest($this->context->getService('apiLogger'), $code, $end - $start);
105
        }
106
107
        // output to nette
108 3
        $this->getHttpResponse()->setCode($code);
109 3
        $this->sendResponse($response);
110
    }
111
112
    /**
113
     * Get handler information triplet (endpoint, handler, authorization)
114
     *
115
     * @return array
116
     */
117 3
    private function getHandler()
118
    {
119 3
        return $this->apiDecider->getApiHandler(
120 3
            $this->getRequest()->getMethod(),
121 3
            $this->params['version'],
122 3
            $this->params['package'],
123 3
            $this->params['apiAction']
124 3
        );
125
    }
126
127
    /**
128
     * Check authorization
129
     *
130
     * @param ApiAuthorizationInterface  $authorization
131
     *
132
     * @return bool
133
     */
134 3
    private function checkAuth(ApiAuthorizationInterface $authorization)
135
    {
136 3 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 3
        return true;
142
    }
143
144
    /**
145
     * Process input parameters
146
     *
147
     * @param ApiHandlerInterface   $handler
148
     *
149
     * @return array|bool
150
     */
151 3
    private function processParams(ApiHandlerInterface $handler)
152
    {
153 3
        $paramsProcessor = new ParamsProcessor($handler->params());
154 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...
155
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
156
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
157
            return false;
158
        }
159 3
        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 3
    protected function sendCorsHeaders()
195
    {
196 3
        if ($this->corsHeader == 'auto') {
197
            $domain = $this->getRequestDomain();
198
            if ($domain !== false) {
199
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
200
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
201
            }
202
            return;
203
        }
204
205 3
        if ($this->corsHeader == '*') {
206 3
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
207 3
            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 false;
224
    }
225
}
226