rustack_sts_http/
request.rs1use rustack_sts_model::error::StsError;
7
8#[must_use]
10pub fn parse_form_params(body: &[u8]) -> Vec<(String, String)> {
11 form_urlencoded::parse(body)
12 .map(|(k, v)| (k.into_owned(), v.into_owned()))
13 .collect()
14}
15
16pub fn get_required_param<'a>(
20 params: &'a [(String, String)],
21 key: &str,
22) -> Result<&'a str, StsError> {
23 params
24 .iter()
25 .find(|(k, _)| k == key)
26 .map(|(_, v)| v.as_str())
27 .ok_or_else(|| {
28 StsError::invalid_parameter_value(format!("Missing required parameter: {key}"))
29 })
30}
31
32#[must_use]
34pub fn get_optional_param<'a>(params: &'a [(String, String)], key: &str) -> Option<&'a str> {
35 params
36 .iter()
37 .find(|(k, _)| k == key)
38 .map(|(_, v)| v.as_str())
39}
40
41#[must_use]
47pub fn parse_session_tags(params: &[(String, String)]) -> Vec<rustack_sts_model::types::Tag> {
48 let mut tags = Vec::new();
49 let mut index = 1;
50
51 loop {
52 let key_param = format!("Tags.member.{index}.Key");
53 let value_param = format!("Tags.member.{index}.Value");
54
55 let key = params
56 .iter()
57 .find(|(k, _)| k == &key_param)
58 .map(|(_, v)| v.clone());
59 let value = params
60 .iter()
61 .find(|(k, _)| k == &value_param)
62 .map(|(_, v)| v.clone());
63
64 match (key, value) {
65 (Some(k), Some(v)) => {
66 tags.push(rustack_sts_model::types::Tag { key: k, value: v });
67 index += 1;
68 }
69 (Some(k), None) => {
70 tags.push(rustack_sts_model::types::Tag {
71 key: k,
72 value: String::new(),
73 });
74 index += 1;
75 }
76 _ => break,
77 }
78 }
79
80 tags
81}
82
83#[must_use]
89pub fn parse_transitive_tag_keys(params: &[(String, String)]) -> Vec<String> {
90 let mut keys = Vec::new();
91 let mut index = 1;
92
93 loop {
94 let param = format!("TransitiveTagKeys.member.{index}");
95 match params.iter().find(|(k, _)| k == ¶m) {
96 Some((_, v)) => {
97 keys.push(v.clone());
98 index += 1;
99 }
100 None => break,
101 }
102 }
103
104 keys
105}
106
107#[must_use]
109pub fn parse_policy_arns(params: &[(String, String)]) -> Vec<String> {
110 let mut arns = Vec::new();
111 let mut index = 1;
112
113 loop {
114 let param = format!("PolicyArns.member.{index}.arn");
115 match params.iter().find(|(k, _)| k == ¶m) {
116 Some((_, v)) => {
117 arns.push(v.clone());
118 index += 1;
119 }
120 None => break,
121 }
122 }
123
124 arns
125}
126
127#[must_use]
131pub fn extract_access_key_from_auth(auth_header: &str) -> Option<String> {
132 let cred_start = auth_header.find("Credential=")?;
133 let cred_value = &auth_header[cred_start + 11..];
134 let cred_end = cred_value.find('/')?;
135 Some(cred_value[..cred_end].to_owned())
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_should_parse_form_params() {
144 let body = b"Action=GetCallerIdentity&Version=2011-06-15";
145 let params = parse_form_params(body);
146 assert_eq!(params.len(), 2);
147 assert_eq!(
148 params[0],
149 ("Action".to_owned(), "GetCallerIdentity".to_owned())
150 );
151 }
152
153 #[test]
154 fn test_should_get_required_param() {
155 let params = vec![("RoleArn".to_owned(), "arn:aws:iam::123:role/R".to_owned())];
156 assert_eq!(
157 get_required_param(¶ms, "RoleArn").unwrap(),
158 "arn:aws:iam::123:role/R"
159 );
160 }
161
162 #[test]
163 fn test_should_error_on_missing_required_param() {
164 let params: Vec<(String, String)> = vec![];
165 let err = get_required_param(¶ms, "RoleArn").unwrap_err();
166 assert!(err.message.contains("RoleArn"));
167 }
168
169 #[test]
170 fn test_should_get_optional_param() {
171 let params = vec![("DurationSeconds".to_owned(), "3600".to_owned())];
172 assert_eq!(get_optional_param(¶ms, "DurationSeconds"), Some("3600"));
173 assert_eq!(get_optional_param(¶ms, "Missing"), None);
174 }
175
176 #[test]
177 fn test_should_parse_session_tags() {
178 let params = vec![
179 ("Tags.member.1.Key".to_owned(), "Project".to_owned()),
180 ("Tags.member.1.Value".to_owned(), "MyProject".to_owned()),
181 ("Tags.member.2.Key".to_owned(), "Env".to_owned()),
182 ("Tags.member.2.Value".to_owned(), "Dev".to_owned()),
183 ];
184 let tags = parse_session_tags(¶ms);
185 assert_eq!(tags.len(), 2);
186 assert_eq!(tags[0].key, "Project");
187 assert_eq!(tags[0].value, "MyProject");
188 assert_eq!(tags[1].key, "Env");
189 assert_eq!(tags[1].value, "Dev");
190 }
191
192 #[test]
193 fn test_should_parse_transitive_tag_keys() {
194 let params = vec![
195 (
196 "TransitiveTagKeys.member.1".to_owned(),
197 "Project".to_owned(),
198 ),
199 ("TransitiveTagKeys.member.2".to_owned(), "Env".to_owned()),
200 ];
201 let keys = parse_transitive_tag_keys(¶ms);
202 assert_eq!(keys, vec!["Project", "Env"]);
203 }
204
205 #[test]
206 fn test_should_parse_policy_arns() {
207 let params = vec![(
208 "PolicyArns.member.1.arn".to_owned(),
209 "arn:aws:iam::123:policy/P".to_owned(),
210 )];
211 let arns = parse_policy_arns(¶ms);
212 assert_eq!(arns, vec!["arn:aws:iam::123:policy/P"]);
213 }
214
215 #[test]
216 fn test_should_extract_access_key_from_auth() {
217 let auth = "AWS4-HMAC-SHA256 \
218 Credential=AKIAIOSFODNN7EXAMPLE/20260319/us-east-1/sts/aws4_request, \
219 SignedHeaders=content-type;host;x-amz-date, Signature=abc123";
220 assert_eq!(
221 extract_access_key_from_auth(auth),
222 Some("AKIAIOSFODNN7EXAMPLE".to_owned())
223 );
224 }
225
226 #[test]
227 fn test_should_return_none_for_missing_credential() {
228 assert_eq!(extract_access_key_from_auth("Bearer token123"), None);
229 }
230
231 #[test]
232 fn test_should_parse_url_encoded_special_chars() {
233 let body = b"Action=AssumeRole&RoleArn=arn%3Aaws%3Aiam%3A%3A123456789012%3Arole%2FTestRole";
234 let params = parse_form_params(body);
235 assert_eq!(params[1].1, "arn:aws:iam::123456789012:role/TestRole");
236 }
237}