@@ -367,7 +367,7 @@ public void CreatePresignedPost_PolicyDocument_IncludesCustomConditions()
367
367
368
368
[ TestMethod ]
369
369
[ TestCategory ( "S3" ) ]
370
- public void CreatePresignedPost_PolicyDocument_IncludesFormFields ( )
370
+ public void CreatePresignedPost_PolicyDocument_FormFieldsNotInPolicy ( )
371
371
{
372
372
// Arrange
373
373
var request = new CreatePresignedPostRequest
@@ -385,10 +385,13 @@ public void CreatePresignedPost_PolicyDocument_IncludesFormFields()
385
385
var policyBytes = Convert . FromBase64String ( response . Fields [ "Policy" ] ) ;
386
386
var policyJson = System . Text . Encoding . UTF8 . GetString ( policyBytes ) ;
387
387
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\" " ) ) ;
392
395
}
393
396
394
397
#endregion
@@ -603,36 +606,35 @@ public void CreatePresignedPost_UnicodeCharacters_HandledCorrectly()
603
606
604
607
[ TestMethod ]
605
608
[ TestCategory ( "S3" ) ]
606
- public void CreatePresignedPost_DuplicateFieldAndCondition_ShouldDeduplicateInPolicy ( )
609
+ public void CreatePresignedPost_OnlyExplicitConditionsInPolicy ( )
607
610
{
608
- // Arrange - Create a scenario with duplicate field and exact match condition
611
+ // Arrange - Create a scenario with fields and explicit conditions
609
612
var request = new CreatePresignedPostRequest
610
613
{
611
614
BucketName = "test-bucket" ,
612
615
Key = "demo/photo.jpg" ,
613
616
Expires = DateTime . UtcNow . AddHours ( 1 )
614
617
} ;
615
618
616
- // Add ACL in both Fields and Conditions (should be deduplicated)
619
+ // Add fields - these should NOT generate conditions in policy
617
620
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)
621
621
request . Fields [ "Content-Type" ] = "image/jpeg" ;
622
- request . Conditions . Add ( S3PostCondition . ExactMatch ( "Content-Type" , "image/jpeg" ) ) ;
622
+ request . Fields [ "success_action_status" ] = "201" ;
623
623
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" ) ) ;
625
626
request . Conditions . Add ( S3PostCondition . StartsWith ( "key" , "demo/" ) ) ;
626
627
request . Conditions . Add ( S3PostCondition . ContentLengthRange ( 1024 , 5242880 ) ) ;
627
628
628
629
// Act
629
630
var response = _s3Client . CreatePresignedPost ( request ) ;
630
631
631
- // Assert - Verify response contains the fields
632
+ // Assert - Verify response contains all fields
632
633
Assert . AreEqual ( "public-read" , response . Fields [ "acl" ] ) ;
633
634
Assert . AreEqual ( "image/jpeg" , response . Fields [ "Content-Type" ] ) ;
635
+ Assert . AreEqual ( "201" , response . Fields [ "success_action_status" ] ) ;
634
636
635
- // Assert - Verify policy document has no duplicates
637
+ // Assert - Verify policy document contains only explicit conditions
636
638
var policyBytes = Convert . FromBase64String ( response . Fields [ "Policy" ] ) ;
637
639
var policyJson = System . Text . Encoding . UTF8 . GetString ( policyBytes ) ;
638
640
var policyDoc = JsonDocument . Parse ( policyJson ) ;
@@ -644,45 +646,78 @@ public void CreatePresignedPost_DuplicateFieldAndCondition_ShouldDeduplicateInPo
644
646
conditionsList . Add ( condition ) ;
645
647
}
646
648
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
648
661
var aclConditions = conditionsList . Count ( c =>
649
662
c . ValueKind == JsonValueKind . Object &&
650
663
c . TryGetProperty ( "acl" , out var aclProp ) &&
651
664
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" ) ;
654
666
655
- // Count exact match conditions for Content-Type
667
+ // Content-Type field should NOT generate a condition
656
668
var contentTypeConditions = conditionsList . Count ( c =>
657
669
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" ) ;
662
672
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
664
680
var startsWithConditions = conditionsList . Count ( c =>
665
681
c . ValueKind == JsonValueKind . Array &&
666
682
c . GetArrayLength ( ) >= 3 &&
667
683
c [ 0 ] . GetString ( ) == "starts-with" &&
668
684
c [ 1 ] . GetString ( ) == "$key" &&
669
685
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" ) ;
672
687
688
+ // Explicit content-length-range condition should be in policy
673
689
var contentLengthConditions = conditionsList . Count ( c =>
674
690
c . ValueKind == JsonValueKind . Array &&
675
691
c . GetArrayLength ( ) >= 3 &&
676
692
c [ 0 ] . GetString ( ) == "content-length-range" &&
677
693
c [ 1 ] . GetInt64 ( ) == 1024 &&
678
694
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" ) ;
679
712
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" ) ;
681
716
}
682
717
683
718
[ TestMethod ]
684
719
[ TestCategory ( "S3" ) ]
685
- public void CreatePresignedPost_FieldWithoutMatchingCondition_ShouldIncludeBoth ( )
720
+ public void CreatePresignedPost_FieldWithoutMatchingCondition_ShouldNotBeInPolicy ( )
686
721
{
687
722
// Arrange - Create fields that don't match conditions
688
723
var request = new CreatePresignedPostRequest
@@ -704,51 +739,70 @@ public void CreatePresignedPost_FieldWithoutMatchingCondition_ShouldIncludeBoth(
704
739
// Assert
705
740
var policyBytes = Convert . FromBase64String ( response . Fields [ "Policy" ] ) ;
706
741
var policyJson = System . Text . Encoding . UTF8 . GetString ( policyBytes ) ;
707
- var policyDoc = JsonDocument . Parse ( policyJson ) ;
708
742
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 ) ;
709
749
var conditions = policyDoc . RootElement . GetProperty ( "conditions" ) ;
710
750
var conditionsList = new List < JsonElement > ( ) ;
711
751
foreach ( var condition in conditions . EnumerateArray ( ) )
712
752
{
713
753
conditionsList . Add ( condition ) ;
714
754
}
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
717
757
var publicReadConditions = conditionsList . Count ( c =>
718
758
c . ValueKind == JsonValueKind . Object &&
719
759
c . TryGetProperty ( "acl" , out var aclProp ) &&
720
760
aclProp . GetString ( ) == "public-read" ) ;
761
+ Assert . AreEqual ( 0 , publicReadConditions , "Should not have public-read ACL condition" ) ;
721
762
763
+ // Should have private condition we explicitly added
722
764
var privateConditions = conditionsList . Count ( c =>
723
765
c . ValueKind == JsonValueKind . Object &&
724
766
c . TryGetProperty ( "acl" , out var aclProp ) &&
725
767
aclProp . GetString ( ) == "private" ) ;
768
+ Assert . AreEqual ( 1 , privateConditions , "Should have private ACL condition from explicit condition" ) ;
726
769
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
731
771
var contentTypeConditions = conditionsList . Count ( c =>
732
772
c . ValueKind == JsonValueKind . Object &&
733
773
c . TryGetProperty ( "Content-Type" , out var ctProp ) &&
734
774
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" ) ;
737
776
738
- // Should have x-amz-meta-test condition from condition
777
+ // Should have x-amz-meta-test condition we explicitly added
739
778
var metaTestConditions = conditionsList . Count ( c =>
740
779
c . ValueKind == JsonValueKind . Object &&
741
780
c . TryGetProperty ( "x-amz-meta-test" , out var metaProp ) &&
742
781
metaProp . GetString ( ) == "value" ) ;
782
+ Assert . AreEqual ( 1 , metaTestConditions , "Should have x-amz-meta-test condition from explicit condition" ) ;
743
783
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" ) ;
745
799
}
746
800
747
801
[ TestMethod ]
748
802
[ TestCategory ( "S3" ) ]
749
- public void CreatePresignedPost_OnlyStartsWith_ShouldNotDeduplicate ( )
803
+ public void CreatePresignedPost_StartsWithCondition_ShouldBeInPolicy ( )
750
804
{
751
- // Arrange - Ensure StartsWith conditions are never deduplicated
805
+ // Arrange - Ensure StartsWith conditions are preserved
752
806
var request = new CreatePresignedPostRequest
753
807
{
754
808
BucketName = "test-bucket" ,
@@ -757,7 +811,7 @@ public void CreatePresignedPost_OnlyStartsWith_ShouldNotDeduplicate()
757
811
758
812
request . Fields [ "Content-Type" ] = "image/jpeg" ;
759
813
760
- // Add StartsWith condition - should not be deduplicated even if field exists
814
+ // Add StartsWith condition - should be in policy
761
815
request . Conditions . Add ( S3PostCondition . StartsWith ( "Content-Type" , "image/" ) ) ;
762
816
763
817
// Act
@@ -775,23 +829,26 @@ public void CreatePresignedPost_OnlyStartsWith_ShouldNotDeduplicate()
775
829
conditionsList . Add ( condition ) ;
776
830
}
777
831
778
- // Should have exact match condition from field
832
+ // Should NOT have exact match condition from field (Python behavior)
779
833
var exactMatchConditions = conditionsList . Count ( c =>
780
834
c . ValueKind == JsonValueKind . Object &&
781
835
c . TryGetProperty ( "Content-Type" , out var ctProp ) &&
782
836
ctProp . GetString ( ) == "image/jpeg" ) ;
783
837
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" ) ;
785
839
786
- // Should have starts-with condition from condition
840
+ // Should have starts-with condition we explicitly added
787
841
var startsWithConditions = conditionsList . Count ( c =>
788
842
c . ValueKind == JsonValueKind . Array &&
789
843
c . GetArrayLength ( ) >= 3 &&
790
844
c [ 0 ] . GetString ( ) == "starts-with" &&
791
845
c [ 1 ] . GetString ( ) == "$Content-Type" &&
792
846
c [ 2 ] . GetString ( ) == "image/" ) ;
793
847
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" ] ) ;
795
852
}
796
853
797
854
#endregion
0 commit comments