Skip to content

0.2.0 rc #11

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

Merged
merged 12 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.3"
version = "0.2.0-rc"
authors = ["Rob Waller <rdwaller1984@gmail.com>"]
edition = "2018"
keywords = ["csp", "json", "content-security", "csp-generator", "security"]
Expand Down
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,23 @@ Each of the methods accepts two arguments a list of CSP directives you wish to u
**Example Code**

```rust
use csp_generator::directives;
use csp_generator::{directives, Csp};

let json = r#"
fn main() {
let json = r#"
[
{"domain": "example.com", "directive": ["connect-src"]},
{"domain": "test.com", "directive": ["connect-src", "script-src"]}
{"domain": "example.com", "directives": ["connect-src"]},
{"domain": "test.com", "directives": ["connect-src", "script-src"]}
]
"#;
"#;

let csp: String = csp_generator::enforce(directives::get_directives(), json);
let csp: Csp = csp_generator::enforce(directives::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);
// This is the CSP Directives String: script-src test.com; connect-src example.com test.com;
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);
// This is the CSP Directives String: script-src test.com; connect-src example.com test.com;
}
```

## JSON Configuration
Expand Down Expand Up @@ -88,7 +90,8 @@ You just need to comply with the `csp_generator::directives::GetDirectives` trai
This override will generate a CSP directive string which only makes use of the script-src and connect-src.

```rust
use crate::GetDirectives;
use csp_generator::directives::GetDirectives;
use csp_generator::Csp;

pub struct MyDirectives {
list: Vec<String>,
Expand All @@ -101,14 +104,30 @@ impl GetDirectives for MyDirectives {
}

// Construct MyDirectives Struct with the directives you wish to use.
pub fn my_directives() -> Directives {
fn my_directives() -> MyDirectives {
MyDirectives {
list: vec![
String::from("script-src"),
String::from("connect-src"),
],
}
}

pub fn main() {
let json = r#"
[
{"domain": "example.com", "directives": ["connect-src"]},
{"domain": "test.com", "directives": ["connect-src", "img-src"]}
]
"#;

let csp: Csp = csp_generator::report_only(my_directives(), json);

println!("This is the CSP Header: {}", csp.header);
// This is the CSP Header: Content-Security-Policy-Report-Only
println!("This is the CSP Directives String: {}", csp.csp);
// This is the CSP Directives String: connect-src example.com test.com;
}
```

## License
Expand Down
43 changes: 20 additions & 23 deletions src/csp/line.rs → src/csp/directive.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
use crate::domains::{Collection, Item};
use crate::csp::domains;
use crate::domains::Collection;

