Skip to content

Commit

Permalink
Merge pull request #10 from brokenhandsio/vapor3
Browse files Browse the repository at this point in the history
Vapor 3
  • Loading branch information
0xTim committed May 7, 2018
2 parents 8cd8e2e + 466baca commit a4435a8
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 328 deletions.
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()
}
}

}

0 comments on commit a4435a8

Please sign in to comment.