Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vapor 3 #10

Merged
merged 28 commits into from
May 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
db0f397
Port over most source code excluding interacting with storage
0xTim Dec 18, 2017
e55c558
First tests compiling
0xTim Dec 18, 2017
bf6636c
This test should now be passing - works on a group route
0xTim Dec 18, 2017
a1ce0d7
Bring back in the rest of the tests
0xTim Dec 18, 2017
126f5e6
Get the middleware into the responder chain
0xTim Dec 19, 2017
dde3deb
Implement per-page CSP again
0xTim Dec 19, 2017
3245518
Work out correct order for middleware and tidy up
0xTim Dec 19, 2017
9d15836
Bring back file tests
0xTim Dec 19, 2017
338167f
Update to latest Beta
0xTim Dec 20, 2017
4d8e6ef
Use recommended method for adding middleware
0xTim Dec 20, 2017
86a1d0b
Update to latest beta
0xTim Jan 2, 2018
8f546bf
Update Xcode image for CI
0xTim Jan 2, 2018
6add0be
Update to latest beta
0xTim Feb 11, 2018
75f8c9f
Merge branch 'master' into vapor3
bre7 Apr 2, 2018
7f6053e
NIO updates (HTTP headers accessors)
bre7 Apr 2, 2018
11e2ce9
Merge pull request #12 from bre7/vapor3
0xTim Apr 5, 2018
408890a
Update tests for RC2
0xTim Apr 5, 2018
1ed87ef
Fix FileMiddleware
0xTim Apr 5, 2018
32be17a
Bring back per page CSP
0xTim Apr 6, 2018
9868745
Run tests on Xcode 9.3 beta
0xTim Apr 6, 2018
8ac6087
Update README for Vapor 3
0xTim Apr 6, 2018
fab1471
Update README about middleware positioning.
ccrazy88 Apr 10, 2018
7d1edc6
Tweak usage section of README.
ccrazy88 Apr 10, 2018
2bac7f0
Merge pull request #13 from ccrazy88/patch-1
0xTim Apr 10, 2018
36e530e
Update to latest RCs
0xTim Apr 30, 2018
a71e042
Attempt to get code coverage working
0xTim Apr 30, 2018
6c6d4c1
Tidy up whitespace
0xTim Apr 30, 2018
466baca
Update to Vapor 3 release!
0xTim May 5, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ language: generic
sudo: required
dist: trusty

osx_image: xcode9.1
osx_image: xcode9.3
before_install:
- if [ $TRAVIS_OS_NAME == "osx" ]; then
brew update;
Expand All @@ -24,7 +24,7 @@ script:
- swift test

after_success:
- eval "$(curl -sL https://raw.githubusercontent.com/vapor-community/swift/master/codecov)"
- eval "$(curl -sL https://raw.githubusercontent.com/vapor-community/swift/swift-4-codecov/codecov-swift4)"

notifications:
email:
Expand Down
11 changes: 10 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
// swift-tools-version:4.0

import PackageDescription

let package = Package(
name: "VaporSecurityHeaders",
products: [
.library(name: "VaporSecurityHeaders", targets: ["VaporSecurityHeaders"]),
],
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
],
targets: [
.target(name: "VaporSecurityHeaders", dependencies: ["Vapor"]),
.testTarget(name: "VaporSecurityHeadersTests", dependencies: ["VaporSecurityHeaders"]),
]
)
17 changes: 0 additions & 17 deletions Package@swift-4.swift

This file was deleted.

40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<br>
<br>
<a href="https://swift.org">
<img src="http://img.shields.io/badge/Swift-4-brightgreen.svg" alt="Language">
<img src="http://img.shields.io/badge/Swift-4.1-brightgreen.svg" alt="Language">
</a>
<a href="https://travis-ci.org/brokenhandsio/VaporSecurityHeaders">
<img src="https://travis-ci.org/brokenhandsio/VaporSecurityHeaders.svg?branch=master" alt="Build Status">
Expand Down Expand Up @@ -35,29 +35,21 @@ These headers will *help* prevent cross-site scripting attacks, SSL downgrade at

