Skip to content

Commit 6ac4cea

Browse files
authored
Merge pull request #11 from RobDWaller/0.2.0-rc
0.2.0 rc
2 parents fb8b879 + 519ac79 commit 6ac4cea

10 files changed

Lines changed: 218 additions & 98 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "csp_generator"
33
description = "Consume a JSON formatted list of domains and CSP directives and output a correctly formatted Content Security Policy string."
4-
version = "0.2.0-beta.3"
4+
version = "0.2.0-rc"
55
authors = ["Rob Waller <[email protected]>"]
66
edition = "2018"
77
keywords = ["csp", "json", "content-security", "csp-generator", "security"]

README.md

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,23 @@ Each of the methods accepts two arguments a list of CSP directives you wish to u
3838
**Example Code**
3939

4040
```rust
41-
use csp_generator::directives;
41+
use csp_generator::{directives, Csp};
4242

43-
let json = r#"
43+
fn main() {
44+
let json = r#"
4445
[
45-
{"domain": "example.com", "directive": ["connect-src"]},
46-
{"domain": "test.com", "directive": ["connect-src", "script-src"]}
46+
{"domain": "example.com", "directives": ["connect-src"]},
47+
{"domain": "test.com", "directives": ["connect-src", "script-src"]}
4748
]
48-
"#;
49+
"#;
4950

50-
let csp: String = csp_generator::enforce(directives::get_directives(), json);
51+
let csp: Csp = csp_generator::enforce(directives::directives(), json);
5152

52-
println!("This is the CSP Header: {}", csp.header);
53-
// This is the CSP Header: Content-Security-Policy
54-
println!("This is the CSP Directives String: {}", csp.csp);
55-
// This is the CSP Directives String: script-src test.com; connect-src example.com test.com;
53+
println!("This is the CSP Header: {}", csp.header);
54+
// This is the CSP Header: Content-Security-Policy
55+
println!("This is the CSP Directives String: {}", csp.csp);
56+
// This is the CSP Directives String: script-src test.com; connect-src example.com test.com;
57+
}
5658
```
5759

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

9092
```rust
91-
use crate::GetDirectives;
93+
use csp_generator::directives::GetDirectives;
94+
use csp_generator::Csp;
9295

9396
pub struct MyDirectives {
9497
list: Vec<String>,
@@ -101,14 +104,30 @@ impl GetDirectives for MyDirectives {
101104
}
102105

103106
// Construct MyDirectives Struct with the directives you wish to use.
104-
pub fn my_directives() -> Directives {
107+
fn my_directives() -> MyDirectives {
105108
MyDirectives {
106109
list: vec![
107110
String::from("script-src"),
108111
String::from("connect-src"),
109112
],
110113
}
111114
}
115+
116+
pub fn main() {
117+
let json = r#"
118+
[
119+
{"domain": "example.com", "directives": ["connect-src"]},
120+
{"domain": "test.com", "directives": ["connect-src", "img-src"]}
121+
]
122+
"#;
123+
124+
let csp: Csp = csp_generator::report_only(my_directives(), json);
125+
126+
println!("This is the CSP Header: {}", csp.header);
127+
// This is the CSP Header: Content-Security-Policy-Report-Only
128+
println!("This is the CSP Directives String: {}", csp.csp);
129+
// This is the CSP Directives String: connect-src example.com test.com;
130+
}
112131
```
113132

114133
## License

src/csp/line.rs renamed to src/csp/directive.rs

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,33 @@
1-
use crate::domains::{Collection, Item};
1+
use crate::csp::domains;
2+
use crate::domains::Collection;
23

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

610
for domain in domains {
7-
if domain.directives.contains(&directive) {
11+
directive_line.push_str(domain.as_str());
12+
if domains.last().unwrap() != domain {
813
directive_line.push_str(" ");
9-
directive_line.push_str(domain.domain.as_str());
1014
}
1115
}
1216

13-
directive_line.push_str("; ");
17+
directive_line.push_str(";");
1418
directive_line
1519
}
1620

17-
fn check_line(directive_line: String, check: String) -> String {
18-
if directive_line == check {
21+
// Generate a CSP directive line for the supplied directive based on the
22+
// domains config.
23+
pub fn generate(directive: String, domains: Collection) -> String {
24+
let found = domains::find(&directive.as_str(), &domains);
25+
26+
if found.is_empty() {
1927
return String::from("");
2028
}
2129

22-
directive_line
23-
}
24-
25-
fn create_check(mut directive: String) -> String {
26-
directive.push_str("; ");
27-
directive
28-
}
29-
30-
pub fn build(directive: String, domains: Collection) -> String {
31-
let directive_line = domains_to_directive(directive.clone(), domains);
32-
33-
check_line(directive_line, create_check(directive))
30+
make(&directive.as_str(), &found)
3431
}
3532

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

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

57-
assert_eq!(connect_src, String::from("connect-src *.example.com; "));
54+
assert_eq!(connect_src, String::from("connect-src *.example.com;"));
5855
}
5956

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

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

7471
assert_eq!(default_src, String::from(""));
7572
}

