authz 0.1.5

Permission-based authorization library
Documentation
extern crate serde;
extern crate serde_json;

use std::str;
use std::error::Error;
use std::collections::HashSet;

static PART_DELIMETER: &'static str = ":";
static SUBPART_DELIMETER: &'static str = ",";

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Permission {

    #[serde(default = "default_part")]
    domain: String,

    #[serde(default = "default_hash_part")]
    actions: HashSet<String>,

    #[serde(default = "default_hash_part")]
    targets: HashSet<String>
}

fn default_part() -> String {
    "*".to_string()
}

fn default_hash_part() -> HashSet<String> {
    ["*"].into_iter().map(|s| s.to_string()).collect()
}

impl<'a> Permission {
    pub fn new(wildcard_perm: &str) -> Permission {
        let (domain, actions, targets) = Permission::init_parts(wildcard_perm);
        let perm = Permission {
            domain: domain,
            actions: actions,
            targets: targets,
        };
        perm
    }

    fn part_from_str(s: Option<&str>) -> HashSet<String> {
        match s {
            Some("") | None => {
                let mut set = HashSet::new();
                set.insert(String::from("*"));
                set
            }
            Some(s) => {
                let mut set = HashSet::new();
                for rule in s.split(SUBPART_DELIMETER).map(str::trim) {
                    set.insert(String::from(rule));
                }
                set
            }
        }
    }

    fn init_parts(wildcard_perm: &str) -> (String, HashSet<String>, HashSet<String>) {
        let mut iter = wildcard_perm.split(PART_DELIMETER).map(str::trim);

        let domain = match iter.next() {
            Some("") | Some("*") | None => String::from("*"),
            Some(domain) => String::from(domain),
        };
        let actions = Permission::part_from_str(iter.next());
        let targets = Permission::part_from_str(iter.next());

        (domain, actions, targets)
    }

    pub fn implies_from_str(&self, wildcard_permission: &str) -> bool {
        let permission = Permission::new(wildcard_permission);
        self.implies_from_perm(&permission)
    }

    pub fn implies_from_perm(&self, permission: &Permission) -> bool {
        if self.domain != "*" {
            if self.domain != permission.domain {
                return false;
            }
        }

        if !self.actions.contains("*") {
            if !&self.actions.is_superset(&permission.actions) {
                return false;
            }
        }

        if !self.targets.contains("*") {
            if !&self.targets.is_superset(&permission.targets) {
                return false;
            }
        }
        return true;
    }
}

pub fn is_permitted_from_str<'a, I>(required_perm: &str, assigned_perms: I) -> i32
    where I: IntoIterator<Item = &'a str>
{
    let required_permission = Permission::new(&required_perm);

    for assigned in assigned_perms {
        let assigned_permission = Permission::new(assigned);
        if assigned_permission.implies_from_perm(&required_permission) {
            return 1;
        }
    }
    return 0;
}

pub fn is_permitted_from_perm(required_perm: &str, assigned_perms: Vec<Permission>) -> i32 {
    let required_permission = Permission::new(required_perm);

    for assigned in assigned_perms {
        if assigned.implies_from_perm(&required_permission) {
            return 1;
        }
    }
    return 0;
}

pub fn perms_from_buffer(serialized_perms: &[u8]) -> Result<Vec<Permission>, Box<Error>> {
    let result = try!(serde_json::from_slice(serialized_perms));
    Ok(result)
}

#[cfg(test)]
mod test {
    extern crate serde_json;
    use authz::{Permission, is_permitted_from_str, is_permitted_from_perm, perms_from_buffer};
    use std::collections::HashSet;

    #[test]
    fn test_new_permission() {

        struct Perm<'a> {
            wildcard_perm: &'a str,
            domain: &'a str,
            actions: Vec<&'a str>,
            targets: Vec<&'a str>,
        }

        let tests = [Perm { wildcard_perm: "", domain: "*", actions: vec!["*"], targets: vec!["*"],},
                     Perm { wildcard_perm: "domain1", domain: "domain1", actions: vec!["*"], targets: vec!["*"],},
                     Perm { wildcard_perm: "domain1:action1", domain: "domain1", actions: vec!["action1"], targets: vec!["*"], },
                     Perm { wildcard_perm: ":action1, action2, action3", domain: "*", actions: vec!["action1", "action2", "action3"], targets: vec!["*"],},
                     Perm { wildcard_perm: "domain1:action1, action2:target1, target2", domain: "domain1", actions: vec!["action1", "action2"], targets: vec!["target1", "target2"], }];

