1 | <?php |
||
2 | |||
3 | namespace HughCube\HttpSecurity; |
||
4 | |||
5 | use Closure; |
||
6 | use HughCube\HttpSecurity\Exceptions\ClientIpHasChangeHttpException; |
||
7 | use HughCube\HttpSecurity\Exceptions\IpAccessDeniedHttpException; |
||
8 | use HughCube\HttpSecurity\Exceptions\UserAgentHasChangeHttpException; |
||
9 | use Illuminate\Contracts\Config\Repository; |
||
10 | use Illuminate\Support\Str; |
||
11 | use ReflectionClass; |
||
12 | use Symfony\Component\HttpFoundation\IpUtils; |
||
13 | use Symfony\Component\HttpFoundation\Request; |
||
14 | use Symfony\Component\HttpFoundation\Response; |
||
15 | |||
16 | class Middleware |
||
17 | { |
||
18 | /** |
||
19 | * The config repository instance. |
||
20 | * |
||
21 | * @var \Illuminate\Contracts\Config\Repository |
||
22 | */ |
||
23 | protected $config; |
||
24 | |||
25 | /** |
||
26 | * Create a new trusted proxies middleware instance. |
||
27 | * |
||
28 | * @param \Illuminate\Contracts\Config\Repository $config |
||
29 | */ |
||
30 | public function __construct(Repository $config) |
||
31 | { |
||
32 | $this->config = $config; |
||
33 | } |
||
34 | |||
35 | /** |
||
36 | * Handle an incoming request. |
||
37 | * |
||
38 | * @param \Illuminate\Http\Request $request |
||
39 | * @param \Closure $next |
||
40 | * |
||
41 | * @return mixed |
||
42 | * @throws \ReflectionException |
||
43 | * |
||
44 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException |
||
45 | */ |
||
46 | public function handle(Request $request, Closure $next) |
||
47 | { |
||
48 | $response = $next($request); |
||
49 | |||
50 | /** |
||
51 | * Call all guards. |
||
52 | */ |
||
53 | $reflection = new ReflectionClass($this); |
||
54 | foreach ($reflection->getMethods() as $method) { |
||
55 | if (!$method->isPublic() || !Str::endsWith($method->getName(), 'Guard')) { |
||
56 | continue; |
||
57 | } |
||
58 | |||
59 | $method->invokeArgs($this, [$request, $response]); |
||
60 | } |
||
61 | |||
62 | return $response; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * @param string $key |
||
67 | * |
||
68 | * @return mixed |
||
69 | */ |
||
70 | protected function getGuardConfig($key, $default = null) |
||
71 | { |
||
72 | return $this->config->get("httpSecurity.{$key}", $default); |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Determine if a session driver has been configured. |
||
77 | * |
||
78 | * @param Request $request |
||
79 | * @return bool |
||
80 | */ |
||
81 | protected function sessionIsStarted(Request $request) |
||
82 | { |
||
83 | /** @var \Illuminate\Contracts\Session\Session $session */ |
||
84 | $session = $request->getSession(); |
||
85 | |||
86 | return $session->isStarted(); |
||
87 | } |
||
88 | |||
89 | protected function buildCacheKey($key) |
||
90 | { |
||
91 | return "HttpSecurity:" . md5(serialize($key)); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * @param Request $request |
||
96 | * @param Response $response |
||
97 | */ |
||
98 | public function contentMimeGuard($request, $response) |
||
99 | { |
||
100 | if (false == $this->getGuardConfig('contentMime.enable')) { |
||
101 | return; |
||
102 | } |
||
103 | |||
104 | if (!$response instanceof Response) { |
||
105 | return; |
||
106 | } |
||
107 | |||
108 | $response->headers->set('X-Content-Type-Options', 'nosniff', false); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * @param Request $request |
||
113 | * @param Response $response |
||
114 | */ |
||
115 | public function poweredByHeaderGuard($request, $response) |
||
116 | { |
||
117 | if (false == $this->getGuardConfig('poweredByHeader.enable')) { |
||
118 | return; |
||
119 | } |
||
120 | |||
121 | if (!$response instanceof Response) { |
||
122 | return; |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * Remove X-Powered-By header. |
||
127 | */ |
||
128 | if (function_exists('header_remove')) { |
||
129 | @header_remove('X-Powered-By'); // PHP 5.3+ |
||
0 ignored issues
–
show
|
|||
130 | } else { |
||
131 | @ini_set('expose_php', 'off'); |
||
132 | } |
||
133 | |||
134 | $options = $this->getGuardConfig('poweredByHeader.options'); |
||
135 | if (null === $options) { |
||
136 | return; |
||
137 | } |
||
138 | |||
139 | $response->headers->set('X-Powered-By', $options, false); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * @param Request $request |
||
144 | * @param Response $response |
||
145 | */ |
||
146 | public function uaCompatibleGuard($request, $response) |
||
147 | { |
||
148 | if (false == $this->getGuardConfig('uaCompatible.enable')) { |
||
149 | return; |
||
150 | } |
||
151 | |||
152 | if (!$response instanceof Response) { |
||
153 | return; |
||
154 | } |
||
155 | |||
156 | $policy = $this->getGuardConfig('uaCompatible.policy'); |
||
157 | if (null === $policy) { |
||
158 | return; |
||
159 | } |
||
160 | |||
161 | $response->headers->set('X-Ua-Compatible', $policy, false); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param Request $request |
||
166 | * @param Response $response |
||
167 | */ |
||
168 | public function hstsGuard($request, $response) |
||
169 | { |
||
170 | $enable = $this->getGuardConfig('hsts.enable'); |
||
171 | if (false == (null === $enable ? $request->isSecure() : $enable)) { |
||
172 | return; |
||
173 | } |
||
174 | |||
175 | if (!$response instanceof Response) { |
||
176 | return; |
||
177 | } |
||
178 | |||
179 | $maxAge = $this->getGuardConfig('hsts.maxAge', -1); |
||
180 | if (0 >= $maxAge) { |
||
181 | return; |
||
182 | } |
||
183 | |||
184 | $includeSubDomains = $this->getGuardConfig('hsts.includeSubDomains', false); |
||
185 | $preload = $this->getGuardConfig('hsts.preload', false); |
||
186 | |||
187 | $header = ''; |
||
188 | $header .= ("max-age={$maxAge}"); |
||
189 | $header .= ($includeSubDomains ? '; includeSubDomains' : ''); |
||
190 | $header .= ($preload ? '; preload' : ''); |
||
191 | $response->headers->set('Strict-Transport-Security', $header, false); |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * @param Request $request |
||
196 | * @param Response $response |
||
197 | */ |
||
198 | public function xssProtectionGuard($request, $response) |
||
199 | { |
||
200 | if (false == $this->getGuardConfig('xssProtection.enable')) { |
||
201 | return; |
||
202 | } |
||
203 | |||
204 | if (!$response instanceof Response) { |
||
205 | return; |
||
206 | } |
||
207 | |||
208 | $policy = $this->getGuardConfig('xssProtection.policy'); |
||
209 | if (null === $policy) { |
||
210 | return; |
||
211 | } |
||
212 | |||
213 | $response->headers->set('X-XSS-Protection', strval($policy), false); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * @param Request $request |
||
218 | * @param Response $response |
||
219 | */ |
||
220 | public function refererHotlinkingGuard($request, $response) |
||
221 | { |
||
222 | if (false == $this->getGuardConfig('refererHotlinking.enable')) { |
||
223 | return; |
||
224 | } |
||
225 | |||
226 | $allow = false; |
||
227 | $referer = $request->headers->get('Referer'); |
||
228 | |||
229 | // 如果 Referer 为空直接通过 |
||
230 | $allowEmpty = $this->getGuardConfig('refererHotlinking.allowEmpty', true); |
||
231 | if (!$allow && $allowEmpty && null == $referer) { |
||
232 | $allow = true; |
||
233 | } |
||
234 | |||
235 | // 去匹配允许的条件, 如果 allowedPatterns 为空直接通过 |
||
236 | $allowPatterns = $this->getGuardConfig('refererHotlinking.allowPatterns', []); |
||
237 | $allow = $allow || empty($allowPatterns); |
||
238 | foreach ($allowPatterns as $pathPattern => $refererPatterns) { |
||
239 | if ($allow) { |
||
240 | break; |
||
241 | } |
||
242 | |||
243 | if (!Str::is($pathPattern, $request->getPathInfo())) { |
||
244 | continue; |
||
245 | } |
||
246 | |||
247 | $allow = Str::is($refererPatterns, $referer); |
||
248 | break; |
||
249 | } |
||
250 | |||
251 | // 不允许的 |
||
252 | $forbidPatterns = $this->getGuardConfig('refererHotlinking.forbidPatterns', []); |
||
253 | foreach ($forbidPatterns as $pathPattern => $refererPatterns) { |
||
254 | if (!$allow) { |
||
255 | break; |
||
256 | } |
||
257 | |||
258 | if (!Str::is($pathPattern, $request->getPathInfo())) { |
||
259 | continue; |
||
260 | } |
||
261 | |||
262 | $allow = !Str::is($refererPatterns, $referer); |
||
263 | break; |
||
264 | } |
||
265 | |||
266 | if (!$allow) { |
||
267 | # throw new RefererHotlinkingHttpException("HTTP referer not allow"); |
||
268 | } |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * @param Request $request |
||
273 | * @param Response $response |
||
274 | */ |
||
275 | public function clientIpChangeGuard($request, $response) |
||
276 | { |
||
277 | if (false == $this->getGuardConfig('clientIpChange.enable')) { |
||
278 | return; |
||
279 | } |
||
280 | |||
281 | if (!$this->sessionIsStarted($request)) { |
||
282 | return; |
||
283 | } |
||
284 | |||
285 | $clientIpHash = crc32(serialize($request->getClientIp())); |
||
286 | |||
287 | $sessionKey = $this->buildCacheKey(__METHOD__); |
||
288 | if (!$request->getSession()->has($sessionKey)) { |
||
289 | $request->getSession()->set($sessionKey, $clientIpHash); |
||
290 | } |
||
291 | |||
292 | if ($clientIpHash !== $request->getSession()->get($sessionKey)) { |
||
293 | throw new ClientIpHasChangeHttpException('Ip has change.'); |
||
294 | } |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param Request $request |
||
299 | * @param Response $response |
||
300 | */ |
||
301 | protected function userAgentChangeGuard($request, $response) |
||
302 | { |
||
303 | if (false == $this->getGuardConfig('userAgentChange.enable')) { |
||
304 | return; |
||
305 | } |
||
306 | |||
307 | if (!$this->sessionIsStarted($request)) { |
||
308 | return; |
||
309 | } |
||
310 | |||
311 | $userAgentHash = crc32(serialize($request->headers->get('User-Agent'))); |
||
312 | |||
313 | $sessionKey = $this->buildCacheKey(__METHOD__); |
||
314 | if (!$request->getSession()->has($sessionKey)) { |
||
315 | $request->getSession()->set($sessionKey, $userAgentHash); |
||
316 | } |
||
317 | |||
318 | if ($userAgentHash !== $request->getSession()->get($sessionKey)) { |
||
319 | throw new UserAgentHasChangeHttpException('User-Agent has change.'); |
||
320 | } |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * @param Request $request |
||
325 | * @param Response $response |
||
326 | */ |
||
327 | protected function ipAccessGuard($request, $response) |
||
328 | { |
||
329 | if (false == $this->getGuardConfig('ipAccess.enable')) { |
||
330 | return; |
||
331 | } |
||
332 | |||
333 | $clientIp = $request->getClientIp(); |
||
334 | if (null == $clientIp) { |
||
0 ignored issues
–
show
|
|||
335 | return; |
||
336 | } |
||
337 | |||
338 | $allow = false; |
||
339 | |||
340 | // 去匹配允许条件, 如果 allowedIps 为空直接通过 |
||
341 | $allowedIps = $this->getGuardConfig('ipAccess.allowedIps', []); |
||
342 | $allow = ($allow || empty($allowedIps)); |
||
343 | foreach ($allowedIps as $pathPattern => $ipPatterns) { |
||
344 | if ($allow) { |
||
345 | break; |
||
346 | } |
||
347 | |||
348 | if (!Str::is($pathPattern, $request->getPathInfo())) { |
||
349 | continue; |
||
350 | } |
||
351 | |||
352 | $allow = IpUtils::checkIp($clientIp, $ipPatterns); |
||
353 | break; |
||
354 | } |
||
355 | |||
356 | // 不允许的 |
||
357 | $forbidPatterns = $this->getGuardConfig('ipAccess.forbidIps', []); |
||
358 | foreach ($forbidPatterns as $pathPattern => $ipPatterns) { |
||
359 | if (!$allow) { |
||
360 | break; |
||
361 | } |
||
362 | |||
363 | if (!Str::is($pathPattern, $request->getPathInfo())) { |
||
364 | continue; |
||
365 | } |
||
366 | |||
367 | $allow = !IpUtils::checkIp($clientIp, $ipPatterns); |
||
368 | break; |
||
369 | } |
||
370 | |||
371 | if (!$allow) { |
||
372 | throw new IpAccessDeniedHttpException('Not allowed ip.'); |
||
373 | } |
||
374 | } |
||
375 | } |
||
376 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.