Skip to content

Move S3PostUploadSignedPolicy outside of _bcl folder #3900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions generator/.DevConfigs/c2e4913e-b19a-4bca-b372-322bffb0662b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"services": [
{
"serviceName": "S3",
"type": "patch",
"changeLogMessages": [
"Move S3PostUploadSignedPolicy out of _bcl folder"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@
*
*/

using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;

using Amazon.Runtime;
using Amazon.Util;
using System.Globalization;
using Amazon.Util.Internal;
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Xml.Serialization;

namespace Amazon.S3.Util
{
Expand All @@ -47,14 +47,59 @@ namespace Amazon.S3.Util
public class S3PostUploadSignedPolicy
{
private static JsonDocumentOptions options = new JsonDocumentOptions { AllowTrailingCommas = true };
/// <summary>
/// Given a policy and AWS credentials, produce a S3PostUploadSignedPolicy.
/// </summary>
/// <param name="policy">JSON string representing the policy to sign</param>
/// <param name="credentials">Credentials to sign the policy with</param>
/// <param name="region">Service region endpoint.</param>
/// <returns>A signed policy object for use with an S3PostUploadRequest.</returns>
public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredentials credentials, RegionEndpoint region)

private static string
KEY_POLICY = "policy",
KEY_SIGNATURE = "signature",
KEY_ACCESSKEY = "access_key";

/// <summary>
/// The policy document which governs what uploads can be done.
/// </summary>
public string Policy { get; set; }

/// <summary>
/// The signature for the policy.
/// </summary>
public string Signature { get; set; }

/// <summary>
/// The AWS Access Key Id for the credential pair that produced the signature.
/// </summary>
public string AccessKeyId { get; set; }

/// <summary>
/// The security token from session or instance credentials.
/// </summary>
public string SecurityToken { get; set; }

/// <summary>
/// The signing algorithm used. Required as a field in the post Amazon
/// S3 can re-calculate the signature.
/// </summary>
public string Algorithm { get; set; }

/// <summary>
/// The date value in ISO8601 format. It is the same date used in
/// creating the signing key.
/// </summary>
public string Date { get; set; }

/// <summary>
/// In addition to the access key ID, this provides scope information
/// used in calculating the signing key for signature calculation.
/// </summary>
public string Credential { get; set; }


Comment on lines +51 to +94
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is all existing code, i just moved it to the top of file

/// <summary>
/// Given a policy and AWS credentials, produce a S3PostUploadSignedPolicy.
/// </summary>
/// <param name="policy">JSON string representing the policy to sign</param>
/// <param name="credentials">Credentials to sign the policy with</param>
/// <param name="region">Service region endpoint.</param>
/// <returns>A signed policy object for use with an S3PostUploadRequest.</returns>
public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredentials credentials, RegionEndpoint region)
{
var signedAt = AWSSDKUtils.CorrectedUtcNow;

Expand All @@ -71,7 +116,7 @@ public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredent
};
if (iCreds.UseToken) { extraConditions[S3Constants.PostFormDataSecurityToken] = iCreds.Token; }

var policyBytes = addConditionsToPolicy(policy, extraConditions);
var policyBytes = AddConditionsToPolicy(policy, extraConditions);

var base64Policy = Convert.ToBase64String(policyBytes);

Expand All @@ -91,75 +136,102 @@ public static S3PostUploadSignedPolicy GetSignedPolicy(string policy, AWSCredent
};
}

private static byte[] addConditionsToPolicy(string policy, Dictionary<string, string> newConditions)
/// <summary>
/// Framework-agnostic implementation to add conditions to a policy document.
/// Uses JsonDocument/JsonElement instead of JsonNode.
/// </summary>
private static byte[] AddConditionsToPolicy(string policy, Dictionary<string, string> newConditions)
Copy link
Contributor Author

@GarrettBeatty GarrettBeatty Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only function that i really modified in this PR. the functionality is the same but it uses different logic to parse the json

{
var json = JsonNode.Parse(policy, null, options) as JsonObject;
var jsonConditions = json["conditions"] as JsonArray;

if (jsonConditions != null)
using (JsonDocument policyDoc = JsonDocument.Parse(policy, options))
{
foreach (var newCond in newConditions)
using (var ms = new MemoryStream())
{
bool found = false;
for (int i = 0; i < jsonConditions.Count; i++)
using (var writer = new Utf8JsonWriter(ms))
{
if (jsonConditions[i] is JsonObject obj && obj.ContainsKey(newCond.Key))
writer.WriteStartObject();

// Copy all properties except "conditions" from the original policy
foreach (var property in policyDoc.RootElement.EnumerateObject())
{
obj[newCond.Key] = newCond.Value;
found = true;
if (property.Name != "conditions")
{
property.WriteTo(writer);
}
}
}

if (!found)
{
var newCondition = new JsonObject
// Write conditions array with modifications
writer.WritePropertyName("conditions");
writer.WriteStartArray();

bool foundConditions = policyDoc.RootElement.TryGetProperty("conditions", out JsonElement conditionsElement);

if (foundConditions && conditionsElement.ValueKind == JsonValueKind.Array)
{
[newCond.Key] = newCond.Value
};
jsonConditions.Add(newCondition);
}
}
}
return Encoding.UTF8.GetBytes(json.ToJsonString().Trim());
}

/// <summary>
/// The policy document which governs what uploads can be done.
/// </summary>
public string Policy { get; set; }
// Process each existing condition
foreach (JsonElement condition in conditionsElement.EnumerateArray())
{
bool shouldCopyCondition = true;
string matchedKey = null;

/// <summary>
/// The signature for the policy.
/// </summary>
public string Signature { get; set; }
// Check if this condition is an object that contains any of our new condition keys
if (condition.ValueKind == JsonValueKind.Object)
{
foreach (var newCond in newConditions.Keys.ToList())
{
if (condition.TryGetProperty(newCond, out _))
{
matchedKey = newCond;
shouldCopyCondition = false;
break;
}
}
}

/// <summary>
/// The AWS Access Key Id for the credential pair that produced the signature.
/// </summary>
public string AccessKeyId { get; set; }
if (shouldCopyCondition)
{
// Copy the original condition
condition.WriteTo(writer);
}
else if (matchedKey != null)
{
// Write updated condition
writer.WriteStartObject();
foreach (var property in condition.EnumerateObject())
{
if (property.Name == matchedKey)
{
writer.WriteString(matchedKey, newConditions[matchedKey]);

/// <summary>
/// The security token from session or instance credentials.
/// </summary>
public string SecurityToken { get; set; }

/// <summary>
/// The signing algorithm used. Required as a field in the post Amazon
/// S3 can re-calculate the signature.
/// </summary>
public string Algorithm { get; set; }
// Remove this key from newConditions since we've handled it
newConditions.Remove(matchedKey);
}
else
{
property.WriteTo(writer);
}
}
writer.WriteEndObject();
}
}
}

