-
Notifications
You must be signed in to change notification settings - Fork 868
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
base: development
Are you sure you want to change the base?
Changes from all commits
8ba135e
d6bf0e0
a8c3271
24fac4c
dd78028
a4d807b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
|
@@ -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 | ||
{ | ||
|
@@ -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; } | ||
|
||
|
||
/// <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; | ||
|
||
|
@@ -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); | ||
|
||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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); | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
@@ -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; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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