diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index c29423b13..4d82db92a 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit c29423b13379a7e69c1d0f31980f03feae3ac4c9 +Subproject commit 4d82db92aab28505a7df5df720062ec0dbf3d833 diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md index 52063126d..360d329b6 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiEnvironment Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. Configure `appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "Environment": "development", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "Environment": "staging", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "Environment": "staging", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` * 1.1 When the current environment is development: database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md index 0486c0a4a..15bbfa5af 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiEnvironment Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. 配置`appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "Environment": "development", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "Environment": "staging", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "Environment": "staging", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index 6f94cac43..a53225f29 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiTenant Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. Configure `appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "TenantId": "00000000-0000-0000-0000-000000000002", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "TenantId": "00000000-0000-0000-0000-000000000003", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "TenantId": "00000000-0000-0000-0000-000000000003", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md index 1fe9c2b03..adbcf935b 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md @@ -10,22 +10,22 @@ Install-Package Masa.Contrib.Isolation.MultiTenant Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. 配置`appsettings.json` ``` appsettings.json { "ConnectionStrings": { - "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", - "Isolations": [ - { - "TenantId": "00000000-0000-0000-0000-000000000002", - "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" - }, - { - "TenantId": "00000000-0000-0000-0000-000000000003", - "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" - } - ] - } + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + "IsolationConnectionStrings": [ + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "TenantId": "00000000-0000-0000-0000-000000000003", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] } ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md index b00debb19..5670d5b91 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -12,7 +12,7 @@ Install-Package Masa.Contrib.Isolation.MultiTenant // Multi-tenant isolation On- Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. Configure `appsettings.json` ``` appsettings.json { "ConnectionStrings": { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md index d6e79f64b..46c47cfd3 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -12,7 +12,7 @@ Install-Package Masa.Contrib.Isolation.MultiTenant // 多租户隔离 按需引 Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` -1. 配置appsettings.json +1. 配置`appsettings.json` ``` appsettings.json { "ConnectionStrings": { diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs new file mode 100644 index 000000000..7abe23e71 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/BaseClient.cs @@ -0,0 +1,32 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public abstract class BaseClient +{ + protected readonly ICredentialProvider CredentialProvider; + protected readonly AliyunStorageOptions Options; + + public BaseClient(ICredentialProvider credentialProvider, + AliyunStorageOptions options) + { + CredentialProvider = credentialProvider; + Options = options; + } + + public virtual IOss GetClient() + { + var credential = GetCredential(); + return new OssClient(Options.Endpoint, credential.AccessKeyId, credential.AccessKeySecret, credential.SecurityToken); + } + + public virtual (string AccessKeyId, string AccessKeySecret, string? SecurityToken) GetCredential() + { + if (!CredentialProvider.SupportSts) + return new(Options.AccessKeyId, Options.AccessKeySecret, null); + + var securityToken = CredentialProvider.GetSecurityToken(); + return new(securityToken.AccessKeyId, securityToken.AccessKeySecret, securityToken.SessionToken); + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs index a9d16b5dd..0458c1111 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Client.cs @@ -3,38 +3,33 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; -public class Client : IClient +public class Client : BaseClient, IClient { - private readonly AliyunStorageOptions _options; - private readonly IMemoryCache _cache; + private readonly bool _supportCallback; private readonly ILogger? _logger; - public Client(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) + public Client(ICredentialProvider credentialProvider, AliyunStorageOptions options, ILogger? logger) + : base(credentialProvider, options) { - _options = options; - _cache = cache; + _supportCallback = !string.IsNullOrEmpty(options.CallbackBody) && !string.IsNullOrEmpty(options.CallbackUrl); _logger = logger; } + public Client(ICredentialProvider credentialProvider, IOptionsMonitor options, ILogger? logger) + : this(credentialProvider, options.CurrentValue, logger) + { + } + /// /// Obtain temporary authorization credentials through STS service /// /// public TemporaryCredentialsResponse GetSecurityToken() { - if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) - { - temporaryCredentials = GetTemporaryCredentials( - _options.RegionId, - _options.AccessKeyId, - _options.AccessKeySecret, - _options.RoleArn, - _options.RoleSessionName, - _options.Policy, - _options.DurationSeconds); - SetTemporaryCredentials(temporaryCredentials); - } - return temporaryCredentials!; + if (!CredentialProvider.SupportSts) + throw new ArgumentException($"{nameof(Options.RoleArn)} or {nameof(Options.RoleSessionName)} cannot be empty or null"); + + return CredentialProvider.GetSecurityToken(); } /// @@ -44,47 +39,104 @@ public TemporaryCredentialsResponse GetSecurityToken() /// public string GetToken() => throw new NotSupportedException("GetToken is not supported, please use GetSecurityToken"); - protected virtual TemporaryCredentialsResponse GetTemporaryCredentials( - string regionId, - string accessKeyId, - string accessKeySecret, - string roleArn, - string roleSessionName, - string policy, - long durationSeconds) + public Task GetObjectAsync( + string bucketName, + string objectName, + Action callback, + CancellationToken cancellationToken = default) + { + var client = GetClient(); + var result = client.GetObject(bucketName, objectName); + callback.Invoke(result.Content); + return Task.CompletedTask; + } + + public Task GetObjectAsync( + string bucketName, + string objectName, + long offset, + long length, + Action callback, + CancellationToken cancellationToken = default) + { + if (length < 0 && length != -1) + throw new ArgumentOutOfRangeException(nameof(length), $"{length} should be greater than 0 or -1"); + + var client = GetClient(); + var request = new GetObjectRequest(bucketName, objectName); + request.SetRange(offset, length > 0 ? offset + length : length); + var result = client.GetObject(request); + callback.Invoke(result.Content); + return Task.CompletedTask; + } + + public Task PutObjectAsync( + string bucketName, + string objectName, + Stream data, + CancellationToken cancellationToken = default) { - IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); - DefaultAcsClient client = new DefaultAcsClient(profile); - var request = new AssumeRoleRequest - { - ContentType = AliyunFormatType.JSON, - RoleArn = roleArn, - RoleSessionName = roleSessionName, - DurationSeconds = durationSeconds - }; - if (!string.IsNullOrEmpty(policy)) - request.Policy = policy; - var response = client.GetAcsResponse(request); - - // if (response.HttpResponse.isSuccess()) - // { - return new TemporaryCredentialsResponse( //todo: Get Sts response information is null, waiting for repair: https://github.com/aliyun/aliyun-openapi-net-sdk/pull/401 - response.Credentials.AccessKeyId, - response.Credentials.AccessKeySecret, - response.Credentials.SecurityToken, - DateTime.Parse(response.Credentials.Expiration)); - // } - // - // string message = $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId},Status: {response.HttpResponse.Status}, Message: {System.Text.Encoding.Default.GetString(response.HttpResponse.Content)}"; - // _logger?.LogWarning(message); - // - // throw new Exception(message); + var client = GetClient(); + var objectMetadata = _supportCallback ? BuildCallbackMetadata(Options.CallbackUrl, Options.CallbackBody) : null; + var result = !Options.EnableResumableUpload || Options.BigObjectContentLength > data.Length ? + client.PutObject(bucketName, objectName, data, objectMetadata) : + client.ResumableUploadObject(new UploadObjectRequest(bucketName, objectName, data) + { + PartSize = Options.PartSize, + Metadata = objectMetadata + }); + _logger?.LogDebug("----- Upload {ObjectName} from {BucketName} - ({Result})", + objectName, + bucketName, + new UploadObjectResponse(result)); + return Task.CompletedTask; + } + + protected virtual ObjectMetadata BuildCallbackMetadata(string callbackUrl, string callbackBody) + { + string callbackHeaderBuilder = new CallbackHeaderBuilder(callbackUrl, callbackBody).Build(); + var metadata = new ObjectMetadata(); + metadata.AddHeader(HttpHeaders.Callback, callbackHeaderBuilder); + return metadata; + } + + public Task ObjectExistsAsync( + string bucketName, + string objectName, + CancellationToken cancellationToken = default) + { + var client = GetClient(); + var exist = client.DoesObjectExist(bucketName, objectName); + return Task.FromResult(exist); + } + + public async Task DeleteObjectAsync( + string bucketName, + string objectName, + CancellationToken cancellationToken = default) + { + var client = GetClient(); + if (await ObjectExistsAsync(bucketName, objectName, cancellationToken) == false) + return; + + var result = client.DeleteObject(bucketName, objectName); + _logger?.LogDebug("----- Delete {ObjectName} from {BucketName} - ({Result})", + objectName, + bucketName, + result); } - protected virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) + public Task DeleteObjectAsync( + string bucketName, + IEnumerable objectNames, + CancellationToken cancellationToken = default) { - var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - 10; - if (timespan >= 0) - _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); + var client = GetClient(); + var result = client.DeleteObjects(new DeleteObjectsRequest(bucketName, objectNames.ToList(), Options.Quiet)); + _logger?.LogDebug("----- Delete {ObjectNames} from {BucketName} - ({Result})", + objectNames, + bucketName, + result); + return Task.CompletedTask; } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs new file mode 100644 index 000000000..380f4c49d --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultCredentialProvider.cs @@ -0,0 +1,132 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public class DefaultCredentialProvider : ICredentialProvider +{ + private readonly IOssClientFactory _ossClientFactory; + protected readonly AliyunStorageOptions _options; + private readonly IMemoryCache _cache; + protected readonly ILogger? _logger; + + public bool SupportSts { get; init; } + + public DefaultCredentialProvider( + IOssClientFactory ossClientFactory, + AliyunStorageOptions options, + IMemoryCache cache, + ILogger? logger) + { + _ossClientFactory = ossClientFactory; + _options = options; + SupportSts = !string.IsNullOrEmpty(options.Sts.RegionId) && + !string.IsNullOrEmpty(options.RoleArn) && + !string.IsNullOrEmpty(options.RoleSessionName); + _cache = cache; + _logger = logger; + } + + public DefaultCredentialProvider( + IOssClientFactory ossClientFactory, + IOptionsMonitor options, + IMemoryCache cache, + ILogger? logger) + : this(ossClientFactory, options.CurrentValue, cache, logger) + { + } + + public DefaultCredentialProvider( + IOssClientFactory ossClientFactory, + IOptionsMonitor options, + IMemoryCache cache, + ILogger? logger) + : this(ossClientFactory, GetAliyunStorageOptions(options.CurrentValue), cache, logger) + { + } + + /// + /// Obtain temporary authorization credentials through STS service + /// + /// + public virtual TemporaryCredentialsResponse GetSecurityToken() + { + if (!_cache.TryGetValue(_options.TemporaryCredentialsCacheKey, out TemporaryCredentialsResponse? temporaryCredentials)) + { + temporaryCredentials = GetTemporaryCredentials( + _options.Sts.RegionId!, + _options.AccessKeyId, + _options.AccessKeySecret, + _options.RoleArn, + _options.RoleSessionName, + _options.Policy, + _options.Sts.GetDurationSeconds()); + SetTemporaryCredentials(temporaryCredentials); + } + return temporaryCredentials!; + } + + public virtual TemporaryCredentialsResponse GetTemporaryCredentials( + string regionId, + string accessKeyId, + string accessKeySecret, + string roleArn, + string roleSessionName, + string policy, + long durationSeconds) + { + IAcsClient client = _ossClientFactory.GetAcsClient(accessKeyId, accessKeySecret, regionId); + var request = new AssumeRoleRequest + { + ContentType = AliyunFormatType.JSON, + RoleArn = roleArn, + RoleSessionName = roleSessionName, + DurationSeconds = durationSeconds + }; + if (!string.IsNullOrEmpty(policy)) + request.Policy = policy; + var response = client.GetAcsResponse(request); + // if (response.HttpResponse.isSuccess()) //todo: Get Sts response information is null, waiting for repair: https://github.com/aliyun/aliyun-openapi-net-sdk/pull/401 + // { + return new TemporaryCredentialsResponse( + response.Credentials.AccessKeyId, + response.Credentials.AccessKeySecret, + response.Credentials.SecurityToken, + DateTime.Parse(response.Credentials.Expiration)); + // } + + // string responseContent = Encoding.Default.GetString(response.HttpResponse.Content); + // string message = + // $"Aliyun.Client: Failed to obtain temporary credentials, RequestId: {response.RequestId}, Status: {response.HttpResponse.Status}, Message: {responseContent}"; + // _logger?.LogWarning( + // "Aliyun.Client: Failed to obtain temporary credentials, RequestId: {RequestId}, Status: {Status}, Message: {Message}", + // response.RequestId, response.HttpResponse.Status, responseContent); + // + // throw new Exception(message); + } + + public virtual void SetTemporaryCredentials(TemporaryCredentialsResponse credentials) + { + var timespan = (DateTime.UtcNow - credentials.Expiration!.Value).TotalSeconds - _options.Sts.GetEarlyExpires(); + if (timespan >= 0) _cache.Set(_options.TemporaryCredentialsCacheKey, credentials, TimeSpan.FromSeconds(timespan)); + } + + private static AliyunStorageOptions GetAliyunStorageOptions(AliyunStorageConfigureOptions options) + { + AliyunStorageOptions aliyunStorageOptions = options.Storage; + aliyunStorageOptions.AccessKeyId = TryUpdate(options.Storage.AccessKeyId, options.AccessKeyId)!; + aliyunStorageOptions.AccessKeySecret = TryUpdate(options.Storage.AccessKeySecret, options.AccessKeySecret)!; + aliyunStorageOptions.Sts.RegionId = TryUpdate(options.Storage.Sts.RegionId, options.Sts.RegionId); + aliyunStorageOptions.Sts.DurationSeconds = options.Storage.Sts.DurationSeconds ?? options.Sts.DurationSeconds ?? options.Sts.GetDurationSeconds(); + aliyunStorageOptions.Sts.EarlyExpires = options.Storage.Sts.EarlyExpires ?? options.Sts.EarlyExpires ?? options.Sts.GetEarlyExpires(); + return aliyunStorageOptions; + } + + private static string? TryUpdate(string? source, string? destination) + { + if (!string.IsNullOrWhiteSpace(source)) + return source; + + return destination; + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs new file mode 100644 index 000000000..fdcbb2036 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/DefaultOssClientFactory.cs @@ -0,0 +1,16 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public class DefaultOssClientFactory : IOssClientFactory +{ + public IOss GetClient(string accessKeyId, string accessKeySecret, string? securityToken, string endpoint) + => new OssClient(endpoint, accessKeyId, accessKeySecret, securityToken); + + public IAcsClient GetAcsClient(string accessKeyId, string accessKeySecret, string regionId) + { + IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret); + return new DefaultAcsClient(profile); + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs new file mode 100644 index 000000000..49f66c615 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ICredentialProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +/// +/// For internal use, structure may change at any time +/// +public interface ICredentialProvider +{ + bool SupportSts { get; } + + TemporaryCredentialsResponse GetSecurityToken(); +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs new file mode 100644 index 000000000..78cc91091 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/IOssClientFactory.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun; + +public interface IOssClientFactory +{ + IOss GetClient(string AccessKeyId, string AccessKeySecret, string? SecurityToken, string endpoint); + + IAcsClient GetAcsClient(string AccessKeyId, string AccessKeySecret, string regionId); +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs index 1fbe2a40a..26f9e138e 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Const.cs @@ -5,7 +5,17 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; internal class Const { - public const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.TemporaryCredentials"; + public const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.Storage.TemporaryCredentials"; public const string DEFAULT_SECTION = "Aliyun"; + + public const string INTERNAL_ENDPOINT_SUFFIX = "-internal.aliyuncs.com"; + + public const string PUBLIC_ENDPOINT_DOMAIN_SUFFIX = ".aliyuncs.com"; + + public const string ERROR_ENDPOINT_MESSAGE = "Unrecognized endpoint, failed to get RegionId"; + + public const int DEFAULT_DURATION_SECONDS = 3600; + + public const int DEFAULT_EARLY_EXPIRES = 10; } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs new file mode 100644 index 000000000..3c2e261f4 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/ObjectStorageExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; + +internal class ObjectStorageExtensions +{ + internal static string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) + { + if (string.IsNullOrEmpty(parameter)) + throw new ArgumentException($"{parameterName} cannot be null and empty string"); + + return parameter; + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs new file mode 100644 index 000000000..2874a5e71 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Internal/Response/UploadObjectResponse.cs @@ -0,0 +1,44 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal.Response; + +internal class UploadObjectResponse +{ + public string ETag { get; set; } + + public string VersionId { get; set; } + + public string RequestId { get; set; } + + public HttpStatusCode HttpStatusCode { get; set; } + + public long ContentLength { get; set; } + + public string Response { get; set; } + + public IDictionary ResponseMetadata { get; set; } + + public UploadObjectResponse(PutObjectResult result) + { + ETag = result.ETag; + VersionId = result.VersionId; + HttpStatusCode = result.HttpStatusCode; + RequestId = result.RequestId; + ContentLength = result.ContentLength; + Response = GetCallbackResponse(result); + ResponseMetadata = result.ResponseMetadata; + } + + private string GetCallbackResponse(PutObjectResult putObjectResult) + { + using var stream = putObjectResult.ResponseStream; + if (stream == null) + return string.Empty; + + var buffer = new byte[4 * 1024]; + var bytesRead = stream.Read(buffer, 0, buffer.Length); + string callbackResponse = Encoding.Default.GetString(buffer, 0, bytesRead); + return callbackResponse; + } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj index 744892984..5f89cc427 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Masa.Contrib.Storage.ObjectStorage.Aliyun.csproj @@ -6,8 +6,13 @@ enable + + + + + @@ -16,6 +21,7 @@ + diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs new file mode 100644 index 000000000..bccda4777 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; + +public class AliyunOptions +{ + public string AccessKeyId { get; set; } + + public string AccessKeySecret { get; set; } +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs new file mode 100644 index 000000000..5a0893230 --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageConfigureOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; + +public class AliyunStorageConfigureOptions : AliyunOptions +{ + public AliyunStorageOptions Storage { get; set; } = new(); + + public AliyunStsOptions Sts { get; set; } = new(); +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs index 0f6a364ec..3f1ab8790 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStorageOptions.cs @@ -3,84 +3,136 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; -public class AliyunStorageOptions +public class AliyunStorageOptions : AliyunOptions { - public string AccessKeyId { get; set; } + public AliyunStsOptions Sts { get; set; } = new(); - public string AccessKeySecret { get; set; } + private string _endpoint; - public string RegionId { get; set; } + public string Endpoint + { + get => _endpoint; + set => _endpoint = value?.Trim() ?? string.Empty; + } + + private string _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; + + public string TemporaryCredentialsCacheKey + { + get => _temporaryCredentialsCacheKey; + set => _temporaryCredentialsCacheKey = + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); + } + + /// + /// If policy is empty, the user will get all permissions under this role + /// + public string Policy { get; set; } public string RoleArn { get; set; } public string RoleSessionName { get; set; } - private int _durationSeconds = 3600; + /// + /// The server address of the callback request + /// + public string CallbackUrl { get; set; } /// - /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. - /// default: 3600 - /// unit: second + /// The value of the request body when the callback is initiated /// - public int DurationSeconds - { - get => _durationSeconds; - set - { - if (value < 900 || value > 43200) - throw new ArgumentOutOfRangeException(nameof(DurationSeconds), "DurationSeconds must be in range of 900-43200"); - - _durationSeconds = value; - } - } + public string CallbackBody { get; set; } /// - /// If policy is empty, the user will get all permissions under this role + /// Large files enable resume after power failure + /// default: true /// - public string Policy { get; set; } + public bool EnableResumableUpload { get; set; } - private string _temporaryCredentialsCacheKey = Const.TEMPORARY_CREDENTIALS_CACHEKEY; + /// + /// large file length + /// unit: Byte + /// default: 5GB + /// + public long BigObjectContentLength { get; set; } - public string TemporaryCredentialsCacheKey + /// + /// Gets or sets the size of the part. + /// + /// The size of the part. + public long? PartSize { get; set; } + + /// + /// true: quiet mode; false: detail mode + /// default: true + /// + public bool Quiet { get; set; } + + public AliyunStorageOptions() { - get => _temporaryCredentialsCacheKey; - set => _temporaryCredentialsCacheKey = CheckNullOrEmptyAndReturnValue(value, nameof(TemporaryCredentialsCacheKey)); + Quiet = true; + CallbackUrl = string.Empty; + CallbackBody = "bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}"; + EnableResumableUpload = true; + PartSize = null; + BigObjectContentLength = 5 * (long)Math.Pow(1024, 3); } - public AliyunStorageOptions() { } + private AliyunStorageOptions(string accessKeyId, string accessKeySecret) : this() + { + AccessKeyId = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(accessKeyId, nameof(accessKeyId)); + AccessKeySecret = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(accessKeySecret, nameof(accessKeySecret)); + } - public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string regionId, string roleArn, string roleSessionName) : this() + public AliyunStorageOptions(string accessKeyId, string accessKeySecret, string endpoint) + : this(accessKeyId, accessKeySecret) { - AccessKeyId = CheckNullOrEmptyAndReturnValue(accessKeyId, nameof(accessKeyId)); - AccessKeySecret = CheckNullOrEmptyAndReturnValue(accessKeySecret, nameof(accessKeySecret)); - RegionId = CheckNullOrEmptyAndReturnValue(regionId, nameof(regionId)); - RoleArn = CheckNullOrEmptyAndReturnValue(roleArn, nameof(roleArn)); - RoleSessionName = CheckNullOrEmptyAndReturnValue(roleSessionName, nameof(roleSessionName)); + Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } - public AliyunStorageOptions SetPolicy(string policy) + public AliyunStorageOptions( + string accessKeyId, + string accessKeySecret, + string endpoint, + string roleArn, + string roleSessionName) + : this(accessKeyId, accessKeySecret, endpoint, roleArn, roleSessionName, null) { - Policy = policy; - return this; } - public AliyunStorageOptions SetTemporaryCredentialsCacheKey(string temporaryCredentialsCacheKey) + public AliyunStorageOptions( + string accessKeyId, + string accessKeySecret, + string endpoint, + AliyunStsOptions? stsOptions) + : this(accessKeyId, accessKeySecret) { - TemporaryCredentialsCacheKey = temporaryCredentialsCacheKey; - return this; + Sts = stsOptions ?? new(); + Endpoint = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(endpoint, nameof(endpoint)); } - public AliyunStorageOptions SetDurationSeconds(int durationSeconds) + public AliyunStorageOptions( + string accessKeyId, + string accessKeySecret, + string endpoint, + string roleArn, + string roleSessionName, + AliyunStsOptions? stsOptions) + : this(accessKeyId, accessKeySecret, endpoint, stsOptions) { - DurationSeconds = durationSeconds; - return this; + RoleArn = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(roleArn, nameof(roleArn)); + RoleSessionName = ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(roleSessionName, nameof(roleSessionName)); } - internal string CheckNullOrEmptyAndReturnValue(string? parameter, string parameterName) + public AliyunStorageOptions SetPolicy(string policy) { - if (string.IsNullOrEmpty(parameter)) - throw new ArgumentException($"{parameterName} cannot be null and empty string"); + Policy = policy; + return this; + } - return parameter; + public AliyunStorageOptions SetTemporaryCredentialsCacheKey(string temporaryCredentialsCacheKey) + { + TemporaryCredentialsCacheKey = temporaryCredentialsCacheKey; + return this; } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs new file mode 100644 index 000000000..3cdff636e --- /dev/null +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/Options/AliyunStsOptions.cs @@ -0,0 +1,62 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; + +public class AliyunStsOptions +{ + /// + /// sts region id + /// If the RegionId is missing, the temporary Sts credential cannot be obtained. + /// https://help.aliyun.com/document_detail/371859.html + /// https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb + /// + public string? RegionId { get; set; } + + private long? _durationSeconds = null; + + /// + /// Set the validity period of the temporary access credential, the minimum is 900, and the maximum is 43200. + /// default: 3600 + /// unit: second + /// + public long? DurationSeconds + { + get => _durationSeconds; + set + { + if (value < 900 || value > 43200) + throw new ArgumentOutOfRangeException(nameof(DurationSeconds), $"{nameof(DurationSeconds)} must be in range of 900-43200"); + + _durationSeconds = value; + } + } + + private long? _earlyExpires = null; + + /// + /// Voucher expires early + /// default: 10 + /// unit: second + /// + public long? EarlyExpires + { + get => _earlyExpires; + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(EarlyExpires), $"{nameof(EarlyExpires)} must be Greater than 0"); + + _earlyExpires = value; + } + } + + public AliyunStsOptions(string? regionId = null) + { + RegionId = regionId; + } + + public long GetDurationSeconds() => DurationSeconds ?? Const.DEFAULT_DURATION_SECONDS; + + public long GetEarlyExpires() => EarlyExpires ?? Const.DEFAULT_EARLY_EXPIRES; +} diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md index d9b8858b9..40ccc5d9d 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.md @@ -9,7 +9,11 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun ```` support: -* GetSecurityToken to get the security token +* GetSecurityToken: Gets the security token(RoleArn, RoleSessionName are required) +* GetObjectAsync: Gets the stream of object data +* PutObjectAsync: Upload objects via Stream +* ObjectExistsAsync: Determine whether the object exists +* DeleteObjectAsync: Delete object ### Usage 1: @@ -19,12 +23,18 @@ support: "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId", - "RoleArn": "Replace-With-Your-RoleArn", - "RoleSessionName": "Replace-With-Your-RoleSessionName", - "DurationSeconds": 3600,//optional, default: 3600s - "Policy": "",//optional - "TemporaryCredentialsCacheKey": "Aliyun.TemporaryCredentials"//optional, default: Aliyun.TemporaryCredentials + "Sts": :{ + "RegionId": "Replace-With-Your-RegionId",//https://www.alibabacloud.com/help/en/resource-access-management/latest/endpoints#reference-sdg-3pv-xdb + "DurationSeconds": 3600,//Temporary certificate validity period, default: 3600s + "EarlyExpires": 10//default: 10s + }, + "Storage": { + "Endpoint": "Replace-With-Your-Endpoint",//https://www.alibabacloud.com/help/en/object-storage-service/latest/regions-and-endpoints#section-plb-2vy-5db + "RoleArn": "Replace-With-Your-RoleArn", + "RoleSessionName": "Replace-With-Your-RoleSessionName", + "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials",//optional, default: Aliyun.Storage.TemporaryCredentials + "Policy": ""//optional + } } } ```` @@ -40,7 +50,11 @@ builder.Services.AddAliyunStorage(); 1. Add Alibaba Cloud Storage Service ````C# -builder.Services.AddAliyunStorage(new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "regionId", "roleArn", "roleSessionName")); +var configuration = builder.Configuration; +builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) +{ + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]); +}); ```` ### Usage 3: @@ -48,7 +62,11 @@ builder.Services.AddAliyunStorage(new AliyunStorageOptions("AccessKeyId", "Acces 1. Add Alibaba Cloud Storage Service ````C# -builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration ["Aliyun:RoleSessionName"])); +var configuration = builder.Configuration; +builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) +{ + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]) +}); ```` > The difference from usage 2 is that the configuration can take effect without restarting the project after the configuration update \ No newline at end of file diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md index 4c3ea5197..6bbe36935 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/README.zh-CN.md @@ -9,22 +9,36 @@ Install-Package Masa.Contrib.Storage.ObjectStorage.Aliyun ``` 支持: -* GetSecurityToken 获取安全令牌 + +* GetSecurityToken: 获取安全令牌 (需提供RoleArn、RoleSessionName) +* GetObjectAsync: 获取对象数据的流 +* PutObjectAsync: 通过Stream上传对象 +* ObjectExistsAsync: 判断对象是否存在 +* DeleteObjectAsync: 删除对象 ### 用法1: 1. 配置appsettings.json + ``` C# { "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId", - "RoleArn": "Replace-With-Your-RoleArn", - "RoleSessionName": "Replace-With-Your-RoleSessionName", + "Sts": :{ + "RegionId":"Replace-With-Your-Sts-RegionId",//https://help.aliyun.com/document_detail/371859.html + "DurationSeconds":3600,//临时证书有效期, default: 3600秒 + "EarlyExpires":10//default: 10秒 + }, + "RegionId": "Replace-With-Your-RegionId",//https://help.aliyun.com/document_detail/371859.html "DurationSeconds": 3600,//选填、默认: 3600s - "Policy": "",//选填 - "TemporaryCredentialsCacheKey": "Aliyun.TemporaryCredentials"//选填、默认: Aliyun.TemporaryCredentials + "Storage": { + "Endpoint": "Replace-With-Your-Endpoint",//https://help.aliyun.com/document_detail/31837.html + "RoleArn": "Replace-With-Your-RoleArn", + "RoleSessionName": "Replace-With-Your-RoleSessionName", + "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials",//选填、默认: Aliyun.Storage.TemporaryCredentials + "Policy": ""//选填 + } } } ``` @@ -41,7 +55,10 @@ builder.Services.AddAliyunStorage(); ```C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); +builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) +{ + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]); +}); ``` ### 用法3: @@ -50,7 +67,11 @@ builder.Services.AddAliyunStorage(new AliyunStorageOptions(configuration["Aliyun ```C# var configuration = builder.Configuration; -builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RegionId"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"])); +builder.Services.AddAliyunStorage(() => new AliyunStorageOptions(configuration["Aliyun:AccessKeyId"], configuration["Aliyun:AccessKeySecret"], configuration["Aliyun:RoleArn"], configuration["Aliyun:RoleSessionName"]) +{ + Sts = new AliyunStsOptions(configuration["Aliyun:RegionId"]) +}); ``` -> 与用法2的区别在于配置更新后无需重启项目即可生效 \ No newline at end of file +> 与用法2的区别在于配置更新后无需重启项目即可生效 + diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs index 46a04715e..1532d2372 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/ServiceCollectionExtensions.cs @@ -18,24 +18,25 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic if (string.IsNullOrEmpty(sectionName)) throw new ArgumentException(sectionName, nameof(sectionName)); - return services.AddAliyunStorageCore(() => - { - services.TryAddConfigure(sectionName); - services.AddSingleton(serviceProvider => - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - CheckAliYunStorageOptions(optionsMonitor.CurrentValue, $"Failed to get {nameof(IOptionsMonitor)}"); - return new Client(optionsMonitor.CurrentValue, GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider)); - }); - }); + services.AddAliyunStorageDepend(); + services.TryAddConfigure(sectionName); + services.TryAddSingleton(); + services.TryAddSingleton(serviceProvider => new DefaultCredentialProvider( + GetOssClientFactory(serviceProvider), + GetAliyunStorageConfigurationOption(serviceProvider), + GetMemoryCache(serviceProvider), + GetDefaultCredentialProviderLogger(serviceProvider))); + services.TryAddSingleton(serviceProvider => new Client( + GetCredentialProvider(serviceProvider), + GetAliyunStorageOption(serviceProvider), + GetClientLogger(serviceProvider))); + return services; } public static IServiceCollection AddAliyunStorage(this IServiceCollection services, AliyunStorageOptions options) { ArgumentNullException.ThrowIfNull(options, nameof(options)); - - string message = $"{options.AccessKeyId}, {options.AccessKeySecret}, {options.RegionId}, {options.RoleArn}, {options.RoleSessionName} are required and cannot be empty"; - CheckAliYunStorageOptions(options, message); + CheckAliYunStorageOptions(options); return services.AddAliyunStorage(() => options); } @@ -44,18 +45,23 @@ public static IServiceCollection AddAliyunStorage(this IServiceCollection servic { ArgumentNullException.ThrowIfNull(func, nameof(func)); - return services.AddAliyunStorageCore(() => services.AddSingleton(serviceProvider - => new Client(func.Invoke(), GetMemoryCache(serviceProvider), GetClientLogger(serviceProvider)))); + services.AddAliyunStorageDepend(); + services.TryAddSingleton(); + services.TryAddSingleton(serviceProvider => new DefaultCredentialProvider( + GetOssClientFactory(serviceProvider), + func.Invoke(), + GetMemoryCache(serviceProvider), + GetDefaultCredentialProviderLogger(serviceProvider))); + services.TryAddSingleton(serviceProvider => new Client( + GetCredentialProvider(serviceProvider), + func.Invoke(), + GetClientLogger(serviceProvider))); + return services; } - private static IServiceCollection AddAliyunStorageCore(this IServiceCollection services, Action action) + private static IServiceCollection AddAliyunStorageDepend(this IServiceCollection services) { - if (services.Any(service => service.ImplementationType == typeof(AliyunStorageProvider))) - return services; - - services.AddSingleton(); services.AddMemoryCache(); - action.Invoke(); return services; } @@ -64,9 +70,10 @@ private static IServiceCollection TryAddConfigure( string sectionName) where TOptions : class { - IConfiguration? - configuration = - services.BuildServiceProvider().GetService(); //Todo: Follow-up needs to support IMasaConfiguration + var serviceProvider = services.BuildServiceProvider(); + IConfiguration? configuration = serviceProvider.GetService()?.GetConfiguration(SectionTypes.Local) ?? + serviceProvider.GetService(); + if (configuration == null) return services; @@ -82,29 +89,33 @@ private static IServiceCollection TryAddConfigure( return services; } + private static IOssClientFactory GetOssClientFactory(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService(); + + private static ICredentialProvider GetCredentialProvider(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService(); + + private static IOptionsMonitor GetAliyunStorageConfigurationOption(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService>(); + + private static IOptionsMonitor GetAliyunStorageOption(IServiceProvider serviceProvider) + => serviceProvider.GetRequiredService>(); + private static IMemoryCache GetMemoryCache(IServiceProvider serviceProvider) => serviceProvider.GetRequiredService(); private static ILogger? GetClientLogger(IServiceProvider serviceProvider) => serviceProvider.GetService>(); - private static void CheckAliYunStorageOptions(AliyunStorageOptions options, string? message = default) + private static ILogger? GetDefaultCredentialProviderLogger(IServiceProvider serviceProvider) + => serviceProvider.GetService>(); + + private static void CheckAliYunStorageOptions(AliyunStorageOptions options) { ArgumentNullException.ThrowIfNull(options, nameof(options)); - if (options.AccessKeyId == null && - options.AccessKeySecret == null && - options.RegionId == null && - options.RoleArn == null && - options.RoleSessionName == null) - throw new ArgumentException(message); - - options.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); - options.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); - options.CheckNullOrEmptyAndReturnValue(options.RegionId, nameof(options.RegionId)); - options.CheckNullOrEmptyAndReturnValue(options.RoleArn, nameof(options.RoleArn)); - options.CheckNullOrEmptyAndReturnValue(options.RoleSessionName, nameof(options.RoleSessionName)); - } + if (options.AccessKeyId == null && options.AccessKeySecret == null) + throw new ArgumentException($"{nameof(options.AccessKeyId)}, {nameof(options.AccessKeySecret)} are required and cannot be empty"); - private class AliyunStorageProvider - { + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeyId, nameof(options.AccessKeyId)); + ObjectStorageExtensions.CheckNullOrEmptyAndReturnValue(options.AccessKeySecret, nameof(options.AccessKeySecret)); } } diff --git a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs index 9372bb316..2a8e3b172 100644 --- a/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs +++ b/src/Storage/Masa.Contrib.Storage.ObjectStorage.Aliyun/_Imports.cs @@ -4,9 +4,13 @@ global using Aliyun.Acs.Core; global using Aliyun.Acs.Core.Auth.Sts; global using Aliyun.Acs.Core.Profile; +global using Aliyun.OSS; +global using Aliyun.OSS.Util; +global using Masa.BuildingBlocks.Configuration; global using Masa.BuildingBlocks.Storage.ObjectStorage; global using Masa.BuildingBlocks.Storage.ObjectStorage.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal; +global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Internal.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; @@ -14,5 +18,6 @@ global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; +global using System.Net; +global using System.Text; global using AliyunFormatType = Aliyun.Acs.Core.Http.FormatType; - diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs new file mode 100644 index 000000000..d1efef2d9 --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/BaseTest.cs @@ -0,0 +1,21 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +[TestClass] +public class BaseTest +{ + protected AliyunStorageOptions _aLiYunStorageOptions; + protected const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; + + public BaseTest() + { + _aLiYunStorageOptions = new AliyunStorageOptions( + "AccessKeyId", + "AccessKeySecret", + HANG_ZHOUE_PUBLIC_ENDPOINT, + "RoleArn", + "RoleSessionName"); + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs deleted file mode 100644 index 5ecbaf333..000000000 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomNullClient.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; - -public class CustomNullClient : Client -{ - public string Message = "You are not authorized to do this action. You should be authorized by RAM."; - - public CustomNullClient(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) : base(options, cache, logger) - { - } - - protected override TemporaryCredentialsResponse GetTemporaryCredentials( - string regionId, - string accessKeyId, - string accessKeySecret, - string roleArn, - string roleSessionName, - string policy, - long durationSeconds) => throw new Exception(Message); -} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs new file mode 100644 index 000000000..44aa86e03 --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeClient.cs @@ -0,0 +1,74 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +public class CustomizeClient : Client +{ + public Mock? Oss; + + public CustomizeClient(ICredentialProvider credentialProvider, AliyunStorageOptions options, ILogger? logger) : base( + credentialProvider, options, logger) + { + } + + public override IOss GetClient() + { + if (Oss != null) + return Oss.Object; + + Oss = new(); + Oss.Setup(c => c.GetObject(It.IsAny(), It.IsAny())).Returns(GetOssObject()).Verifiable(); + Oss.Setup(c => c.GetObject(It.IsAny())).Returns(GetOssObject()).Verifiable(); + Oss.Setup(c => c.PutObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(GetPutObjectResult()).Verifiable(); + Oss.Setup(c => c.ResumableUploadObject(It.IsAny())).Returns(GetPutObjectResult()).Verifiable(); + Oss.Setup(c => c.DoesObjectExist(It.IsAny(), "1.jpg")).Returns(false).Verifiable(); + Oss.Setup(c => c.DoesObjectExist(It.IsAny(), "2.jpg")).Returns(true).Verifiable(); + Oss.Setup(c => c.DeleteObject(It.IsAny(), "1.jpg")).Returns(GetDeleteFail()).Verifiable(); + Oss.Setup(c => c.DeleteObject(It.IsAny(), "2.jpg")).Returns(GetDeleteSuccess()).Verifiable(); + Oss.Setup(c => c.DeleteObjects(It.IsAny())).Returns(GetDeleteObjectsResult()).Verifiable(); + return Oss.Object; + } + + private OssObject GetOssObject() + { + string objectName = string.Empty; + var constructor = typeof(OssObject).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, new[] { typeof(string) })!; + OssObject ossObject = (constructor.Invoke(new object[] { objectName }) as OssObject)!; + ossObject.ResponseStream = null; + return ossObject; + } + + private PutObjectResult GetPutObjectResult() + { + var constructor = typeof(PutObjectResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as PutObjectResult)!; + result.ResponseStream = new MemoryStream(Encoding.Default.GetBytes("test")); + return result; + } + + private DeleteObjectResult GetDeleteFail() + { + var constructor = typeof(DeleteObjectResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as DeleteObjectResult)!; + result.HttpStatusCode = HttpStatusCode.NotFound; + return result; + } + + private DeleteObjectResult GetDeleteSuccess() + { + var constructor = typeof(DeleteObjectResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as DeleteObjectResult)!; + result.HttpStatusCode = HttpStatusCode.OK; + return result; + } + + private DeleteObjectsResult GetDeleteObjectsResult() + { + var constructor = typeof(DeleteObjectsResult).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes)!; + var result = (constructor.Invoke(Array.Empty()) as DeleteObjectsResult)!; + result.HttpStatusCode = HttpStatusCode.OK; + return result; + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeCredentialProvider.cs similarity index 58% rename from test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomClient.cs rename to test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeCredentialProvider.cs index f4a4dc0aa..2bf0753c6 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomClient.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeCredentialProvider.cs @@ -3,28 +3,32 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; -public class CustomClient : Client +public class CustomizeCredentialProvider : DefaultCredentialProvider { - public readonly TemporaryCredentialsResponse TemporaryCredentials; + public readonly TemporaryCredentialsResponse TemporaryCredentials = new( + "accessKeyId", + "secretAccessKey", + "sessionToken", + DateTime.UtcNow.AddHours(-1)); - public CustomClient(AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) : base(options, cache, logger) + public CustomizeCredentialProvider(IOssClientFactory ossClientFactory, AliyunStorageOptions options, IMemoryCache cache, + ILogger? logger) : base(ossClientFactory, options, cache, logger) { - TemporaryCredentials = new( - "accessKeyId", - "secretAccessKey", - "sessionToken", - DateTime.UtcNow.AddHours(-1)); } - protected override TemporaryCredentialsResponse GetTemporaryCredentials( + public CustomizeCredentialProvider(IOssClientFactory ossClientFactory, IOptionsMonitor options, IMemoryCache cache, + ILogger? logger) : base(ossClientFactory, options, cache, logger) + { + } + + public override TemporaryCredentialsResponse GetTemporaryCredentials( string regionId, string accessKeyId, string accessKeySecret, string roleArn, string roleSessionName, string policy, - long durationSeconds) - => TemporaryCredentials; + long durationSeconds) => TemporaryCredentials; public TemporaryCredentialsResponse TestGetTemporaryCredentials(string regionId, string accessKeyId, diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs new file mode 100644 index 000000000..ee7bd8657 --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/CustomizeNullClient.cs @@ -0,0 +1,26 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +public class CustomizeNullClient : DefaultCredentialProvider +{ + public string Message = "You are not authorized to do this action. You should be authorized by RAM."; + + public override TemporaryCredentialsResponse GetTemporaryCredentials( + string regionId, + string accessKeyId, + string accessKeySecret, + string roleArn, + string roleSessionName, + string policy, + long durationSeconds) => throw new Exception(Message); + + public CustomizeNullClient(IOssClientFactory ossClientFactory, AliyunStorageOptions options, IMemoryCache cache, ILogger? logger) : base(ossClientFactory, options, cache, logger) + { + } + + public CustomizeNullClient(IOssClientFactory ossClientFactory, IOptionsMonitor options, IMemoryCache cache, ILogger? logger) : base(ossClientFactory, options, cache, logger) + { + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs index d0c628619..4861e61f6 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestALiYunStorageOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) MASA Stack All rights reserved. +// Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; @@ -6,12 +6,16 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] public class TestALiYunStorageOptions { + private const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; + private const string HANG_ZHOUE_INTERNAL_ENDPOINT = "oss-cn-hangzhou-internal.aliyuncs.com"; + private const string TEMPORARY_CREDENTIALS_CACHEKEY = "Aliyun.Storage.TemporaryCredentials"; + [DataTestMethod] - [DataRow("", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName", "accessKeyId")] - [DataRow("AccessKeyId", "", "RegionId", "RoleArn", "RoleSessionName", "accessKeySecret")] + [DataRow("", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName", "accessKeyId")] + [DataRow("AccessKeyId", "", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName", "accessKeySecret")] [DataRow("AccessKeyId", "AccessKeySecret", "", "RoleArn", "RoleSessionName", "regionId")] - [DataRow("AccessKeyId", "AccessKeySecret", "RegionId", "", "RoleSessionName", "roleArn")] - [DataRow("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "", "roleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "", "RoleSessionName", "roleArn")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "", "roleSessionName")] public void TestErrorParameterThrowArgumentException( string accessKeyId, string accessKeySecret, @@ -28,51 +32,39 @@ public void TestErrorParameterThrowArgumentException( [TestMethod] public void TestDurationSecondsGreaterThan43200ReturnThrowArgumentOutOfRangeException() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var aliyunStsOptions = new AliyunStsOptions(); Assert.ThrowsException(() => - options.SetDurationSeconds(43201), + aliyunStsOptions.DurationSeconds = 43201, "DurationSeconds must be in range of 900-43200"); } [TestMethod] public void TestDurationSecondsLessThan900ReturnThrowArgumentOutOfRangeException() { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var aliyunStsOptions = new AliyunStsOptions(); Assert.ThrowsException(() => - options.SetDurationSeconds(899), + aliyunStsOptions.DurationSeconds = 899, "DurationSeconds must be in range of 900-43200"); } [DataTestMethod] - [DataRow("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName")] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_INTERNAL_ENDPOINT, "RoleArn", "RoleSessionName")] public void TestSuccessParameterReturnInitializationSuccessful( string accessKeyId, string accessKeySecret, - string regionId, + string endpoint, string roleArn, string roleSessionName) { - var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, regionId, roleArn, roleSessionName); + var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint, roleArn, roleSessionName); Assert.IsTrue(options.AccessKeyId == accessKeyId); Assert.IsTrue(options.AccessKeySecret == accessKeySecret); - Assert.IsTrue(options.RegionId == regionId); + Assert.IsTrue(options.Sts.RegionId == null); Assert.IsTrue(options.RoleArn == roleArn); Assert.IsTrue(options.RoleSessionName == roleSessionName); } - [DataTestMethod] - [DataRow(900)] - [DataRow(901)] - [DataRow(43200)] - [DataRow(43199)] - [DataRow(2000)] - public void TestDurationSecondsLessThan43200AndGreaterThan900ReturnTrue(int durationSeconds) - { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); - options.SetDurationSeconds(durationSeconds); - Assert.IsTrue(options.DurationSeconds == durationSeconds); - } - [DataTestMethod] [DataRow("", "temporaryCredentialsCacheKey")] [DataRow(null, "temporaryCredentialsCacheKey")] @@ -80,7 +72,7 @@ public void TestErrorTemporaryCredentialsCacheKeyReturnThrowArgumentException( string temporaryCredentialsCacheKey, string temporaryCredentialsCacheKeyName) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); Assert.ThrowsException(() => options.SetTemporaryCredentialsCacheKey(temporaryCredentialsCacheKey), $"{temporaryCredentialsCacheKeyName} cannot be empty"); @@ -90,7 +82,7 @@ public void TestErrorTemporaryCredentialsCacheKeyReturnThrowArgumentException( [DataRow("Aliyun.TemporaryCredentials")] public void TestNotNullTemporaryCredentialsCacheKeyReturnSuccess(string temporaryCredentialsCacheKey) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); options.SetTemporaryCredentialsCacheKey(temporaryCredentialsCacheKey); Assert.IsTrue(options.TemporaryCredentialsCacheKey == temporaryCredentialsCacheKey); } @@ -101,8 +93,47 @@ public void TestNotNullTemporaryCredentialsCacheKeyReturnSuccess(string temporar [DataRow("Policy")] public void TestSetPolicyReturnSuccess(string policy) { - var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "RoleArn", "RoleSessionName"); options.SetPolicy(policy); Assert.IsTrue(options.Policy == policy); } + + [DataTestMethod] + [DataRow("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT)] + public void TestNullRoleArnAndSessionNameReturnSessionNameIsNull( + string accessKeyId, + string accessKeySecret, + string endpoint) + { + var options = new AliyunStorageOptions(accessKeyId, accessKeySecret, endpoint); + Assert.IsTrue(options.Endpoint == HANG_ZHOUE_PUBLIC_ENDPOINT); + Assert.IsTrue(options.TemporaryCredentialsCacheKey == TEMPORARY_CREDENTIALS_CACHEKEY); + Assert.IsTrue(options.Quiet); + Assert.IsNotNull(options.CallbackBody); + Assert.IsTrue(options.EnableResumableUpload); + Assert.IsTrue(options.BigObjectContentLength == 5 * 1024L * 1024 * 1024); + Assert.IsNull(options.RoleArn); + Assert.IsNull(options.RoleSessionName); + + + } + + [TestMethod] + public void TestAliyunStsOptionsDefaultReturnDurationSecondsIs3600() + { + AliyunStsOptions stsOptions = new AliyunStsOptions(); + Assert.IsTrue(stsOptions.RegionId == null); + Assert.IsTrue(stsOptions.GetEarlyExpires() == 10); + Assert.IsTrue(stsOptions.GetDurationSeconds() == 3600); + } + + [TestMethod] + public void TestEarlyExpireLessThanZeroReturnThrowArgumentOutOfRangeException() + { + var options = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); + Assert.ThrowsException(() => options.Sts = new AliyunStsOptions() + { + EarlyExpires = -1 + }); + } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs index f2eb54c42..f5d2c4bb9 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestClient.cs @@ -4,139 +4,136 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] -public class TestClient +public class TestClient : BaseTest { - private AliyunStorageOptions _aLiYunStorageOptions; + private CustomizeClient _client; [TestInitialize] public void Initialize() { - _aLiYunStorageOptions = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", "RegionId", "RoleArn", "RoleSessionName"); + Mock credentialProvider = new(); + credentialProvider.Setup(provider => provider.SupportSts).Returns(false); + _client = new CustomizeClient(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); } [TestMethod] public void TestGetTokenAndNullLoggerReturnFalse() { - Mock memoryCache = new(); - var client = new Client(_aLiYunStorageOptions, memoryCache.Object, null); + Mock credentialProvider = new(); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, null); Assert.ThrowsException(() => client.GetToken(), "GetToken is not supported, please use GetSecurityToken"); } [TestMethod] public void TestGetTokenAndNotNullLoggerReturnFalse() { - Mock memoryCache = new(); - var client = new Client(_aLiYunStorageOptions, memoryCache.Object, NullLogger.Instance); + Mock credentialProvider = new(); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); Assert.ThrowsException(() => client.GetToken(), "GetToken is not supported, please use GetSecurityToken"); } [TestMethod] public void TestGetSecurityTokenByCacheReturnSuccess() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); + Mock credentialProvider = new(); TemporaryCredentialsResponse temporaryCredentials = new( "accessKeyId", "secretAccessKey", "sessionToken", DateTime.UtcNow.AddHours(-1)); - memoryCache.Set(_aLiYunStorageOptions.TemporaryCredentialsCacheKey, temporaryCredentials); - var client = new Client(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); + credentialProvider.Setup(provider => provider.GetSecurityToken()).Returns(temporaryCredentials); + credentialProvider.Setup(provider => provider.SupportSts).Returns(true); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); var responseBase = client.GetSecurityToken(); Assert.IsTrue(responseBase == temporaryCredentials); } [TestMethod] - public void TestGetSecurityTokenByCacheNotFoundReturnSuccess() + public void TestEmptyRoleArnGetSecurityTokenReturnThrowArgumentException() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - var securityToken = client.GetSecurityToken(); - - Assert.IsTrue(securityToken.Expiration == client.TemporaryCredentials.Expiration && - securityToken.AccessKeyId == client.TemporaryCredentials.AccessKeyId && - securityToken.AccessKeySecret == client.TemporaryCredentials.AccessKeySecret && - securityToken.SessionToken == client.TemporaryCredentials.SessionToken); - Assert.IsNotNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + Mock credentialProvider = new(); + credentialProvider.Setup(provider => provider.SupportSts).Returns(false); + _aLiYunStorageOptions = new AliyunStorageOptions("AccessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT); + var client = new Client(credentialProvider.Object, _aLiYunStorageOptions, NullLogger.Instance); + Assert.ThrowsException(() => client.GetSecurityToken()); } [TestMethod] - public void TestGetSecurityTokenByCacheNotFoundAndGetTemporaryCredentialsIsNullReturnError() + public async Task TestGetObjectAsyncReturnGetObjecVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomNullClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - Assert.ThrowsException(() => client.GetSecurityToken(), client.Message); - Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + await _client.GetObjectAsync("bucketName", "objectName", stream => + { + Assert.IsTrue(stream == null); + }); + _client.Oss!.Verify(oss => oss.GetObject(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] - public void TestSetTemporaryCredentialsAndExpirationLessThan10SecondsReturnSkip() + public async Task TestGetObjectAsyncByOffsetReturnGetObjecVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - client.TestExpirationTimeLessThan10Second(5); - Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + await _client.GetObjectAsync("bucketName", "objectName", 1, 2, stream => + { + Assert.IsTrue(stream == null); + }); + _client.Oss!.Verify(oss => oss.GetObject(It.IsAny()), Times.Once); } - [DataTestMethod] - [DataRow(15)] - [DataRow(20)] - public void TestSetTemporaryCredentialsAndExpirationGreatherThanOrEqual10SecondsReturnSkip(int durationSeconds) + [TestMethod] + public async Task TestGetObjectAsyncByLengthLessThan0AndNotEqualMinus1ReturnThrowArgumentOutOfRangeException() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - client.TestExpirationTimeLessThan10Second(durationSeconds); - var res = memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey); - Assert.IsNotNull(res); + await Assert.ThrowsExceptionAsync(async () + => await _client.GetObjectAsync("bucketName", "objectName", 1, -2, null!)); } [TestMethod] - public void TestGetTemporaryCredentialsReturnNull() + public async Task TestPutObjectAsyncReturnPutObjectVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, NullLogger.Instance); - Assert.ThrowsException(() => client.TestGetTemporaryCredentials( - "cn-shanghai", - "accessKeyId", - "accessKeySecret", - "roleArn", - "roleSessionName", - String.Empty, - 3600)); + string str = "JIm"; + await _client.PutObjectAsync("bucketName", "objectName", new MemoryStream(Encoding.Default.GetBytes(str))); + _client.Oss!.Verify(oss => oss.PutObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); } [TestMethod] - public void TestGetTemporaryCredentialsAndNullLoggerReturnThrowException() + public async Task TestPutObjectAsyncReturnResumableUploadObjectVerifytOnce() { - var services = new ServiceCollection(); - services.AddMemoryCache(); - var serviceProvider = services.BuildServiceProvider(); - var memoryCache = serviceProvider.GetRequiredService(); - var client = new CustomClient(_aLiYunStorageOptions, memoryCache, null); - Assert.ThrowsException(() => client.TestGetTemporaryCredentials( - "cn-shanghai", - "accessKeyId", - "accessKeySecret", - "roleArn", - "roleSessionName", - "policy", - 3600)); + _aLiYunStorageOptions.BigObjectContentLength = 2; + string str = "JIm"; + await _client.PutObjectAsync("bucketName", "objectName", new MemoryStream(Encoding.Default.GetBytes(str))); + _client.Oss!.Verify(oss => oss.ResumableUploadObject(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestObjectExistsAsyncReturnNotFound() + { + Assert.IsFalse(await _client.ObjectExistsAsync("bucketName", "1.jpg")); + } + + [TestMethod] + public async Task TestObjectExistsAsyncReturnExist() + { + Assert.IsTrue(await _client.ObjectExistsAsync("bucketName", "2.jpg")); + } + + [TestMethod] + public async Task TestDeleteObjectAsyncReturnVerifytNever() + { + await _client.DeleteObjectAsync("bucketName", "1.jpg"); + _client.Oss!.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestDeleteObjectAsyncReturnVerifytOnce() + { + await _client.DeleteObjectAsync("bucketName", "2.jpg"); + _client.Oss!.Verify(oss => oss.DoesObjectExist(It.IsAny(), It.IsAny()), Times.Once); + _client.Oss!.Verify(oss => oss.DeleteObject(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestDeleteMultiObjectAsyncReturnVerifytOnce() + { + await _client.DeleteObjectAsync("bucketName", new[] { "2.jpg", "1.jpg" }); + _client.Oss!.Verify(oss => oss.DeleteObjects(It.IsAny()), Times.Once); } } diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs new file mode 100644 index 000000000..8088706ad --- /dev/null +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestCredentialProvider.cs @@ -0,0 +1,124 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; + +[TestClass] +public class TestCredentialProvider : BaseTest +{ + [TestMethod] + public void TestGetSecurityTokenByCacheNotFoundReturnSuccess() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + var securityToken = client.GetSecurityToken(); + Assert.IsTrue(securityToken.Expiration == client.TemporaryCredentials.Expiration && + securityToken.AccessKeyId == client.TemporaryCredentials.AccessKeyId && + securityToken.AccessKeySecret == client.TemporaryCredentials.AccessKeySecret && + securityToken.SessionToken == client.TemporaryCredentials.SessionToken); + Assert.IsNotNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + } + + [TestMethod] + public void TestGetSecurityTokenByCacheNotFoundAndGetTemporaryCredentialsIsNullReturnError() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeNullClient(serviceProvider.GetRequiredService() + , _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + Assert.ThrowsException(() => client.GetSecurityToken(), client.Message); + Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + } + + [TestMethod] + public void TestSetTemporaryCredentialsAndExpirationLessThan10SecondsReturnSkip() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + client.TestExpirationTimeLessThan10Second(5); + Assert.IsNull(memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey)); + } + + [DataTestMethod] + [DataRow(15)] + [DataRow(20)] + public void TestSetTemporaryCredentialsAndExpirationGreatherThanOrEqual10SecondsReturnSkip(int durationSeconds) + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + client.TestExpirationTimeLessThan10Second(durationSeconds); + var res = memoryCache.Get(_aLiYunStorageOptions.TemporaryCredentialsCacheKey); + Assert.IsNotNull(res); + } + + [TestMethod] + public void TestGetTemporaryCredentialsReturnNull() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + Assert.ThrowsException(() => client.TestGetTemporaryCredentials( + "cn-shanghai", + "accessKeyId", + "accessKeySecret", + "roleArn", + "roleSessionName", + string.Empty, + 3600)); + } + + [TestMethod] + public void TestGetTemporaryCredentialsAndNullLoggerReturnThrowException() + { + var services = new ServiceCollection(); + services.AddMemoryCache(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var memoryCache = serviceProvider.GetRequiredService(); + var client = new CustomizeCredentialProvider(serviceProvider.GetRequiredService(), + _aLiYunStorageOptions, + memoryCache, + NullLogger.Instance); + Assert.ThrowsException(() => client.TestGetTemporaryCredentials( + "cn-shanghai", + "accessKeyId", + "accessKeySecret", + "roleArn", + "roleSessionName", + "policy", + 3600)); + } +} diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs index bfb1fa1f0..d351a2c6e 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/TestStorage.cs @@ -1,5 +1,3 @@ -using Masa.BuildingBlocks.Storage.ObjectStorage; - // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. @@ -8,21 +6,24 @@ namespace Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests; [TestClass] public class TestStorage { + private const string HANG_ZHOUE_REGIONID = "oss-cn-hangzhou"; + + private const string HANG_ZHOUE_PUBLIC_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; + [TestMethod] - public void TestAddAliyunStorageReturnThrowArgumentException() + public void TestAddAliyunStorageAndNotAddConfigurationReturnClientIsNotNull() { - var services = new ServiceCollection(); + IServiceCollection services = new ServiceCollection(); services.AddAliyunStorage(); var serviceProvider = services.BuildServiceProvider(); - Assert.ThrowsException(() => serviceProvider.GetService(), - $"Failed to get {nameof(IOptionsMonitor)}"); + Assert.IsNotNull(serviceProvider.GetService()); } [TestMethod] public void TestAddAliyunStorageByEmptySectionReturnThrowArgumentException() { var services = new ServiceCollection(); - Assert.ThrowsException(() => services.AddAliyunStorage(String.Empty)); + Assert.ThrowsException(() => services.AddAliyunStorage(string.Empty)); } [TestMethod] @@ -48,17 +49,16 @@ public void TestAddAliyunStorageAndNullALiYunStorageOptionsReturnThrowArgumentNu } [TestMethod] - public void TestAddAliyunStorageByEmptyALiYunStorageOptionsReturnThrowArgumentNullException() + public void TestAddAliyunStorageByEmptyAccessKeyIdReturnThrowArgumentNullException() { - var services = new ServiceCollection(); - Assert.ThrowsException(() => services.AddAliyunStorage(new AliyunStorageOptions())); + Assert.ThrowsException(() => new AliyunStorageOptions(null!, null!, null!)); } [TestMethod] public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() { var services = new ServiceCollection(); - services.AddAliyunStorage(new AliyunStorageOptions("accessKeyId", "AccessKeySecret", "regionId", "roleArn", "roleSessionName")); + services.AddAliyunStorage(new AliyunStorageOptions("accessKeyId", "AccessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "roleArn", "roleSessionName")); var serviceProvider = services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); } @@ -67,7 +67,7 @@ public void TestAddAliyunStorageByALiYunStorageOptionsReturnClientNotNull() public void TestAddMultiAliyunStorageReturnClientCountIs1() { var services = new ServiceCollection(); - AliyunStorageOptions options = new AliyunStorageOptions("accessKeyId", "accessKeySecret", "regionId", "roleArn", "roleSessionName"); + AliyunStorageOptions options = new AliyunStorageOptions("accessKeyId", "accessKeySecret", HANG_ZHOUE_PUBLIC_ENDPOINT, "roleArn", "roleSessionName"); services.AddAliyunStorage(options).AddAliyunStorage(options); var serviceProvider = services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs index 19081a874..a33b28211 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/_Imports.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. See LICENSE.txt in the project root for license information. global using Aliyun.Acs.Core.Exceptions; +global using Aliyun.OSS; +global using Masa.BuildingBlocks.Storage.ObjectStorage; global using Masa.BuildingBlocks.Storage.ObjectStorage.Response; global using Masa.Contrib.Storage.ObjectStorage.Aliyun.Options; global using Microsoft.Extensions.Caching.Memory; @@ -12,3 +14,6 @@ global using Microsoft.Extensions.Options; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; +global using System.Net; +global using System.Reflection; +global using System.Text; diff --git a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json index 68205675e..d383cce64 100644 --- a/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json +++ b/test/Masa.Contrib.Storage.ObjectStorage.Aliyun.Tests/appsettings.json @@ -2,11 +2,16 @@ "Aliyun": { "AccessKeyId": "Replace-With-Your-AccessKeyId", "AccessKeySecret": "Replace-With-Your-AccessKeySecret", - "RegionId": "Replace-With-Your-RegionId", - "RoleArn": "Replace-With-Your-RoleArn", - "RoleSessionName": "Replace-With-Your-RoleSessionName", - "DurationSeconds": 900, - "Policy": "", - "TemporaryCredentialsCacheKey": "Aliyun.TemporaryCredentials" + "Sts": { + "RegionId": "cn-hangzhou", + "DurationSeconds": 900 + }, + "Storage": { + "RoleArn": "Replace-With-Your-RoleArn", + "RoleSessionName": "Replace-With-Your-RoleSessionName", + "TemporaryCredentialsCacheKey": "Aliyun.Storage.TemporaryCredentials", + "Policy": "", + "Endpoint": "oss-cn-hangzhou.aliyuncs.com" + } } } \ No newline at end of file