Skip to content

Commit 2625b99

Browse files
committed
Update fields and conditions logic to match python
1 parent 07c591f commit 2625b99

File tree

2 files changed

+109
-83
lines changed

2 files changed

+109
-83
lines changed

sdk/src/Services/S3/Custom/AmazonS3Client.Extensions.cs

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -754,21 +754,15 @@ private static IRequest MarshallCreatePresignedPost(CreatePresignedPostRequest c
754754
return request;
755755
}
756756

757-
/// <summary>
758-
/// Builds the policy document JSON string from the request using Utf8JsonWriter.
759-
/// This approach follows AWS SDK patterns and is Native AOT compatible.
760-
/// </summary>
761-
/// <param name="request">The CreatePresignedPostRequest containing the policy conditions.</param>
762-
/// <returns>A JSON string representing the policy document.</returns>
763757
private string BuildPolicyDocument(CreatePresignedPostRequest request)
764758
{
765-
#if !NETFRAMEWORK
759+
#if !NETFRAMEWORK
766760
using var arrayPoolBufferWriter = new ArrayPoolBufferWriter<byte>();
767761
using var writer = new Utf8JsonWriter(arrayPoolBufferWriter);
768-
#else
762+
#else
769763
using var memoryStream = new MemoryStream();
770764
using var writer = new Utf8JsonWriter(memoryStream);
771-
#endif
765+
#endif
772766

773767
writer.WriteStartObject();
774768
writer.WriteString("expiration", request.Expires.Value.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
@@ -787,45 +781,20 @@ private string BuildPolicyDocument(CreatePresignedPostRequest request)
787781
writer.WriteEndObject();
788782
}
789783

790-
// Track field conditions to avoid duplicates
791-
var fieldConditions = new HashSet<string>();
792-
793-
// Add field conditions
794-
foreach (var field in request.Fields)
795-
{
796-
writer.WriteStartObject();
797-
writer.WriteString(field.Key, field.Value);
798-
writer.WriteEndObject();
799-
800-
// Track this field+value combination
801-
fieldConditions.Add($"{field.Key}:{field.Value}");
802-
}
803-
804-
// Add custom conditions, skipping duplicates of field conditions
805784
foreach (var condition in request.Conditions)
806785
{
807-
// Skip ExactMatch conditions that duplicate field conditions
808-
if (condition is ExactMatchCondition exactMatch)
809-
{
810-
var conditionKey = $"{exactMatch.FieldName}:{exactMatch.ExpectedValue}";
811-
if (fieldConditions.Contains(conditionKey))
812-
{
813-
continue; // Skip duplicate
814-
}
815-
}
816-
817786
condition.WriteToJsonWriter(writer);
818787
}
819788

820789
writer.WriteEndArray();
821790
writer.WriteEndObject();
822791
writer.Flush();
823792

824-
#if !NETFRAMEWORK
793+
#if !NETFRAMEWORK
825794
return Encoding.UTF8.GetString(arrayPoolBufferWriter.WrittenMemory.ToArray());
826-
#else
795+
#else
827796
return Encoding.UTF8.GetString(memoryStream.ToArray());
828-
#endif
797+
#endif
829798
}
830799

831800
#endregion

sdk/test/Services/S3/UnitTests/Custom/CreatePresignedPostTests.cs

Lines changed: 103 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ public void CreatePresignedPost_PolicyDocument_IncludesCustomConditions()
367367

368368
[TestMethod]
369369
[TestCategory("S3")]
370-
public void CreatePresignedPost_PolicyDocument_IncludesFormFields()
370+
public void CreatePresignedPost_PolicyDocument_FormFieldsNotInPolicy()
371371
{
372372
// Arrange
373373
var request = new CreatePresignedPostRequest
@@ -385,10 +385,13 @@ public void CreatePresignedPost_PolicyDocument_IncludesFormFields()
385385
var policyBytes = Convert.FromBase64String(response.Fields["Policy"]);
386386
var policyJson = System.Text.Encoding.UTF8.GetString(policyBytes);
387387

388-
Assert.IsTrue(policyJson.Contains("\"success_action_status\""));
389-
Assert.IsTrue(policyJson.Contains("\"201\""));
390-
Assert.IsTrue(policyJson.Contains("\"x-amz-meta-category\""));
391-
Assert.IsTrue(policyJson.Contains("\"photos\""));
388+
// Verify fields are in the form data
389+
Assert.AreEqual("201", response.Fields["success_action_status"]);
390+
Assert.AreEqual("photos", response.Fields["x-amz-meta-category"]);
391+
392+
// Verify fields are NOT in the policy document (Python-style behavior)
393+
Assert.IsFalse(policyJson.Contains("\"success_action_status\""));
394+
Assert.IsFalse(policyJson.Contains("\"x-amz-meta-category\""));
392395
}
393396

