diff --git a/Docs/API.md b/Docs/API.md index 50dac3190a..87fa1b82c6 100644 --- a/Docs/API.md +++ b/Docs/API.md @@ -2762,6 +2762,48 @@ catch(MinioException e) } ``` + +### GetPresignedUrlAsync(GetPresignedUrlArgs args) + +`Task GetPresignedUrlAsync(GetPresignedUrlArgs args)` + +Generates a presigned URL for HTTP operation using the giving HTTP method (GET, PUT, DELETE). Browsers/Mobile clients may point to this URL to upload objects directly to a bucket even if it is private. This presigned URL can have an associated expiration time in seconds after which it is no longer operational. The default expiry is set to 7 days. + +__Parameters__ + + +| Param | Type | Description | +|:---------|:------------------------|:-------------------------------------------------------------------------------------| +| ``args`` | _GetPresignedUrlArgs_ | GetPresignedUrlArgs arguments object with HTTP method, bucket, object names & expiry | + +| Return Type | Exceptions | +|:------------------------------------------------------------|:-------------------------------------------------------------------| +| ``Task`` : string contains URL to upload the object | Listed Exceptions: | +| | ``InvalidBucketNameException`` : upon invalid bucket name | +| | ``InvalidKeyException`` : upon an invalid access key or secret key | +| | ``ConnectionException`` : upon connection error | +| | ``InvalidExpiryRangeException`` : upon invalid expiry range. | + + +__Example__ + +```cs +try +{ + var httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Put; + GetPresignedUrlArgs args = GetPresignedUrlArgs(httpMethod) + .WithBucket("mybucket") + .WithObject("myobject") + .WithExpiry(60 * 60 * 24); + String url = await minioClient.GetPresignedUrlAsync(args); + Console.WriteLine(url); +} +catch(MinioException e) +{ + Console.WriteLine("Error occurred: " + e); +} +``` + ### PresignedPostPolicy(PresignedPostPolicyArgs args) diff --git a/Minio.Examples/Cases/GetPresignedUrl.cs b/Minio.Examples/Cases/GetPresignedUrl.cs new file mode 100644 index 0000000000..c199e78a1c --- /dev/null +++ b/Minio.Examples/Cases/GetPresignedUrl.cs @@ -0,0 +1,56 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.DataModel.Args; + +namespace Minio.Examples.Cases; + +public static class GetPresignedUrl +{ + public static async Task Run(IMinioClient client, + string bucketName = "my-bucket-name", + string objectName = "my-object-name") + { + if (client is null) throw new ArgumentNullException(nameof(client)); + + try + { + await GetPresignedUrlByRequest(client, + GetPresignedUrlArgs.PresignedUrlHttpMethod.Get, bucketName, objectName).ConfigureAwait(false); + await GetPresignedUrlByRequest(client, + GetPresignedUrlArgs.PresignedUrlHttpMethod.Put, bucketName, objectName).ConfigureAwait(false); + await GetPresignedUrlByRequest(client, + GetPresignedUrlArgs.PresignedUrlHttpMethod.Delete, bucketName, objectName).ConfigureAwait(false); + } + catch (Exception e) + { + Console.WriteLine($"Exception {e.Message}"); + } + } + + private static async Task GetPresignedUrlByRequest(IMinioClient client, + GetPresignedUrlArgs.PresignedUrlHttpMethod requestMethod, + string bucketName = "my-bucket-name", + string objectName = "my-object-name") + { + var args = new GetPresignedUrlArgs(requestMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(1000); + var presignedUrl = await client.GetPresignedUrlAsync(args).ConfigureAwait(false); + Console.WriteLine($"Presigned '{requestMethod}' URL: {presignedUrl}"); + } +} diff --git a/Minio.Examples/Program.cs b/Minio.Examples/Program.cs index 955d1000dd..53b9abdf5f 100644 --- a/Minio.Examples/Program.cs +++ b/Minio.Examples/Program.cs @@ -20,6 +20,7 @@ using System.Security.Cryptography; using System.Text; using Minio.DataModel; +using Minio.DataModel.Args; using Minio.DataModel.Encryption; using Minio.DataModel.Notification; using Minio.DataModel.ObjectLock; @@ -261,6 +262,9 @@ await SetBucketReplication.Run(minioClient, bucketName, destBucketName, replicat // Get the presigned url for a PUT object request await PresignedPutObject.Run(minioClient, bucketName, objectName).ConfigureAwait(false); + + // Get the presigned url's of an object for HTTP methods + await GetPresignedUrl.Run(minioClient, bucketName, objectName).ConfigureAwait(false); // Delete the list of objects await RemoveObjects.Run(minioClient, bucketName, objectsList).ConfigureAwait(false); diff --git a/Minio.Functional.Tests/FunctionalTest.cs b/Minio.Functional.Tests/FunctionalTest.cs index 84f02cb53b..2c73723c6e 100644 --- a/Minio.Functional.Tests/FunctionalTest.cs +++ b/Minio.Functional.Tests/FunctionalTest.cs @@ -45,280 +45,8 @@ namespace Minio.Functional.Tests; [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Keep private const lowercase")] -public static class FunctionalTest +public static partial class FunctionalTest { - private const int KB = 1024; - private const int MB = 1024 * 1024; - private const int GB = 1024 * 1024 * 1024; - - private const string dataFile1B = "datafile-1-b"; - - private const string dataFile10KB = "datafile-10-kB"; - private const string dataFile6MB = "datafile-6-MB"; - - private const string makeBucketSignature = - "Task MakeBucketAsync(string bucketName, string location = 'us-east-1', CancellationToken cancellationToken = default(CancellationToken))"; - - private const string listBucketsSignature = - "Task ListBucketsAsync(CancellationToken cancellationToken = default(CancellationToken))"; - - private const string bucketExistsSignature = - "Task BucketExistsAsync(string bucketName, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeBucketSignature = - "Task RemoveBucketAsync(string bucketName, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string listObjectsSignature = - "IObservable ListObjectsAsync(string bucketName, string prefix = null, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string putObjectSignature = - "Task PutObjectAsync(PutObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getObjectSignature = - "Task GetObjectAsync(GetObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string listIncompleteUploadsSignature = - "IObservable ListIncompleteUploads(ListIncompleteUploads args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string listenBucketNotificationsSignature = - "IObservable ListenBucketNotificationsAsync(ListenBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string listenNotificationsSignature = - "IObservable ListenNotifications(ListenBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string copyObjectSignature = - "Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string statObjectSignature = - "Task StatObjectAsync(StatObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeObjectSignature1 = - "Task RemoveObjectAsync(RemoveObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeObjectSignature2 = - "Task> RemoveObjectsAsync(RemoveObjectsArgs, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeIncompleteUploadSignature = - "Task RemoveIncompleteUploadAsync(RemoveIncompleteUploadArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string presignedPutObjectSignature = - "Task PresignedPutObjectAsync(PresignedPutObjectArgs args)"; - - private const string presignedGetObjectSignature = - "Task PresignedGetObjectAsync(PresignedGetObjectArgs args)"; - - private const string presignedPostPolicySignature = - "Task> PresignedPostPolicyAsync(PresignedPostPolicyArgs args)"; - - private const string getBucketPolicySignature = - "Task GetPolicyAsync(GetPolicyArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setBucketPolicySignature = - "Task SetPolicyAsync(SetPolicyArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getBucketNotificationSignature = - "Task GetBucketNotificationAsync(GetBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setBucketNotificationSignature = - "Task SetBucketNotificationAsync(SetBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeAllBucketsNotificationSignature = - "Task RemoveAllBucketNotificationsAsync(RemoveAllBucketNotifications args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setBucketEncryptionSignature = - "Task SetBucketEncryptionAsync(SetBucketEncryptionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getBucketEncryptionSignature = - "Task GetBucketEncryptionAsync(GetBucketEncryptionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeBucketEncryptionSignature = - "Task RemoveBucketEncryptionAsync(RemoveBucketEncryptionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string selectObjectSignature = - "Task SelectObjectContentAsync(SelectObjectContentArgs args,CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setObjectLegalHoldSignature = - "Task SetObjectLegalHoldAsync(SetObjectLegalHoldArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getObjectLegalHoldSignature = - "Task GetObjectLegalHoldAsync(GetObjectLegalHoldArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setObjectLockConfigurationSignature = - "Task SetObjectLockConfigurationAsync(SetObjectLockConfigurationArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getObjectLockConfigurationSignature = - "Task GetObjectLockConfigurationAsync(GetObjectLockConfigurationArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string deleteObjectLockConfigurationSignature = - "Task RemoveObjectLockConfigurationAsync(GetObjectLockConfigurationArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getBucketTagsSignature = - "Task GetBucketTagsAsync(GetBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setBucketTagsSignature = - "Task SetBucketTagsAsync(SetBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string deleteBucketTagsSignature = - "Task RemoveBucketTagsAsync(RemoveBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setVersioningSignature = - "Task SetVersioningAsync(SetVersioningArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getVersioningSignature = - "Task GetVersioningAsync(GetVersioningArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string removeVersioningSignature = - "Task RemoveBucketTagsAsync(RemoveBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getObjectTagsSignature = - "Task GetObjectTagsAsync(GetObjectTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setObjectTagsSignature = - "Task SetObjectTagsAsync(SetObjectTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string deleteObjectTagsSignature = - "Task RemoveObjectTagsAsync(RemoveObjectTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setObjectRetentionSignature = - "Task SetObjectRetentionAsync(SetObjectRetentionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getObjectRetentionSignature = - "Task GetObjectRetentionAsync(GetObjectRetentionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string clearObjectRetentionSignature = - "Task ClearObjectRetentionAsync(ClearObjectRetentionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string getBucketLifecycleSignature = - "Task GetBucketLifecycleAsync(GetBucketLifecycleArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string setBucketLifecycleSignature = - "Task SetBucketLifecycleAsync(SetBucketLifecycleArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private const string deleteBucketLifecycleSignature = - "Task RemoveBucketLifecycleAsync(RemoveBucketLifecycleArgs args, CancellationToken cancellationToken = default(CancellationToken))"; - - private static readonly Random rnd = new(); - - private static readonly RandomStreamGenerator rsg = new(100 * MB); - - private static string Bash(string cmd) - { - var Replacements = new Dictionary - (StringComparer.Ordinal) - { - { "$", "\\$" }, - { "(", "\\(" }, - { ")", "\\)" }, - { "{", "\\{" }, - { "}", "\\}" }, - { "[", "\\[" }, - { "]", "\\]" }, - { "@", "\\@" }, - { "%", "\\%" }, - { "&", "\\&" }, - { "#", "\\#" }, - { "+", "\\+" } - }; - - foreach (var toReplace in Replacements.Keys) - cmd = cmd.Replace(toReplace, Replacements[toReplace], StringComparison.Ordinal); - var cmdNoReturn = cmd + " >/dev/null 2>&1"; - - using var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "/bin/bash", - Arguments = $"-c \"{cmdNoReturn}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - CreateNoWindow = true - } - }; - - _ = process.Start(); - var result = process.StandardOutput.ReadLine(); - process.WaitForExit(); - - return result; - } - - // Create a file of given size from random byte array or optionally create a symbolic link - // to the dataFileName residing in MINT_DATA_DIR - private static string CreateFile(int size, string dataFileName = null) - { - var fileName = GetRandomName(); - - if (!IsMintEnv()) - { - var data = new byte[size]; - rnd.NextBytes(data); - - File.WriteAllBytes(fileName, data); - return GetFilePath(fileName); - } - - return GetFilePath(dataFileName); - } - - public static string GetRandomObjectName(int length = 5) - { - // Server side does not allow the following characters in object names - // '-', '_', '.', '/', '*' -#if NET6_0_OR_GREATER - var characters = "abcd+%$#@&{}[]()"; -#else - var characters = "abcdefgh+%$#@&"; -#endif - var result = new StringBuilder(length); - - for (var i = 0; i < length; i++) result.Append(characters[rnd.Next(characters.Length)]); - return result.ToString(); - } - - // Generate a random string - public static string GetRandomName(int length = 5) - { - var characters = "0123456789abcdefghijklmnopqrstuvwxyz"; - if (length > 50) length = 50; - - var result = new StringBuilder(length); - for (var i = 0; i < length; i++) _ = result.Append(characters[rnd.Next(characters.Length)]); - - return "minio-dotnet-example-" + result; - } - - internal static void GenerateRandom500MB_File(string fileName) - { - using var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); - var fileSize = 500L * 1024 * 1024; - var segments = fileSize / 10000; - var last_seg = fileSize % 10000; - using var br = new BinaryWriter(fs); - - for (long i = 0; i < segments; i++) - br.Write(new byte[10000]); - - br.Write(new byte[last_seg]); - br.Close(); - } - - // Return true if running in Mint mode - public static bool IsMintEnv() - { - return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MINT_DATA_DIR")); - } - - // Get full path of file - public static string GetFilePath(string fileName) - { - var dataDir = Environment.GetEnvironmentVariable("MINT_DATA_DIR"); - if (!string.IsNullOrEmpty(dataDir)) return $"{dataDir}/{fileName}"; - - var path = Directory.GetCurrentDirectory(); - return $"{path}/{fileName}"; - } - internal static Task RunCoreTests(IMinioClient minioClient) { ConcurrentBag coreTestsTasks = new() @@ -340,6 +68,8 @@ internal static Task RunCoreTests(IMinioClient minioClient) // Test Presigned Get/Put operations PresignedGetObject_Test1(minioClient), PresignedPutObject_Test1(minioClient), + GetPresignedUrl_Get_Test1(minioClient), + GetPresignedUrl_Put_Delete_Test1(minioClient), // Test incomplete uploads ListIncompleteUpload_Test1(minioClient), @@ -1321,6 +1051,13 @@ internal static async Task UploadObjectAsync(IMinioClient minio, string url, str await minio.WrapperPutAsync(url, stream).ConfigureAwait(false); } + internal static async Task DeleteObjectAsync(IMinioClient minio, string url) + { + using var response = await minio.WrapperDeleteAsync(url).ConfigureAwait(false); + if (string.IsNullOrEmpty(Convert.ToString(response.Content)) || HttpStatusCode.OK != response.StatusCode) + throw new InvalidOperationException("Unable to delete via presigned URL" + nameof(response.Content)); + } + internal static async Task PresignedPostPolicy_Test1(IMinioClient minio) { var startTime = DateTime.Now; @@ -5951,6 +5688,375 @@ internal static async Task PresignedPutObject_Test2(IMinioClient minio) #endregion + #region Get Presigned Url + + internal static async Task GetPresignedUrl_Get_Test1(IMinioClient minio) + { + var startTime = DateTime.Now; + var bucketName = GetRandomName(15); + var objectName = GetRandomObjectName(10); + var expiresInt = 1000; + var downloadFile = GetRandomObjectName(10); + + var args = new Dictionary + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString(CultureInfo.InvariantCulture) } + }; + try + { + await Setup_Test(minio, bucketName).ConfigureAwait(false); + using (var filestream = rsg.GenerateStreamFromSeed(1 * KB)) + { + var putObjectArgs = new PutObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName) + .WithStreamData(filestream) + .WithObjectSize(filestream.Length); + + _ = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); + } + + var statObjectArgs = new StatObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName); + var stats = await minio.StatObjectAsync(statObjectArgs).ConfigureAwait(false); + + var httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Get; + var preArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(expiresInt); + var presigned_url = await minio.GetPresignedUrlAsync(preArgs).ConfigureAwait(false); + + await DownloadObjectAsync(minio, presigned_url, downloadFile).ConfigureAwait(false); + var writtenInfo = new FileInfo(downloadFile); + var file_read_size = writtenInfo.Length; + // Compare the size of the file downloaded using the generated + // presigned_url (expected value) and the actual object size on the server + Assert.AreEqual(file_read_size, stats.Size); + new MintLogger("GetPresignedUrl_Get_Test1", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket", TestStatus.PASS, + DateTime.Now - startTime, args: args).Log(); + } + catch (Exception ex) + { + new MintLogger("GetPresignedUrl_Get_Test1", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket", TestStatus.FAIL, + DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); + throw; + } + finally + { + File.Delete(downloadFile); + await TearDown(minio, bucketName).ConfigureAwait(false); + } + } + + internal static async Task GetPresignedUrl_Get_Test2(IMinioClient minio) + { + var startTime = DateTime.Now; + var bucketName = GetRandomName(15); + var objectName = GetRandomObjectName(10); + var expiresInt = 0; + var args = new Dictionary + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString(CultureInfo.InvariantCulture) } + }; + try + { + await Setup_Test(minio, bucketName).ConfigureAwait(false); + using (var filestream = rsg.GenerateStreamFromSeed(1 * KB)) + { + var putObjectArgs = new PutObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName) + .WithStreamData(filestream) + .WithObjectSize(filestream.Length); + + _ = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); + } + + var statObjectArgs = new StatObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName); + var stats = await minio.StatObjectAsync(statObjectArgs).ConfigureAwait(false); + var httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Get; + var preArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(0); + var presigned_url = await minio.GetPresignedUrlAsync(preArgs).ConfigureAwait(false); + throw new InvalidOperationException( + "GetPresignedUrlAsync expected to throw an InvalidExpiryRangeException."); + } + catch (InvalidExpiryRangeException) + { + new MintLogger("GetPresignedUrl_Get_Test2", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when invalid expiry is set.", + TestStatus.PASS, DateTime.Now - startTime, args: args).Log(); + } + catch (InvalidOperationException ex) + { + new MintLogger("GetPresignedUrl_Get_Test2", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when invalid expiry is set.", + TestStatus.FAIL, DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); + throw; + } + catch (Exception ex) + { + new MintLogger("GetPresignedUrl_Get_Test2", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when invalid expiry is set.", + TestStatus.FAIL, DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); + throw; + } + finally + { + await TearDown(minio, bucketName).ConfigureAwait(false); + } + } + + internal static async Task GetPresignedUrl_Get_Test3(IMinioClient minio) + { + var startTime = DateTime.Now; + var bucketName = GetRandomName(15); + var objectName = GetRandomObjectName(10); + var expiresInt = 1000; + var reqDate = DateTime.UtcNow.AddSeconds(-50); + var downloadFile = GetRandomObjectName(10); + var args = new Dictionary + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString(CultureInfo.InvariantCulture) }, + { + "reqParams", + "response-content-type:application/json,response-content-disposition:attachment;filename= MyDoc u m e nt.json ;" + }, + { "reqDate", reqDate.ToString(CultureInfo.InvariantCulture) } + }; + try + { + await Setup_Test(minio, bucketName).ConfigureAwait(false); + using (var filestream = rsg.GenerateStreamFromSeed(1 * KB)) + { + var putObjectArgs = new PutObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName) + .WithStreamData(filestream) + .WithObjectSize(filestream.Length); + + _ = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); + } + + var statObjectArgs = new StatObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName); + var stats = await minio.StatObjectAsync(statObjectArgs).ConfigureAwait(false); + var reqParams = new Dictionary + (StringComparer.Ordinal) + { + ["response-content-type"] = "application/json", + ["response-content-disposition"] = "attachment;filename= MyDoc u m e nt.json ;" + }; + var httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Get; + var preArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(1000) + .WithHeaders(reqParams) + .WithRequestDate(reqDate); + var presigned_url = await minio.GetPresignedUrlAsync(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)); + + Assert.IsTrue(response.Content.Headers.GetValues("Content-Type") + .Contains(reqParams["response-content-type"], StringComparer.Ordinal)); + Assert.IsTrue(response.Content.Headers.GetValues("Content-Disposition") + .Contains(reqParams["response-content-disposition"], StringComparer.Ordinal)); + Assert.IsTrue(response.Content.Headers.GetValues("Content-Length") + .Contains(stats.Size.ToString(CultureInfo.InvariantCulture), StringComparer.Ordinal)); + + using (var fs = new FileStream(downloadFile, FileMode.CreateNew)) + { + await response.Content.CopyToAsync(fs).ConfigureAwait(false); + } + + var writtenInfo = new FileInfo(downloadFile); + var file_read_size = writtenInfo.Length; + + // Compare the size of the file downloaded with the generated + // presigned_url (expected) and the actual object size on the server + Assert.AreEqual(file_read_size, stats.Size); + new MintLogger("GetPresignedUrl_Get_Test3", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when override response headers sent", + TestStatus.PASS, DateTime.Now - startTime, args: args).Log(); + } + catch (Exception ex) + { + new MintLogger("GetPresignedUrl_Get_Test3", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when override response headers sent", + TestStatus.FAIL, DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); + throw; + } + finally + { + File.Delete(downloadFile); + await TearDown(minio, bucketName).ConfigureAwait(false); + } + } + + internal static async Task GetPresignedUrl_Put_Delete_Test1(IMinioClient minio) + { + var startTime = DateTime.Now; + var bucketName = GetRandomName(15); + var objectName = GetRandomObjectName(10); + var expiresInt = 1000; + var fileName = CreateFile(10 * KB, dataFile10KB); + + var args = new Dictionary + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString(CultureInfo.InvariantCulture) } + }; + try + { + await Setup_Test(minio, bucketName).ConfigureAwait(false); + // Upload with presigned url + var httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Put; + var presignedPutObjectArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(1000); + var presigned_url = await minio.GetPresignedUrlAsync(presignedPutObjectArgs).ConfigureAwait(false); + await UploadObjectAsync(minio, presigned_url, fileName).ConfigureAwait(false); + // Get stats for object from server + var statObjectArgs = new StatObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName); + var stats = await minio.StatObjectAsync(statObjectArgs).ConfigureAwait(false); + // Compare with file used for upload + var writtenInfo = new FileInfo(fileName); + var file_written_size = writtenInfo.Length; + Assert.AreEqual(file_written_size, stats.Size); + new MintLogger("GetPresignedUrl_Put_Delete_Test1", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url uploads object to bucket", TestStatus.PASS, + DateTime.Now - startTime, args: args).Log(); + + // Delete with presigned url + httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Delete; + presignedPutObjectArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(1000); + presigned_url = await minio.GetPresignedUrlAsync(presignedPutObjectArgs).ConfigureAwait(false); + await DeleteObjectAsync(minio, presigned_url).ConfigureAwait(false); + + new MintLogger("GetPresignedUrl_Put_Delete_Test1", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url deletes object from bucket", TestStatus.PASS, + DateTime.Now - startTime, args: args).Log(); + } + catch (Exception ex) + { + new MintLogger("GetPresignedUrl_Put_Delete_Test1", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url uploads object to bucket", TestStatus.FAIL, + DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); + throw; + } + finally + { + await TearDown(minio, bucketName).ConfigureAwait(false); + if (!IsMintEnv()) File.Delete(fileName); + } + } + + internal static async Task GetPresignedUrl_Put_Delete_Test2(IMinioClient minio) + { + var startTime = DateTime.Now; + var bucketName = GetRandomName(15); + var objectName = GetRandomObjectName(10); + var expiresInt = 0; + + var args = new Dictionary + (StringComparer.Ordinal) + { + { "bucketName", bucketName }, + { "objectName", objectName }, + { "expiresInt", expiresInt.ToString(CultureInfo.InvariantCulture) } + }; + try + { + await Setup_Test(minio, bucketName).ConfigureAwait(false); + using (var filestream = rsg.GenerateStreamFromSeed(1 * KB)) + { + var putObjectArgs = new PutObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName) + .WithStreamData(filestream) + .WithObjectSize(filestream.Length); + + _ = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); + } + + var statObjectArgs = new StatObjectArgs() + .WithBucket(bucketName) + .WithObject(objectName); + var stats = await minio.StatObjectAsync(statObjectArgs).ConfigureAwait(false); + var httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Put; + var presignedPutObjectArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(0); + var presigned_url = await minio.GetPresignedUrlAsync(presignedPutObjectArgs).ConfigureAwait(false); + new MintLogger("GetPresignedUrl_Put_Delete_Test2", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when invalid expiry is set.", + TestStatus.PASS, DateTime.Now - startTime, args: args).Log(); + + // Delete with presigned url + httpMethod = GetPresignedUrlArgs.PresignedUrlHttpMethod.Delete; + presignedPutObjectArgs = new GetPresignedUrlArgs(httpMethod) + .WithBucket(bucketName) + .WithObject(objectName) + .WithExpiry(1000); + presigned_url = await minio.GetPresignedUrlAsync(presignedPutObjectArgs).ConfigureAwait(false); + await DeleteObjectAsync(minio, presigned_url).ConfigureAwait(false); + + new MintLogger("GetPresignedUrl_Put_Delete_Test1", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url deletes object from bucket", TestStatus.PASS, + DateTime.Now - startTime, args: args).Log(); + } + catch (InvalidExpiryRangeException) + { + new MintLogger("GetPresignedUrl_Put_Delete_Test2", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when invalid expiry is set.", + TestStatus.PASS, DateTime.Now - startTime, args: args).Log(); + } + catch (Exception ex) + { + new MintLogger("GetPresignedUrl_Put_Delete_Test2", getPresignedUrlSignature, + "Tests whether GetPresignedUrl url retrieves object from bucket when invalid expiry is set.", + TestStatus.FAIL, DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); + throw; + } + finally + { + await TearDown(minio, bucketName).ConfigureAwait(false); + } + } + + #endregion + #region List Incomplete Upload internal static async Task ListIncompleteUpload_Test1(IMinioClient minio) diff --git a/Minio.Functional.Tests/FunctionalTests-setup.cs b/Minio.Functional.Tests/FunctionalTests-setup.cs new file mode 100644 index 0000000000..3c22dbd00f --- /dev/null +++ b/Minio.Functional.Tests/FunctionalTests-setup.cs @@ -0,0 +1,302 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, + * (C) 2017-2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace Minio.Functional.Tests; + +[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Keep private const lowercase")] +[SuppressMessage("Design", "MA0048:File name must match type name")] +public static partial class FunctionalTest +{ + private const int KB = 1024; + private const int MB = 1024 * 1024; + private const int GB = 1024 * 1024 * 1024; + + private const string dataFile1B = "datafile-1-b"; + + private const string dataFile10KB = "datafile-10-kB"; + private const string dataFile6MB = "datafile-6-MB"; + + private const string makeBucketSignature = + "Task MakeBucketAsync(string bucketName, string location = 'us-east-1', CancellationToken cancellationToken = default(CancellationToken))"; + + private const string listBucketsSignature = + "Task ListBucketsAsync(CancellationToken cancellationToken = default(CancellationToken))"; + + private const string bucketExistsSignature = + "Task BucketExistsAsync(string bucketName, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeBucketSignature = + "Task RemoveBucketAsync(string bucketName, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string listObjectsSignature = + "IObservable ListObjectsAsync(string bucketName, string prefix = null, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string putObjectSignature = + "Task PutObjectAsync(PutObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getObjectSignature = + "Task GetObjectAsync(GetObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string listIncompleteUploadsSignature = + "IObservable ListIncompleteUploads(ListIncompleteUploads args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string listenBucketNotificationsSignature = + "IObservable ListenBucketNotificationsAsync(ListenBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string listenNotificationsSignature = + "IObservable ListenNotifications(ListenBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string copyObjectSignature = + "Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string statObjectSignature = + "Task StatObjectAsync(StatObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeObjectSignature1 = + "Task RemoveObjectAsync(RemoveObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeObjectSignature2 = + "Task> RemoveObjectsAsync(RemoveObjectsArgs, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeIncompleteUploadSignature = + "Task RemoveIncompleteUploadAsync(RemoveIncompleteUploadArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string presignedPutObjectSignature = + "Task PresignedPutObjectAsync(PresignedPutObjectArgs args)"; + + private const string presignedGetObjectSignature = + "Task PresignedGetObjectAsync(PresignedGetObjectArgs args)"; + + private const string getPresignedUrlSignature = + "Task GetPresignedUrlAsync(GetPresignedUrlArgs args)"; + + private const string presignedPostPolicySignature = + "Task> PresignedPostPolicyAsync(PresignedPostPolicyArgs args)"; + + private const string getBucketPolicySignature = + "Task GetPolicyAsync(GetPolicyArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setBucketPolicySignature = + "Task SetPolicyAsync(SetPolicyArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getBucketNotificationSignature = + "Task GetBucketNotificationAsync(GetBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setBucketNotificationSignature = + "Task SetBucketNotificationAsync(SetBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeAllBucketsNotificationSignature = + "Task RemoveAllBucketNotificationsAsync(RemoveAllBucketNotifications args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setBucketEncryptionSignature = + "Task SetBucketEncryptionAsync(SetBucketEncryptionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getBucketEncryptionSignature = + "Task GetBucketEncryptionAsync(GetBucketEncryptionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeBucketEncryptionSignature = + "Task RemoveBucketEncryptionAsync(RemoveBucketEncryptionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string selectObjectSignature = + "Task SelectObjectContentAsync(SelectObjectContentArgs args,CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setObjectLegalHoldSignature = + "Task SetObjectLegalHoldAsync(SetObjectLegalHoldArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getObjectLegalHoldSignature = + "Task GetObjectLegalHoldAsync(GetObjectLegalHoldArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setObjectLockConfigurationSignature = + "Task SetObjectLockConfigurationAsync(SetObjectLockConfigurationArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getObjectLockConfigurationSignature = + "Task GetObjectLockConfigurationAsync(GetObjectLockConfigurationArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string deleteObjectLockConfigurationSignature = + "Task RemoveObjectLockConfigurationAsync(GetObjectLockConfigurationArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getBucketTagsSignature = + "Task GetBucketTagsAsync(GetBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setBucketTagsSignature = + "Task SetBucketTagsAsync(SetBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string deleteBucketTagsSignature = + "Task RemoveBucketTagsAsync(RemoveBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setVersioningSignature = + "Task SetVersioningAsync(SetVersioningArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getVersioningSignature = + "Task GetVersioningAsync(GetVersioningArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string removeVersioningSignature = + "Task RemoveBucketTagsAsync(RemoveBucketTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getObjectTagsSignature = + "Task GetObjectTagsAsync(GetObjectTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setObjectTagsSignature = + "Task SetObjectTagsAsync(SetObjectTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string deleteObjectTagsSignature = + "Task RemoveObjectTagsAsync(RemoveObjectTagsArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setObjectRetentionSignature = + "Task SetObjectRetentionAsync(SetObjectRetentionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getObjectRetentionSignature = + "Task GetObjectRetentionAsync(GetObjectRetentionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string clearObjectRetentionSignature = + "Task ClearObjectRetentionAsync(ClearObjectRetentionArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string getBucketLifecycleSignature = + "Task GetBucketLifecycleAsync(GetBucketLifecycleArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string setBucketLifecycleSignature = + "Task SetBucketLifecycleAsync(SetBucketLifecycleArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private const string deleteBucketLifecycleSignature = + "Task RemoveBucketLifecycleAsync(RemoveBucketLifecycleArgs args, CancellationToken cancellationToken = default(CancellationToken))"; + + private static readonly Random rnd = new(); + + private static readonly RandomStreamGenerator rsg = new(100 * MB); + + private static string Bash(string cmd) + { + var Replacements = new Dictionary + (StringComparer.Ordinal) + { + { "$", "\\$" }, + { "(", "\\(" }, + { ")", "\\)" }, + { "{", "\\{" }, + { "}", "\\}" }, + { "[", "\\[" }, + { "]", "\\]" }, + { "@", "\\@" }, + { "%", "\\%" }, + { "&", "\\&" }, + { "#", "\\#" }, + { "+", "\\+" } + }; + + foreach (var toReplace in Replacements.Keys) + cmd = cmd.Replace(toReplace, Replacements[toReplace], StringComparison.Ordinal); + var cmdNoReturn = cmd + " >/dev/null 2>&1"; + + using var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{cmdNoReturn}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + _ = process.Start(); + var result = process.StandardOutput.ReadLine(); + process.WaitForExit(); + + return result; + } + + // Create a file of given size from random byte array or optionally create a symbolic link + // to the dataFileName residing in MINT_DATA_DIR + private static string CreateFile(int size, string dataFileName = null) + { + var fileName = GetRandomName(); + + if (!IsMintEnv()) + { + var data = new byte[size]; + rnd.NextBytes(data); + + File.WriteAllBytes(fileName, data); + return GetFilePath(fileName); + } + + return GetFilePath(dataFileName); + } + + public static string GetRandomObjectName(int length = 5) + { + // Server side does not allow the following characters in object names + // '-', '_', '.', '/', '*' +#if NET6_0_OR_GREATER + var characters = "abcd+%$#@&{}[]()"; +#else + var characters = "abcdefgh+%$#@&"; +#endif + var result = new StringBuilder(length); + + for (var i = 0; i < length; i++) result.Append(characters[rnd.Next(characters.Length)]); + return result.ToString(); + } + + // Generate a random string + public static string GetRandomName(int length = 5) + { + var characters = "0123456789abcdefghijklmnopqrstuvwxyz"; + if (length > 50) length = 50; + + var result = new StringBuilder(length); + for (var i = 0; i < length; i++) _ = result.Append(characters[rnd.Next(characters.Length)]); + + return "minio-dotnet-example-" + result; + } + + internal static void GenerateRandom500MB_File(string fileName) + { + using var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); + var fileSize = 500L * 1024 * 1024; + var segments = fileSize / 10000; + var last_seg = fileSize % 10000; + using var br = new BinaryWriter(fs); + + for (long i = 0; i < segments; i++) + br.Write(new byte[10000]); + + br.Write(new byte[last_seg]); + br.Close(); + } + + // Return true if running in Mint mode + public static bool IsMintEnv() + { + return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MINT_DATA_DIR")); + } + + // Get full path of file + public static string GetFilePath(string fileName) + { + var dataDir = Environment.GetEnvironmentVariable("MINT_DATA_DIR"); + if (!string.IsNullOrEmpty(dataDir)) return $"{dataDir}/{fileName}"; + + var path = Directory.GetCurrentDirectory(); + return $"{path}/{fileName}"; + } +} diff --git a/Minio.Functional.Tests/Program.cs b/Minio.Functional.Tests/Program.cs index 5fc6ed93ac..df0a4d33c2 100644 --- a/Minio.Functional.Tests/Program.cs +++ b/Minio.Functional.Tests/Program.cs @@ -212,6 +212,11 @@ public static async Task Main(string[] args) functionalTestTasks.Add(FunctionalTest.PresignedPutObject_Test1(minioClient)); functionalTestTasks.Add(FunctionalTest.PresignedPutObject_Test2(minioClient)); // FunctionalTest.PresignedPostPolicy_Test1(minioClient).Wait(); + functionalTestTasks.Add(FunctionalTest.GetPresignedUrl_Get_Test1(minioClient)); + functionalTestTasks.Add(FunctionalTest.GetPresignedUrl_Get_Test2(minioClient)); + functionalTestTasks.Add(FunctionalTest.GetPresignedUrl_Get_Test3(minioClient)); + functionalTestTasks.Add(FunctionalTest.GetPresignedUrl_Put_Delete_Test1(minioClient)); + functionalTestTasks.Add(FunctionalTest.GetPresignedUrl_Put_Delete_Test2(minioClient)); // Test incomplete uploads functionalTestTasks.Add(FunctionalTest.ListIncompleteUpload_Test1(minioClient)); diff --git a/Minio/ApiEndpoints/IObjectOperations.cs b/Minio/ApiEndpoints/IObjectOperations.cs index 2dc35fdd90..a07cc84bbf 100644 --- a/Minio/ApiEndpoints/IObjectOperations.cs +++ b/Minio/ApiEndpoints/IObjectOperations.cs @@ -293,6 +293,20 @@ IAsyncEnumerable ListIncompleteUploadsEnumAsync(ListIncompleteUploadsArg /// When object is not found /// When configuration XML provided is invalid Task PresignedPutObjectAsync(PresignedPutObjectArgs args); + + /// + /// Get Presigned Url -returns a presigned url of an object for HTTP method without credentials.URL can have a maximum expiry of + /// upto 7 days or a minimum of 1 second. + /// + /// GetPresignedUrlArgs Arguments Object which encapsulates HTTP method, bucket, object names, expiry + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + Task GetPresignedUrlAsync(GetPresignedUrlArgs args); /// /// Tests the object's existence and returns metadata about existing objects. diff --git a/Minio/ApiEndpoints/ObjectOperations.cs b/Minio/ApiEndpoints/ObjectOperations.cs index e523a7c2dd..87de2e6ae6 100644 --- a/Minio/ApiEndpoints/ObjectOperations.cs +++ b/Minio/ApiEndpoints/ObjectOperations.cs @@ -274,6 +274,31 @@ public async Task PresignedPutObjectAsync(PresignedPutObjectArgs args) return authenticator.PresignURL(requestMessageBuilder, args.Expiry, Config.Region, Config.SessionToken); } + /// + /// Get Presigned Url -returns a presigned url of an object for HTTP method without credentials.URL can have a maximum expiry of + /// upto 7 days or a minimum of 1 second. + /// + /// GetPresignedUrlArgs Arguments Object which encapsulates HTTP method, bucket, object names, expiry + /// + /// When access or secret key is invalid + /// When bucket name is invalid + /// When object name is invalid + /// When bucket is not found + /// When object is not found + /// When configuration XML provided is invalid + public async Task GetPresignedUrlAsync(GetPresignedUrlArgs args) + { + args?.Validate(); + var requestMessageBuilder = await this.CreateRequest(args.RequestMethod, args.BucketName, + args.ObjectName, + args.Headers, // contentType + Convert.ToString(args.GetType(), CultureInfo.InvariantCulture), // metaData + Utils.ObjectToByteArray(args.RequestBody)).ConfigureAwait(false); + var authenticator = new V4Authenticator(Config.Secure, Config.AccessKey, Config.SecretKey, Config.Region, + Config.SessionToken); + return authenticator.PresignURL(requestMessageBuilder, args.Expiry, Config.Region, Config.SessionToken); + } + /// /// Get the configuration object for Legal Hold Status /// diff --git a/Minio/DataModel/Args/GetPresignedUrlArgs.cs b/Minio/DataModel/Args/GetPresignedUrlArgs.cs new file mode 100644 index 0000000000..64ba47b39b --- /dev/null +++ b/Minio/DataModel/Args/GetPresignedUrlArgs.cs @@ -0,0 +1,69 @@ +/* + * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2020, 2021 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Minio.Exceptions; +using Minio.Helper; + +namespace Minio.DataModel.Args; + +public class GetPresignedUrlArgs : ObjectArgs +{ + public GetPresignedUrlArgs(PresignedUrlHttpMethod requestMethod) + { + RequestMethod = ToHttpMethod(requestMethod); + } + + internal int Expiry { get; set; } + internal DateTime? RequestDate { get; set; } + + internal override void Validate() + { + base.Validate(); + if (!Utils.IsValidExpiry(Expiry)) + throw new InvalidExpiryRangeException("Expiry range should be between 1 and " + + Constants.DefaultExpiryTime); + } + + public GetPresignedUrlArgs WithExpiry(int expiry) + { + Expiry = expiry; + return this; + } + + public GetPresignedUrlArgs WithRequestDate(DateTime? d) + { + RequestDate = d; + return this; + } + + public enum PresignedUrlHttpMethod + { + Get, + Put, + Delete + } + + private static HttpMethod ToHttpMethod(PresignedUrlHttpMethod method) + { + return method switch + { + PresignedUrlHttpMethod.Get => HttpMethod.Get, + PresignedUrlHttpMethod.Put => HttpMethod.Put, + PresignedUrlHttpMethod.Delete => HttpMethod.Delete, + _ => throw new ArgumentOutOfRangeException(nameof(method), method, null) + }; + } +} diff --git a/Minio/DataModel/Args/PresignedGetObjectArgs.cs b/Minio/DataModel/Args/PresignedGetObjectArgs.cs index 872e8aecb6..2febd5b7b7 100644 --- a/Minio/DataModel/Args/PresignedGetObjectArgs.cs +++ b/Minio/DataModel/Args/PresignedGetObjectArgs.cs @@ -33,7 +33,7 @@ internal override void Validate() { base.Validate(); if (!Utils.IsValidExpiry(Expiry)) - throw new InvalidExpiryRangeException("expiry range should be between 1 and " + + throw new InvalidExpiryRangeException("Expiry range should be between 1 and " + Constants.DefaultExpiryTime); } diff --git a/Minio/IMinioClient.cs b/Minio/IMinioClient.cs index 6700881251..ba98acb9d7 100644 --- a/Minio/IMinioClient.cs +++ b/Minio/IMinioClient.cs @@ -30,5 +30,6 @@ public interface IMinioClient : IBucketOperations, IObjectOperations, IDisposabl void SetTraceOff(); void SetTraceOn(IRequestLogger logger = null); Task WrapperGetAsync(Uri uri); + Task WrapperDeleteAsync(Uri uri); Task WrapperPutAsync(Uri uri, StreamContent strm); } diff --git a/Minio/MinioClient.cs b/Minio/MinioClient.cs index 4b30cd710d..4223b94c54 100644 --- a/Minio/MinioClient.cs +++ b/Minio/MinioClient.cs @@ -67,6 +67,14 @@ public Task WrapperPutAsync(Uri uri, StreamContent strm) return Task.Run(async () => await Config.HttpClient.PutAsync(uri, strm).ConfigureAwait(false)); } + /// + /// Runs httpClient's DeleteAsync method + /// + public Task WrapperDeleteAsync(Uri uri) + { + return Config.HttpClient.DeleteAsync(uri); + } + /// /// Sets HTTP tracing On.Writes output to Console /// diff --git a/Minio/RequestExtensions.cs b/Minio/RequestExtensions.cs index 5cd32ec522..d26a984ae7 100644 --- a/Minio/RequestExtensions.cs +++ b/Minio/RequestExtensions.cs @@ -13,6 +13,9 @@ namespace Minio; public static class RequestExtensions { + /// + /// Runs httpClient's GetObjectAsync method + /// [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "This is done in the interface. String is provided here for convenience")] public static Task WrapperGetAsync(this IMinioClient minioClient, string url) @@ -22,6 +25,18 @@ public static Task WrapperGetAsync(this IMinioClient minioC : minioClient.WrapperGetAsync(new Uri(url)); } + /// + /// Runs httpClient's DeleteObjectAsync method + /// + [SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", + Justification = "This is done in the interface. String is provided here for convenience")] + public static Task WrapperDeleteAsync(this IMinioClient minioClient, string url) + { + return minioClient is null + ? throw new ArgumentNullException(nameof(minioClient)) + : minioClient.WrapperDeleteAsync(new Uri(url)); + } + /// /// Runs httpClient's PutObjectAsync method ///