Repository: getmoto/moto
Pull request #8114 (2024-09-10 13:29:37)

IAM: Escape SAML metadata XML document when creating a SAML provider
G'day,

When passing an XML document the characters in the XML are causing the response value to return with a different value.

```
Error: reading IAM SAML Provider (arn:aws:iam::123456789012:saml-provider/sso-test): operation error IAM: GetSAMLProvider, https response error StatusCode: 200, RequestID: aZ44KsVPnA1fs9yDDYUHUddNxQrOiUyu8scM4elFE6CUdtCNCI1O, deserialization failed, failed to decode response body, expected value for SAMLMetadataDocument element, got xml.StartElement type {{urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor} [{{ xmlns} urn:oasis:names:tc:SAML:2.0:metadata} {{xmlns saml} urn:oasis:names:tc:SAML:2.0:assertion} {{xmlns ds} http://www.w3.org/2000/09/xmldsig#} {{ entityID} urn:amazon:webservices} {{ validUntil} 2025-08-30T00:00:00Z}]} instead
```

[Sample metadata](https://signin.aws.amazon.com/static/saml-metadata.xml) included to replicate the issue.

```
Terraform v1.9.5
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v5.66.0
```


Patch:
diff --git a/.github/workflows/data-update_config-managed-rules.yml b/.github/workflows/data-update_config-managed-rules.yml
index fd27f0e54..16504d8e1 100644
--- a/.github/workflows/data-update_config-managed-rules.yml
+++ b/.github/workflows/data-update_config-managed-rules.yml
@@ -37,7 +37,7 @@ jobs:
         cd scripts
         ./pull_down_aws_managed_rules.py
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-config-managed-rules-${{ github.run_number }}'
         title: 'chore: update Config Managed Rules'
diff --git a/.github/workflows/data-update_ec2-instance-offerings.yml b/.github/workflows/data-update_ec2-instance-offerings.yml
index 2cdca875f..8655b6208 100644
--- a/.github/workflows/data-update_ec2-instance-offerings.yml
+++ b/.github/workflows/data-update_ec2-instance-offerings.yml
@@ -42,7 +42,7 @@ jobs:
         pip install boto3
         scripts/ec2_get_instance_type_offerings.py
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-ec2-offerings-${{ github.run_number }}'
         title: 'chore: update EC2 Instance Offerings'
diff --git a/.github/workflows/data-update_ec2-instance-types.yml b/.github/workflows/data-update_ec2-instance-types.yml
index de1c06481..64c3b9d37 100644
--- a/.github/workflows/data-update_ec2-instance-types.yml
+++ b/.github/workflows/data-update_ec2-instance-types.yml
@@ -42,7 +42,7 @@ jobs:
         pip install boto3
         scripts/get_instance_info.py
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-ec2-types-${{ github.run_number }}'
         title: 'chore: update EC2 Instance Types'
diff --git a/.github/workflows/data-update_iam-managed-policies.yml b/.github/workflows/data-update_iam-managed-policies.yml
index 87b78764f..fcfd65ed8 100644
--- a/.github/workflows/data-update_iam-managed-policies.yml
+++ b/.github/workflows/data-update_iam-managed-policies.yml
@@ -42,7 +42,7 @@ jobs:
         pip install -r requirements-dev.txt
         make aws_managed_policies
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-iam-managed-policies-${{ github.run_number }}'
         title: 'chore: update IAM Managed Policies'
diff --git a/.github/workflows/data-update_ssm-default-amis.yml b/.github/workflows/data-update_ssm-default-amis.yml
index 30ed7193c..f22b38467 100644
--- a/.github/workflows/data-update_ssm-default-amis.yml
+++ b/.github/workflows/data-update_ssm-default-amis.yml
@@ -43,7 +43,7 @@ jobs:
         pip install moto[all]
         python scripts/ssm_get_default_amis.py
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-ssm-amis-${{ github.run_number }}'
         title: "chore: update SSM Instance AMI's"
diff --git a/.github/workflows/data-update_ssm-default-parameters.yml b/.github/workflows/data-update_ssm-default-parameters.yml
index 8bf4f6f42..1a1d398d6 100644
--- a/.github/workflows/data-update_ssm-default-parameters.yml
+++ b/.github/workflows/data-update_ssm-default-parameters.yml
@@ -43,7 +43,7 @@ jobs:
         pip install moto[all]
         python scripts/ssm_get_default_params.py
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-ssm-params-${{ github.run_number }}'
         title: "chore: update SSM Instance parameters"
diff --git a/.github/workflows/data-update_ssm-optimized-amis.yml b/.github/workflows/data-update_ssm-optimized-amis.yml
index d15d3d168..96a9600e7 100644
--- a/.github/workflows/data-update_ssm-optimized-amis.yml
+++ b/.github/workflows/data-update_ssm-optimized-amis.yml
@@ -43,7 +43,7 @@ jobs:
         pip install moto[all]
         python scripts/ssm_get_optimized_amis.py
     - name: Create PR
-      uses: peter-evans/create-pull-request@v6
+      uses: peter-evans/create-pull-request@v7
       with:
         branch: 'chore-update-ssm-amis-${{ github.run_number }}'
         title: "chore: update SSM Instance AMI's"
diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py
index ae7c94f62..4d4d327e0 100644
--- a/moto/dynamodb/exceptions.py
+++ b/moto/dynamodb/exceptions.py
@@ -305,11 +305,11 @@ class DuplicateUpdateExpression(InvalidUpdateExpression):
         )
 
 
