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()) { |
|
|
|
|
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()) { |
|
|
|
|
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
|
|
|
|
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.