Skip to content

Commit

Permalink
Merge pull request #11 from RobDWaller/0.2.0-rc
Browse files Browse the repository at this point in the history
0.2.0 rc
  • Loading branch information
RobDWaller authored May 8, 2020
2 parents fb8b879 + 519ac79 commit 6ac4cea
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 98 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.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

0 comments on commit 6ac4cea

Please sign in to comment.