mirror of https://github.com/abpframework/abp.git
committed by
GitHub
17 changed files with 747 additions and 0 deletions
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<WarningsAsErrors>Nullable</WarningsAsErrors> |
||||
|
<AssemblyName>Volo.Abp.BlobStoring.Google</AssemblyName> |
||||
|
<PackageId>Volo.Abp.BlobStoring.Google</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" /> |
||||
|
<PackageReference Include="Google.Cloud.Storage.V1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
[DependsOn(typeof(AbpBlobStoringModule))] |
||||
|
public class AbpBlobStoringGoogleModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class DefaultGoogleBlobNameCalculator : IGoogleBlobNameCalculator, ITransientDependency |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
|
||||
|
public DefaultGoogleBlobNameCalculator(ICurrentTenant currentTenant) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
public virtual string Calculate(BlobProviderArgs args) |
||||
|
{ |
||||
|
return CurrentTenant.Id == null |
||||
|
? $"host/{args.BlobName}" |
||||
|
: $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public static class GoogleBlobContainerConfigurationExtensions |
||||
|
{ |
||||
|
public static GoogleBlobProviderConfiguration GetGoogleConfiguration( |
||||
|
this BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
return new GoogleBlobProviderConfiguration(containerConfiguration); |
||||
|
} |
||||
|
|
||||
|
public static BlobContainerConfiguration UseGoogle( |
||||
|
this BlobContainerConfiguration containerConfiguration, |
||||
|
Action<GoogleBlobProviderConfiguration> googleConfigureAction) |
||||
|
{ |
||||
|
containerConfiguration.ProviderType = typeof(GoogleBlobProvider); |
||||
|
containerConfiguration.NamingNormalizers.TryAdd<GoogleBlobNamingNormalizer>(); |
||||
|
|
||||
|
googleConfigureAction(new GoogleBlobProviderConfiguration(containerConfiguration)); |
||||
|
|
||||
|
return containerConfiguration; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,120 @@ |
|||||
|
using System.Globalization; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// https://cloud.google.com/storage/docs/buckets#naming
|
||||
|
/// </summary>
|
||||
|
public string NormalizeContainerName(string containerName) |
||||
|
{ |
||||
|
using (CultureHelper.Use(CultureInfo.InvariantCulture)) |
||||
|
{ |
||||
|
// All letters in a Bucket name must be lowercase.
|
||||
|
containerName = containerName.ToLower(); |
||||
|
|
||||
|
// Bucket names must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters.
|
||||
|
if(containerName.Contains(".")) |
||||
|
{ |
||||
|
if (containerName.Length > 222) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, 222); |
||||
|
} |
||||
|
|
||||
|
var parts = containerName.Split('.'); |
||||
|
for (var i = 0; i < parts.Length; i++) |
||||
|
{ |
||||
|
if (parts[i].Length > 63) |
||||
|
{ |
||||
|
parts[i] = parts[i].Substring(0, 63); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
containerName = string.Join(".", parts); |
||||
|
} |
||||
|
else if (containerName.Length > 63) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, 63); |
||||
|
} |
||||
|
|
||||
|
//Bucket names can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Spaces are not allowed. Names containing dots require verification.
|
||||
|
containerName = Regex.Replace(containerName, "[^a-z0-9-_.]", string.Empty); |
||||
|
|
||||
|
//Be a syntactically valid DNS name (for example, bucket..example.com is not valid because it contains two dots in a row).
|
||||
|
containerName = Regex.Replace(containerName, "[.]{2,}", "."); |
||||
|
|
||||
|
//Bucket names cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4).
|
||||
|
containerName = Regex.Replace(containerName, "^(?:(?:^|\\.)(?:2(?:5[0-5]|[0-4]\\d)|1?\\d?\\d)){4}$", string.Empty); |
||||
|
|
||||
|
//Bucket names cannot begin with the "goog" prefix.
|
||||
|
containerName = Regex.Replace(containerName, "^goog", string.Empty); |
||||
|
|
||||
|
//Bucket names cannot contain "google" or close misspellings, such as "g00gle".
|
||||
|
containerName = Regex.Replace(containerName, "google", string.Empty); |
||||
|
|
||||
|
//Bucket names must start and end with a number or letter.
|
||||
|
containerName = RemoveInvalidStartEndCharacters(containerName); |
||||
|
|
||||
|
// Bucket names must be from 3 through 63 characters long. Names containing dots can contain up to 222 characters.
|
||||
|
if (containerName.Length < 3) |
||||
|
{ |
||||
|
var length = containerName.Length; |
||||
|
for (var i = 0; i < 3 - length; i++) |
||||
|
{ |
||||
|
containerName += "0"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return containerName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual string RemoveInvalidStartEndCharacters(string containerName) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(containerName)) |
||||
|
{ |
||||
|
return containerName; |
||||
|
} |
||||
|
|
||||
|
if (!char.IsLetterOrDigit(containerName[0])) |
||||
|
{ |
||||
|
containerName = containerName.Substring(1); |
||||
|
return RemoveInvalidStartEndCharacters(containerName); |
||||
|
} |
||||
|
|
||||
|
if (!char.IsLetterOrDigit(containerName[containerName.Length - 1])) |
||||
|
{ |
||||
|
containerName = containerName.Substring(0, containerName.Length - 1); |
||||
|
return RemoveInvalidStartEndCharacters(containerName); |
||||
|
} |
||||
|
|
||||
|
return containerName; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// https://cloud.google.com/storage/docs/objects#naming
|
||||
|
/// </summary>
|
||||
|
public string NormalizeBlobName(string blobName) |
||||
|
{ |
||||
|
//Object names can contain any sequence of valid Unicode characters, of length 1-1024 bytes when UTF-8 encoded
|
||||
|
if (blobName.Length > 1024) |
||||
|
{ |
||||
|
blobName = blobName.Substring(0, 1024); |
||||
|
} |
||||
|
|
||||
|
//Object names cannot contain Carriage Return or Line Feed characters.
|
||||
|
blobName = Regex.Replace(blobName, "[\r\n]", string.Empty); |
||||
|
|
||||
|
//Object names cannot start with .well-known/acme-challenge/.
|
||||
|
blobName = Regex.Replace(blobName, "^\\.well-known/acme-challenge/", string.Empty); |
||||
|
|
||||
|
//Objects cannot be named . or ...
|
||||
|
blobName = Regex.Replace(blobName, "^\\.\\.?$", string.Empty); |
||||
|
|
||||
|
return blobName; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,156 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
|
using Google; |
||||
|
using Google.Apis.Auth.OAuth2; |
||||
|
using Google.Cloud.Storage.V1; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobProvider : BlobProviderBase, ITransientDependency |
||||
|
{ |
||||
|
protected IGoogleBlobNameCalculator GoogleBlobNameCalculator { get; } |
||||
|
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } |
||||
|
|
||||
|
public GoogleBlobProvider(IGoogleBlobNameCalculator googleBlobNameCalculator, IBlobNormalizeNamingService blobNormalizeNamingService) |
||||
|
{ |
||||
|
GoogleBlobNameCalculator = googleBlobNameCalculator; |
||||
|
BlobNormalizeNamingService = blobNormalizeNamingService; |
||||
|
} |
||||
|
|
||||
|
public async override Task SaveAsync(BlobProviderSaveArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
var containerName = GetContainerName(args); |
||||
|
|
||||
|
if (await BlobExistsAsync(args, blobName) && !args.OverrideExisting) |
||||
|
{ |
||||
|
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{GetContainerName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); |
||||
|
} |
||||
|
|
||||
|
if (configuration.CreateContainerIfNotExists) |
||||
|
{ |
||||
|
await CreateContainerIfNotExists(args); |
||||
|
} |
||||
|
|
||||
|
await storageClient.UploadObjectAsync(containerName, blobName, contentType: "application/octet-stream", args.BlobStream); |
||||
|
} |
||||
|
|
||||
|
public async override Task<bool> DeleteAsync(BlobProviderDeleteArgs args) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
var containerName = GetContainerName(args); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await storageClient.DeleteObjectAsync(containerName, blobName); |
||||
|
} |
||||
|
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public async override Task<bool> ExistsAsync(BlobProviderExistsArgs args) |
||||
|
{ |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
return await BlobExistsAsync(args, blobName); |
||||
|
} |
||||
|
|
||||
|
public async override Task<Stream?> GetOrNullAsync(BlobProviderGetArgs args) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var blobName = GoogleBlobNameCalculator.Calculate(args); |
||||
|
var containerName = GetContainerName(args); |
||||
|
|
||||
|
if(!await BlobExistsAsync(args, blobName)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var stream = new MemoryStream(); |
||||
|
|
||||
|
await storageClient.DownloadObjectAsync(containerName, blobName, stream); |
||||
|
|
||||
|
stream.Seek(0, SeekOrigin.Begin); |
||||
|
|
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetContainerName(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
return configuration.ContainerName.IsNullOrWhiteSpace() |
||||
|
? args.ContainerName |
||||
|
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName!); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
if(!await ContainerExistsAsync(args, storageClient)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await storageClient.GetObjectAsync(GetContainerName(args), blobName); |
||||
|
} |
||||
|
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args) |
||||
|
{ |
||||
|
var storageClient = await GetStorageClientClientAsync(args); |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
if(await ContainerExistsAsync(args, storageClient)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await storageClient.CreateBucketAsync(configuration.ProjectId, GetContainerName(args)); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<bool> ContainerExistsAsync(BlobProviderArgs args, StorageClient client) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await client.GetBucketAsync(GetContainerName(args)); |
||||
|
} |
||||
|
catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<StorageClient> GetStorageClientClientAsync(BlobProviderArgs args) |
||||
|
{ |
||||
|
var configuration = args.Configuration.GetGoogleConfiguration(); |
||||
|
var googleCredential = GoogleCredential.FromServiceAccountCredential( |
||||
|
new ServiceAccountCredential( |
||||
|
new ServiceAccountCredential.Initializer(configuration.ClientEmail) |
||||
|
{ |
||||
|
ProjectId = configuration.ProjectId, |
||||
|
Scopes = configuration.Scopes |
||||
|
} |
||||
|
.FromPrivateKey(configuration.PrivateKey) |
||||
|
)); |
||||
|
|
||||
|
return await StorageClient.CreateAsync(googleCredential); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobProviderConfiguration |
||||
|
{ |
||||
|
private readonly BlobContainerConfiguration _containerConfiguration; |
||||
|
|
||||
|
public GoogleBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) |
||||
|
{ |
||||
|
_containerConfiguration = containerConfiguration; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unique identifier for your project.
|
||||
|
/// For more info see: https://cloud.google.com/resource-manager/docs/creating-managing-projects
|
||||
|
/// </summary>
|
||||
|
public string? ProjectId { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ProjectId); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ProjectId, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Email address that generated by the Google Cloud.
|
||||
|
/// </summary>
|
||||
|
public string? ClientEmail { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ClientEmail); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ClientEmail, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Private key that generated by Google Cloud.
|
||||
|
/// Starts with '-----BEGIN PRIVATE KEY-----'
|
||||
|
/// and ends with '-----END PRIVATE KEY-----'
|
||||
|
/// </summary>
|
||||
|
public string? PrivateKey { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.PrivateKey); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.PrivateKey, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Available OAuth 2.0 scopes.
|
||||
|
/// </summary>
|
||||
|
public List<string>? Scopes { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.Scopes, new List<string>()); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.Scopes, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The name can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.). Spaces are not allowed. Names containing dots require verification.
|
||||
|
/// Must start and end with a number or letter.
|
||||
|
/// Must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters.
|
||||
|
/// Cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4).
|
||||
|
/// Cannot begin with the "goog" prefix.
|
||||
|
/// Cannot contain "google" or close misspellings, such as "g00gle".
|
||||
|
/// If this parameter is not specified, the ContainerName of the <see cref="BlobProviderArgs"/> will be used.
|
||||
|
/// </summary>
|
||||
|
public string? ContainerName { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault<string>(GoogleBlobProviderConfigurationNames.ContainerName); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.ContainerName, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: false.
|
||||
|
/// </summary>
|
||||
|
public bool CreateContainerIfNotExists { |
||||
|
get => _containerConfiguration.GetConfigurationOrDefault(GoogleBlobProviderConfigurationNames.CreateContainerIfNotExists, false); |
||||
|
set => _containerConfiguration.SetConfiguration(GoogleBlobProviderConfigurationNames.CreateContainerIfNotExists, value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public static class GoogleBlobProviderConfigurationNames |
||||
|
{ |
||||
|
public const string ProjectId = "Google.ProjectId"; |
||||
|
public const string ClientEmail = "Google.ClientEmail"; |
||||
|
public const string PrivateKey = "Google.PrivateKey"; |
||||
|
public const string Scopes = "Google.Scopes"; |
||||
|
public const string ContainerName = "Google.ContainerName"; |
||||
|
public const string CreateContainerIfNotExists = "Google.CreateContainerIfNotExists"; |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public interface IGoogleBlobNameCalculator |
||||
|
{ |
||||
|
string Calculate(BlobProviderArgs args); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.test.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net8.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<UserSecretsId>9f0d2c00-80c1-435b-bfab-2c39c8249091</UserSecretsId> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Volo.Abp.Testing; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class AbpBlobStoringGoogleTestCommonBase : AbpIntegratedTest<AbpBlobStoringGoogleTestCommonModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class AbpBlobStoringGoogleTestBase : AbpIntegratedTest<AbpBlobStoringGoogleTestModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Google.Apis.Auth.OAuth2; |
||||
|
using Google.Cloud.Storage.V1; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This module will not try to connect to Google Cloud Storage.
|
||||
|
/// </summary>
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringGoogleModule), |
||||
|
typeof(AbpBlobStoringTestModule) |
||||
|
)] |
||||
|
public class AbpBlobStoringGoogleTestCommonModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBlobStoringGoogleTestCommonModule) |
||||
|
)] |
||||
|
public class AbpBlobStoringGoogleTestModule : AbpModule |
||||
|
{ |
||||
|
private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; |
||||
|
|
||||
|
private string _clientEmail; |
||||
|
private string _projectId; |
||||
|
private string _privateKey; |
||||
|
|
||||
|
private readonly string _randomContainerName = "abp-test-container-" + Guid.NewGuid().ToString("N"); |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => |
||||
|
{ |
||||
|
builder.AddUserSecrets(UserSecretsId); |
||||
|
})); |
||||
|
|
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
_clientEmail = configuration["Google:ClientEmail"]; |
||||
|
_projectId = configuration["Google:ProjectId"]; |
||||
|
_privateKey = configuration["Google:PrivateKey"]; |
||||
|
|
||||
|
Configure<AbpBlobStoringOptions>(options => |
||||
|
{ |
||||
|
options.Containers.ConfigureAll((containerName, containerConfiguration) => |
||||
|
{ |
||||
|
containerConfiguration.UseGoogle(google => |
||||
|
{ |
||||
|
google.ClientEmail = _clientEmail; |
||||
|
google.ProjectId = _projectId = "wide-origin-296910"; |
||||
|
google.PrivateKey = _privateKey; |
||||
|
google.ContainerName = _randomContainerName; |
||||
|
google.CreateContainerIfNotExists = true; |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void OnApplicationShutdown(ApplicationShutdownContext context) |
||||
|
{ |
||||
|
var googleCredential = GoogleCredential.FromServiceAccountCredential( |
||||
|
new ServiceAccountCredential( |
||||
|
new ServiceAccountCredential.Initializer(_clientEmail) |
||||
|
{ |
||||
|
ProjectId = _projectId |
||||
|
} |
||||
|
.FromPrivateKey(_privateKey) |
||||
|
)); |
||||
|
|
||||
|
var client = StorageClient.Create(googleCredential); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
client.DeleteBucket(_randomContainerName, new DeleteBucketOptions |
||||
|
{ |
||||
|
DeleteObjects = true |
||||
|
}); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
// ignored
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class DefaultGoogleBlobNamingNormalizerProvider_Tests: AbpBlobStoringGoogleTestCommonBase |
||||
|
{ |
||||
|
private readonly IBlobNamingNormalizer _blobNamingNormalizer; |
||||
|
|
||||
|
public DefaultGoogleBlobNamingNormalizerProvider_Tests() |
||||
|
{ |
||||
|
_blobNamingNormalizer = GetRequiredService<IBlobNamingNormalizer>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Lowercase() |
||||
|
{ |
||||
|
var filename = "ThisIsMyContainerName"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("thisismycontainername"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Only_Letters_Numbers_Dash_Dots_Underscores() |
||||
|
{ |
||||
|
var filename = ",./this-i,/s-my-c,/ont,/ai+*/=!@#$n^&*er.name+/"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this-is-my-container.name"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Only_Start_With_Letters_Or_Numbers() |
||||
|
{ |
||||
|
var filename = "-this.--is-.-.-my--_container---name-"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this.--is-.-.-my--_container---name"); |
||||
|
|
||||
|
filename = ".this.--is-.-.-my--container---name0._"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this.--is-.-.-my--container---name0"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Min_Length() |
||||
|
{ |
||||
|
var filename = "a"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.Length.ShouldBeGreaterThanOrEqualTo(3); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Max_Length() |
||||
|
{ |
||||
|
var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.Length.ShouldBeLessThanOrEqualTo(63); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Must_Not_Be_Ip_Address() |
||||
|
{ |
||||
|
var filename = "192.168.5.4"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("000"); |
||||
|
|
||||
|
filename = "a.192.168.5.4"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("a.192.168.5.4"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Dots() |
||||
|
{ |
||||
|
var filename = ".this..is.my.container....name."; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this.is.my.container.name"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_DNS() |
||||
|
{ |
||||
|
var filename = "bucket...example..com"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("bucket.example.com"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void NormalizeContainerName_Max_Length_Dash() |
||||
|
{ |
||||
|
var filename = "-this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b-p-"; |
||||
|
filename = _blobNamingNormalizer.NormalizeContainerName(filename); |
||||
|
filename.ShouldBe("this-is-my-container-name-abpabpabpabpabpabpabpabp-a-b-p-a--b"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
/* |
||||
|
//Please set the correct connection string in secrets.json and continue the test.
|
||||
|
public class GoogleBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringGoogleTestModule> |
||||
|
{ |
||||
|
public GoogleBlobContainer_Tests() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
*/ |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Google; |
||||
|
|
||||
|
public class GoogleBlobNameCalculator_Tests : AbpBlobStoringGoogleTestCommonBase |
||||
|
{ |
||||
|
private readonly IGoogleBlobNameCalculator _calculator; |
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
|
||||
|
private const string GoogleContainerName = "/"; |
||||
|
private const string GoogleSeparator = "/"; |
||||
|
|
||||
|
public GoogleBlobNameCalculator_Tests() |
||||
|
{ |
||||
|
_calculator = GetRequiredService<IGoogleBlobNameCalculator>(); |
||||
|
_currentTenant = GetRequiredService<ICurrentTenant>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Default_Settings() |
||||
|
{ |
||||
|
_calculator.Calculate( |
||||
|
GetArgs("my-container", "my-blob") |
||||
|
).ShouldBe($"host{GoogleSeparator}my-blob"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Default_Settings_With_TenantId() |
||||
|
{ |
||||
|
var tenantId = Guid.NewGuid(); |
||||
|
|
||||
|
using (_currentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
_calculator.Calculate( |
||||
|
GetArgs("my-container", "my-blob") |
||||
|
).ShouldBe($"tenants{GoogleSeparator}{tenantId:D}{GoogleSeparator}my-blob"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static BlobProviderArgs GetArgs( |
||||
|
string containerName, |
||||
|
string blobName) |
||||
|
{ |
||||
|
return new BlobProviderGetArgs( |
||||
|
containerName, |
||||
|
new BlobContainerConfiguration().UseGoogle(x => |
||||
|
{ |
||||
|
x.ContainerName = containerName; |
||||
|
}), |
||||
|
blobName |
||||
|
); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue