use {
crate::{utils::validate_name, PrincipalError},
scratchstack_arn::{
utils::{validate_account_id, validate_partition},
Arn,
},
std::{
fmt::{Display, Formatter, Result as FmtResult},
str::FromStr,
},
};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AssumedRole {
partition: String,
account_id: String,
role_name: String,
session_name: String,
}
impl AssumedRole {
pub fn new(partition: &str, account_id: &str, role_name: &str, session_name: &str) -> Result<Self, PrincipalError> {
validate_partition(partition)?;
validate_account_id(account_id)?;
validate_name(role_name, 64, PrincipalError::InvalidRoleName)?;
validate_name(session_name, 64, PrincipalError::InvalidSessionName)?;
if session_name.len() < 2 {
Err(PrincipalError::InvalidSessionName(session_name.into()))
} else {
Ok(Self {
partition: partition.into(),
account_id: account_id.into(),
role_name: role_name.into(),
session_name: session_name.into(),
})
}
}
#[inline]
pub fn partition(&self) -> &str {
&self.partition
}
#[inline]
pub fn account_id(&self) -> &str {
&self.account_id
}
#[inline]
pub fn role_name(&self) -> &str {
&self.role_name
}
#[inline]
pub fn session_name(&self) -> &str {
&self.session_name
}
}
impl FromStr for AssumedRole {
type Err = PrincipalError;
fn from_str(arn: &str) -> Result<Self, PrincipalError> {
let parsed_arn = Arn::from_str(arn)?;
Self::try_from(&parsed_arn)
}
}
impl From<&AssumedRole> for Arn {
fn from(role: &AssumedRole) -> Arn {
Arn::new(
&role.partition,
"sts",
"",
&role.account_id,
&format!("assumed-role/{}/{}", role.role_name, role.session_name),
)
.unwrap()
}
}
impl Display for AssumedRole {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"arn:{}:sts::{}:assumed-role/{}/{}",
self.partition, self.account_id, self.role_name, self.session_name
)
}
}
impl TryFrom<&Arn> for AssumedRole {
type Error = PrincipalError;
fn try_from(arn: &Arn) -> Result<Self, Self::Error> {
let service = arn.service();
let region = arn.region();
let resource = arn.resource();
if service != "sts" {
return Err(PrincipalError::InvalidService(service.to_string()));
}
if !region.is_empty() {
return Err(PrincipalError::InvalidRegion(region.to_string()));
}
let resource_parts: Vec<&str> = resource.split('/').collect();
if resource_parts.len() != 3 || resource_parts[0] != "assumed-role" {
return Err(PrincipalError::InvalidResource(resource.to_string()));
}
Self::new(arn.partition(), arn.account_id(), resource_parts[1], resource_parts[2])
}
}
#[cfg(test)]
mod tests {
use {
super::AssumedRole,
crate::{PrincipalIdentity, PrincipalSource},
scratchstack_arn::Arn,
std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
str::FromStr,
},
};
#[test]
fn check_components() {
let role = AssumedRole::new("aws", "123456789012", "role", "session").unwrap();
assert_eq!(role.partition(), "aws");
assert_eq!(role.account_id(), "123456789012");
assert_eq!(role.role_name(), "role");
assert_eq!(role.session_name(), "session");
let arn: Arn = (&role).into();
assert_eq!(arn.partition(), "aws");
assert_eq!(arn.service(), "sts");
assert_eq!(arn.region(), "");
assert_eq!(arn.account_id(), "123456789012");
assert_eq!(arn.resource(), "assumed-role/role/session");
let p = PrincipalIdentity::from(role);
let source = p.source();
assert_eq!(source, PrincipalSource::Aws);
assert_eq!(source.to_string(), "AWS".to_string());
}
#[test]
#[allow(clippy::redundant_clone)]
fn check_derived() {
let r1a = AssumedRole::new("aws", "123456789012", "role1", "session1").unwrap();
let r1b = AssumedRole::new("aws", "123456789012", "role1", "session1").unwrap();
let r2 = AssumedRole::new("aws", "123456789012", "role1", "session2").unwrap();
let r3 = AssumedRole::new("aws", "123456789012", "role2", "session2").unwrap();
let r4 = AssumedRole::new("aws", "123456789013", "role2", "session2").unwrap();
let r5 = AssumedRole::new("awt", "123456789013", "role2", "session2").unwrap();
assert_eq!(r1a, r1b);
assert_ne!(r1a, r2);
assert_eq!(r1a.clone(), r1a);
let mut h1a = DefaultHasher::new();
let mut h1b = DefaultHasher::new();
let mut h2 = DefaultHasher::new();
r1a.hash(&mut h1a);
r1b.hash(&mut h1b);
r2.hash(&mut h2);
let hash1a = h1a.finish();
let hash1b = h1b.finish();
let hash2 = h2.finish();
assert_eq!(hash1a, hash1b);
assert_ne!(hash1a, hash2);
assert!(r1a <= r1b);
assert!(r1a < r2);
assert!(r2 < r3);
assert!(r3 > r2);
assert!(r3 > r1a);
assert!(r3 < r4);
assert!(r4 > r3);
assert!(r4 < r5);
assert!(r5 > r4);
assert_eq!(r1a.clone().max(r2.clone()), r2);
assert_eq!(r1a.clone().min(r2), r1a);
assert_eq!(r1a.to_string(), "arn:aws:sts::123456789012:assumed-role/role1/session1");
let _ = format!("{r1a:?}");
}
#[test]
fn check_valid_assumed_roles() {
let r1a = AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap();
let r1b = AssumedRole::new("aws", "123456789012", "Role_name", "session_name").unwrap();
let r2 =
AssumedRole::new("aws2", "123456789012", "Role@Foo=bar,baz_=world-1234", "Session@1234,_=-,.OK").unwrap();
assert_eq!(r1a, r1b);
assert_ne!(r1a, r2);
assert!(r1a <= r1b);
assert!(r1a >= r1b);
assert_eq!(r1a.partition(), "aws");
assert_eq!(r1a.account_id(), "123456789012");
assert_eq!(r1a.role_name(), "Role_name");
assert_eq!(r1a.session_name(), "session_name");
assert!(r1a < r2);
assert!(r1a <= r2);
assert!(r2 > r1a);
assert!(r2 >= r1a);
assert!(r2 != r1a);
assert_eq!(r1a.to_string(), "arn:aws:sts::123456789012:assumed-role/Role_name/session_name");
assert_eq!(r1b.to_string(), "arn:aws:sts::123456789012:assumed-role/Role_name/session_name");
assert_eq!(
r2.to_string(),
"arn:aws2:sts::123456789012:assumed-role/Role@Foo=bar,baz_=world-1234/Session@1234,_=-,.OK"
);
let r1c = r1a.clone();
assert!(r1a == r1c);
AssumedRole::new("partition-with-32-characters1234", "123456789012", "role-name", "session_name").unwrap();
AssumedRole::new(
"aws",
"123456789012",
"role-name-with_64-characters====================================",
"session@1234",
)
.unwrap();
AssumedRole::new(
"aws",
"123456789012",
"role-name",
"session-name-with-64-characters=================================",
)
.unwrap();
let _ = format!("{r1a:?}");
}
#[test]
fn check_invalid_assumed_roles() {
let err = AssumedRole::new("", "123456789012", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: """#);
let err = AssumedRole::new("partition-with-33-characters12345", "123456789012", "role-name", "session_name")
.unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: "partition-with-33-characters12345""#);
let err = AssumedRole::new("-aws", "123456789012", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: "-aws""#);
let err = AssumedRole::from_str("arn:-aws:sts::123456789012:assumed-role/role-name/session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: "-aws""#);
let err = AssumedRole::new("aws-", "123456789012", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: "aws-""#);
let err = AssumedRole::new("aws--us", "123456789012", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: "aws--us""#);
let err = AssumedRole::new("aw!", "123456789012", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid partition: "aw!""#);
let err = AssumedRole::new("aws", "", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid account id: """#);
let err = AssumedRole::new("aws", "a23456789012", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid account id: "a23456789012""#);
let err = AssumedRole::new("aws", "1234567890123", "role-name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid account id: "1234567890123""#);
let err = AssumedRole::new("aws", "123456789012", "", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid role name: """#);
let err = AssumedRole::new(
"aws",
"123456789012",
"role-name-with-65-characters=====================================",
"session-name",
)
.unwrap_err();
assert_eq!(
err.to_string(),
r#"Invalid role name: "role-name-with-65-characters=====================================""#
);
let err = AssumedRole::from_str("arn:aws:sts::123456789012:assumed-role/role-name-with-65-characters=====================================/session-name")
.unwrap_err();
assert_eq!(
err.to_string(),
r#"Invalid role name: "role-name-with-65-characters=====================================""#
);
let err = AssumedRole::new("aws", "123456789012", "role+name", "session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid role name: "role+name""#);
let err = AssumedRole::new("aws", "123456789012", "role-name", "").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid session name: """#);
let err = AssumedRole::new("aws", "123456789012", "role-name", "s").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid session name: "s""#);
let err = AssumedRole::new(
"aws",
"123456789012",
"role-name",
"session-name-with-65-characters==================================",
)
.unwrap_err();
assert_eq!(
err.to_string(),
r#"Invalid session name: "session-name-with-65-characters==================================""#
);
let err = AssumedRole::new("aws", "123456789012", "role-name", "session+name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid session name: "session+name""#);
let err =
AssumedRole::from_str("arn:aws:iam::123456789012:assumed-role/role/role-name/session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid service name: "iam""#);
let err = AssumedRole::from_str("arn:aws:sts::123456789012:user/role/role-name/session-name").unwrap_err();
assert_eq!(err.to_string(), r#"Invalid resource: "user/role/role-name/session-name""#);
}
}