        for test in tests.iter() {
            let perm: Permission = Permission::new(test.wildcard_perm);

            let expected_actions: HashSet<String> =
                test.actions.iter().map(|x| x.to_string()).collect();
            let expected_targets: HashSet<String> =
                test.targets.iter().map(|x| x.to_string()).collect();

            assert_eq!(perm.domain == test.domain, true);
            assert_eq!(perm.actions == expected_actions, true);
            assert_eq!(perm.targets == expected_targets, true);
        }
    }

    #[test]
    fn test_part_from_str() {
        let tests = vec![(Some(""), vec!["*"]),
                         (None, vec!["*"]),
                         (Some("action1, action2, action3"), vec!["action1", "action2", "action3"]),
                         (Some("incorrect format"), vec!["incorrect format"])];
        for &(ref input, ref expected) in tests.iter(){
            let expected_result: HashSet<String> = expected.iter().map(|x| x.to_string()).collect();
            let result: HashSet<String> = Permission::part_from_str(*input);
            assert_eq!(result, expected_result);
        }
    }

    #[test]
    fn test_implies_from_str() {
        let perm = Permission::new("domain1:action1");
        assert_eq!(perm.implies_from_str("domain1:action1:target1"), true);
        assert_eq!(perm.implies_from_str("domain1:action2"), false);
    }

    #[test]
    fn test_implies_from_perm() {
        let perm1: Permission = Permission::new("domain1:action1");
        let perm2: Permission = Permission::new("domain1:action1,action2");
        let perm3: Permission = Permission::new("domain1:action1,action2:target1");
        let perm4: Permission = Permission::new("domain1:action3,action4:target2,target3");
        let perm5: Permission = Permission::new("domain1:action1,action2,action3,action4");
        let perm6: Permission = Permission::new(":action1,action2,action3,action4");
        let perm7: Permission = Permission::new("domain1:action1,action3:target1, target2");
        let perm7b: Permission = Permission::new("domain1:action5");
        let perm8: Permission = Permission::new("");

        assert_eq!(perm5.implies_from_perm(&perm1), true);
        assert_eq!(perm5.implies_from_perm(&perm2), true);
        assert_eq!(perm5.implies_from_perm(&perm3), true);
        assert_eq!(perm5.implies_from_perm(&perm4), true);
        assert_eq!(perm1.implies_from_perm(&perm5), false);
        assert_eq!(perm3.implies_from_perm(&perm5), false);
        assert_eq!(perm6.implies_from_perm(&perm7), true);
        assert_eq!(perm7.implies_from_perm(&perm6), false);
        assert_eq!(perm6.implies_from_perm(&perm7b), false);
        assert_eq!(perm7b.implies_from_perm(&perm6), false);
        assert_eq!(perm8.implies_from_perm(&perm1), true);
        assert_eq!(perm1.implies_from_perm(&perm8), false);
        assert_eq!(perm8.implies_from_perm(&perm2), true);
        assert_eq!(perm2.implies_from_perm(&perm8), false);
        assert_eq!(perm8.implies_from_perm(&perm3), true);
        assert_eq!(perm3.implies_from_perm(&perm8), false);
        assert_eq!(perm8.implies_from_perm(&perm4), true);
        assert_eq!(perm4.implies_from_perm(&perm8), false);
        assert_eq!(perm8.implies_from_perm(&perm5), true);
        assert_eq!(perm5.implies_from_perm(&perm8), false);
        assert_eq!(perm8.implies_from_perm(&perm6), true);
        assert_eq!(perm6.implies_from_perm(&perm8), false);
        assert_eq!(perm8.implies_from_perm(&perm7), true);
        assert_eq!(perm7.implies_from_perm(&perm8), false);
    }

    #[test]
    fn test_internalis_permitted_from_str(){
        let required: &str = "domain2:action4:target7";
        let assigned: Vec<&str> = vec!["domain1:action1", "domain2:action3,action4"];
        assert_eq!(is_permitted_from_str(required, assigned.into_iter()), 1);
    }

    #[test]
    fn test_is_permitted_from_perm() {
        let required: &str = "domain2:action4:target7";
        let assigned: Vec<Permission> = vec![Permission::new("domain1:action1"),
                                             Permission::new("domain2:action3,action4")];
        assert_eq!(is_permitted_from_perm(required, assigned), 1);
    }

    #[test]
    fn test_perms_from_buffer() {
        let permissions: Vec<Permission> = vec![Permission::new("domain1:action1"),
                                                Permission::new("domain2:action3,action4")];
        let serialized = serde_json::to_string(&permissions).unwrap();
        let result: Vec<Permission> = perms_from_buffer(serialized.as_bytes()).unwrap();
        assert_eq!(result, permissions);
    }
}