Skip to content
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

Fixed presign url signing #1158

Open
wants to merge 4 commits into
base: master
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
4 changes: 2 additions & 2 deletions Minio.Functional.Tests/FunctionalTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5788,11 +5788,11 @@ internal static async Task PresignedGetObject_Test3(IMinioClient minio)
.WithBucket(bucketName)
.WithObject(objectName)
.WithExpiry(1000)
.WithHeaders(reqParams)
.WithParameters(reqParams)
.WithRequestDate(reqDate);
var presigned_url = await minio.PresignedGetObjectAsync(preArgs).ConfigureAwait(false);

using var response = await minio.WrapperGetAsync(presigned_url).ConfigureAwait(false);

if (response.StatusCode != HttpStatusCode.OK ||
string.IsNullOrEmpty(Convert.ToString(response.Content, CultureInfo.InvariantCulture)))
throw new InvalidOperationException("Unable to download via presigned URL " + nameof(response.Content));
Expand Down
6 changes: 3 additions & 3 deletions Minio.Functional.Tests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ public static async Task Main(string[] args)
// If the following test is run against AWS, then the SDK throws
// "Listening for bucket notification is specific only to `minio`
// server endpoints".
await FunctionalTest.ListenBucketNotificationsAsync_Test1(minioClient).ConfigureAwait(false);
functionalTestTasks.Add(FunctionalTest.ListenBucketNotificationsAsync_Test2(minioClient));
functionalTestTasks.Add(FunctionalTest.ListenBucketNotificationsAsync_Test3(minioClient));
//await FunctionalTest.ListenBucketNotificationsAsync_Test1(minioClient).ConfigureAwait(false);
//functionalTestTasks.Add(FunctionalTest.ListenBucketNotificationsAsync_Test2(minioClient));
//functionalTestTasks.Add(FunctionalTest.ListenBucketNotificationsAsync_Test3(minioClient));

// Check if bucket exists
functionalTestTasks.Add(FunctionalTest.BucketExists_Test(minioClient));
Expand Down
35 changes: 21 additions & 14 deletions Minio.Tests/AuthenticatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,21 @@ public void GetPresignCanonicalRequestTest()
var authenticator = new V4Authenticator(false, "my-access-key", "my-secret-key");

var request = new Uri(
"http://localhost:9001/bucket/object-name?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20200501%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200501T154533Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host");
"https://localhost:9000/bucket/object-name?X-Amz-Expires=43200&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20240815%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240815T153925Z&X-Amz-SignedHeaders=content-language%3Bhost%3Bx-special");
var headersToSign = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "X-Special".ToLowerInvariant(), "special" }, { "Content-Language".ToLowerInvariant(), "en" }
{ "X-Special".ToLowerInvariant(), "special" },
{ "Content-Language".ToLowerInvariant(), "en" },
{ "host", "localhost:9000" }
};

var canonicalRequest = authenticator.GetPresignCanonicalRequest(HttpMethod.Put, request, headersToSign);
var canonicalQueryString =
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20240815%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240815T153925Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=content-language%3Bhost%3Bx-special";

var canonicalRequest =
authenticator.GetPresignCanonicalRequest(HttpMethod.Put, request, headersToSign, canonicalQueryString);
Assert.AreEqual(
string.Join('\n', "PUT", "/bucket/object-name",
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20200501%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200501T154533Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&content-language=en&x-special=special",
"host:localhost:9001", "", "host", "UNSIGNED-PAYLOAD"),
"PUT\n/bucket/object-name\nX-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20240815%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240815T153925Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=content-language%3Bhost%3Bx-special\ncontent-language:en\nhost:localhost:9000\nx-special:special\n\ncontent-language;host;x-special\nUNSIGNED-PAYLOAD",
canonicalRequest);
}

Expand All @@ -146,26 +150,29 @@ public void GetPresignCanonicalRequestWithParametersTest()
var authenticator = new V4Authenticator(false, "my-access-key", "my-secret-key");

