artifact_app/
security.rs

1use dev_prefix::*;
2use types::*;
3
4
5/// validate that all files that could be affected by
6/// the project are within the repo and the `settings.artifact_paths`
7/// partof: #SPC-security
8pub fn validate(repo: &Path, project: &Project) -> Result<()> {
9    let mut files: HashSet<&PathBuf> = HashSet::new();
10    files.extend(project.artifacts.values().map(|a| &a.def));
11    files.extend(project.files.iter());
12    files.extend(project.repo_map.keys());
13
14    for f in files {
15        if !f.is_absolute() {
16            let msg = format!("{} is not an absolute path", f.display());
17            return Err(ErrorKind::Internal(msg).into());
18        }
19        // only allow files that are in the cwd repo
20        if !f.starts_with(repo) {
21            let msg = format!(
22                "{} is not a subdir of cwd repo {}",
23                f.display(),
24                repo.display()
25            );
26            return Err(ErrorKind::Security(msg).into());
27        }
28        // only allow files that are in the artifact_paths
29        if !project
30            .settings
31            .artifact_paths
32            .iter()
33            .any(|p| f.starts_with(p))
34        {
35            let msg = format!(
36                "{} is not a subdir of any artifact_paths {:?}",
37                f.display(),
38                project.settings.artifact_paths
39            );
40            return Err(ErrorKind::Security(msg).into());
41        }
42        // files that do not already exist are invalid
43        if !f.exists() {
44            let msg = format!(
45                "{} does not already exist and cannot be created here",
46                f.display()
47            );
48            return Err(ErrorKind::Security(msg).into());
49        }
50    }
51    Ok(())
52}
53
54pub fn validate_settings(repo: &Path, settings: &Settings) -> Result<()> {
55    if settings.artifact_paths.iter().any(|p| !p.starts_with(repo)) {
56        //TODO improve message
57        let msg = "`artifact_paths` invalid".to_string();
58        Err(ErrorKind::Security(msg).into())
59    } else {
60        Ok(())
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use test_data;
68    use utils;
69    use user;
70    use tempdir;
71    use fs_extra::dir;
72
73    #[test]
74    /// partof: #TST-security-bounds
75    fn test_bounds() {
76        test_bounds_init();
77        test_bounds_edit();
78    }
79
80    fn test_bounds_init() {
81        let design = test_data::TINVALID_BOUNDS.join("repo").join("design");
82        let repo = utils::find_repo(&design).unwrap();
83        match user::load_repo(&repo) {
84            Err(e) => {
85                match *e.kind() {
86                    ErrorKind::Security(_) => { /* expected */ }
87                    _ => panic!("Unexpected error: {:?}", e.display()),
88                }
89            }
90            Ok(_) => panic!(
91                "CRITICAL: Fmt suceeded when it should not have -- may need to reset with git"
92            ),
93        }
94    }
95
96    fn test_bounds_edit() {
97        let tmpdir = tempdir::TempDir::new("artifact").unwrap();
98        let writedir = tmpdir.path();
99        dir::copy(
100            &test_data::TSIMPLE_DIR.as_path(),
101            &writedir,
102            &dir::CopyOptions::new(),
103        ).unwrap();
104        let simple = writedir.join("simple");
105        let repo = utils::find_repo(&simple).unwrap();
106        let mut project = user::load_repo(&repo).unwrap();
107        let (name, mut art) = Artifact::from_str("[SPC-out_bounds]\n").unwrap();
108        art.def = writedir.to_path_buf();
109
110        project.artifacts.insert(name, art);
111
112        assert!(validate(&repo, &project).is_err());
113    }
114}