From 5f49f7cc7580e811f5d0c2493edacf2ee02570f6 Mon Sep 17 00:00:00 2001
From: 82marbag <69267416+82marbag@users.noreply.github.com>
Date: Thu, 21 Jul 2022 10:11:08 -0400
Subject: [PATCH] forbid timezone in rfc3339  (#1521)

* forbid rfc3339 timezone

According to [0], "Date time as defined by the date-time production
in RFC3339 section 5.6 with no UTC offset..."

This commit makes the parsing compliant.

[0] https://awslabs.github.io/smithy/1.0/spec/core/protocol-traits.html#timestampformat-trait

Signed-off-by: Daniele Ahmed <ahmeddan@amazon.de>
---
 aws/rust-runtime/aws-config/Cargo.toml        |  1 +
 .../aws-config/src/credential_process.rs      | 27 +++++++++----------
 .../protocol/ServerProtocolTestGenerator.kt   |  2 --
 .../aws-smithy-types/src/date_time/format.rs  | 11 ++++++++
 4 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml
index daf10d31f..fb2cd1846 100644
--- a/aws/rust-runtime/aws-config/Cargo.toml
+++ b/aws/rust-runtime/aws-config/Cargo.toml
@@ -23,6 +23,7 @@ aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async" }
 aws-smithy-client = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-client", default-features = false }
 aws-smithy-types = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-types" }
 aws-types = { path = "../../sdk/build/aws-sdk/sdk/aws-types" }
+time = { version = "0.3.4", features = ["parsing"] }
 tokio = { version = "1", features = ["sync"] }
 tracing = { version = "0.1" }
 hyper = { version = "0.14", default-features = false }
diff --git a/aws/rust-runtime/aws-config/src/credential_process.rs b/aws/rust-runtime/aws-config/src/credential_process.rs
index 5a75089ac..fc3409b40 100644
--- a/aws/rust-runtime/aws-config/src/credential_process.rs
+++ b/aws/rust-runtime/aws-config/src/credential_process.rs
@@ -7,13 +7,13 @@
 
 use crate::json_credentials::{json_parse_loop, InvalidJsonCredentials, RefreshableCredentials};
 use aws_smithy_json::deserialize::Token;
-use aws_smithy_types::date_time::Format;
-use aws_smithy_types::DateTime;
 use aws_types::credentials::{future, CredentialsError, ProvideCredentials};
 use aws_types::{credentials, Credentials};
 use std::fmt;
 use std::process::Command;
 use std::time::SystemTime;
+use time::format_description::well_known::Rfc3339;
+use time::OffsetDateTime;
 
 #[derive(Clone)]
 pub(crate) struct CommandWithSensitiveArgs<T>(T);
@@ -232,19 +232,18 @@ pub(crate) fn parse_credential_process_json_credentials(
         secret_access_key.ok_or(InvalidJsonCredentials::MissingField("SecretAccessKey"))?;
     let session_token = session_token.ok_or(InvalidJsonCredentials::MissingField("Token"))?;
     let expiration = expiration.ok_or(InvalidJsonCredentials::MissingField("Expiration"))?;
-    let expiration = SystemTime::try_from(
-        DateTime::from_str(expiration.as_ref(), Format::DateTime).map_err(|err| {
+    let expiration =
+        SystemTime::try_from(OffsetDateTime::parse(&expiration, &Rfc3339).map_err(|err| {
             InvalidJsonCredentials::InvalidField {
                 field: "Expiration",
                 err: err.into(),
             }
-        })?,
-    )
-    .map_err(|_| {
-        InvalidJsonCredentials::Other(
-            "credential expiration time cannot be represented by a DateTime".into(),
-        )
-    })?;
+        })?)
+        .map_err(|_| {
+            InvalidJsonCredentials::Other(
+                "credential expiration time cannot be represented by a DateTime".into(),
+            )
+        })?;
     Ok(RefreshableCredentials {
         access_key_id,
         secret_access_key,
@@ -256,10 +255,10 @@ pub(crate) fn parse_credential_process_json_credentials(
 #[cfg(test)]
 mod test {
     use crate::credential_process::CredentialProcessProvider;
-    use aws_smithy_types::date_time::Format;
-    use aws_smithy_types::DateTime;
     use aws_types::credentials::ProvideCredentials;
     use std::time::SystemTime;
+    use time::format_description::well_known::Rfc3339;
+    use time::OffsetDateTime;
 
     #[tokio::test]
     async fn test_credential_process() {
@@ -274,7 +273,7 @@ mod test {
             creds.expiry(),
             Some(
                 SystemTime::try_from(
-                    DateTime::from_str("2022-05-02T18:36:00+00:00", Format::DateTime)
+                    OffsetDateTime::parse("2022-05-02T18:36:00+00:00", &Rfc3339)
                         .expect("static datetime")
                 )
                 .expect("static datetime")
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt
index e694e2cc4..38446695d 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt
@@ -695,12 +695,10 @@ class ServerProtocolTestGenerator(
             FailingTest(RestJson, "RestJsonBodyShortUnderflowOverflow_case2", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonBodyShortUnderflowOverflow_case3", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonHeaderMalformedStringInvalidBase64MediaType_case1", TestType.MalformedRequest),
-            FailingTest(RestJson, "RestJsonBodyTimestampDateTimeRejectsUTCOffsets_case0", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonBodyTimestampDefaultRejectsMalformedEpochSeconds_case5", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonBodyTimestampDefaultRejectsMalformedEpochSeconds_case7", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonBodyTimestampDefaultRejectsMalformedEpochSeconds_case9", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonPathTimestampDefaultRejectsDifferent8601Formats_case13", TestType.MalformedRequest),
-            FailingTest(RestJson, "RestJsonPathTimestampDefaultRejectsUTCOffsets", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonQueryTimestampDefaultRejectsDifferent8601Formats_case13", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonMalformedUnionNoFieldsSet", TestType.MalformedRequest),
             FailingTest(RestJson, "RestJsonMalformedSetDuplicateBlobs", TestType.MalformedRequest),
diff --git a/rust-runtime/aws-smithy-types/src/date_time/format.rs b/rust-runtime/aws-smithy-types/src/date_time/format.rs
index 9923aeb36..dd5d72d1b 100644
--- a/rust-runtime/aws-smithy-types/src/date_time/format.rs
+++ b/rust-runtime/aws-smithy-types/src/date_time/format.rs
@@ -377,6 +377,11 @@ pub(crate) mod rfc3339 {
     // Timezones not supported:
     // Not OK: 1985-04-12T23:20:50-02:00
     pub(crate) fn parse(s: &str) -> Result<DateTime, DateTimeParseError> {
+        if !matches!(s.chars().last(), Some('Z')) {
+            return Err(DateTimeParseError::Invalid(
+                "Smithy does not support timezone offsets in RFC-3339 date times".into(),
+            ));
+        }
         let date_time = OffsetDateTime::parse(s, &Rfc3339).map_err(|err| {
             DateTimeParseError::Invalid(format!("invalid RFC-3339 date-time: {}", err).into())
         })?;
@@ -626,6 +631,12 @@ mod tests {
         assert_eq!(e2, expected);
     }
 
+    #[test]
+    fn parse_rfc3339_timezone_forbidden() {
+        let dt = rfc3339::parse("1985-04-12T23:20:50-02:00");
+        assert!(matches!(dt.unwrap_err(), DateTimeParseError::Invalid(_)));
+    }
+
     #[test]
     fn http_date_out_of_range() {
         assert_eq!(
-- 
GitLab