aws_smithy_runtime/client/auth/
http.rs1use aws_smithy_http::query_writer::QueryWriter;
9use aws_smithy_runtime_api::box_error::BoxError;
10use aws_smithy_runtime_api::client::auth::http::{
11 HTTP_API_KEY_AUTH_SCHEME_ID, HTTP_BASIC_AUTH_SCHEME_ID, HTTP_BEARER_AUTH_SCHEME_ID,
12 HTTP_DIGEST_AUTH_SCHEME_ID,
13};
14use aws_smithy_runtime_api::client::auth::{
15 AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, Sign,
16};
17use aws_smithy_runtime_api::client::identity::http::{Login, Token};
18use aws_smithy_runtime_api::client::identity::{Identity, SharedIdentityResolver};
19use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
20use aws_smithy_runtime_api::client::runtime_components::{GetIdentityResolver, RuntimeComponents};
21use aws_smithy_types::base64::encode;
22use aws_smithy_types::config_bag::ConfigBag;
23
24#[derive(Copy, Clone, Debug)]
26pub enum ApiKeyLocation {
27 Query,
29 Header,
31}
32
33#[derive(Debug)]
35pub struct ApiKeyAuthScheme {
36 signer: ApiKeySigner,
37}
38
39impl ApiKeyAuthScheme {
40 pub fn new(
42 scheme: impl Into<String>,
43 location: ApiKeyLocation,
44 name: impl Into<String>,
45 ) -> Self {
46 Self {
47 signer: ApiKeySigner {
48 scheme: scheme.into(),
49 location,
50 name: name.into(),
51 },
52 }
53 }
54}
55
56impl AuthScheme for ApiKeyAuthScheme {
57 fn scheme_id(&self) -> AuthSchemeId {
58 HTTP_API_KEY_AUTH_SCHEME_ID
59 }
60
61 fn identity_resolver(
62 &self,
63 identity_resolvers: &dyn GetIdentityResolver,
64 ) -> Option<SharedIdentityResolver> {
65 identity_resolvers.identity_resolver(self.scheme_id())
66 }
67
68 fn signer(&self) -> &dyn Sign {
69 &self.signer
70 }
71}
72
73#[derive(Debug)]
74struct ApiKeySigner {
75 scheme: String,
76 location: ApiKeyLocation,
77 name: String,
78}
79
80impl Sign for ApiKeySigner {
81 fn sign_http_request(
82 &self,
83 request: &mut HttpRequest,
84 identity: &Identity,
85 _auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
86 _runtime_components: &RuntimeComponents,
87 _config_bag: &ConfigBag,
88 ) -> Result<(), BoxError> {
89 let api_key = identity
90 .data::<Token>()
91 .ok_or("HTTP ApiKey auth requires a `Token` identity")?;
92 match self.location {
93 ApiKeyLocation::Header => {
94 request
95 .headers_mut()
96 .try_append(
97 self.name.to_ascii_lowercase(),
98 format!("{} {}", self.scheme, api_key.token()),
99 )
100 .map_err(|_| {
101 "API key contains characters that can't be included in a HTTP header"
102 })?;
103 }
104 ApiKeyLocation::Query => {
105 let mut query = QueryWriter::new_from_string(request.uri())?;
106 query.insert(&self.name, api_key.token());
107 request
108 .set_uri(query.build_uri())
109 .expect("query writer returns a valid URI")
110 }
111 }
112
113 Ok(())
114 }
115}
116
117#[derive(Debug, Default)]
119pub struct BasicAuthScheme {
120 signer: BasicAuthSigner,
121}
122
123impl BasicAuthScheme {
124 pub fn new() -> Self {
126 Self {
127 signer: BasicAuthSigner,
128 }
129 }
130}
131
132impl AuthScheme for BasicAuthScheme {
133 fn scheme_id(&self) -> AuthSchemeId {
134 HTTP_BASIC_AUTH_SCHEME_ID
135 }
136
137 fn identity_resolver(
138 &self,
139 identity_resolvers: &dyn GetIdentityResolver,
140 ) -> Option<SharedIdentityResolver> {
141 identity_resolvers.identity_resolver(self.scheme_id())
142 }
143
144 fn signer(&self) -> &dyn Sign {
145 &self.signer
146 }
147}
148
149#[derive(Debug, Default)]
150struct BasicAuthSigner;
151
152impl Sign for BasicAuthSigner {
153 fn sign_http_request(
154 &self,
155 request: &mut HttpRequest,
156 identity: &Identity,
157 _auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
158 _runtime_components: &RuntimeComponents,
159 _config_bag: &ConfigBag,
160 ) -> Result<(), BoxError> {
161 let login = identity
162 .data::<Login>()
163 .ok_or("HTTP basic auth requires a `Login` identity")?;
164 request.headers_mut().insert(
165 http_02x::header::AUTHORIZATION,
166 http_02x::HeaderValue::from_str(&format!(
167 "Basic {}",
168 encode(format!("{}:{}", login.user(), login.password()))
169 ))
170 .expect("valid header value"),
171 );
172 Ok(())
173 }
174}
175
176#[derive(Debug, Default)]
178pub struct BearerAuthScheme {
179 signer: BearerAuthSigner,
180}
181
182impl BearerAuthScheme {
183 pub fn new() -> Self {
185 Self {
186 signer: BearerAuthSigner,
187 }
188 }
189}
190
191impl AuthScheme for BearerAuthScheme {
192 fn scheme_id(&self) -> AuthSchemeId {
193 HTTP_BEARER_AUTH_SCHEME_ID
194 }
195
196 fn identity_resolver(
197 &self,
198 identity_resolvers: &dyn GetIdentityResolver,
199 ) -> Option<SharedIdentityResolver> {
200 identity_resolvers.identity_resolver(self.scheme_id())
201 }
202
203 fn signer(&self) -> &dyn Sign {
204 &self.signer
205 }
206}
207
208#[derive(Debug, Default)]
209struct BearerAuthSigner;
210
211impl Sign for BearerAuthSigner {
212 fn sign_http_request(
213 &self,
214 request: &mut HttpRequest,
215 identity: &Identity,
216 _auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
217 _runtime_components: &RuntimeComponents,
218 _config_bag: &ConfigBag,
219 ) -> Result<(), BoxError> {
220 let token = identity
221 .data::<Token>()
222 .ok_or("HTTP bearer auth requires a `Token` identity")?;
223 request.headers_mut().insert(
224 http_02x::header::AUTHORIZATION,
225 http_02x::HeaderValue::from_str(&format!("Bearer {}", token.token())).map_err(
226 |_| "Bearer token contains characters that can't be included in a HTTP header",
227 )?,
228 );
229 Ok(())
230 }
231}
232
233#[derive(Debug, Default)]
235pub struct DigestAuthScheme {
236 signer: DigestAuthSigner,
237}
238
239impl DigestAuthScheme {
240 pub fn new() -> Self {
242 Self {
243 signer: DigestAuthSigner,
244 }
245 }
246}
247
248impl AuthScheme for DigestAuthScheme {
249 fn scheme_id(&self) -> AuthSchemeId {
250 HTTP_DIGEST_AUTH_SCHEME_ID
251 }
252
253 fn identity_resolver(
254 &self,
255 identity_resolvers: &dyn GetIdentityResolver,
256 ) -> Option<SharedIdentityResolver> {
257 identity_resolvers.identity_resolver(self.scheme_id())
258 }
259
260 fn signer(&self) -> &dyn Sign {
261 &self.signer
262 }
263}
264
265#[derive(Debug, Default)]
266struct DigestAuthSigner;
267
268impl Sign for DigestAuthSigner {
269 fn sign_http_request(
270 &self,
271 _request: &mut HttpRequest,
272 _identity: &Identity,
273 _auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
274 _runtime_components: &RuntimeComponents,
275 _config_bag: &ConfigBag,
276 ) -> Result<(), BoxError> {
277 unimplemented!(
278 "support for signing with Smithy's `@httpDigestAuth` auth scheme is not implemented yet"
279 )
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use aws_smithy_runtime_api::client::identity::http::Login;
287 use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
288 use aws_smithy_types::body::SdkBody;
289
290 #[test]
291 fn test_api_key_signing_headers() {
292 let signer = ApiKeySigner {
293 scheme: "SomeSchemeName".into(),
294 location: ApiKeyLocation::Header,
295 name: "some-header-name".into(),
296 };
297 let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
298 let config_bag = ConfigBag::base();
299 let identity = Identity::new(Token::new("some-token", None), None);
300 let mut request: HttpRequest = http_02x::Request::builder()
301 .uri("http://example.com/Foobaz")
302 .body(SdkBody::empty())
303 .unwrap()
304 .try_into()
305 .unwrap();
306 signer
307 .sign_http_request(
308 &mut request,
309 &identity,
310 AuthSchemeEndpointConfig::empty(),
311 &runtime_components,
312 &config_bag,
313 )
314 .expect("success");
315 assert_eq!(
316 "SomeSchemeName some-token",
317 request.headers().get("some-header-name").unwrap()
318 );
319 assert_eq!("http://example.com/Foobaz", request.uri().to_string());
320 }
321
322 #[test]
323 fn test_api_key_signing_query() {
324 let signer = ApiKeySigner {
325 scheme: "".into(),
326 location: ApiKeyLocation::Query,
327 name: "some-query-name".into(),
328 };
329 let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
330 let config_bag = ConfigBag::base();
331 let identity = Identity::new(Token::new("some-token", None), None);
332 let mut request: HttpRequest = http_02x::Request::builder()
333 .uri("http://example.com/Foobaz")
334 .body(SdkBody::empty())
335 .unwrap()
336 .try_into()
337 .unwrap();
338 signer
339 .sign_http_request(
340 &mut request,
341 &identity,
342 AuthSchemeEndpointConfig::empty(),
343 &runtime_components,
344 &config_bag,
345 )
346 .expect("success");
347 assert!(request.headers().get("some-query-name").is_none());
348 assert_eq!(
349 "http://example.com/Foobaz?some-query-name=some-token",
350 request.uri().to_string()
351 );
352 }
353
354 #[test]
355 fn test_basic_auth() {
356 let signer = BasicAuthSigner;
357 let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
358 let config_bag = ConfigBag::base();
359 let identity = Identity::new(Login::new("Aladdin", "open sesame", None), None);
360 let mut request = http_02x::Request::builder()
361 .body(SdkBody::empty())
362 .unwrap()
363 .try_into()
364 .unwrap();
365
366 signer
367 .sign_http_request(
368 &mut request,
369 &identity,
370 AuthSchemeEndpointConfig::empty(),
371 &runtime_components,
372 &config_bag,
373 )
374 .expect("success");
375 assert_eq!(
376 "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
377 request.headers().get("Authorization").unwrap()
378 );
379 }
380
381 #[test]
382 fn test_bearer_auth() {
383 let signer = BearerAuthSigner;
384
385 let config_bag = ConfigBag::base();
386 let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
387 let identity = Identity::new(Token::new("some-token", None), None);
388 let mut request = http_02x::Request::builder()
389 .body(SdkBody::empty())
390 .unwrap()
391 .try_into()
392 .unwrap();
393 signer
394 .sign_http_request(
395 &mut request,
396 &identity,
397 AuthSchemeEndpointConfig::empty(),
398 &runtime_components,
399 &config_bag,
400 )
401 .expect("success");
402 assert_eq!(
403 "Bearer some-token",
404 request.headers().get("Authorization").unwrap()
405 );
406 }
407
408 #[test]
409 fn test_bearer_auth_overwrite_existing_header() {
410 let signer = BearerAuthSigner;
411
412 let config_bag = ConfigBag::base();
413 let runtime_components = RuntimeComponentsBuilder::for_tests().build().unwrap();
414 let identity = Identity::new(Token::new("some-token", None), None);
415 let mut request = http_02x::Request::builder()
416 .header("Authorization", "wrong")
417 .body(SdkBody::empty())
418 .unwrap()
419 .try_into()
420 .unwrap();
421 signer
422 .sign_http_request(
423 &mut request,
424 &identity,
425 AuthSchemeEndpointConfig::empty(),
426 &runtime_components,
427 &config_bag,
428 )
429 .expect("success");
430 assert_eq!(
431 "Bearer some-token",
432 request.headers().get("Authorization").unwrap()
433 );
434 }
435}