Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Google Cloud Storage BLOB Provider #20747

Merged
merged 3 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<PackageVersion Include="FluentValidation" Version="11.8.0" />
<PackageVersion Include="Google.Cloud.Storage.V1" Version="4.10.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageVersion Include="HtmlSanitizer" Version="8.0.746" />
Expand Down
14 changes: 14 additions & 0 deletions framework/Volo.Abp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.RemoteServices.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Abstractions", "src\Volo.Abp.AspNetCore.Abstractions\Volo.Abp.AspNetCore.Abstractions.csproj", "{E1051CD0-9262-4869-832D-B951723F4DDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google", "src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj", "{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google.Tests", "test\Volo.Abp.BlobStoring.Google.Tests\Volo.Abp.BlobStoring.Google.Tests.csproj", "{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1393,6 +1397,14 @@ Global
{E1051CD0-9262-4869-832D-B951723F4DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1051CD0-9262-4869-832D-B951723F4DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1051CD0-9262-4869-832D-B951723F4DDE}.Release|Any CPU.Build.0 = Release|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}.Release|Any CPU.Build.0 = Release|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1628,6 +1640,8 @@ Global
{DFAF8763-D1D6-4EB4-B459-20E31007FE2F} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{DACD4485-61BE-4DE5-ACAE-4FFABC122500} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{E1051CD0-9262-4869-832D-B951723F4DDE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{DEEB5200-BBF9-464D-9B7E-8FC035A27E94} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Volo.Abp.Modularity;

namespace Volo.Abp.BlobStoring.Google;

[DependsOn(typeof(AbpBlobStoringModule))]
public class AbpBlobStoringGoogleModule : AbpModule
{

}
Original file line number Diff line number Diff line change
@@ -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}";
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading