mod common;
use common::Fixture;
use gunnar_core::{
Grant, HashKind, Level, OperatorAccessState, Principal, PrincipalKind, Scope, Visibility,
};
const NOW: i64 = 1_791_000_000_000;
const HOUR_MS: i64 = 3_600_000;
struct World {
fx: Fixture,
site: gunnar_core::Repo,
public_site: gunnar_core::Repo,
forge: gunnar_core::Repo,
own: gunnar_core::Repo,
}
fn world() -> World {
let fx = Fixture::new();
for (id, kind) in [
("op", PrincipalKind::User),
("acme", PrincipalKind::User),
("carol", PrincipalKind::User),
("staff", PrincipalKind::Group),
("robot", PrincipalKind::Machine),
] {
fx.auth
.add_principal(Principal::new(id, id, kind).unwrap())
.unwrap();
}
fx.auth.grant(Grant::server("op", Level::Admin)).unwrap();
fx.auth.grant(Grant::server("staff", Level::Admin)).unwrap();
fx.auth.add_to_group("staff", "carol").unwrap();
fx.auth.grant(Grant::server("robot", Level::Admin)).unwrap();
fx.auth
.enroll_employee(
"acme",
Principal::new("bob", "Bob", PrincipalKind::User).unwrap(),
)
.unwrap();
let site = fx.repo("acme", "site");
let public_site = fx.repo_with("acme", "www", HashKind::Sha256, Visibility::Public);
let forge = fx.repo("nordisk", "gunnar");
let own = fx.repo("op", "notes");
World {
fx,
site,
public_site,
forge,
own,
}
}
#[test]
fn a_server_grant_stops_at_an_accounts_door() {
let w = world();
let auth = &w.fx.auth;
for level in [Level::Read, Level::Write, Level::Admin] {
assert!(
!auth.may("op", &w.site, level).unwrap(),
"the operator {level:?} acme/site"
);
assert!(
!auth.may_namespace("op", "acme", level).unwrap(),
"the operator {level:?} namespace acme"
);
assert!(
!auth.may("carol", &w.site, level).unwrap(),
"a server grant through a group, {level:?}"
);
assert!(
auth.may("robot", &w.site, level).unwrap(),
"a machine's server grant, {level:?}"
);
}
assert!(
auth.may("op", &w.forge, Level::Admin).unwrap(),
"a namespace no principal owns"
);
assert!(auth.may_namespace("op", "nordisk", Level::Admin).unwrap());
assert!(
auth.may("op", &w.own, Level::Admin).unwrap(),
"the operator's own account"
);
assert!(
auth.may_server("op", Level::Admin).unwrap(),
"the server itself"
);
assert!(auth.may("op", &w.public_site, Level::Read).unwrap());
assert!(!auth.may("op", &w.public_site, Level::Write).unwrap());
assert!(auth.may("acme", &w.site, Level::Admin).unwrap());
assert!(
auth.may("bob", &w.site, Level::Read).unwrap(),
"an employee reads its account"
);
}
#[test]
fn a_grant_the_account_admin_made_still_reaches() {
let w = world();
let auth = &w.fx.auth;
auth.grant(Grant {
principal: "op".into(),
scope: Scope::Repo(gunnar_core::RepoRef::new("acme", "site").unwrap()),
level: Level::Read,
})
.unwrap();
assert!(
auth.may("op", &w.site, Level::Read).unwrap(),
"a repository grant on acme/site"
);
assert!(!auth.may("op", &w.site, Level::Write).unwrap());
}
#[test]
fn who_is_shielded_from_whom() {
let w = world();
let auth = &w.fx.auth;
assert!(auth.shielded("op", "acme").unwrap());
assert!(
auth.shielded("op", "acme/team").unwrap(),
"a nested namespace is the account's"
);
assert!(!auth.shielded("acme", "acme").unwrap());
assert!(
!auth.shielded("bob", "acme").unwrap(),
"an employee is inside"
);
assert!(
!auth.shielded("op", "nordisk").unwrap(),
"no account owns nordisk"
);
assert!(!auth.shielded("op", "op").unwrap());
assert_eq!(
auth.account_of_namespace("acme/team").unwrap().as_deref(),
Some("acme")
);
assert_eq!(auth.account_of_namespace("nordisk").unwrap(), None);
assert_eq!(
auth.account_of_principal("bob").unwrap().as_deref(),
Some("acme")
);
assert_eq!(
auth.account_of_principal("acme").unwrap().as_deref(),
Some("acme")
);
assert_eq!(
auth.account_of_principal("robot").unwrap(),
None,
"a machine is no account"
);
assert!(auth.is_account_admin("acme", "acme").unwrap());
assert!(!auth.is_account_admin("bob", "acme").unwrap());
assert!(
!auth.is_account_admin("op", "acme").unwrap(),
"server Admin is not the account's admin"
);
auth.grant(Grant::namespace("bob", "acme", Level::Admin))
.unwrap();
assert!(
auth.is_account_admin("bob", "acme").unwrap(),
"an admin the account admin appointed"
);
}
#[test]
fn the_account_admin_approves_a_time_limited_look_and_takes_it_back() {
let w = world();
let auth = &w.fx.auth;
let asked = auth
.request_operator_access("acme", "op", "ticket 4711: a push hangs", 60, NOW)
.unwrap();
assert_eq!(asked.state, OperatorAccessState::Pending);
assert_eq!(asked.code.len(), 7, "a code for the eye: {asked:?}");
assert!(
!auth.may_at("op", &w.site, Level::Read, NOW + 1).unwrap(),
"asking is not having"
);
let again = auth
.request_operator_access("acme", "op", "again", 60, NOW + 5)
.unwrap();
assert_eq!(again.id, asked.id);
assert!(auth
.decide_operator_access(&asked.id, "bob", true, None, NOW + 10)
.is_err());
assert!(auth
.decide_operator_access(&asked.id, "op", true, None, NOW + 10)
.is_err());
let granted = auth
.decide_operator_access(&asked.id, "acme", true, None, NOW + 10)
.unwrap();
assert_eq!(granted.state, OperatorAccessState::Approved);
assert_eq!(
granted.granted_until_unix_ms,
NOW + 10 + HOUR_MS,
"the minutes asked for"
);
assert_eq!(granted.decided_by, "acme");
assert!(
auth.may_at("op", &w.site, Level::Read, NOW + 20).unwrap(),
"the approved look reads"
);
assert!(
!auth.may_at("op", &w.site, Level::Write, NOW + 20).unwrap(),
"and only reads"
);
assert!(!auth.shielded_at("op", "acme", NOW + 20).unwrap());
assert!(
!auth
.may_at("op", &w.site, Level::Read, NOW + 10 + HOUR_MS + 1)
.unwrap(),
"expired"
);
let asked = auth
.request_operator_access("acme", "op", "once more", 30, NOW + 2 * HOUR_MS)
.unwrap();
assert_ne!(
asked.id, granted.id,
"a new request after the last one ended"
);
auth.decide_operator_access(&asked.id, "acme", true, Some(15), NOW + 2 * HOUR_MS)
.unwrap();
assert!(auth
.may_at("op", &w.site, Level::Read, NOW + 2 * HOUR_MS + 1)
.unwrap());
let revoked = auth
.revoke_operator_access(&asked.id, "acme", NOW + 2 * HOUR_MS + 2)
.unwrap();
assert_eq!(revoked.state, OperatorAccessState::Revoked);
assert!(!auth
.may_at("op", &w.site, Level::Read, NOW + 2 * HOUR_MS + 3)
.unwrap());
let rows = auth.operator_access(Some("acme")).unwrap();
assert_eq!(rows.len(), 1, "one row per (account, operator): the latest");
assert!(auth.operator_access(Some("globex")).unwrap().is_empty());
}
#[test]
fn a_refused_or_unanswered_request_grants_nothing_and_limits_hold() {
let w = world();
let auth = &w.fx.auth;
let asked = auth
.request_operator_access("acme", "op", "why", 60, NOW)
.unwrap();
let refused = auth
.decide_operator_access(&asked.id, "acme", false, None, NOW + 1)
.unwrap();
assert_eq!(refused.state, OperatorAccessState::Refused);
assert!(!auth.may_at("op", &w.site, Level::Read, NOW + 2).unwrap());
assert!(
auth.decide_operator_access(&asked.id, "acme", true, None, NOW + 3)
.is_err(),
"decided once"
);
let asked = auth
.request_operator_access("acme", "op", "why", 60, NOW + 10)
.unwrap();
let late = NOW + 10 + gunnar_core::OPERATOR_REQUEST_TTL_MS + 1;
assert!(
auth.decide_operator_access(&asked.id, "acme", true, None, late)
.is_err(),
"too late"
);
assert!(
auth.request_operator_access("acme", "op", "", 60, NOW)
.is_err(),
"no reason"
);
assert!(auth
.request_operator_access(
"acme",
"op",
"why",
gunnar_core::OPERATOR_ACCESS_MAX_MINUTES + 1,
NOW
)
.is_err());
assert!(
auth.request_operator_access("nordisk", "op", "why", 60, NOW)
.is_err(),
"not an account"
);
assert!(
auth.request_operator_access("op", "op", "why", 60, NOW)
.is_err(),
"your own"
);
}
#[test]
fn removing_the_account_removes_its_grants() {
let w = world();
let auth = &w.fx.auth;
let asked = auth
.request_operator_access("acme", "op", "why", 60, NOW)
.unwrap();
auth.decide_operator_access(&asked.id, "acme", true, None, NOW)
.unwrap();
auth.remove_principal("acme").unwrap();
assert!(auth.operator_access(Some("acme")).unwrap().is_empty());
}
#[test]
fn the_boxs_first_identity_is_the_estate_and_no_account() {
let fx = Fixture::new();
fx.auth
.enroll_first_user("estate", common::KAT_ED25519)
.unwrap();
for id in ["op", "acme"] {
fx.auth
.add_principal(Principal::new(id, id, PrincipalKind::User).unwrap())
.unwrap();
}
fx.auth.grant(Grant::server("op", Level::Admin)).unwrap();
let seed = fx.repo("estate", "seed");
let site = fx.repo("acme", "site");
assert!(
fx.auth.may("op", &seed, Level::Admin).unwrap(),
"the estate's namespace is the operator's"
);
assert!(
fx.auth.may("estate", &site, Level::Read).unwrap(),
"the installer's key is the estate's plumbing"
);
assert!(
!fx.auth.may("op", &site, Level::Read).unwrap(),
"a person's account is still closed to the operator"
);
assert_eq!(fx.auth.account_of_namespace("estate").unwrap(), None);
assert_eq!(
fx.auth.account_of_namespace("acme").unwrap().as_deref(),
Some("acme")
);
}