Skip to content

Commit

Permalink
Merge pull request #10 from RobDWaller/0.2.0-beta.3
Browse files Browse the repository at this point in the history
0.2.0 beta.3
  • Loading branch information
RobDWaller authored May 5, 2020
2 parents c2f2116 + 9400925 commit fb8b879
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 82 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "csp_generator"
description = "Consume a JSON formatted list of domains and CSP directives and output a correctly formatted Content Security Policy string."
version = "0.2.0-beta.2"
version = "0.2.0-beta.3"
authors = ["Rob Waller <rdwaller1984@gmail.com>"]
edition = "2018"
keywords = ["csp", "json", "content-security", "csp-generator", "security"]
Expand Down
57 changes: 23 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

Managing and creating Content Security Policies can be a challenge. The Content Security Policy header format does not lend itself to managing lots of domains across multiple directives. Especially if you need to allow Google Analytics.

This Rust library allows you to generate a CSP header string from well organised JSON strings. The JSON structure this library accepts allows you to much more easily manage many domains and many directives for your website CSP policies.
This Rust library allows you to generate a CSP header string from well organised JSON strings. The JSON structure this library accepts allows you to more easily manage many domains and many directives for your website CSP policies.

If you need to learn more about Content Security Policies we suggest you read the following resources:

- [Content Security Policies Website](https://content-security-policy.com/)
- [MDN Content Security Policies Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
- [RFC 7762](https://tools.ietf.org/html/rfc7762)

## Installation

Expand All @@ -17,7 +18,7 @@ To install csp_generator in your Rust project simply add it as a dependency with
**Example Cargo.toml**
```toml
[dependencies]
csp_generator = "0.1.0-beta"
csp_generator = "0.2.0-beta.3"
```

## Usage
Expand All @@ -30,36 +31,28 @@ This library exposes three methods:

The `enforce()` and `report_only()` methods will return a struct which contains a header string and a csp string. This will make sure you have the correct CSP header and CSP directive string dependent on whether you wish to use enforcement or report only modes.

If you just wish to only return the CSP directive string call the `csp_only()` method.
If you only wish to return the CSP directive string call the `csp_only()` method.

Each of the methods accepts two arguments a list of CSP directives you wish to use, plus the JSON config. You can use the built in CSP directives list configuration if you wish as it contains all the standard CSP directives, see `csp_generator::config`. However, as this functionality complies with an interface, it can be overridden.

**Example Code**

```rust
extern crate csp_generator;
use csp_generator::config;
use csp_generator::directives;

let json = r#"
{
"domains": [
{"domain": "example.com", "directive": ["connect-src"]},
{"domain": "test.com", "directive": ["connect-src", "script-src"]}
]
}
[
{"domain": "example.com", "directive": ["connect-src"]},
{"domain": "test.com", "directive": ["connect-src", "script-src"]}
]
"#;

let csp: String = csp_generator::enforce(config::get_directives(), json);
let csp: String = csp_generator::enforce(directives::get_directives(), json);

println!("This is the CSP Header: {}", csp.header);
// This is the CSP Header: Content-Security-Policy
println!("This is the CSP Directives String: {}", csp.csp);
```

**Example Output**

```sh
This is the CSP Header: Content-Security-Policy
This is the CSP Directives String: script-src test.com; connect-src example.com test.com;
// This is the CSP Directives String: script-src test.com; connect-src example.com test.com;
```

## JSON Configuration
Expand All @@ -68,24 +61,19 @@ This library relies on a specific JSON format. This is an array of objects which

**Format**
```js
{
"domains": [
"domain": string,
"directive": array<string>
]
}
[
{"domain": string, "directive": array<string>}
]
```

**Example Config**
In this example we associate example.com with the connect-src directive and the test.com domain with the connect-src and script-src directives.

```js
{
"domains": [
{"domain": "example.com", "directive": ["connect-src"]},
{"domain": "test.com", "directive": ["connect-src", "script-src"]}
]
}
[
{"domain": "example.com", "directive": ["connect-src"]},
{"domain": "test.com", "directive": ["connect-src", "script-src"]}
]
```

## CSP Directives List
Expand All @@ -94,7 +82,7 @@ Along with supplying a list of domains and directives in JSON format, we also ne

You can use the built CSP directives list config, as it contains a list of all the standard CSP directives. But if you wish to override this you can.

You just need to comply with the `csp_generator::GetDirectives` trait (interface).
You just need to comply with the `csp_generator::directives::GetDirectives` trait (interface).

**Example Override**
This override will generate a CSP directive string which only makes use of the script-src and connect-src.
Expand All @@ -112,8 +100,9 @@ impl GetDirectives for MyDirectives {
}
}

pub fn get_directives() -> Directives {
Directives {
// Construct MyDirectives Struct with the directives you wish to use.
pub fn my_directives() -> Directives {
MyDirectives {
list: vec![
String::from("script-src"),
String::from("connect-src"),
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions src/directives/mod.rs → src/csp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::directives::GetDirectives;
use crate::domains;
use crate::parse;
use crate::GetDirectives;
use serde_json::error;
use std::thread::JoinHandle;

Expand All @@ -17,7 +17,7 @@ fn threads_to_directives(threads: Vec<JoinHandle<String>>) -> String {
directives.trim().to_string()
}

pub fn build(directives_list: impl GetDirectives, json: &str) -> Result<String, error::Error> {
pub fn generate(directives_list: impl GetDirectives, json: &str) -> Result<String, error::Error> {
let domains: Result<domains::Collection, error::Error> = parse::json(json);

match domains {
Expand All @@ -36,7 +36,7 @@ pub fn build(directives_list: impl GetDirectives, json: &str) -> Result<String,
// -----
#[cfg(test)]
mod directives_test {
use crate::config;
use crate::directives;
use serde_json::error;

#[test]
Expand All @@ -48,7 +48,7 @@ mod directives_test {
]
"#;

let csp: Result<String, error::Error> = super::build(config::get_directives(), json);
let csp: Result<String, error::Error> = super::generate(directives::directives(), json);

assert_eq!(
csp.unwrap(),
Expand Down
2 changes: 1 addition & 1 deletion src/directives/threads.rs → src/csp/threads.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::directives::line;
use crate::csp::line;
use crate::domains::Collection;
use std::thread;
use std::thread::JoinHandle;
Expand Down
11 changes: 7 additions & 4 deletions src/config.rs → src/directives.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use crate::GetDirectives;

pub struct Directives {
list: Vec<String>,
}

pub trait GetDirectives {
fn get_directives(&self) -> Vec<String>;
}

impl GetDirectives for Directives {
fn get_directives(&self) -> Vec<String> {
self.list.clone()
}
}

pub fn get_directives() -> Directives {
// Named constructor for directives module.
pub fn directives() -> Directives {
Directives {
list: vec![
String::from("default-src"),
Expand Down Expand Up @@ -42,7 +45,7 @@ mod config_test {

#[test]
fn test_get_directives() {
let config: super::Directives = super::get_directives();
let config: super::Directives = super::directives();

assert_eq!(config.get_directives()[0], String::from("default-src"));
assert_eq!(config.get_directives()[9], String::from("sandbox"));
Expand Down
2 changes: 2 additions & 0 deletions src/domains.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Clone)]
pub struct Item {
pub domain: String,
Expand Down
46 changes: 16 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
use serde_json::error;

#[macro_use]
extern crate serde_derive;

pub mod config;
mod directives;
mod csp;
pub mod directives;
pub mod domains;
mod parse;

use csp::generate;
use directives::GetDirectives;
use serde_json::error;

pub struct Csp {
pub header: String,
pub csp: String,
}

pub trait GetDirectives {
fn get_directives(&self) -> Vec<String>;
}

fn generate_csp(header: String, csp: String) -> Csp {
Csp { header, csp }
}

fn parse_csp_result(header: String, result: Result<String, error::Error>) -> Csp {
match result {
Ok(result) => generate_csp(header, result),
Ok(result) => Csp {
header,
csp: result,
},
Err(e) => panic!("Could not parse JSON: {}", e),
}
}

pub fn enforce(directives: impl GetDirectives, json: &str) -> Csp {
parse_csp_result(
String::from("Content-Security-Policy"),
directives::build(directives, json),
"Content-Security-Policy".to_string(),
generate(directives, json),
)
}

pub fn report_only(directives: impl GetDirectives, json: &str) -> Csp {
parse_csp_result(
String::from("Content-Security-Policy-Report-Only"),
directives::build(directives, json),
"Content-Security-Policy-Report-Only".to_string(),
generate(directives, json),
)
}

pub fn csp_only(directives: impl GetDirectives, json: &str) -> String {
parse_csp_result(
String::from("Content-Security-Policy"),
directives::build(directives, json),
"Content-Security-Policy".to_string(),
generate(directives, json),
)
.csp
}
Expand All @@ -66,14 +60,6 @@ mod csp_generator_test {
assert_eq!(csp.csp, String::from("csp"));
}

#[test]
fn test_generate_csp() {
let csp = super::generate_csp(String::from("my header"), String::from("my csp"));

assert_eq!(csp.header, String::from("my header"));
assert_eq!(csp.csp, String::from("my csp"));
}

#[test]
fn test_parse_csp_result() {
let csp = super::parse_csp_result(String::from("CSP"), Ok(String::from("Hello World")));
Expand Down
16 changes: 8 additions & 8 deletions tests/csp_generator_test.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(test)]
mod csp_generator_test {
extern crate csp_generator;
use csp_generator::config;
use csp_generator::directives;

#[test]
fn test_enforce() {
Expand All @@ -12,7 +12,7 @@ mod csp_generator_test {
]
"#;

let csp: csp_generator::Csp = csp_generator::enforce(config::get_directives(), json);
let csp: csp_generator::Csp = csp_generator::enforce(directives::directives(), json);

assert_eq!(csp.header, String::from("Content-Security-Policy"));
assert_eq!(
Expand All @@ -26,7 +26,7 @@ mod csp_generator_test {
fn test_enforce_empty_fail() {
let json = "";

csp_generator::enforce(config::get_directives(), json);
csp_generator::enforce(directives::directives(), json);
}

#[test]
Expand All @@ -39,7 +39,7 @@ mod csp_generator_test {
]
"#;

csp_generator::enforce(config::get_directives(), json);
csp_generator::enforce(directives::directives(), json);
}

#[test]
Expand All @@ -51,7 +51,7 @@ mod csp_generator_test {
]
"#;

let csp: csp_generator::Csp = csp_generator::report_only(config::get_directives(), json);
let csp: csp_generator::Csp = csp_generator::report_only(directives::directives(), json);

assert_eq!(
csp.header,
Expand All @@ -68,7 +68,7 @@ mod csp_generator_test {
fn test_report_only_empty_fail() {
let json = "";

csp_generator::report_only(config::get_directives(), json);
csp_generator::report_only(directives::directives(), json);
}

#[test]
Expand All @@ -81,7 +81,7 @@ mod csp_generator_test {
]
"#;

csp_generator::report_only(config::get_directives(), json);
csp_generator::report_only(directives::directives(), json);
}

#[test]
Expand All @@ -93,7 +93,7 @@ mod csp_generator_test {
]
"#;

let csp: String = csp_generator::csp_only(config::get_directives(), json);
let csp: String = csp_generator::csp_only(directives::directives(), json);

assert_eq!(
csp,
Expand Down

0 comments on commit fb8b879

Please sign in to comment.