-class TooManyAddClauses(InvalidUpdateExpression):
-    msg = 'The "ADD" section can only be used once in an update expression;'
-
-    def __init__(self) -> None:
-        super().__init__(self.msg)
+class TooManyClauses(InvalidUpdateExpression):
+    def __init__(self, _type: str) -> None:
+        super().__init__(
+            f'The "{_type}" section can only be used once in an update expression;'
+        )
 
 
 class ResourceNotFoundException(JsonRESTError):
diff --git a/moto/dynamodb/parsing/ast_nodes.py b/moto/dynamodb/parsing/ast_nodes.py
index 95875d90d..28985bf1a 100644
--- a/moto/dynamodb/parsing/ast_nodes.py
+++ b/moto/dynamodb/parsing/ast_nodes.py
@@ -8,7 +8,7 @@ from moto.dynamodb.models import DynamoType
 from ..exceptions import (
     DuplicateUpdateExpression,
     MockValidationException,
-    TooManyAddClauses,
+    TooManyClauses,
 )
 
 
@@ -29,9 +29,10 @@ class Node(metaclass=abc.ABCMeta):
 
     def validate(self, limit_set_actions: bool = False) -> None:
         if self.type == "UpdateExpression":
-            nr_of_clauses = len(self.find_clauses([UpdateExpressionAddClause]))
-            if nr_of_clauses > 1:
-                raise TooManyAddClauses()
+            if len(self.find_clauses([UpdateExpressionAddClause])) > 1:
+                raise TooManyClauses("ADD")
+            if len(self.find_clauses([UpdateExpressionRemoveClause])) > 1:
+                raise TooManyClauses("REMOVE")
             set_actions = self.find_clauses([UpdateExpressionSetAction])
             # set_attributes = ["attr", "map.attr", attr.list[2], ..]
             set_attributes = [s.children[0].to_str() for s in set_actions]
@@ -176,8 +177,11 @@ class UpdateExpressionRemoveAction(UpdateExpressionClause):
 
     def __lt__(self, other):
         self_value = self._get_value()
-
-        return self_value < other._get_value()
+        other_value = other._get_value()
+        if isinstance(self_value, int) and isinstance(other_value, int):
+            return self_value < other_value
+        else:
+            return str(self_value) < str(other_value)
 
 
 class UpdateExpressionAddActions(UpdateExpressionClause):
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 172e55aa8..3e06a6cfb 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -223,6 +223,7 @@ class SAMLProvider(BaseModel):
         saml_metadata_document: Optional[str] = None,
     ):
         self.account_id = account_id
+        self.create_date = utcnow()
         self.region_name = region_name
         self.name = name
         self.saml_metadata_document = saml_metadata_document
@@ -231,6 +232,10 @@ class SAMLProvider(BaseModel):
     def arn(self) -> str:
         return f"arn:{get_partition(self.region_name)}:iam::{self.account_id}:saml-provider/{self.name}"
 
