From 643ab8d52637c6b972140a8fbb1e378eaba5ebca Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 20 Jul 2020 23:25:36 +0800 Subject: [PATCH] Add aws s3 blob provider --- framework/Volo.Abp.sln | 7 + .../Volo.Abp.BlobStoring.Aws/FodyWeavers.xml | 3 + .../Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd | 30 ++++ .../Volo.Abp.BlobStoring.Aws.csproj | 24 +++ .../Aws/AbpBlobStoringAzureModule.cs | 10 ++ .../Aws/AssumeRoleCredentialsCacheItem.cs | 30 ++++ ...AwsBlobContainerConfigurationExtensions.cs | 25 +++ .../Aws/AwsBlobNamingNormalizer.cs | 52 ++++++ .../Abp/BlobStoring/Aws/AwsBlobProvider.cs | 148 +++++++++++++++ .../Aws/AwsBlobProviderConfiguration.cs | 107 +++++++++++ .../Aws/AwsBlobProviderConfigurationNames.cs | 19 ++ .../Aws/DefaultAmazonS3ClientFactory.cs | 168 ++++++++++++++++++ .../BlobStoring/Aws/IAmazonS3ClientFactory.cs | 10 ++ .../BlobStoring/Aws/IAwsBlobNameCalculator.cs | 7 + 14 files changed, 640 insertions(+) create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAzureModule.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AssumeRoleCredentialsCacheItem.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 5a83ae1460..1c8e0baeca 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -319,6 +319,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun.Tests", "test\Volo.Abp.BlobStoring.Aliyun.Tests\Volo.Abp.BlobStoring.Aliyun.Tests.csproj", "{8E49687A-E69F-49F2-8DB0-428D0883A937}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws", "src\Volo.Abp.BlobStoring.Aws\Volo.Abp.BlobStoring.Aws.csproj", "{50968CDE-1029-4051-B2E5-B69D0ECF2A18}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -949,6 +951,10 @@ Global {8E49687A-E69F-49F2-8DB0-428D0883A937}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.Build.0 = Release|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1110,6 +1116,7 @@ Global {60D0E384-965E-4F81-9D71-B28F419254FC} = {447C8A77-E5F0-4538-8687-7383196D04EA} {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {8E49687A-E69F-49F2-8DB0-428D0883A937} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {50968CDE-1029-4051-B2E5-B69D0ECF2A18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj b/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj new file mode 100644 index 0000000000..d30601d1e8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.0 + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAzureModule.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAzureModule.cs new file mode 100644 index 0000000000..0b386fbec1 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAzureModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Azure +{ + [DependsOn(typeof(AbpBlobStoringModule))] + public class AbpBlobStoringAzureModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AssumeRoleCredentialsCacheItem.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AssumeRoleCredentialsCacheItem.cs new file mode 100644 index 0000000000..dbc4783978 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AssumeRoleCredentialsCacheItem.cs @@ -0,0 +1,30 @@ +using System; +using Volo.Abp.Caching; + +namespace Volo.Abp.BlobStoring.Aws +{ + [Serializable] + [CacheName("TemporaryCredentials")] + public class TemporaryCredentialsCacheItem + { + public const string Key = "AwsTemporaryCredentialsCache"; + + public string AccessKeyId { get; set; } + + public string SecretAccessKey { get; set; } + + public string SessionToken { get; set; } + + public TemporaryCredentialsCacheItem() + { + + } + + public TemporaryCredentialsCacheItem(string accessKeyId,string secretAccessKey,string sessionToken) + { + AccessKeyId = accessKeyId; + SecretAccessKey = secretAccessKey; + SessionToken = sessionToken; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..d63e408aab --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs @@ -0,0 +1,25 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + public static class AwsBlobContainerConfigurationExtensions + { + public static AwsBlobProviderConfiguration GetAwsConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new AwsBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseAws( + this BlobContainerConfiguration containerConfiguration, + Action awsConfigureAction) + { + containerConfiguration.ProviderType = typeof(AwsBlobProvider); + containerConfiguration.NamingNormalizers.TryAdd(); + + awsConfigureAction(new AwsBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs new file mode 100644 index 0000000000..cd8eca021e --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs @@ -0,0 +1,52 @@ +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + /// + ///https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html + /// + public virtual string NormalizeContainerName(string containerName) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); + + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); + + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) + { + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } + } + + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } + + return containerName; + } + + /// + /// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html + /// + public virtual string NormalizeBlobName(string blobName) + { + return blobName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs new file mode 100644 index 0000000000..17831b6f4b --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs @@ -0,0 +1,148 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobProvider : BlobProviderBase, ITransientDependency + { + protected IAwsBlobNameCalculator AwsBlobNameCalculator { get; } + protected IAmazonS3ClientFactory AmazonS3ClientFactory { get; } + + public AwsBlobProvider(IAwsBlobNameCalculator awsBlobNameCalculator, + IAmazonS3ClientFactory amazonS3ClientFactory) + { + AwsBlobNameCalculator = awsBlobNameCalculator; + AmazonS3ClientFactory = amazonS3ClientFactory; + } + + public override async Task SaveAsync(BlobProviderSaveArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var configuration = args.Configuration.GetAwsConfiguration(); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!args.OverrideExisting && await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + throw new BlobAlreadyExistsException( + $"Saving BLOB '{args.BlobName}' does already exists in the container '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + + if (configuration.CreateContainerIfNotExists) + { + await amazonS3Client.PutBucketAsync(containerName); + } + + await amazonS3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = containerName, + Key = blobName, + InputStream = args.BlobStream + }); + } + } + + public override async Task DeleteAsync(BlobProviderDeleteArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + return false; + } + + await amazonS3Client.DeleteObjectAsync(new DeleteObjectRequest + { + BucketName = containerName, + Key = blobName + }); + + return true; + } + } + + public override async Task ExistsAsync(BlobProviderExistsArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + return await BlobExistsAsync(amazonS3Client, containerName, blobName); + } + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + return null; + } + + var response = await amazonS3Client.GetObjectAsync(new GetObjectRequest + { + BucketName = containerName, + Key = blobName + }); + + var memoryStream = new MemoryStream(); + await response.ResponseStream.CopyToAsync(memoryStream); + return memoryStream; + } + } + + protected virtual async Task GetAmazonS3Client(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAwsConfiguration(); + + return await AmazonS3ClientFactory.GetAmazonS3Client(configuration); + } + + private async Task BlobExistsAsync(AmazonS3Client amazonS3Client, string containerName, string blobName) + { + // Make sure Blob Container exists. + if (!await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, containerName)) + { + return false; + } + + try + { + await amazonS3Client.GetObjectMetadataAsync(containerName, blobName); + } + catch (Exception ex) + { + if (ex is AmazonS3Exception) + { + return false; + } + + throw; + } + + return true; + } + + private static string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAwsConfiguration(); + return configuration.ContainerName.IsNullOrWhiteSpace() + ? args.ContainerName + : configuration.ContainerName; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs new file mode 100644 index 0000000000..fde0f10fc9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs @@ -0,0 +1,107 @@ +using Amazon; +using Amazon.Runtime; +using Amazon.Runtime.CredentialManagement; +using Amazon.S3; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobProviderConfiguration + { + public string AccessKeyId + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.AccessKeyId); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.AccessKeyId, value); + } + + public string SecretAccessKey + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.SecretAccessKey); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.SecretAccessKey, value); + } + + public bool UseAwsCredentials + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.UseAwsCredentials); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseAwsCredentials, value); + } + + public bool UseTemporaryCredentials + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryCredentials); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryCredentials, value); + } + + public bool UseTemporaryFederatedCredentials + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryFederatedCredentials); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryFederatedCredentials, value); + } + + public string ProfileName + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.ProfileName); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ProfileName, value); + } + + public string ProfilesLocation + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.ProfilesLocation); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ProfilesLocation, value); + } + + /// + /// Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 129600. + /// + public int DurationSeconds + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.DurationSeconds, 0); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.DurationSeconds, value); + } + + public string Name + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Name); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Name, value); + } + + public string Policy + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Policy); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Policy, value); + } + + public RegionEndpoint Region + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Region); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Region, Check.NotNull(value, nameof(value))); + } + + /// + /// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number. + /// Each hyphen must be preceded and followed by a non-hyphen character. + /// The name must also be between 3 and 63 characters long. + /// If this parameter is not specified, the ContainerName of the will be used. + /// + public string ContainerName + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.ContainerName); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// Default value: false. + /// + public bool CreateContainerIfNotExists + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.CreateContainerIfNotExists, false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.CreateContainerIfNotExists, value); + } + + private readonly BlobContainerConfiguration _containerConfiguration; + + public AwsBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..f27efc1552 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.BlobStoring.Aws +{ + public static class AwsBlobProviderConfigurationNames + { + public const string AccessKeyId = "Aws.AccessKeyId"; + public const string SecretAccessKey = "Aws.SecretAccessKey"; + public const string UseAwsCredentials = "Aws.UseAWSCredentials"; + public const string UseTemporaryCredentials = "Aws.UseTemporaryCredentials"; + public const string UseTemporaryFederatedCredentials = "Aws.UseTemporaryFederatedCredentials"; + public const string ProfileName = "Aws.ProfileName"; + public const string ProfilesLocation = "Aws.ProfilesLocation"; + public const string DurationSeconds = "Aws.DurationSeconds"; + public const string Name = "Aws.Name"; + public const string Policy = "Aws.Policy"; + public const string Region = "Aws.Region"; + public const string ContainerName = "Aws.ContainerName"; + public const string CreateContainerIfNotExists = "Aws.CreateContainerIfNotExists"; + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs new file mode 100644 index 0000000000..febda577fc --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs @@ -0,0 +1,168 @@ +using System; +using System.Threading.Tasks; +using Amazon.Runtime; +using Amazon.Runtime.CredentialManagement; +using Amazon.S3; +using Amazon.SecurityToken; +using Amazon.SecurityToken.Model; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAmazonS3ClientFactory : IAmazonS3ClientFactory, ITransientDependency + { + protected IDistributedCache Cache { get; } + + public DefaultAmazonS3ClientFactory(IDistributedCache cache) + { + Cache = cache; + } + + public virtual async Task GetAmazonS3Client( + AwsBlobProviderConfiguration configuration) + { + if (configuration.UseAwsCredentials) + { + return new AmazonS3Client(GetAwsCredentials(configuration), configuration.Region); + } + + if (configuration.UseTemporaryCredentials) + { + return new AmazonS3Client(await GetTemporaryCredentialsAsync(configuration), configuration.Region); + } + + if (configuration.UseTemporaryFederatedCredentials) + { + return new AmazonS3Client(await GetTemporaryFederatedCredentialsAsync(configuration), + configuration.Region); + } + + Check.NotNullOrWhiteSpace(configuration.AccessKeyId, nameof(configuration.AccessKeyId)); + Check.NotNullOrWhiteSpace(configuration.SecretAccessKey, nameof(configuration.SecretAccessKey)); + + return new AmazonS3Client(configuration.AccessKeyId, configuration.SecretAccessKey); + } + + protected virtual AWSCredentials GetAwsCredentials( + AwsBlobProviderConfiguration configuration) + { + var chain = new CredentialProfileStoreChain(configuration.ProfilesLocation); + + if (chain.TryGetAWSCredentials(configuration.ProfileName, out var awsCredentials)) + { + return awsCredentials; + } + + throw new AmazonS3Exception("Not found aws credentials"); + } + + protected virtual async Task GetTemporaryCredentialsAsync( + AwsBlobProviderConfiguration configuration) + { + var temporaryCredentialsCache = await Cache.GetAsync(TemporaryCredentialsCacheItem.Key); + + if (temporaryCredentialsCache == null) + { + AmazonSecurityTokenServiceClient stsClient; + + if (!configuration.AccessKeyId.IsNullOrEmpty() && !configuration.SecretAccessKey.IsNullOrEmpty()) + { + stsClient = new AmazonSecurityTokenServiceClient(configuration.AccessKeyId, + configuration.SecretAccessKey); + } + else + { + stsClient = new AmazonSecurityTokenServiceClient(GetAwsCredentials(configuration)); + } + + using (stsClient) + { + var getSessionTokenRequest = new GetSessionTokenRequest + { + DurationSeconds = configuration.DurationSeconds + }; + + var sessionTokenResponse = + await stsClient.GetSessionTokenAsync(getSessionTokenRequest); + + var credentials = sessionTokenResponse.Credentials; + + temporaryCredentialsCache = + await SetTemporaryCredentialsCache(credentials, configuration.DurationSeconds); + } + } + + var sessionCredentials = new SessionAWSCredentials( + temporaryCredentialsCache.AccessKeyId, + temporaryCredentialsCache.SecretAccessKey, + temporaryCredentialsCache.SessionToken); + return sessionCredentials; + } + + protected virtual async Task GetTemporaryFederatedCredentialsAsync( + AwsBlobProviderConfiguration configuration) + { + Check.NotNullOrWhiteSpace(configuration.Name, nameof(configuration.Name)); + + var temporaryCredentialsCache = await Cache.GetAsync(TemporaryCredentialsCacheItem.Key); + + if (temporaryCredentialsCache == null) + { + AmazonSecurityTokenServiceClient stsClient; + + if (!configuration.AccessKeyId.IsNullOrEmpty() && !configuration.SecretAccessKey.IsNullOrEmpty()) + { + stsClient = new AmazonSecurityTokenServiceClient(configuration.AccessKeyId, + configuration.SecretAccessKey); + } + else + { + stsClient = new AmazonSecurityTokenServiceClient(GetAwsCredentials(configuration)); + } + + using (stsClient) + { + var federationTokenRequest = + new GetFederationTokenRequest + { + DurationSeconds = configuration.DurationSeconds, + Name = configuration.Name, + Policy = configuration.Policy + }; + + var federationTokenResponse = + await stsClient.GetFederationTokenAsync(federationTokenRequest); + var credentials = federationTokenResponse.Credentials; + + temporaryCredentialsCache = + await SetTemporaryCredentialsCache(credentials, configuration.DurationSeconds); + } + } + + var sessionCredentials = new SessionAWSCredentials( + temporaryCredentialsCache.AccessKeyId, + temporaryCredentialsCache.SecretAccessKey, + temporaryCredentialsCache.SessionToken); + return sessionCredentials; + } + + private async Task SetTemporaryCredentialsCache( + Credentials credentials, + int durationSeconds) + { + var temporaryCredentialsCache = new TemporaryCredentialsCacheItem(credentials.AccessKeyId, + credentials.SecretAccessKey, + credentials.SessionToken); + + await Cache.SetAsync(TemporaryCredentialsCacheItem.Key, temporaryCredentialsCache, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(durationSeconds - 10) + }); + + return temporaryCredentialsCache; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs new file mode 100644 index 0000000000..92d7ddc906 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Amazon.S3; + +namespace Volo.Abp.BlobStoring.Aws +{ + public interface IAmazonS3ClientFactory + { + Task GetAmazonS3Client(AwsBlobProviderConfiguration configuration); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs new file mode 100644 index 0000000000..e4c470c505 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Aws +{ + public interface IAwsBlobNameCalculator + { + string Calculate(BlobProviderArgs args); + } +}