use ic_auth::canonical::{
CanonicalAuthError, ScopeLabelError, validate_scope_label, validate_scope_label_detailed,
validate_scopes,
};
use ic_auth::certificate_issuance::{GrantValidationError, validate_grants};
use ic_auth_protocol_types::DelegatedRoleGrant;
const STATIC_SCOPE: Result<(), ScopeLabelError> = validate_scope_label_detailed("fleet:read_all-1");
const STATIC_INVALID: Result<(), ScopeLabelError> = validate_scope_label_detailed("fleet:_read");
fn grant(role: &str, scopes: &[&str]) -> DelegatedRoleGrant {
DelegatedRoleGrant {
target: role.parse().unwrap(),
scopes: scopes.iter().map(|value| (*value).into()).collect(),
}
}
#[test]
fn const_and_runtime_scope_checks_preserve_detailed_byte_causes_and_canonical_projection() {
assert_eq!(STATIC_SCOPE, Ok(()));
assert_eq!(
STATIC_INVALID,
Err(ScopeLabelError::InvalidSegmentStart { offset: 6 })
);
let cases = [
("", ScopeLabelError::Empty),
(":read", ScopeLabelError::EmptySegment),
("read:", ScopeLabelError::EmptySegment),
("read::write", ScopeLabelError::EmptySegment),
("Read", ScopeLabelError::InvalidSegmentStart { offset: 0 }),
(
"read:_write",
ScopeLabelError::InvalidSegmentStart { offset: 5 },
),
("read:é", ScopeLabelError::InvalidSegmentStart { offset: 5 }),
("readé", ScopeLabelError::InvalidByte { offset: 4 }),
("read/write:", ScopeLabelError::InvalidByte { offset: 4 }),
(
"read:Write::",
ScopeLabelError::InvalidSegmentStart { offset: 5 },
),
];
for (scope, expected) in cases {
assert_eq!(validate_scope_label_detailed(scope), Err(expected));
assert_eq!(
validate_scope_label(scope),
Err(if scope.is_empty() {
CanonicalAuthError::EmptyScope
} else {
CanonicalAuthError::InvalidScope {
scope: scope.into(),
}
})
);
}
for scope in ["a", "0", "a_b-c", "0:1:a-b_c", "fleet:read_all-1"] {
assert_eq!(validate_scope_label_detailed(scope), Ok(()));
assert_eq!(validate_scope_label(scope), Ok(()));
}
assert_eq!(validate_scope_label_detailed(&"a".repeat(64)), Ok(()));
let oversized = format!("_{}", "a".repeat(64));
assert_eq!(
validate_scope_label_detailed(&oversized),
Err(ScopeLabelError::TooLong { bytes: 65, max: 64 })
);
assert_eq!(
validate_scope_label(&oversized),
Err(CanonicalAuthError::InvalidScope { scope: oversized })
);
}
#[test]
fn ordered_scopes_allow_empty_and_report_grammar_before_order_at_each_label() {
assert_eq!(validate_scopes(&[]), Ok(()));
assert_eq!(
validate_scopes(&["a".into(), "a:b".into(), "b".into()]),
Ok(())
);
for scopes in [["b", "a"], ["a", "a"]] {
assert_eq!(
validate_scopes(&scopes.map(String::from)),
Err(CanonicalAuthError::NonCanonicalScopes)
);
}
assert_eq!(
validate_scopes(&["b".into(), "Bad".into()]),
Err(CanonicalAuthError::InvalidScope {
scope: "Bad".into()
})
);
}
#[test]
fn standalone_grants_enforce_explicit_bounds_without_a_certificate() {
use GrantValidationError as E;
assert_eq!(validate_grants(&[], 0, 0), Err(E::Empty));
let grants = [grant("a", &["read", "write"]), grant("b", &["read"])];
assert_eq!(validate_grants(&grants, 2, 2), Ok(()));
assert_eq!(
validate_grants(&grants, 1, 2),
Err(E::TooMany { found: 2, max: 1 })
);
assert_eq!(
validate_grants(&grants[..1], 0, 2),
Err(E::TooMany { found: 1, max: 0 })
);
for max in [0, 1] {
assert_eq!(
validate_grants(&grants, 2, max),
Err(E::TooManyScopes {
role: "a".parse().unwrap(),
found: 2,
max,
})
);
}
assert_eq!(
validate_grants(&[grant("a", &[])], 1, 0),
Err(E::EmptyScopes {
role: "a".parse().unwrap()
})
);
}
#[test]
fn standalone_grant_refusals_preserve_input_order_for_mixed_invalid_values() {
use GrantValidationError as E;
let grants = [grant("b", &["Bad"]), grant("a", &[])];
assert_eq!(
validate_grants(&grants, 1, 1),
Err(E::TooMany { found: 2, max: 1 })
);
assert_eq!(
validate_grants(&grants, 2, 1),
Err(E::Canonical(CanonicalAuthError::InvalidScope {
scope: "Bad".into()
}))
);
for role in ["a", "b"] {
assert_eq!(
validate_grants(&[grant("b", &["read"]), grant(role, &[])], 2, 1),
Err(E::Canonical(CanonicalAuthError::NonCanonicalRoles))
);
}
assert_eq!(
validate_grants(&[grant("a", &["Bad", "Bad"])], 1, 1),
Err(E::TooManyScopes {
role: "a".parse().unwrap(),
found: 2,
max: 1
})
);
for scopes in [["write", "read"], ["read", "read"]] {
assert_eq!(
validate_grants(&[grant("a", &scopes)], 1, 2),
Err(E::Canonical(CanonicalAuthError::NonCanonicalScopes))
);
}
}