394397
#endregion
@@ -603,36 +606,35 @@ public void CreatePresignedPost_UnicodeCharacters_HandledCorrectly()
603606

604607
[TestMethod]
605608
[TestCategory("S3")]
606-
public void CreatePresignedPost_DuplicateFieldAndCondition_ShouldDeduplicateInPolicy()
609+
public void CreatePresignedPost_OnlyExplicitConditionsInPolicy()
607610
{
608-
// Arrange - Create a scenario with duplicate field and exact match condition
611+
// Arrange - Create a scenario with fields and explicit conditions
609612
var request = new CreatePresignedPostRequest
610613
{
611614
BucketName = "test-bucket",
612615
Key = "demo/photo.jpg",
613616
Expires = DateTime.UtcNow.AddHours(1)
614617
};
615618

616-
// Add ACL in both Fields and Conditions (should be deduplicated)
619+
// Add fields - these should NOT generate conditions in policy
617620
request.Fields["acl"] = "public-read";
618-
request.Conditions.Add(S3PostCondition.ExactMatch("acl", "public-read"));
619-
620-
// Add Content-Type in both Fields and Conditions (should be deduplicated)
621621
request.Fields["Content-Type"] = "image/jpeg";
622-
request.Conditions.Add(S3PostCondition.ExactMatch("Content-Type", "image/jpeg"));
622+
request.Fields["success_action_status"] = "201";
623623

624-
// Add a non-duplicate condition (should remain)
624+
// Add explicit conditions - these SHOULD appear in policy
625+
request.Conditions.Add(S3PostCondition.ExactMatch("acl", "public-read"));
625626
request.Conditions.Add(S3PostCondition.StartsWith("key", "demo/"));
626627
request.Conditions.Add(S3PostCondition.ContentLengthRange(1024, 5242880));
627628

628629
// Act
629630
var response = _s3Client.CreatePresignedPost(request);
630631

631-
// Assert - Verify response contains the fields
632+
// Assert - Verify response contains all fields
632633
Assert.AreEqual("public-read", response.Fields["acl"]);
633634
Assert.AreEqual("image/jpeg", response.Fields["Content-Type"]);
635+
Assert.AreEqual("201", response.Fields["success_action_status"]);
634636

635-
// Assert - Verify policy document has no duplicates
637+
// Assert - Verify policy document contains only explicit conditions
636638
var policyBytes = Convert.FromBase64String(response.Fields["Policy"]);
637639
var policyJson = System.Text.Encoding.UTF8.GetString(policyBytes);
638640
var policyDoc = JsonDocument.Parse(policyJson);
@@ -644,45 +646,78 @@ public void CreatePresignedPost_DuplicateFieldAndCondition_ShouldDeduplicateInPo
644646
conditionsList.Add(condition);
645647
}
646648

647-
// Count exact match conditions for ACL
649+
// Bucket and key conditions are always added
650+
var bucketConditions = conditionsList.Count(c =>
651+
c.ValueKind == JsonValueKind.Object &&
652+
c.TryGetProperty("bucket", out _));
653+
Assert.AreEqual(1, bucketConditions, "Should have bucket condition");
654+
655+
var keyConditions = conditionsList.Count(c =>
656+
c.ValueKind == JsonValueKind.Object &&
657+
c.TryGetProperty("key", out _));
658+
Assert.AreEqual(1, keyConditions, "Should have key condition");
659+
660+
// Only explicit condition for ACL should be in policy
648661
var aclConditions = conditionsList.Count(c =>
649662
c.ValueKind == JsonValueKind.Object &&
650663
c.TryGetProperty("acl", out var aclProp) &&
651664
aclProp.GetString() == "public-read");
652-
653-
Assert.AreEqual(1, aclConditions, "Should have exactly one ACL condition (no duplicates)");
665+
Assert.AreEqual(1, aclConditions, "Should have ACL condition from explicit condition");
654666

655-
// Count exact match conditions for Content-Type
667+
// Content-Type field should NOT generate a condition
656668
var contentTypeConditions = conditionsList.Count(c =>
657669
c.ValueKind == JsonValueKind.Object &&
658-
c.TryGetProperty("Content-Type", out var ctProp) &&
659-
ctProp.GetString() == "image/jpeg");
660-
661-
Assert.AreEqual(1, contentTypeConditions, "Should have exactly one Content-Type condition (no duplicates)");
670+
c.TryGetProperty("Content-Type", out _));
671+
Assert.AreEqual(0, contentTypeConditions, "Should NOT have Content-Type condition");
662672