var request = new Uri(
"http://localhost:9001/bucket/object-name?uploadId=upload-id&partNumber=1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20200501%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200501T154533Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host");
"https://localhost:9000/bucket/object-name?X-Amz-Expires=43200&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20240815%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240815T153925Z&X-Amz-SignedHeaders=content-language%3Bhost%3Bx-special");
var headersToSign = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "X-Special".ToLowerInvariant(), "special" }, { "Content-Language".ToLowerInvariant(), "en" }
{ "X-Special".ToLowerInvariant(), "special" },
{ "Content-Language".ToLowerInvariant(), "en" },
{ "host", "localhost:9000" }
};

var canonicalRequest = authenticator.GetPresignCanonicalRequest(HttpMethod.Put, request, headersToSign);
var canonicalQueryString =
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20240815%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240815T153925Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=content-language%3Bhost%3Bx-special";

var canonicalRequest =
authenticator.GetPresignCanonicalRequest(HttpMethod.Put, request, headersToSign, canonicalQueryString);
Assert.AreEqual(
string.Join('\n', "PUT", "/bucket/object-name",
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20200501%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200501T154533Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&content-language=en&partNumber=1&uploadId=upload-id&x-special=special",
"host:localhost:9001", "", "host", "UNSIGNED-PAYLOAD"),
"PUT\n/bucket/object-name\nX-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=my-access-key%2F20240815%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240815T153925Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=content-language%3Bhost%3Bx-special\ncontent-language:en\nhost:localhost:9000\nx-special:special\n\ncontent-language;host;x-special\nUNSIGNED-PAYLOAD",
canonicalRequest);
}

private Tuple<string, string> GetHeaderKV(HttpRequestMessageBuilder request, string headername)
{
var key = request.HeaderParameters.Keys.FirstOrDefault(o =>
string.Equals(o, headername, StringComparison.OrdinalIgnoreCase));
if (key is not null) return Tuple.Create(key, request.HeaderParameters[key]);
return null;
return key is not null ? Tuple.Create(key, request.HeaderParameters[key]) : null;
}

private bool HasPayloadHeader(HttpRequestMessageBuilder request, string headerName)
Expand Down
2 changes: 1 addition & 1 deletion Minio.Tests/OperationsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public async Task PresignedGetObjectWithHeaders()
var signedUrl = await client.PresignedGetObjectAsync(presignedGetObjectArgs).ConfigureAwait(false);