fn domains_to_directive(directive: String, domains: Vec<Item>) -> String {
let mut directive_line = directive.clone();
// Make an individual CSP directive.
fn make(directive: &str, domains: &[String]) -> String {
let mut directive_line = String::new();
directive_line.push_str(directive);
directive_line.push_str(" ");

for domain in domains {
if domain.directives.contains(&directive) {
directive_line.push_str(domain.as_str());
if domains.last().unwrap() != domain {
directive_line.push_str(" ");
directive_line.push_str(domain.domain.as_str());
}
}

directive_line.push_str("; ");
directive_line.push_str(";");
directive_line
}

fn check_line(directive_line: String, check: String) -> String {
if directive_line == check {
// Generate a CSP directive line for the supplied directive based on the
// domains config.
pub fn generate(directive: String, domains: Collection) -> String {
let found = domains::find(&directive.as_str(), &domains);

if found.is_empty() {
return String::from("");
}

directive_line
}

fn create_check(mut directive: String) -> String {
directive.push_str("; ");
directive
}

pub fn build(directive: String, domains: Collection) -> String {
let directive_line = domains_to_directive(directive.clone(), domains);

check_line(directive_line, create_check(directive))
make(&directive.as_str(), &found)
}

// -----
Expand All @@ -52,9 +49,9 @@ mod lines_test {
let mut domain_list: Collection = Vec::new();
domain_list.push(item);

let connect_src: String = super::build(String::from("connect-src"), domain_list);
let connect_src: String = super::generate(String::from("connect-src"), domain_list);

assert_eq!(connect_src, String::from("connect-src *.example.com; "));
assert_eq!(connect_src, String::from("connect-src *.example.com;"));
}

#[test]
Expand All @@ -69,7 +66,7 @@ mod lines_test {
let mut domain_list: Collection = Vec::new();
domain_list.push(item);

let default_src: String = super::build(String::from("default-src"), domain_list);
let default_src: String = super::generate(String::from("default-src"), domain_list);

assert_eq!(default_src, String::from(""));
}
Expand Down
85 changes: 85 additions & 0 deletions src/csp/domains.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::domains::Item;

// Find the domains associated with the CSP directive
pub fn find(directive: &str, domains: &[Item]) -> Vec<String> {
let mut domains_found: Vec<String> = vec![];

for domain in domains {
if domain.directives.contains(&directive.to_string()) {
domains_found.push(domain.domain.to_string());
}
}

domains_found
}

// -----
// Tests
// -----
#[cfg(test)]
mod domains_test {
use crate::domains::{Collection, Item};

#[test]
fn test_find() {
let item1 = Item {
domain: "foo".to_string(),
directives: vec!["img-src".to_string()],
};
let item2 = Item {
domain: "bar".to_string(),
directives: vec!["img-src".to_string()],
};

let domains: Collection = vec![item1, item2];

let directive = "img-src";

let result: Vec<String> = super::find(&directive, &domains);

assert_eq!(result.len(), 2);
assert_eq!(result[0], "foo".to_string());
assert_eq!(result[1], "bar".to_string());
}

#[test]
fn test_find_one() {
let item1 = Item {
domain: "foo".to_string(),
directives: vec!["script-src".to_string()],
};
let item2 = Item {
domain: "bar".to_string(),
directives: vec!["img-src".to_string()],
};

let domains: Collection = vec![item1, item2];

let directive = "script-src";

let result: Vec<String> = super::find(&directive, &domains);

assert_eq!(result.len(), 1);
assert_eq!(result[0], "foo".to_string());
}

#[test]
fn test_find_none() {
let item1 = Item {
domain: "foo".to_string(),
directives: vec!["script-src".to_string()],
};
let item2 = Item {
domain: "bar".to_string(),
directives: vec!["img-src".to_string()],
};

let domains: Collection = vec![item1, item2];

let directive = "connect-src";

let result: Vec<String> = super::find(&directive, &domains);

assert!(result.is_empty());
}
}
57 changes: 43 additions & 14 deletions src/csp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,60 @@
// The core module for parsing the JSON config and generating the Content
// Security Policy.
//
// ToDo: Uses Threads, this may be overkill.
mod directive;
mod domains;

use crate::directives::GetDirectives;
use crate::domains;
use crate::domains::Collection;
use crate::parse;
use serde_json::error;
use std::thread;
use std::thread::JoinHandle;

mod line;
mod threads;
// Collect the generated directives and compile them into a CSP string.
fn directives_to_csp(directives: Vec<JoinHandle<String>>) -> String {
let mut csp: String = String::new();

fn threads_to_directives(threads: Vec<JoinHandle<String>>) -> String {
let mut directives: String = String::new();
for directive in directives {
let directive_string = directive.join().unwrap();

for thread in threads {
directives.push_str(thread.join().unwrap().as_str());
if !directive_string.is_empty() {
csp.push_str(directive_string.as_str());
csp.push_str(" ");
}
}

directives.trim().to_string()
csp.trim().to_string()
}

pub fn generate(directives_list: impl GetDirectives, json: &str) -> Result<String, error::Error> {
let domains: Result<domains::Collection, error::Error> = parse::json(json);
// Make each directive based on the domains collection supplied and the
// directives config.
fn make_directives(directives_config: Vec<String>, domains: Collection) -> Vec<JoinHandle<String>> {
let mut directives: Vec<JoinHandle<String>> = vec![];

for directive_item in directives_config {
let domains_clone = domains.clone();

directives.push(thread::spawn(move || {
directive::generate(directive_item, domains_clone)
}));
}

directives
}

// Parse the JSON config and generate the Content Security Policy.
pub fn generate(directives_config: impl GetDirectives, json: &str) -> Result<String, error::Error> {
let domains: Result<Collection, error::Error> = parse::json(json);

match domains {
Ok(domains) => {
let threads: Vec<JoinHandle<String>> =
threads::build_lines(directives_list.get_directives(), domains);
let config = directives_config.get_directives();

let directives: Vec<JoinHandle<String>> = make_directives(config, domains);

Ok(threads_to_directives(threads))
Ok(directives_to_csp(directives))
}
Err(e) => Err(e),
}
Expand All @@ -35,7 +64,7 @@ pub fn generate(directives_list: impl GetDirectives, json: &str) -> Result<Strin
// Tests
// -----
#[cfg(test)]
mod directives_test {
mod csp_test {
use crate::directives;
use serde_json::error;

Expand Down
44 changes: 0 additions & 44 deletions src/csp/threads.rs

This file was deleted.

Loading