663-
// Verify non-duplicate conditions are still present
673+
// success_action_status field should NOT generate a condition
674+
var successActionConditions = conditionsList.Count(c =>
675+
c.ValueKind == JsonValueKind.Object &&
676+
c.TryGetProperty("success_action_status", out _));
677+
Assert.AreEqual(0, successActionConditions, "Should NOT have success_action_status condition");
678+
679+
// Explicit starts-with condition should be in policy
664680
var startsWithConditions = conditionsList.Count(c =>
665681
c.ValueKind == JsonValueKind.Array &&
666682
c.GetArrayLength() >= 3 &&
667683
c[0].GetString() == "starts-with" &&
668684
c[1].GetString() == "$key" &&
669685
c[2].GetString() == "demo/");
670-
671-
Assert.AreEqual(1, startsWithConditions, "Should have exactly one starts-with condition");
686+
Assert.AreEqual(1, startsWithConditions, "Should have starts-with condition");
672687

688+
// Explicit content-length-range condition should be in policy
673689
var contentLengthConditions = conditionsList.Count(c =>
674690
c.ValueKind == JsonValueKind.Array &&
675691
c.GetArrayLength() >= 3 &&
676692
c[0].GetString() == "content-length-range" &&
677693
c[1].GetInt64() == 1024 &&
678694
c[2].GetInt64() == 5242880);
695+
Assert.AreEqual(1, contentLengthConditions, "Should have content-length-range condition");
696+
697+
// Check for AWS signing fields that are always added to the policy
698+
var algorithmConditions = conditionsList.Count(c =>
699+
c.ValueKind == JsonValueKind.Object &&
700+
c.TryGetProperty("x-amz-algorithm", out _));
701+
Assert.AreEqual(1, algorithmConditions, "Should have x-amz-algorithm condition");
702+
703+
var credentialConditions = conditionsList.Count(c =>
704+
c.ValueKind == JsonValueKind.Object &&
705+
c.TryGetProperty("x-amz-credential", out _));
706+
Assert.AreEqual(1, credentialConditions, "Should have x-amz-credential condition");
707+
708+
var dateConditions = conditionsList.Count(c =>
709+
c.ValueKind == JsonValueKind.Object &&
710+
c.TryGetProperty("x-amz-date", out _));
711+
Assert.AreEqual(1, dateConditions, "Should have x-amz-date condition");
679712

680-
Assert.AreEqual(1, contentLengthConditions, "Should have exactly one content-length-range condition");
713+
// Total conditions should be:
714+
// bucket + key + acl + starts-with + content-length-range + algorithm + credential + date = 8
715+
Assert.AreEqual(8, conditionsList.Count, "Should have exactly 8 conditions");
681716
}
682717