# Usage

To use Vapor Security Headers, just add the middleware to your `Config` and then to your `droplet.json`. Vapor Security Headers makes this easy to do with a `builder` function on the factory:
To use Vapor Security Headers, just register the middleware with your services and add it to your `MiddlewareConfig`. Vapor Security Headers makes this easy to do with a `build` function on the factory. In `configure.swift` add:

```swift
let config = Config()
let securityHeadersFactory = SecurityHeadersFactory()
config.addConfigurable(middleware: securityHeadersFactory.builder(), name: "security-headers"))
let drop = Droplet(config)
services.register(securityHeadersFactory.build())

var middlewareConfig = MiddlewareConfig()
// ...
middlewareConfig.use(SecurityHeaders.self)
services.register(middlewareConfig)
```

The default factory will add default values to your site for Content-Security-Policy, X-XSS-Protection, X-Frame-Options and X-Content-Type-Options.

***Note:*** You should ensure you set the security headers as the first middleware in your `droplet.json` to make sure the headers get added to all responses:

```json
{
...
"middleware": [
"security-headers",
...
],
...
}
```
***Note:*** You should ensure you set the security headers as the last middleware in your `MiddlewareConfig` (i.e., the first middleware to be applied to responses) to make sure the headers get added to all responses.

If you want to add your own values, it is easy to do using the factory. For instance, to add a content security policy configuration, just do:

Expand All @@ -72,7 +64,7 @@ You will need to add it as a dependency in your `Package.swift` file:
```swift
dependencies: [
...,
.package(url: "https://github.com/brokenhandsio/VaporSecurityHeaders.git", from: "1.1.0")
.package(url: "https://github.com/brokenhandsio/VaporSecurityHeaders.git", from: "2.0.0")
]
```