/// <summary>
/// The date value in ISO8601 format. It is the same date used in
/// creating the signing key.
/// </summary>
public string Date { get; set; }
// Add any remaining new conditions
foreach (var newCond in newConditions)
{
writer.WriteStartObject();
writer.WriteString(newCond.Key, newCond.Value);
writer.WriteEndObject();
}

/// <summary>
/// In addition to the access key ID, this provides scope information
/// used in calculating the signing key for signature calculation.
/// </summary>
public string Credential { get; set; }
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();

return ms.ToArray();
}
}
}
}

/// <summary>
/// Get the policy document as a human readable string.
Expand All @@ -170,31 +242,35 @@ public string GetReadablePolicy()
return Encoding.UTF8.GetString(Convert.FromBase64String(this.Policy));
}

private static string
KEY_POLICY = "policy",
KEY_SIGNATURE = "signature",
KEY_ACCESSKEY = "access_key";


/// <summary>
/// JSON representation of this object
/// </summary>
/// <returns>JSON string</returns>
public string ToJson()
{
var json = new JsonObject
var obj = new Dictionary<string, string>
{
[KEY_POLICY] = this.Policy,
[KEY_SIGNATURE] = this.Signature,
[KEY_ACCESSKEY] = this.AccessKeyId
};

return json.ToJsonString();
return JsonSerializerHelper.Serialize<Dictionary<string, string>>(
obj,
DictionaryStringStringJsonSerializerContexts.Default
);
}

/// <summary>
/// XML Representation of this object
/// </summary>
/// <returns>XML String</returns>
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
Justification = "This suppression is here to ignore the warnings since we have not made this function AOT compatible yet.")]

#endif
public string ToXml()
{
StringBuilder xml = new StringBuilder(1024);
Expand All @@ -206,6 +282,41 @@ public string ToXml()
return xml.ToString();
}

/// <summary>
/// Create an instance of this class from an XML string.
/// </summary>
/// <param name="policyXml">XML string generated by ToXml()</param>
/// <returns>Instance of S3PostUploadSignedPolicy</returns>
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
Justification = "This suppression is here to ignore the warnings since we have not made this function AOT compatible yet.")]

#endif
public static S3PostUploadSignedPolicy GetSignedPolicyFromXml(string policyXml)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is the same, i just added the AssemblyLoadTrimming warning

{
var reader = new StringReader(policyXml);
XmlSerializer serializer = new XmlSerializer(typeof(S3PostUploadSignedPolicy));

S3PostUploadSignedPolicy policy;
try
{
policy = serializer.Deserialize(reader) as S3PostUploadSignedPolicy;
}
catch (Exception e)
{
throw new ArgumentException("Could not parse XML", e);
}

if (String.IsNullOrEmpty(policy.AccessKeyId))
throw new ArgumentException("XML Document requries 'AccessKeyId' field");
if (String.IsNullOrEmpty(policy.Policy))
throw new ArgumentException("XML Document requries 'Policy' field");
if (String.IsNullOrEmpty(policy.Signature))
throw new ArgumentException("XML Document requries 'Signature' field");

return policy;
}

/// <summary>
/// Create an instance of this class from a JSON string.
/// </summary>
Expand Down Expand Up @@ -240,35 +351,5 @@ public static S3PostUploadSignedPolicy GetSignedPolicyFromJson(string policyJson
throw new ArgumentException("Invalid JSON document", e);
}
}

/// <summary>
/// Create an instance of this class from an XML string.
/// </summary>
/// <param name="policyXml">XML string generated by ToXml()</param>
/// <returns>Instance of S3PostUploadSignedPolicy</returns>
public static S3PostUploadSignedPolicy GetSignedPolicyFromXml(string policyXml)
{
var reader = new StringReader(policyXml);
XmlSerializer serializer = new XmlSerializer(typeof(S3PostUploadSignedPolicy));

S3PostUploadSignedPolicy policy;
try
{
policy = serializer.Deserialize(reader) as S3PostUploadSignedPolicy;
}
catch (Exception e)
{
throw new ArgumentException("Could not parse XML", e);
}

if (String.IsNullOrEmpty(policy.AccessKeyId))
throw new ArgumentException("XML Document requries 'AccessKeyId' field");
if (String.IsNullOrEmpty(policy.Policy))
throw new ArgumentException("XML Document requries 'Policy' field");
if (String.IsNullOrEmpty(policy.Signature))
throw new ArgumentException("XML Document requries 'Signature' field");

return policy;
}
}
}