683718
[TestMethod]
684719
[TestCategory("S3")]
685-
public void CreatePresignedPost_FieldWithoutMatchingCondition_ShouldIncludeBoth()
720+
public void CreatePresignedPost_FieldWithoutMatchingCondition_ShouldNotBeInPolicy()
686721
{
687722
// Arrange - Create fields that don't match conditions
688723
var request = new CreatePresignedPostRequest
@@ -704,51 +739,70 @@ public void CreatePresignedPost_FieldWithoutMatchingCondition_ShouldIncludeBoth(
704739
// Assert
705740
var policyBytes = Convert.FromBase64String(response.Fields["Policy"]);
706741
var policyJson = System.Text.Encoding.UTF8.GetString(policyBytes);
707-
var policyDoc = JsonDocument.Parse(policyJson);
708742

743+
// Fields should be in form data
744+
Assert.AreEqual("public-read", response.Fields["acl"]);
745+
Assert.AreEqual("image/jpeg", response.Fields["Content-Type"]);
746+
747+
// Only explicitly provided conditions should be in policy
748+
var policyDoc = JsonDocument.Parse(policyJson);
709749
var conditions = policyDoc.RootElement.GetProperty("conditions");
710750
var conditionsList = new List<JsonElement>();
711751
foreach (var condition in conditions.EnumerateArray())
712752
{
713753
conditionsList.Add(condition);
714754
}
715-
716-
// Should have both ACL conditions since values are different
755+
756+
// No condition for "public-read" since we only provided "private" in conditions
717757
var publicReadConditions = conditionsList.Count(c =>
718758
c.ValueKind == JsonValueKind.Object &&
719759
c.TryGetProperty("acl", out var aclProp) &&
720760
aclProp.GetString() == "public-read");
761+
Assert.AreEqual(0, publicReadConditions, "Should not have public-read ACL condition");
721762

763+
// Should have private condition we explicitly added
722764
var privateConditions = conditionsList.Count(c =>
723765
c.ValueKind == JsonValueKind.Object &&
724766
c.TryGetProperty("acl", out var aclProp) &&
725767
aclProp.GetString() == "private");
768+
Assert.AreEqual(1, privateConditions, "Should have private ACL condition from explicit condition");
726769

727-
Assert.AreEqual(1, publicReadConditions, "Should have public-read ACL condition from field");
728-
Assert.AreEqual(1, privateConditions, "Should have private ACL condition from condition");
729-
730-
// Should have Content-Type condition from field (no matching condition)
770+
// Should NOT have Content-Type condition since it wasn't explicitly added
731771
var contentTypeConditions = conditionsList.Count(c =>
732772
c.ValueKind == JsonValueKind.Object &&
733773
c.TryGetProperty("Content-Type", out var ctProp) &&
734774
ctProp.GetString() == "image/jpeg");
735-
736-
Assert.AreEqual(1, contentTypeConditions, "Should have Content-Type condition from field");
775+
Assert.AreEqual(0, contentTypeConditions, "Should NOT have Content-Type condition");
737776

738-
// Should have x-amz-meta-test condition from condition
777+
// Should have x-amz-meta-test condition we explicitly added
739778
var metaTestConditions = conditionsList.Count(c =>
740779
c.ValueKind == JsonValueKind.Object &&
741780
c.TryGetProperty("x-amz-meta-test", out var metaProp) &&
742781
metaProp.GetString() == "value");
782+
Assert.AreEqual(1, metaTestConditions, "Should have x-amz-meta-test condition from explicit condition");
743783

744-
Assert.AreEqual(1, metaTestConditions, "Should have x-amz-meta-test condition from condition");
784+
// Check for AWS signing fields that are always added to the policy
785+
var algorithmConditions = conditionsList.Count(c =>
786+
c.ValueKind == JsonValueKind.Object &&
787+
c.TryGetProperty("x-amz-algorithm", out _));
788+
Assert.AreEqual(1, algorithmConditions, "Should have x-amz-algorithm condition");
789+
790+
var credentialConditions = conditionsList.Count(c =>
791+
c.ValueKind == JsonValueKind.Object &&
792+
c.TryGetProperty("x-amz-credential", out _));
793+
Assert.AreEqual(1, credentialConditions, "Should have x-amz-credential condition");
794+
795+
var dateConditions = conditionsList.Count(c =>
796+
c.ValueKind == JsonValueKind.Object &&
797+
c.TryGetProperty("x-amz-date", out _));
798+
Assert.AreEqual(1, dateConditions, "Should have x-amz-date condition");
745799
}
746800

747801
[TestMethod]
748802
[TestCategory("S3")]
749-
public void CreatePresignedPost_OnlyStartsWith_ShouldNotDeduplicate()
803+
public void CreatePresignedPost_StartsWithCondition_ShouldBeInPolicy()
750804
{
751-
// Arrange - Ensure StartsWith conditions are never deduplicated
805+
// Arrange - Ensure StartsWith conditions are preserved
752806
var request = new CreatePresignedPostRequest
753807
{
754808
BucketName = "test-bucket",
@@ -757,7 +811,7 @@ public void CreatePresignedPost_OnlyStartsWith_ShouldNotDeduplicate()
757811

758812
request.Fields["Content-Type"] = "image/jpeg";
759813

760-
// Add StartsWith condition - should not be deduplicated even if field exists
814+
// Add StartsWith condition - should be in policy
761815
request.Conditions.Add(S3PostCondition.StartsWith("Content-Type", "image/"));
762816

763817
// Act
@@ -775,23 +829,26 @@ public void CreatePresignedPost_OnlyStartsWith_ShouldNotDeduplicate()
775829
conditionsList.Add(condition);
776830
}
777831

778-
// Should have exact match condition from field
832+
// Should NOT have exact match condition from field (Python behavior)
779833
var exactMatchConditions = conditionsList.Count(c =>
780834
c.ValueKind == JsonValueKind.Object &&
781835
c.TryGetProperty("Content-Type", out var ctProp) &&
782836
ctProp.GetString() == "image/jpeg");
783837

784-
Assert.AreEqual(1, exactMatchConditions, "Should have exact match Content-Type condition from field");
838+
Assert.AreEqual(0, exactMatchConditions, "Should NOT have exact match Content-Type condition from field");
785839

786-
// Should have starts-with condition from condition
840+
// Should have starts-with condition we explicitly added
787841
var startsWithConditions = conditionsList.Count(c =>
788842
c.ValueKind == JsonValueKind.Array &&
789843
c.GetArrayLength() >= 3 &&
790844
c[0].GetString() == "starts-with" &&
791845
c[1].GetString() == "$Content-Type" &&
792846
c[2].GetString() == "image/");
793847

794-
Assert.AreEqual(1, startsWithConditions, "Should have starts-with Content-Type condition from condition");
848+
Assert.AreEqual(1, startsWithConditions, "Should have starts-with Content-Type condition from explicit condition");
849+
850+
// Field should still be in the form data
851+
Assert.AreEqual("image/jpeg", response.Fields["Content-Type"]);
795852
}
796853

797854
#endregion

0 commit comments

Comments
 (0)