#![warn(clippy::all)]
#![feature(doc_cfg)]
use std::{
error::Error,
fmt::{Debug, Display, Formatter, Result as FmtResult},
};
pub mod actor;
pub mod details;
pub mod policy;
pub use actor::PrincipalActor;
pub use policy::PolicyPrincipal;
#[derive(Debug)]
pub enum PrincipalError {
InvalidArn(String),
InvalidPartition(String),
InvalidAccountId(String),
InvalidFederatedUserName(String),
InvalidGroupName(String),
InvalidGroupId(String),
InvalidInstanceProfileName(String),
InvalidInstanceProfileId(String),
InvalidPath(String),
InvalidRegion(String),
InvalidRoleName(String),
InvalidRoleId(String),
#[cfg(feature = "service")]
#[doc(cfg(feature = "service"))]
InvalidServiceName(String),
InvalidSessionName(String),
InvalidUserName(String),
InvalidUserId(String),
}
impl Error for PrincipalError {}
impl Display for PrincipalError {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Self::InvalidArn(arn) => write!(f, "Invalid ARN: {:#?}", arn),
Self::InvalidPartition(partition) => write!(f, "Invalid partition: {:#?}", partition),
Self::InvalidAccountId(account_id) => {
write!(f, "Invalid account id: {:#?}", account_id)
}
Self::InvalidFederatedUserName(user_name) => {
write!(f, "Invalid federated user name: {:#?}", user_name)
}
Self::InvalidGroupName(group_name) => {
write!(f, "Invalid group name: {:#?}", group_name)
}
Self::InvalidGroupId(group_id) => write!(f, "Invalid group id: {:#?}", group_id),
Self::InvalidInstanceProfileName(instance_profile_name) => {
write!(f, "Invalid instance profile name: {:#?}", instance_profile_name)
}
Self::InvalidInstanceProfileId(instance_profile_id) => {
write!(f, "Invalid instance profile id: {:#?}", instance_profile_id)
}
Self::InvalidPath(path) => write!(f, "Invalid path: {:#?}", path),
Self::InvalidRegion(region) => write!(f, "Invalid region: {:#?}", region),
Self::InvalidRoleName(role_name) => write!(f, "Invalid role name: {:#?}", role_name),
Self::InvalidRoleId(role_id) => write!(f, "Invalid role id: {:#?}", role_id),
#[cfg(feature = "service")]
Self::InvalidServiceName(service_name) => {
write!(f, "Invalid service name: {:#?}", service_name)
}
Self::InvalidSessionName(session_name) => {
write!(f, "Invalid session name: {:#?}", session_name)
}
Self::InvalidUserName(user_name) => write!(f, "Invalid user name: {:#?}", user_name),
Self::InvalidUserId(user_id) => write!(f, "Invalid user id: {:#?}", user_id),
}
}
}
pub fn validate_account_id<S: Into<String>>(account_id: S) -> Result<String, PrincipalError> {
let account_id = account_id.into();
let a_bytes = account_id.as_bytes();
if a_bytes.len() != 12 {
return Err(PrincipalError::InvalidAccountId(account_id));
}
for c in a_bytes.iter() {
if !c.is_ascii_digit() {
return Err(PrincipalError::InvalidAccountId(account_id));
}
}
Ok(account_id)
}
fn validate_name<S: Into<String>>(name: S, max_length: usize) -> Result<String, String> {
let name = name.into();
let n_bytes = name.as_bytes();
let n_len = n_bytes.len();
if n_len == 0 || n_len > max_length {
return Err(name);
}
for c in n_bytes {
if !(c.is_ascii_alphanumeric()
|| *c == b','
|| *c == b'-'
|| *c == b'.'
|| *c == b'='
|| *c == b'@'
|| *c == b'_')
{
return Err(name);
}
}
Ok(name)
}
fn validate_identifier<S: Into<String>>(id: S, prefix: &str) -> Result<String, String> {
let id = id.into();
if !id.starts_with(prefix) || id.len() != 20 {
Err(id)
} else {
for c in id.as_bytes() {
if !(c.is_ascii_alphabetic() || (b'2'..=b'7').contains(c)) {
return Err(id);
}
}
Ok(id)
}
}
pub fn validate_partition<S: Into<String>>(partition: S) -> Result<String, PrincipalError> {
let partition = partition.into();
let p_bytes = partition.as_bytes();
let p_len = p_bytes.len();
if p_len == 0 || p_len > 32 {
return Err(PrincipalError::InvalidPartition(partition));
}
let mut last_was_dash = false;
for (i, c) in p_bytes.iter().enumerate() {
if *c == b'-' {
if i == 0 || i == p_len - 1 || last_was_dash {
return Err(PrincipalError::InvalidPartition(partition));
}
last_was_dash = true;
} else if !c.is_ascii_alphanumeric() {
return Err(PrincipalError::InvalidPartition(partition));
} else {
last_was_dash = false;
}
}
Ok(partition)
}
pub fn validate_path<S: Into<String>>(path: S) -> Result<String, PrincipalError> {
let path = path.into();
let p_bytes = path.as_bytes();
let p_len = p_bytes.len();
if p_len == 0 || p_len > 512 {
return Err(PrincipalError::InvalidPath(path));
}
if p_bytes[0] != b'/' || p_bytes[p_len - 1] != b'/' {
return Err(PrincipalError::InvalidPath(path));
}
for c in p_bytes {
if *c < 0x21 || *c > 0x7e {
return Err(PrincipalError::InvalidPath(path));
}
}
Ok(path)
}
#[derive(PartialEq)]
enum RegionParseState {
Start,
LastWasAlpha,
LastWasDash,
LastWasDigit,
}
enum RegionParseSection {
Region,
LocalRegion,
}
pub fn validate_region<S: Into<String>>(region: S) -> Result<String, PrincipalError> {
let region = region.into();
let r_bytes = region.as_bytes();
if region == "local" {
return Ok(region);
}
let mut section = RegionParseSection::Region;
let mut state = RegionParseState::Start;
for c in r_bytes {
if c == &b'-' {
match state {
RegionParseState::Start | RegionParseState::LastWasDash => {
return Err(PrincipalError::InvalidRegion(region));
}
RegionParseState::LastWasAlpha => {
state = RegionParseState::LastWasDash;
}
RegionParseState::LastWasDigit => match section {
RegionParseSection::Region => {
section = RegionParseSection::LocalRegion;
state = RegionParseState::LastWasDash;
}
RegionParseSection::LocalRegion => {
return Err(PrincipalError::InvalidRegion(region));
}
},
}
} else if c.is_ascii_lowercase() {
match state {
RegionParseState::Start | RegionParseState::LastWasDash | RegionParseState::LastWasAlpha => {
state = RegionParseState::LastWasAlpha;
}
_ => {
return Err(PrincipalError::InvalidRegion(region));
}
}
} else if c.is_ascii_digit() {
match state {
RegionParseState::LastWasDash | RegionParseState::LastWasDigit => {
state = RegionParseState::LastWasDigit;
}
_ => {
return Err(PrincipalError::InvalidRegion(region));
}
}
} else {
return Err(PrincipalError::InvalidRegion(region));
}
}
if state == RegionParseState::LastWasDigit {
Ok(region)
} else {
Err(PrincipalError::InvalidRegion(region))
}
}
#[cfg(test)]
mod test {
use super::validate_region;
#[test]
fn check_regions() {
validate_region("us-west-2").unwrap();
validate_region("us-west-2-lax-1").unwrap();
validate_region("local").unwrap();
assert_eq!(validate_region("us-").unwrap_err().to_string(), "Invalid region: \"us-\"");
assert_eq!(validate_region("us-west").unwrap_err().to_string(), "Invalid region: \"us-west\"");
assert_eq!(validate_region("-us-west-2").unwrap_err().to_string(), "Invalid region: \"-us-west-2\"");
assert_eq!(
validate_region("us-west-2-lax-1-lax-2").unwrap_err().to_string(),
"Invalid region: \"us-west-2-lax-1-lax-2\""
);
}
}