Assert.AreEqual(
"https://play.min.io/bucket/object-name?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=Q3AM3UQ867SPQQA43P2F%2F20200501%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200501T154533Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3D%22filename.jpg%22&X-Amz-Signature=de66f04dd4ac35838b9e83d669f7b5a70b452c6468e2b4a9e9c29f42e7fa102d",
"https://play.min.io/bucket/object-name?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=Q3AM3UQ867SPQQA43P2F%2F20200501%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200501T154533Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host%3Bresponse-content-disposition&X-Amz-Signature=44227f1a4c7054e88c3e4866b8827fdd75d2ba0d575e68b53b71b68fc07cbfe3",
signedUrl);
}
}
9 changes: 4 additions & 5 deletions Minio/ApiEndpoints/ObjectOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ public async Task<string> PresignedPutObjectAsync(PresignedPutObjectArgs args)
args?.Validate();
var requestMessageBuilder = await this.CreateRequest(HttpMethod.Put, args.BucketName,
args.ObjectName,
args.Headers, // contentType
Convert.ToString(args.GetType(), CultureInfo.InvariantCulture), // metaData
args.Headers,
args.Parameters,
Utils.ObjectToByteArray(args.RequestBody)).ConfigureAwait(false);
var authenticator = new V4Authenticator(Config.Secure, Config.AccessKey, Config.SecretKey, Config.Region,
Config.SessionToken);
Expand Down Expand Up @@ -402,7 +402,7 @@ public async Task<IList<DeleteError>> RemoveObjectsAsync(RemoveObjectsArgs args,
CancellationToken cancellationToken = default)
{
args?.Validate();
IList<DeleteError> errs = new List<DeleteError>();
IList<DeleteError> errs = [];
errs = args.ObjectNamesVersions.Count > 0
? await RemoveObjectVersionsHelper(args, errs.ToList(), cancellationToken).ConfigureAwait(false)
: await RemoveObjectsHelper(args, errs, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -693,8 +693,7 @@ public async Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancell
throw new InvalidDataException($"Specified byte range ({args.SourceObject
.CopyOperationConditions
.byteRangeStart.ToString(CultureInfo.InvariantCulture)}-{args.SourceObject
.CopyOperationConditions.byteRangeEnd.ToString(CultureInfo.InvariantCulture)
}) does not fit within source object (size={args.SourceObjectInfo.Size
.CopyOperationConditions.byteRangeEnd.ToString(CultureInfo.InvariantCulture)}) does not fit within source object (size={args.SourceObjectInfo.Size
.ToString(CultureInfo.InvariantCulture)})");

if (copySize > Constants.MaxSingleCopyObjectSize ||
Expand Down
18 changes: 17 additions & 1 deletion Minio/DataModel/Args/BucketArgs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -29,6 +29,9 @@ public abstract class BucketArgs<T> : RequestArgs

internal IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);

internal IDictionary<string, string> Parameters { get; set; } =
new Dictionary<string, string>(StringComparer.Ordinal);

public T WithBucket(string bucket)
{
BucketName = bucket;
Expand All @@ -48,6 +51,19 @@ public virtual T WithHeaders(IDictionary<string, string> headers)
return (T)this;
}

public virtual T WithParameters(IDictionary<string, string> parameters)
{
if (parameters is null || parameters.Count <= 0) return (T)this;
Parameters ??= new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var key in parameters.Keys)
{
_ = Parameters.Remove(key);
Parameters[key] = parameters[key];
}

return (T)this;
}

internal virtual void Validate()
{
Utils.ValidateBucketName(BucketName);
Expand Down
10 changes: 10 additions & 0 deletions Minio/Helper/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ namespace Minio.Helper;

internal static class Constants
{
public const string XAmzAlgorithm = "X-Amz-Algorithm";
public const string XAmzExpires = "X-Amz-Expires";
public const string XAmzCredential = "X-Amz-Credential";
public const string XAmzDate = "X-Amz-Date";
public const string XAmzSignedHeaders = "X-Amz-SignedHeaders";
public const string XAmzSignature = "X-Amz-Signature";

public const string DateTimeISO8601Format = "yyyyMMddTHHmmssZ";
public const string DateISO8601Format = "yyyyMMdd";

/// <summary>
/// Maximum number of parts
/// </summary>
Expand Down
11 changes: 4 additions & 7 deletions Minio/Helper/S3utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ internal static bool IsVirtualHostSupported(Uri endpointURL, string bucketName)
// bucketName can be valid but '.' in the hostname will fail SSL
// certificate validation. So do not use host-style for such buckets.
if (string.Equals(endpointURL.Scheme, "https", StringComparison.OrdinalIgnoreCase) &&
bucketName.Contains('.', StringComparison.Ordinal)) return false;
bucketName.Contains('.', StringComparison.Ordinal))
return false;
// Return true for all other cases
return IsAmazonEndPoint(endpointURL.Host);
}
Expand Down Expand Up @@ -80,17 +81,13 @@ internal static bool IsValidIP(string ip)
if (string.IsNullOrEmpty(ip)) return false;

var splitValues = ip.Split('.');
if (splitValues.Length != 4) return false;

return splitValues.All(r => byte.TryParse(r, out var _));
return splitValues.Length == 4 && splitValues.All(r => byte.TryParse(r, out var _));
}

// TrimAll trims leading and trailing spaces and replace sequential spaces with one space, following Trimall()
// in http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
internal static string TrimAll(string s)
{
if (string.IsNullOrEmpty(s))
return s;
return TrimWhitespaceRegex.Replace(s, " ").Trim();
return string.IsNullOrEmpty(s) ? s : TrimWhitespaceRegex.Replace(s, " ").Trim();
}
}
Loading
Loading