+    @property
+    def created_iso_8601(self) -> str:
+        return iso_8601_datetime_without_milliseconds(self.create_date)
+
 
 class OpenIDConnectProvider(BaseModel):
     def __init__(
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
index cb20c45fd..1b2be7c1f 100644
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -1,3 +1,5 @@
+from xml.sax.saxutils import escape
+
 from moto.core.responses import BaseResponse
 
 from .models import IAMBackend, User, iam_backends
@@ -858,7 +860,10 @@ class IamResponse(BaseResponse):
 
     def create_saml_provider(self) -> str:
         saml_provider_name = self._get_param("Name")
-        saml_metadata_document = self._get_param("SAMLMetadataDocument")
+        _saml_metadata_document = self._get_param("SAMLMetadataDocument")
+        saml_metadata_document = escape(
+            _saml_metadata_document, entities={"'": "&apos;", '"': "&quot;"}
+        )
         saml_provider = self.backend.create_saml_provider(
             saml_provider_name, saml_metadata_document
         )
@@ -868,7 +873,10 @@ class IamResponse(BaseResponse):
 
     def update_saml_provider(self) -> str:
         saml_provider_arn = self._get_param("SAMLProviderArn")
-        saml_metadata_document = self._get_param("SAMLMetadataDocument")
+        _saml_metadata_document = self._get_param("SAMLMetadataDocument")
+        saml_metadata_document = escape(
+            _saml_metadata_document, entities={"'": "&apos;", '"': "&quot;"}
+        )
         saml_provider = self.backend.update_saml_provider(
             saml_provider_arn, saml_metadata_document
         )
@@ -2481,7 +2489,7 @@ LIST_SAML_PROVIDERS_TEMPLATE = """<ListSAMLProvidersResponse xmlns="https://iam.
 
 GET_SAML_PROVIDER_TEMPLATE = """<GetSAMLProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
 <GetSAMLProviderResult>
-  <CreateDate>2012-05-09T16:27:11Z</CreateDate>
+  <CreateDate>{{ saml_provider.created_iso_8601 }}</CreateDate>
   <ValidUntil>2015-12-31T21:59:59Z</ValidUntil>
   <SAMLMetadataDocument>{{ saml_provider.saml_metadata_document }}</SAMLMetadataDocument>
 </GetSAMLProviderResult>
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
index 967382100..eb79a3228 100644
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -1694,12 +1694,19 @@ class S3Response(BaseResponse):
         storage_class = self.headers.get("x-amz-storage-class", "STANDARD")
         encryption = self.headers.get("x-amz-server-side-encryption")
         kms_key_id = self.headers.get("x-amz-server-side-encryption-aws-kms-key-id")
+        if_none_match = self.headers.get("If-None-Match")
         bucket_key_enabled = self.headers.get(
             "x-amz-server-side-encryption-bucket-key-enabled"
         )
         if bucket_key_enabled is not None:
             bucket_key_enabled = str(bucket_key_enabled).lower()
 
+        if (
+            if_none_match == "*"
+            and self.backend.get_object(self.bucket_name, key_name) is not None
+        ):
+            raise PreconditionFailed("If-None-Match")
+
         checksum_algorithm, checksum_value = self._get_checksum(response_headers)
 
         bucket = self.backend.get_bucket(self.bucket_name)
diff --git a/other_langs/terraform/iam/data.tf b/other_langs/terraform/iam/data.tf
new file mode 100644
index 000000000..083e23c72
--- /dev/null
+++ b/other_langs/terraform/iam/data.tf
@@ -0,0 +1,20 @@
+data "aws_caller_identity" "this" {}
+
+data "aws_iam_policy_document" "this" {
+  statement {
+    actions = ["sts:AssumeRoleWithSAML"]
+    principals {
+      type        = "Federated"
+      identifiers = [aws_iam_saml_provider.this.arn]
+    }
+    condition {
+      test     = "StringEquals"
+      variable = "SAML:aud"
+      values   = ["https://signin.aws.amazon.com/saml"]
+    }
+  }
+}
+
+data "aws_iam_saml_provider" "this" {
+  arn = aws_iam_saml_provider.this.arn
+}
diff --git a/other_langs/terraform/iam/iam.tf b/other_langs/terraform/iam/iam.tf
index 1a7710d9e..812a1e536 100644
--- a/other_langs/terraform/iam/iam.tf
+++ b/other_langs/terraform/iam/iam.tf
@@ -21,4 +21,9 @@ resource "aws_iam_policy" "policy" {
   tags = {
     t1 = "v1"
   }
-}
\ No newline at end of file
+}
+
+resource "aws_iam_saml_provider" "this" {
+  name                   = "sso-test"
+  saml_metadata_document = file("${path.module}/sample-metadata.xml")
+}
diff --git a/other_langs/terraform/iam/output.tf b/other_langs/terraform/iam/output.tf
new file mode 100644
index 000000000..6f6f6a881
--- /dev/null
+++ b/other_langs/terraform/iam/output.tf
@@ -0,0 +1,11 @@
+output "saml_provider_create_date" {
+  value = data.aws_iam_saml_provider.this.create_date
+}
+
+output "saml_provider_create_name" {
+  value = data.aws_iam_saml_provider.this.name
+}
+
+output "saml_provider_saml_metadata_document" {
+  value = data.aws_iam_saml_provider.this.saml_metadata_document
+}
diff --git a/other_langs/terraform/iam/providers.tf b/other_langs/terraform/iam/providers.tf
index 3e11097e7..b6d68e709 100644
--- a/other_langs/terraform/iam/providers.tf
+++ b/other_langs/terraform/iam/providers.tf
@@ -7,6 +7,7 @@ provider "aws" {
 
   endpoints {
     iam = "http://localhost:5000"
+    sts = "http://localhost:5000"
   }
 
   access_key = "my-access-key"
diff --git a/other_langs/terraform/iam/sample-metadata.xml b/other_langs/terraform/iam/sample-metadata.xml
new file mode 100644
index 000000000..6ff6e914a
--- /dev/null
+++ b/other_langs/terraform/iam/sample-metadata.xml
@@ -0,0 +1,76 @@
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="urn:amazon:webservices" validUntil="2025-08-30T00:00:00Z">
+  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" WantAssertionsSigned="true">
+    <KeyDescriptor use="signing">
+      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:X509Data>
+          <ds:X509Certificate>MIIDcTCCAlmgAwIBAgIIGPlChvWsb9QwDQYJKoZIhvcNAQELBQAwZzEfMB0GA1UE AxMWdXJuOmFtYXpvbjp3ZWJzZXJ2aWNlczEiMCAGA1UEChMZQW1hem9uIFdlYiBT ZXJ2aWNlcywgSW5jLjETMBEGA1UECBMKV2FzaGluZ3RvbjELMAkGA1UEBhMCVVMw HhcNMjQwODMwMDAwMDAwWhcNMjUwODMwMDAwMDAwWjBnMR8wHQYDVQQDExZ1cm46 YW1hem9uOndlYnNlcnZpY2VzMSIwIAYDVQQKExlBbWF6b24gV2ViIFNlcnZpY2Vz LCBJbmMuMRMwEQYDVQQIEwpXYXNoaW5ndG9uMQswCQYDVQQGEwJVUzCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2IcN8WBhWhmwR5AOHJ7LIyl+f7ZiYM OjzsNZeOhvD6t8fIPqXg2Kyt2ixCCB16AQsif/bxYAoqqwrVkuBwG8V9CrDDuvXX 3WL7+hvn/JzmjyTeZxy34ySMppSvR3JNtps1C/stTs90KKAKfP56wg1RIEfA6dWX S/Ebd05Jx1miZb2j2+rX1C8Oc6RhOhBCHbbR/4fzFXxdocs4Krq6r+So2XQ8JChC 6E/c092/hCTBXtBj0D8Lk1DJWI1avMzRbrm53wH+1qTzoZMZu66gsuvWhbeH7B/Q kixxpb2IUNYNIT5A4g8tzzQLeGaMUHpvY8fuzRkX/8Zrd8CQjveOdPUCAwEAAaMh MB8wHQYDVR0OBBYEFCM8vneP4Ut+aXPdeCYsbFkUVMpeMA0GCSqGSIb3DQEBCwUA A4IBAQBCz1qeNzA4R/iXrqswc98bZXT7oj1lzkwRaDy0jsfsgyGwHIJ5VOMxnvFH 564zGlbljCOxrZG9FtdrutYGLHaLJLZdHKH2XQy2stxcP6pnZ29LyAMoIP6fq08M 1tzW0u0fMdC2Hpgi8QHGgguKW1AFFK3vj/sW1PWuMzTwB7ur5BpgMo4Wk074HIRT Z8B1RkY13EnFy7IIEIdreKLY3xwrpUZYnW0Em705T3gt0UrFKfzxrTjMPL3yXzgk wkAvGzGJZtdZ0FNc6ecGVuWkbJ7wvbYh2j557gbk9s0F+24103bfRyyM2jj7cgnK ij1wVZYobnMgnQI+ikrd7ksym6QN</ds:X509Certificate>
+        </ds:X509Data>
+      </ds:KeyInfo>
+    </KeyDescriptor>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
+    <AssertionConsumerService index="1" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="2" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-north-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="3" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://me-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="4" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="5" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-west-3.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="6" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-3.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="7" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://us-east-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="8" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://af-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="9" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-west-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="10" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="11" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sa-east-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="12" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-east-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="13" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-northeast-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="14" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-northeast-3.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="15" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-west-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="16" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="17" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-northeast-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="18" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://us-west-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="19" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://us-west-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="20" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="21" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="22" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ca-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="23" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://me-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="24" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-central-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="25" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-south-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="26" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-south-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="27" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-4.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="28" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://il-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="29" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ca-west-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="30" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-5.signin.aws.amazon.com/saml"/>
+    <AttributeConsumingService index="1">
+      <ServiceName xml:lang="en">AWS Management Console Single Sign-On</ServiceName>
+      <RequestedAttribute isRequired="true" Name="https://aws.amazon.com/SAML/Attributes/Role" FriendlyName="RoleEntitlement"/>
+      <RequestedAttribute isRequired="true" Name="https://aws.amazon.com/SAML/Attributes/RoleSessionName" FriendlyName="RoleSessionName"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" FriendlyName="eduPersonAffiliation"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.2" FriendlyName="eduPersonNickname"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.3" FriendlyName="eduPersonOrgDN"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.4" FriendlyName="eduPersonOrgUnitDN"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.5" FriendlyName="eduPersonPrimaryAffiliation"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" FriendlyName="eduPersonPrincipalName"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.7" FriendlyName="eduPersonEntitlement"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.8" FriendlyName="eduPersonPrimaryOrgUnitDN"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" FriendlyName="eduPersonScopedAffiliation"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" FriendlyName="eduPersonTargetedID"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.11" FriendlyName="eduPersonAssurance"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.2" FriendlyName="eduOrgHomePageURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.3" FriendlyName="eduOrgIdentityAuthNPolicyURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.4" FriendlyName="eduOrgLegalName"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.5" FriendlyName="eduOrgSuperiorURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.6" FriendlyName="eduOrgWhitePagesURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:2.5.4.3" FriendlyName="cn"/>
+    </AttributeConsumingService>
+  </SPSSODescriptor>
+  <Organization>
+    <OrganizationName xml:lang="en">Amazon Web Services, Inc.</OrganizationName>
+    <OrganizationDisplayName xml:lang="en">AWS</OrganizationDisplayName>
+    <OrganizationURL xml:lang="en">https://aws.amazon.com</OrganizationURL>
+  </Organization>
+</EntityDescriptor>


Test patch:
diff --git a/.github/workflows/tests_sdk_ruby.yml b/.github/workflows/tests_sdk_ruby.yml
index 87f08bcb7..e9e6fe224 100644
--- a/.github/workflows/tests_sdk_ruby.yml
+++ b/.github/workflows/tests_sdk_ruby.yml
@@ -13,7 +13,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Ruby ${{ matrix.ruby-version }}
-        uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854
+        uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999
         with:
           ruby-version: ${{ matrix.ruby-version }}
       - name: Set up Python 3.8
diff --git a/tests/test_dynamodb/test_dynamodb_update_expressions.py b/tests/test_dynamodb/test_dynamodb_update_expressions.py
index 3add47cfd..b14e66987 100644
--- a/tests/test_dynamodb/test_dynamodb_update_expressions.py
+++ b/tests/test_dynamodb/test_dynamodb_update_expressions.py
@@ -225,3 +225,65 @@ def test_update_item_with_empty_expression(table_name=None):
     assert (
         err["Message"] == "Invalid UpdateExpression: The expression can not be empty;"
     )
+
+
+@pytest.mark.aws_verified
+@dynamodb_aws_verified()
+def test_update_expression_with_multiple_remove_clauses(table_name=None):
+    ddb_client = boto3.client("dynamodb", "us-east-1")
+    payload = {
+        "pk": {"S": "primary_key"},
+        "current_user": {"M": {"name": {"S": "John"}, "surname": {"S": "Doe"}}},
+        "user_list": {
+            "L": [
+                {"M": {"name": {"S": "John"}, "surname": {"S": "Doe"}}},
+                {"M": {"name": {"S": "Jane"}, "surname": {"S": "Smith"}}},
+            ]
+        },
+        "some_param": {"NULL": True},
+    }
+    ddb_client.put_item(TableName=table_name, Item=payload)
+    with pytest.raises(ClientError) as exc:
+        ddb_client.update_item(
+            TableName=table_name,
+            Key={"pk": {"S": "primary_key"}},
+            UpdateExpression="REMOVE #ulist[0] SET current_user = :current_user REMOVE some_param",
+            ExpressionAttributeNames={"#ulist": "user_list"},
+            ExpressionAttributeValues={":current_user": {"M": {"name": {"S": "Jane"}}}},
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == 'Invalid UpdateExpression: The "REMOVE" section can only be used once in an update expression;'
+    )
+
+
+@pytest.mark.aws_verified
+@dynamodb_aws_verified()
+def test_update_expression_remove_list_and_attribute(table_name=None):
+    ddb_client = boto3.client("dynamodb", "us-east-1")
+    payload = {
+        "pk": {"S": "primary_key"},
+        "user_list": {
+            "L": [
+                {"M": {"name": {"S": "John"}, "surname": {"S": "Doe"}}},
+                {"M": {"name": {"S": "Jane"}, "surname": {"S": "Smith"}}},
+            ]
+        },
+        "some_param": {"NULL": True},
+    }
+    ddb_client.put_item(TableName=table_name, Item=payload)
+    ddb_client.update_item(
+        TableName=table_name,
+        Key={"pk": {"S": "primary_key"}},
+        UpdateExpression="REMOVE #ulist[0], some_param",
+        ExpressionAttributeNames={"#ulist": "user_list"},
+    )
+    item = ddb_client.get_item(
+        TableName=table_name, Key={"pk": {"S": "primary_key"}, "sk": {"S": "sort_key"}}
+    )["Item"]
+    assert item == {
+        "user_list": {"L": [{"M": {"name": {"S": "Jane"}, "surname": {"S": "Smith"}}}]},
+        "pk": {"S": "primary_key"},
+    }
diff --git a/tests/test_elbv2/test_elbv2_target_health.py b/tests/test_elbv2/test_elbv2_target_health.py
index 2647839bd..47d68b5e3 100644
--- a/tests/test_elbv2/test_elbv2_target_health.py
+++ b/tests/test_elbv2/test_elbv2_target_health.py
@@ -3,6 +3,7 @@ from uuid import uuid4
 
 import boto3
 import pytest
+from botocore.exceptions import WaiterError
 
 from . import elbv2_aws_verified
 
@@ -313,8 +314,13 @@ sudo systemctl enable httpd"""
 
         if load_balancer_arn:
             conn.delete_load_balancer(LoadBalancerArn=load_balancer_arn)
-            waiter = conn.get_waiter("load_balancers_deleted")
-            waiter.wait(LoadBalancerArns=[load_balancer_arn])
+            try:
+                waiter = conn.get_waiter("load_balancers_deleted")
+                waiter.wait(LoadBalancerArns=[load_balancer_arn])
+            except WaiterError:
+                # This operations fails against botocore 1.35.15 and up
+                # https://github.com/boto/botocore/issues/3252
+                pass
 
     if target_is_aws:
         # Resources take a while to deregister
@@ -423,8 +429,13 @@ def test_describe_unknown_targets(target_is_aws=False):
 
         if load_balancer_arn:
             conn.delete_load_balancer(LoadBalancerArn=load_balancer_arn)
-            waiter = conn.get_waiter("load_balancers_deleted")
-            waiter.wait(LoadBalancerArns=[load_balancer_arn])
+            try:
+                waiter = conn.get_waiter("load_balancers_deleted")
+                waiter.wait(LoadBalancerArns=[load_balancer_arn])
+            except WaiterError:
+                # This operations fails against botocore 1.35.15 and up
+                # https://github.com/boto/botocore/issues/3252
+                pass
 
     if target_is_aws:
         # Resources take a while to deregister
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index 310d547cf..cab288905 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -67,6 +67,84 @@ MOCK_POLICY_3 = """
 }
 """
 
+MOCK_SAML_SAMPLE_METADATA = """<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="urn:amazon:webservices" validUntil="2025-08-30T00:00:00Z">
+  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" WantAssertionsSigned="true">
+    <KeyDescriptor use="signing">
+      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:X509Data>
+          <ds:X509Certificate>MIIDcTCCAlmgAwIBAgIIGPlChvWsb9QwDQYJKoZIhvcNAQELBQAwZzEfMB0GA1UE AxMWdXJuOmFtYXpvbjp3ZWJzZXJ2aWNlczEiMCAGA1UEChMZQW1hem9uIFdlYiBT ZXJ2aWNlcywgSW5jLjETMBEGA1UECBMKV2FzaGluZ3RvbjELMAkGA1UEBhMCVVMw HhcNMjQwODMwMDAwMDAwWhcNMjUwODMwMDAwMDAwWjBnMR8wHQYDVQQDExZ1cm46 YW1hem9uOndlYnNlcnZpY2VzMSIwIAYDVQQKExlBbWF6b24gV2ViIFNlcnZpY2Vz LCBJbmMuMRMwEQYDVQQIEwpXYXNoaW5ndG9uMQswCQYDVQQGEwJVUzCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2IcN8WBhWhmwR5AOHJ7LIyl+f7ZiYM OjzsNZeOhvD6t8fIPqXg2Kyt2ixCCB16AQsif/bxYAoqqwrVkuBwG8V9CrDDuvXX 3WL7+hvn/JzmjyTeZxy34ySMppSvR3JNtps1C/stTs90KKAKfP56wg1RIEfA6dWX S/Ebd05Jx1miZb2j2+rX1C8Oc6RhOhBCHbbR/4fzFXxdocs4Krq6r+So2XQ8JChC 6E/c092/hCTBXtBj0D8Lk1DJWI1avMzRbrm53wH+1qTzoZMZu66gsuvWhbeH7B/Q kixxpb2IUNYNIT5A4g8tzzQLeGaMUHpvY8fuzRkX/8Zrd8CQjveOdPUCAwEAAaMh MB8wHQYDVR0OBBYEFCM8vneP4Ut+aXPdeCYsbFkUVMpeMA0GCSqGSIb3DQEBCwUA A4IBAQBCz1qeNzA4R/iXrqswc98bZXT7oj1lzkwRaDy0jsfsgyGwHIJ5VOMxnvFH 564zGlbljCOxrZG9FtdrutYGLHaLJLZdHKH2XQy2stxcP6pnZ29LyAMoIP6fq08M 1tzW0u0fMdC2Hpgi8QHGgguKW1AFFK3vj/sW1PWuMzTwB7ur5BpgMo4Wk074HIRT Z8B1RkY13EnFy7IIEIdreKLY3xwrpUZYnW0Em705T3gt0UrFKfzxrTjMPL3yXzgk wkAvGzGJZtdZ0FNc6ecGVuWkbJ7wvbYh2j557gbk9s0F+24103bfRyyM2jj7cgnK ij1wVZYobnMgnQI+ikrd7ksym6QN</ds:X509Certificate>
+        </ds:X509Data>
+      </ds:KeyInfo>
+    </KeyDescriptor>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
+    <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
+    <AssertionConsumerService index="1" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="2" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-north-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="3" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://me-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="4" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="5" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-west-3.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="6" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-3.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="7" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://us-east-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="8" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://af-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="9" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-west-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="10" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="11" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sa-east-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="12" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-east-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="13" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-northeast-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="14" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-northeast-3.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="15" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-west-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="16" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-south-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="17" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-northeast-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="18" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://us-west-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="19" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://us-west-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="20" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="21" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="22" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ca-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="23" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://me-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="24" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-central-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="25" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://eu-south-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="26" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-south-2.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="27" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-4.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="28" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://il-central-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="29" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ca-west-1.signin.aws.amazon.com/saml"/>
+    <AssertionConsumerService index="30" isDefault="false" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ap-southeast-5.signin.aws.amazon.com/saml"/>
+    <AttributeConsumingService index="1">
+      <ServiceName xml:lang="en">AWS Management Console Single Sign-On</ServiceName>
+      <RequestedAttribute isRequired="true" Name="https://aws.amazon.com/SAML/Attributes/Role" FriendlyName="RoleEntitlement"/>
+      <RequestedAttribute isRequired="true" Name="https://aws.amazon.com/SAML/Attributes/RoleSessionName" FriendlyName="RoleSessionName"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" FriendlyName="eduPersonAffiliation"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.2" FriendlyName="eduPersonNickname"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.3" FriendlyName="eduPersonOrgDN"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.4" FriendlyName="eduPersonOrgUnitDN"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.5" FriendlyName="eduPersonPrimaryAffiliation"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" FriendlyName="eduPersonPrincipalName"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.7" FriendlyName="eduPersonEntitlement"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.8" FriendlyName="eduPersonPrimaryOrgUnitDN"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" FriendlyName="eduPersonScopedAffiliation"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" FriendlyName="eduPersonTargetedID"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.11" FriendlyName="eduPersonAssurance"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.2" FriendlyName="eduOrgHomePageURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.3" FriendlyName="eduOrgIdentityAuthNPolicyURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.4" FriendlyName="eduOrgLegalName"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.5" FriendlyName="eduOrgSuperiorURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:1.3.6.1.4.1.5923.1.2.1.6" FriendlyName="eduOrgWhitePagesURI"/>
+      <RequestedAttribute isRequired="false" Name="urn:oid:2.5.4.3" FriendlyName="cn"/>
+    </AttributeConsumingService>
+  </SPSSODescriptor>
+  <Organization>
+    <OrganizationName xml:lang="en">Amazon Web Services, Inc.</OrganizationName>
+    <OrganizationDisplayName xml:lang="en">AWS</OrganizationDisplayName>
+    <OrganizationURL xml:lang="en">https://aws.amazon.com</OrganizationURL>
+  </Organization>
+</EntityDescriptor>
+"""
+
 MOCK_STS_EC2_POLICY_DOCUMENT = """{
       "Version": "2012-10-17",
       "Statement": [
@@ -2554,6 +2632,34 @@ def test_create_saml_provider():
     )
 
 
+@mock_aws()
+def test_create_saml_provider_with_samlmetadatadocument():
+    conn = boto3.client("iam", region_name="us-east-1")
+    response = conn.create_saml_provider(
+        Name="TestSAMLProvider", SAMLMetadataDocument=MOCK_SAML_SAMPLE_METADATA
+    )
+    assert (
+        response["SAMLProviderArn"]
+        == f"arn:aws:iam::{ACCOUNT_ID}:saml-provider/TestSAMLProvider"
+    )
+
+
+@mock_aws()
+def test_update_saml_provider_with_samlmetadatadocument():
+    conn = boto3.client("iam", region_name="us-east-1")
+    sam_provider_create = conn.create_saml_provider(
+        Name="TestSAMLProvider", SAMLMetadataDocument="a" * 1024
+    )
+    saml_provider_update = conn.update_saml_provider(
+        SAMLMetadataDocument=MOCK_SAML_SAMPLE_METADATA,
+        SAMLProviderArn=sam_provider_create["SAMLProviderArn"],
+    )
+    response = conn.get_saml_provider(
+        SAMLProviderArn=saml_provider_update["SAMLProviderArn"]
+    )
+    assert response["SAMLMetadataDocument"] == MOCK_SAML_SAMPLE_METADATA
+
+
 @mock_aws()
 def test_get_saml_provider():
     conn = boto3.client("iam", region_name="us-east-1")
@@ -2566,6 +2672,18 @@ def test_get_saml_provider():
     assert response["SAMLMetadataDocument"] == "a" * 1024
 
 
+@mock_aws()
+def test_get_saml_provider_with_samlmetadatadocument():
+    conn = boto3.client("iam", region_name="us-east-1")
+    saml_provider_create = conn.create_saml_provider(
+        Name="TestSAMLProvider", SAMLMetadataDocument=MOCK_SAML_SAMPLE_METADATA
+    )
+    response = conn.get_saml_provider(
+        SAMLProviderArn=saml_provider_create["SAMLProviderArn"]
+    )
+    assert response["SAMLMetadataDocument"] == MOCK_SAML_SAMPLE_METADATA
+
+
 @mock_aws()
 def test_list_saml_providers():
     conn = boto3.client("iam", region_name="us-east-1")
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index 3f057eb72..6b178665f 100644
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -526,6 +526,37 @@ def test_key_with_special_characters(key):
     assert resp["Body"].read() == b"value"
 
 
+@s3_aws_verified
+@pytest.mark.aws_verified
+@pytest.mark.parametrize("versioned", [True, False], ids=["versioned", "not versioned"])
+def test_conditional_write(versioned, bucket_name=None):
+    s3 = boto3.client("s3", region_name="us-east-1")
+    if versioned:
+        enable_versioning(bucket_name, s3)
+
+    s3.put_object(
+        Key="test_object",
+        Body="test",
+        Bucket=bucket_name,
+        IfNoneMatch="*",
+    )
+
+    with pytest.raises(ClientError) as exc:
+        s3.put_object(
+            Key="test_object",
+            Body="another_test",
+            Bucket=bucket_name,
+            IfNoneMatch="*",
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "PreconditionFailed"
+    assert (
+        err["Message"]
+        == "At least one of the pre-conditions you specified did not hold"
+    )
+    assert err["Condition"] == "If-None-Match"
+
+
 @mock_aws
 def test_bucket_key_listing_order():
     s3_resource = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)