src/csp/domains.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::domains::Item;
2+
3+
// Find the domains associated with the CSP directive
4+
pub fn find(directive: &str, domains: &[Item]) -> Vec<String> {
5+
let mut domains_found: Vec<String> = vec![];
6+
7+
for domain in domains {
8+
if domain.directives.contains(&directive.to_string()) {
9+
domains_found.push(domain.domain.to_string());
10+
}
11+
}
12+
13+
domains_found
14+
}
15+
16+
// -----
17+
// Tests
18+
// -----
19+
#[cfg(test)]
20+
mod domains_test {
21+
use crate::domains::{Collection, Item};
22+
23+
#[test]
24+
fn test_find() {
25+
let item1 = Item {
26+
domain: "foo".to_string(),
27+
directives: vec!["img-src".to_string()],
28+
};
29+
let item2 = Item {
30+
domain: "bar".to_string(),
31+
directives: vec!["img-src".to_string()],
32+
};
33+
34+
let domains: Collection = vec![item1, item2];
35+
36+
let directive = "img-src";
37+
38+
let result: Vec<String> = super::find(&directive, &domains);
39+
40+
assert_eq!(result.len(), 2);
41+
assert_eq!(result[0], "foo".to_string());
42+
assert_eq!(result[1], "bar".to_string());
43+
}
44+
45+
#[test]
46+
fn test_find_one() {
47+
let item1 = Item {
48+
domain: "foo".to_string(),
49+
directives: vec!["script-src".to_string()],
50+
};
51+
let item2 = Item {
52+
domain: "bar".to_string(),
53+
directives: vec!["img-src".to_string()],
54+
};
55+
56+
let domains: Collection = vec![item1, item2];
57+
58+
let directive = "script-src";
59+
60+
let result: Vec<String> = super::find(&directive, &domains);
61+
62+
assert_eq!(result.len(), 1);
63+
assert_eq!(result[0], "foo".to_string());
64+
}
65+
66+
#[test]
67+
fn test_find_none() {
68+
let item1 = Item {
69+
domain: "foo".to_string(),
70+
directives: vec!["script-src".to_string()],
71+
};
72+
let item2 = Item {
73+
domain: "bar".to_string(),
74+
directives: vec!["img-src".to_string()],
75+
};
76+
77+
let domains: Collection = vec![item1, item2];
78+
79+
let directive = "connect-src";
80+
81+
let result: Vec<String> = super::find(&directive, &domains);
82+
83+
assert!(result.is_empty());
84+
}
85+
}

src/csp/mod.rs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,60 @@
1+
// The core module for parsing the JSON config and generating the Content
2+
// Security Policy.
3+
//
4+
// ToDo: Uses Threads, this may be overkill.
5+
mod directive;
6+
mod domains;
7+
18
use crate::directives::GetDirectives;
2-
use crate::domains;
9+
use crate::domains::Collection;
310
use crate::parse;
411
use serde_json::error;
12+
use std::thread;
513
use std::thread::JoinHandle;
614

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

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

13-
for thread in threads {
14-
directives.push_str(thread.join().unwrap().as_str());
22+
if !directive_string.is_empty() {
23+
csp.push_str(directive_string.as_str());
24+
csp.push_str(" ");
25+
}
1526
}
1627

17-
directives.trim().to_string()
28+
csp.trim().to_string()
1829
}
1930

20-
pub fn generate(directives_list: impl GetDirectives, json: &str) -> Result<String, error::Error> {
21-
let domains: Result<domains::Collection, error::Error> = parse::json(json);
31+
// Make each directive based on the domains collection supplied and the
32+
// directives config.
33+
fn make_directives(directives_config: Vec<String>, domains: Collection) -> Vec<JoinHandle<String>> {
34+
let mut directives: Vec<JoinHandle<String>> = vec![];
35+
36+
for directive_item in directives_config {
37+
let domains_clone = domains.clone();
38+
39+
directives.push(thread::spawn(move || {
40+
directive::generate(directive_item, domains_clone)
41+
}));
42+
}
43+
44+
directives
45+
}
46+
47+
// Parse the JSON config and generate the Content Security Policy.
48+
pub fn generate(directives_config: impl GetDirectives, json: &str) -> Result<String, error::Error> {
49+
let domains: Result<Collection, error::Error> = parse::json(json);
2250

2351
match domains {
2452
Ok(domains) => {
25-
let threads: Vec<JoinHandle<String>> =
26-
threads::build_lines(directives_list.get_directives(), domains);
53+
let config = directives_config.get_directives();
54+
55+
let directives: Vec<JoinHandle<String>> = make_directives(config, domains);
2756

28-
Ok(threads_to_directives(threads))
57+
Ok(directives_to_csp(directives))
2958
}
3059
Err(e) => Err(e),
3160
}
@@ -35,7 +64,7 @@ pub fn generate(directives_list: impl GetDirectives, json: &str) -> Result<Strin
3564
// Tests
3665
// -----
3766
#[cfg(test)]
38-
mod directives_test {
67+
mod csp_test {
3968
use crate::directives;
4069
use serde_json::error;
4170

src/csp/threads.rs

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)