From f2b0d8c36b65e07dfef0409c90a59853a984ab57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Kr=C3=A4mer?= Date: Mon, 3 Apr 2017 23:40:01 +0200 Subject: [PATCH] Adding a security middleware A convenience wrapper for commonly used headers --- src/Routing/Middleware/SecurityMiddleware.php | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/Routing/Middleware/SecurityMiddleware.php diff --git a/src/Routing/Middleware/SecurityMiddleware.php b/src/Routing/Middleware/SecurityMiddleware.php new file mode 100644 index 00000000000..58fe98d266e --- /dev/null +++ b/src/Routing/Middleware/SecurityMiddleware.php @@ -0,0 +1,195 @@ +headers['x-content-type-options'] = 'nosniff'; + + return $this; + } + + /** + * X-Download-Options + * + * Reference: https://msdn.microsoft.com/en-us/library/jj542450(v=vs.85).aspx + * + * Available Value: 'noopen' + * + * @return $this + */ + public function noOpen() + { + $this->headers['x-download-options'] = 'noopen'; + + return $this; + } + + /** + * Referrer-Policy + * + * Reference: https://w3c.github.io/webappsec-referrer-policy + * + * Available Value: 'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', + * 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url' + * + * @param string $policy Policy value + * @return $this + */ + public function setReferrerPolicy($policy = 'same-origin') + { + $available = [ + 'no-referrer', 'no-referrer-when-downgrade', 'origin', + 'origin-when-cross-origin', + 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', + 'unsafe-url' + ]; + + $this->checkValues($policy, $available); + $this->headers['referrer-policy'] = $policy; + + return $this; + } + + /** + * X-Frame-Options + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + * + * Available Value: 'deny', 'sameorigin', 'allow-from ' + * + * @param string $mode Mode value + * @param string $url URL if mode is `allow-from` + * @return $this + */ + public function setXFrameOptions($option = 'sameorigin', $url = null) + { + $this->checkValues($option, ['deny', 'sameorigin', 'allow-from']); + + if ($option === 'allow-from') { + if (empty($url)) { + throw new InvalidArgumentException('The 2nd arg $url can not be empty when `allow-from` is used'); + } + $option .= ' ' . $url; + } + + $this->headers['x-frame-options'] = $option; + + return $this; + } + + /** + * X-XSS-Protection + * + * Reference: https://blogs.msdn.microsoft.com/ieinternals/2011/01/31/controlling-the-xss-filter + * + * Available Value: '1', '0', '1; mode=block' + * + * @param string $mode Mode value + * @return $this + */ + public function setXssProtection($mode = '1; mode=block') + { + if ($mode === 'block') { + $mode = '1; mode=block'; + } + + $this->checkValues($mode, ['all', 'none', 'master-only', 'by-content-type', 'by-ftp-filename']); + $this->headers['x-permitted-cross-domain-policies'] = $mode; + + return $this; + } + + /** + * X-Permitted-Cross-Domain-Policies + * + * Reference: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html + * + * Available Value: 'all', 'none', 'master-only', 'by-content-type', 'by-ftp-filename' + * + * @param string $policy Policy value + * @return $this + */ + public function setCrossDomainPolicy($policy = 'all') + { + $this->checkValues($policy, ['all', 'none', 'master-only', 'by-content-type', 'by-ftp-filename']); + $this->headers['x-permitted-cross-domain-policies'] = $policy; + + return $this; + } + + /** + * Convenience method to check if a value is in the list of allowed args + * + * @throws \InvalidArgumentException Thown when a value is invalid. + * @param string $value + * @param array $allowed + * @return void + */ + protected function checkValues($value, array $allowed) + { + if (!in_array($value, $allowed)) { + throw new InvalidArgumentException( + sprintf('Invalid arg `%s`, use one of these: %s', + $value, + implode(', ', $allowed) + ) + ); + } + } + + /** + * Serve assets if the path matches one. + * + * @param \Psr\Http\Message\ServerRequestInterface $request The request. + * @param \Psr\Http\Message\ResponseInterface $response The response. + * @param callable $next Callback to invoke the next middleware. + * @return \Psr\Http\Message\ResponseInterface A response + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) + { + foreach ($this->headers as $header => $value) { + $response = $response->withHeader($header, $value); + } + + return $next($request, $response); + } +}