Expand Down Expand Up @@ -140,14 +132,22 @@ Check out [https://report-uri.io/](https://report-uri.io/) for a free tool to se

### Page Specific CSP

Vapor Security Headers also supports setting the CSP on a route or request basis. If the middleware has been added to the Droplet, you can override the CSP for a request. This allows you to have a strict default CSP, but allow content from extra sources when required, such as only allowing the Javascript for blog comments on the blog page. Create a separate `ContentSecurityPolicyConfiguration` and then add it to the request. For example, inside a route handler, you could do:
Vapor Security Headers also supports setting the CSP on a route or request basis. If the middleware has been added to the `MiddlewareConfig`, you can override the CSP for a request. This allows you to have a strict default CSP, but allow content from extra sources when required, such as only allowing the Javascript for blog comments on the blog page. Create a separate `ContentSecurityPolicyConfiguration` and then add it to the request. For example, inside a route handler, you could do:

```swift
let pageSpecificCSPVaue = "default-src 'none'; script-src https://comments.disqus.com;"
let pageSpecificCSP = ContentSecurityPolicyConfiguration(value: pageSpecificCSPValue)
request.contentSecurityPolicy = pageSpecificCSP
```

You must also enable the `CSPRequestConfiguration` service for this to work. In `configure.swift` add:

```swift
services.register { _ in
return CSPRequestConfiguration()
}
```

## Content-Security-Policy-Report-Only

Content-Security-Policy-Report-Only works in exactly the same way as Content-Security-Policy except that any violations will not block content, but they will be reported back to you. This is extremely useful for testing a CSP before rolling it out over your site. You can run both side by side - so for example have a fairly simply policy under Content-Security-Policy but test a more restrictive policy over Content-Security-Policy-Report-Only. The great thing about this is that your users do all your testing for you!
Expand Down Expand Up @@ -175,7 +175,7 @@ To just enable the protection:
let xssProtectionConfig = XssProtectionConfiguration(option: .enable)
```

To sanitize the page and report the violation:
To sanitise the page and report the violation:

```swift
let xssProtectionConfig = XssProtectionConfiguration(option: .report("https://report-uri.com"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

public struct ContentSecurityPolicyConfiguration: SecurityHeaderConfiguration {

Expand All @@ -9,22 +9,32 @@ public struct ContentSecurityPolicyConfiguration: SecurityHeaderConfiguration {
}

func setHeader(on response: Response, from request: Request) {
if let requestCsp = request.contentSecurityPolicy {
response.headers[HeaderKey.contentSecurityPolicy] = requestCsp.value
if let requestCSP = request.contentSecurityPolicy {
response.http.headers.replaceOrAdd(name: .contentSecurityPolicy, value: requestCSP.value)
} else {
response.headers[HeaderKey.contentSecurityPolicy] = value
response.http.headers.replaceOrAdd(name: .contentSecurityPolicy, value: value)
}
}
}

extension Request {
public class CSPRequestConfiguration: Service {
var configuration: ContentSecurityPolicyConfiguration?
public init() {}
}

extension Request {
public var contentSecurityPolicy: ContentSecurityPolicyConfiguration? {
get {
return storage["cspConfig"] as? ContentSecurityPolicyConfiguration
if let requestConfig = try? privateContainer.make(CSPRequestConfiguration.self) {
return requestConfig.configuration
} else {
return nil
}
}
set {
storage["cspConfig"] = newValue
if let requestConfig = try? privateContainer.make(CSPRequestConfiguration.self) {
requestConfig.configuration = newValue
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

public struct ContentSecurityPolicyReportOnlyConfiguration: SecurityHeaderConfiguration {

Expand All @@ -9,6 +9,6 @@ public struct ContentSecurityPolicyReportOnlyConfiguration: SecurityHeaderConfig
}

func setHeader(on response: Response, from request: Request) {
response.headers[HeaderKey.contentSecurityPolicyReportOnly] = value
response.http.headers.replaceOrAdd(name: .contentSecurityPolicyReportOnly, value: value)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

public struct ContentTypeOptionsConfiguration: SecurityHeaderConfiguration {

Expand All @@ -16,7 +16,7 @@ public struct ContentTypeOptionsConfiguration: SecurityHeaderConfiguration {
func setHeader(on response: Response, from request: Request) {
switch option {
case .nosniff:
response.headers[HeaderKey.xContentTypeOptions] = "nosniff"
response.http.headers.replaceOrAdd(name: .xContentTypeOptions, value: "nosniff")
default:
break
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import HTTP
import Vapor

public struct FrameOptionsConfiguration: SecurityHeaderConfiguration {

Expand All @@ -17,11 +18,11 @@ public struct FrameOptionsConfiguration: SecurityHeaderConfiguration {
func setHeader(on response: Response, from request: Request) {
switch option {
case .deny:
response.headers[HeaderKey.xFrameOptions] = "DENY"
response.http.headers.replaceOrAdd(name: .xFrameOptions, value: "DENY")
case .sameOrigin:
response.headers[HeaderKey.xFrameOptions] = "SAMEORIGIN"
response.http.headers.replaceOrAdd(name: .xFrameOptions, value: "SAMEORIGIN")
case .allow(let from):
response.headers[HeaderKey.xFrameOptions] = "ALLOW-FROM \(from)"
response.http.headers.replaceOrAdd(name: .xFrameOptions, value: "ALLOW-FROM \(from)")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

public struct ReferrerPolicyConfiguration: SecurityHeaderConfiguration {

Expand All @@ -21,6 +21,6 @@ public struct ReferrerPolicyConfiguration: SecurityHeaderConfiguration {
}

func setHeader(on response: Response, from request: Request) {
response.headers[HeaderKey.referrerPolicy] = option.rawValue
response.http.headers.replaceOrAdd(name: .referrerPolicy, value: option.rawValue)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import HTTP
import Vapor

public struct ServerConfiguration: SecurityHeaderConfiguration {
private let value: String
Expand All @@ -8,6 +9,6 @@ public struct ServerConfiguration: SecurityHeaderConfiguration {
}

func setHeader(on response: Response, from request: Request) {
response.headers[HeaderKey.server] = value
response.http.headers.replaceOrAdd(name: .server, value: value)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

public struct StrictTransportSecurityConfiguration: SecurityHeaderConfiguration {

Expand All @@ -21,6 +21,6 @@ public struct StrictTransportSecurityConfiguration: SecurityHeaderConfiguration
headerValue += " preload"
}

response.headers[HeaderKey.strictTransportSecurity] = headerValue
response.http.headers.replaceOrAdd(name: .strictTransportSecurity, value: headerValue)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

public struct XSSProtectionConfiguration: SecurityHeaderConfiguration {

Expand All @@ -18,13 +18,13 @@ public struct XSSProtectionConfiguration: SecurityHeaderConfiguration {
func setHeader(on response: Response, from request: Request) {
switch option {
case .disable:
response.headers[HeaderKey.xXssProtection] = "0"
response.http.headers.replaceOrAdd(name: .xXssProtection, value: "0")
case .enable:
response.headers[HeaderKey.xXssProtection] = "1"
response.http.headers.replaceOrAdd(name: .xXssProtection, value: "1")
case .block:
response.headers[HeaderKey.xXssProtection] = "1; mode=block"
response.http.headers.replaceOrAdd(name: .xXssProtection, value: "1; mode=block")
case .report(let uri):
response.headers[HeaderKey.xXssProtection] = "1; report=\(uri)"
response.http.headers.replaceOrAdd(name: .xXssProtection, value: "1; report=\(uri)")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HTTP
import Vapor

protocol SecurityHeaderConfiguration {
func setHeader(on response: Response, from request: Request)
Expand Down
31 changes: 7 additions & 24 deletions Sources/VaporSecurityHeaders/SecurityHeaders+HeaderKey.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,10 @@
import HTTP
import Vapor

public extension HeaderKey {
static public var contentSecurityPolicy: HeaderKey {
return HeaderKey("content-security-policy")
}
public extension HTTPHeaderName {

static public var xXssProtection: HeaderKey {
return HeaderKey("x-xss-protection")
}

static public var xFrameOptions: HeaderKey {
return HeaderKey("x-frame-options")
}

static public var xContentTypeOptions: HeaderKey {
return HeaderKey("x-content-type-options")
}

static public var contentSecurityPolicyReportOnly: HeaderKey {
return HeaderKey("content-security-policy-report-only")
}

static public var referrerPolicy: HeaderKey {
return HeaderKey("referrer-policy")
}
public static let contentSecurityPolicy = HTTPHeaderName("Content-Security-Policy")
public static let xXssProtection = HTTPHeaderName("X-XSS-Protection")
public static let xContentTypeOptions = HTTPHeaderName("X-Content-Type-Options")
public static let contentSecurityPolicyReportOnly = HTTPHeaderName("Content-Security-Policy-Report-Only")
public static let referrerPolicy = HTTPHeaderName("Referrer-Policy")
}
15 changes: 9 additions & 6 deletions Sources/VaporSecurityHeaders/SecurityHeaders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ public struct SecurityHeaders {

}

extension SecurityHeaders: Middleware {
public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
extension SecurityHeaders: Middleware, Service {

public func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
let response = try next.respond(to: request)

for spec in configurations {
spec.setHeader(on: response, from: request)
}
return response.map(to: Response.self) { response in
for spec in self.configurations {
spec.setHeader(on: response, from: request)
}

return response
return response
}
}
}
6 changes: 0 additions & 6 deletions Sources/VaporSecurityHeaders/SecurityHeadersFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,4 @@ public class SecurityHeadersFactory {
referrerPolicyConfiguration: referrerPolicy)
}

public func builder() -> ((Config) throws -> SecurityHeaders) {
return { _ in
return self.build()
}
}

}
Loading