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

Update config to support configuring file ACLs #11

Open
wants to merge 5 commits into
base: v8-master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Add the following keys to `~/Web.config`
<add key="BucketFileSystem:Region" value="" />
<add key="BucketFileSystem:BucketName" value="" />
<add key="BucketFileSystem:MediaPrefix" value="media" />
<add key="BucketFileSystem:FileACL" value="public-read" />
<add key="BucketFileSystem:FormsPrefix" value="forms" />
<add key="BucketFileSystem:BucketHostname" value="" />
<add key="BucketFileSystem:DisableVirtualPathProvider" value="false" />
Expand All @@ -39,6 +40,7 @@ Add the following keys to `~/Web.config`
| `MediaPrefix` | Sometimes | N/A | The prefix for any media files being added to S3. Essentially a root directory name. Required when using `Umbraco.Storage.S3.Media` |
| `FormsPrefix` | Sometimes | N/A | The prefix for any Umbraco Forms data files being added to S3. Essentially a root directory name. Required when using `Umbraco.Storage.S3.Forms` |
| `BucketName` | Yes | N/A | The name of your S3 bucket. |
| `FileACL` | Yes | N/A | The ACL to apply to S3 items. |
| `BucketHostname` | Sometimes | N/A | The hostname for your bucket (e.g. `test-s3-bucket.s3.eu-west-2.amazonaws.com`). Required when `DisableVirtualPathProvider` is set to `true` |
| `DisableVirtualPathProvider` | No | `false` | Setting this to `true` will disable the VPP functionality. See below for more info. |

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Configuration;
using System;
using System.Configuration;
using Amazon.S3;
using Umbraco.Core;
using Umbraco.Core.Composing;
Expand All @@ -7,6 +8,7 @@
using Umbraco.Core.Logging;
using Umbraco.Forms.Core.Components;
using Umbraco.Forms.Data.FileSystem;
using Umbraco.Storage.S3.Extensions;
using Umbraco.Storage.S3.Services;

namespace Umbraco.Storage.S3.Forms
Expand All @@ -23,53 +25,72 @@ public void Compose(Composition composition)
{

var bucketName = ConfigurationManager.AppSettings[$"{AppSettingsKey}:BucketName"];
if (bucketName != null)
{
var config = CreateConfiguration();

if (bucketName == null) return;

var config = CreateConfiguration();

composition.RegisterUnique(config);
composition.Register<IMimeTypeResolver>(new DefaultMimeTypeResolver());
if (config.CacheEnabled)
composition.Register<IFileCacheProvider>(new FileSystemCacheProvider(TimeSpan.FromMinutes(config.CacheMinutes), "~/App_Data/S3Cache/Forms/"));
else
composition.Register<IFileCacheProvider>(null);

composition.RegisterUnique(config);
composition.Register<IMimeTypeResolver>(new DefaultMimeTypeResolver());

composition.RegisterUniqueFor<IFileSystem, FormsFileSystemForSavedData>(f => new BucketFileSystem(
config: config,
mimeTypeResolver: f.GetInstance<IMimeTypeResolver>(),
fileCacheProvider: null,
logger: f.GetInstance<ILogger>(),
s3Client: new AmazonS3Client(Amazon.RegionEndpoint.GetBySystemName(config.Region))
));
}
composition.RegisterUniqueFor<IFileSystem, FormsFileSystemForSavedData>(f => new BucketFileSystem(
config: config,
mimeTypeResolver: f.GetInstance<IMimeTypeResolver>(),
fileCacheProvider: f.GetInstance<IFileCacheProvider>(),
logger: f.GetInstance<ILogger>(),
s3Client: new AmazonS3Client(Amazon.RegionEndpoint.GetBySystemName(config.Region))
));

}

private BucketFileSystemConfig CreateConfiguration()
{
var bucketName = ConfigurationManager.AppSettings[$"{AppSettingsKey}:BucketName"];
var bucketHostName = ConfigurationManager.AppSettings[$"{AppSettingsKey}:BucketHostname"];
var bucketPrefix = ConfigurationManager.AppSettings[$"{AppSettingsKey}:FormsPrefix"];
var bucketPrefix = ConfigurationManager.AppSettings[$"{AppSettingsKey}:MediaPrefix"];
var region = ConfigurationManager.AppSettings[$"{AppSettingsKey}:Region"];
var fileACL = ConfigurationManager.AppSettings[$"{AppSettingsKey}:FileACL"];
var cacheMinutes = ConfigurationManager.AppSettings[$"{AppSettingsKey}:CacheMinutes"];

bool.TryParse(ConfigurationManager.AppSettings[$"{AppSettingsKey}:DisableVirtualPathProvider"], out var disableVirtualPathProvider);
bool.TryParse(ConfigurationManager.AppSettings[$"{AppSettingsKey}:CacheEnabled"], out var cacheEnabled);

if (string.IsNullOrEmpty(bucketName))
throw new ArgumentNullOrEmptyException("BucketName", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:BucketName' from AppSettings");

if (string.IsNullOrEmpty(bucketPrefix))
throw new ArgumentNullOrEmptyException("BucketPrefix", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:FormsPrefix' from AppSettings");
throw new ArgumentNullOrEmptyException("BucketPrefix", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:MediaPrefix' from AppSettings");

if (string.IsNullOrEmpty(region))
throw new ArgumentNullOrEmptyException("Region", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:Region' from AppSettings");

if (disableVirtualPathProvider && string.IsNullOrEmpty(bucketHostName))
throw new ArgumentNullOrEmptyException("BucketHostname", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:BucketHostname' from AppSettings");

if (string.IsNullOrEmpty(fileACL))
throw new ArgumentNullOrEmptyException("FileACL", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:FileACL' from AppSettings");

if (string.IsNullOrEmpty(cacheMinutes))
throw new ArgumentNullOrEmptyException("CacheMinutes", $"The AWS S3 Bucket File System (Forms) is missing the value '{AppSettingsKey}:CacheMinutes' from AppSettings");
if (!int.TryParse(cacheMinutes, out var minutesToCache))
throw new ArgumentOutOfRangeException("CacheMinutes", $"The AWS S3 Bucket File System (Forms) value '{AppSettingsKey}:CacheMinutes' is not a valid integer");

return new BucketFileSystemConfig
{
BucketName = bucketName,
BucketHostName = bucketHostName,
BucketPrefix = bucketPrefix.Trim(Delimiters),
Region = region,
CannedACL = new S3CannedACL("public-read"),
CannedACL = AclExtensions.ParseCannedAcl(fileACL),
ServerSideEncryptionMethod = "",
DisableVirtualPathProvider = disableVirtualPathProvider
DisableVirtualPathProvider = disableVirtualPathProvider,
CacheEnabled = cacheEnabled,
CacheMinutes = minutesToCache
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="AWSSDK.Core, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604, processorArchitecture=MSIL">
<HintPath>..\packages\AWSSDK.Core.3.3.103.29\lib\net45\AWSSDK.Core.dll</HintPath>
<HintPath>..\packages\AWSSDK.Core.3.7.0.44\lib\net45\AWSSDK.Core.dll</HintPath>
</Reference>
<Reference Include="AWSSDK.S3, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604, processorArchitecture=MSIL">
<HintPath>..\packages\AWSSDK.S3.3.3.104.17\lib\net45\AWSSDK.S3.dll</HintPath>
<HintPath>..\packages\AWSSDK.S3.3.7.1.14\lib\net45\AWSSDK.S3.dll</HintPath>
</Reference>
<Reference Include="EPPlus, Version=4.5.3.2, Culture=neutral, PublicKeyToken=ea159fdaa78159a1, processorArchitecture=MSIL">
<HintPath>..\packages\EPPlus.4.5.3.2\lib\net40\EPPlus.dll</HintPath>
Expand Down Expand Up @@ -181,7 +181,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\AWSSDK.S3.3.3.104.17\analyzers\dotnet\cs\AWSSDK.S3.CodeAnalysis.dll" />
<Analyzer Include="..\packages\AWSSDK.S3.3.7.1.14\analyzers\dotnet\cs\AWSSDK.S3.CodeAnalysis.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Umbraco.SqlServerCE.4.0.0.1\build\Umbraco.SqlServerCE.targets" Condition="Exists('..\packages\Umbraco.SqlServerCE.4.0.0.1\build\Umbraco.SqlServerCE.targets')" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>Our.Umbraco.FileSystemProviders.S3.Forms</id>
<version>8.1.1</version>
<id>CitadelGroup.Umbraco.FileSystemProviders.S3.Forms</id>
<version>8.4.0</version>
<authors>$author$</authors>
<owners>$author$</owners>
<licenseUrl>https://github.com/DannerrQ/Umbraco-S3-Provider</licenseUrl>
<projectUrl>https://github.com/DannerrQ/Umbraco-S3-Provider</projectUrl>
<licenseUrl>https://github.com/kingdamo/Umbraco-S3-Provider</licenseUrl>
<projectUrl>https://github.com/kingdamo/Umbraco-S3-Provider</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>AWS S3 Filesystem provider for Umbraco Forms</description>
<copyright>Copyright 2020</copyright>
<copyright>Copyright 2021</copyright>
<tags>Umbraco Aws S3 Provider Forms</tags>
<dependencies>
<dependency id="Our.Umbraco.FileSystemProviders.S3.Core" version="8.1.0" />
<dependency id="CitadelGroup.Umbraco.FileSystemProviders.S3.Core" version="8.4.0" />
</dependencies>
</metadata>
</package>
4 changes: 2 additions & 2 deletions Umbraco.Storage.S3/Umbraco.Storage.S3.Forms/packages.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AWSSDK.Core" version="3.3.103.29" targetFramework="net472" />
<package id="AWSSDK.S3" version="3.3.104.17" targetFramework="net472" />
<package id="AWSSDK.Core" version="3.7.0.44" targetFramework="net472" />
<package id="AWSSDK.S3" version="3.7.1.14" targetFramework="net472" />
<package id="EPPlus" version="4.5.3.2" targetFramework="net472" />
<package id="LightInject" version="5.4.0" targetFramework="net472" />
<package id="LightInject.Annotation" version="1.1.0" targetFramework="net472" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Configuration;
using System;
using System.Configuration;
using Amazon.S3;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Storage.S3.Extensions;
using Umbraco.Storage.S3.Services;

namespace Umbraco.Storage.S3.Media
Expand All @@ -18,24 +20,28 @@ public void Compose(Composition composition)
{

var bucketName = ConfigurationManager.AppSettings[$"{AppSettingsKey}:BucketName"];
if (bucketName != null)
{
var config = CreateConfiguration();

if (bucketName == null) return;

var config = CreateConfiguration();

composition.RegisterUnique(config);
composition.Register<IMimeTypeResolver>(new DefaultMimeTypeResolver());
composition.RegisterUnique(config);
composition.Register<IMimeTypeResolver>(new DefaultMimeTypeResolver());

composition.SetMediaFileSystem((f) => new BucketFileSystem(
config: config,
mimeTypeResolver: f.GetInstance<IMimeTypeResolver>(),
fileCacheProvider: null,
logger: f.GetInstance<ILogger>(),
s3Client: new AmazonS3Client(Amazon.RegionEndpoint.GetBySystemName(config.Region))
));
if(config.CacheEnabled)
composition.Register<IFileCacheProvider>(new FileSystemCacheProvider(TimeSpan.FromMinutes(config.CacheMinutes),"~/App_Data/S3Cache/Media/"));
else
composition.Register<IFileCacheProvider>(null);

composition.Components().Append<BucketMediaFileSystemComponent>();
composition.SetMediaFileSystem((f) => new BucketFileSystem(
config: config,
mimeTypeResolver: f.GetInstance<IMimeTypeResolver>(),
fileCacheProvider: f.GetInstance<IFileCacheProvider>(),
logger: f.GetInstance<ILogger>(),
s3Client: new AmazonS3Client(Amazon.RegionEndpoint.GetBySystemName(config.Region))
));

}
composition.Components().Append<BucketMediaFileSystemComponent>();

}

Expand All @@ -45,7 +51,11 @@ private BucketFileSystemConfig CreateConfiguration()
var bucketHostName = ConfigurationManager.AppSettings[$"{AppSettingsKey}:BucketHostname"];
var bucketPrefix = ConfigurationManager.AppSettings[$"{AppSettingsKey}:MediaPrefix"];
var region = ConfigurationManager.AppSettings[$"{AppSettingsKey}:Region"];
var fileACL = ConfigurationManager.AppSettings[$"{AppSettingsKey}:FileACL"];
var cacheMinutes = ConfigurationManager.AppSettings[$"{AppSettingsKey}:CacheMinutes"];

bool.TryParse(ConfigurationManager.AppSettings[$"{AppSettingsKey}:DisableVirtualPathProvider"], out var disableVirtualPathProvider);
bool.TryParse(ConfigurationManager.AppSettings[$"{AppSettingsKey}:CacheEnabled"], out var cacheEnabled);

if (string.IsNullOrEmpty(bucketName))
throw new ArgumentNullOrEmptyException("BucketName", $"The AWS S3 Bucket File System (Media) is missing the value '{AppSettingsKey}:BucketName' from AppSettings");
Expand All @@ -59,15 +69,25 @@ private BucketFileSystemConfig CreateConfiguration()
if (disableVirtualPathProvider && string.IsNullOrEmpty(bucketHostName))
throw new ArgumentNullOrEmptyException("BucketHostname", $"The AWS S3 Bucket File System (Media) is missing the value '{AppSettingsKey}:BucketHostname' from AppSettings");

if (string.IsNullOrEmpty(fileACL))
throw new ArgumentNullOrEmptyException("FileACL", $"The AWS S3 Bucket File System (Media) is missing the value '{AppSettingsKey}:FileACL' from AppSettings");

if (string.IsNullOrEmpty(cacheMinutes))
throw new ArgumentNullOrEmptyException("CacheMinutes", $"The AWS S3 Bucket File System (Media) is missing the value '{AppSettingsKey}:CacheMinutes' from AppSettings");
if(!int.TryParse(cacheMinutes, out var minutesToCache))
throw new ArgumentOutOfRangeException("CacheMinutes", $"The AWS S3 Bucket File System (Media) value '{AppSettingsKey}:CacheMinutes' is not a valid integer");

return new BucketFileSystemConfig
{
BucketName = bucketName,
BucketHostName = bucketHostName,
BucketPrefix = bucketPrefix.Trim(Delimiters),
Region = region,
CannedACL = new S3CannedACL("public-read"),
CannedACL = AclExtensions.ParseCannedAcl(fileACL),
ServerSideEncryptionMethod = "",
DisableVirtualPathProvider = disableVirtualPathProvider
DisableVirtualPathProvider = disableVirtualPathProvider,
CacheEnabled = cacheEnabled,
CacheMinutes = minutesToCache
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,54 @@
using System;
using System.Globalization;
using System.IO;
using System.Web;
using System.Web.Hosting;

namespace Umbraco.Storage.S3.Media
{
internal class FileSystemVirtualFile : VirtualFile
{
private readonly DateTimeOffset _lastModified;
private readonly Func<Stream> _stream;

public FileSystemVirtualFile(string virtualPath, Func<Stream> stream) : base(virtualPath)
public FileSystemVirtualFile(string virtualPath, DateTimeOffset lastModified, Func<Stream> stream) : base(virtualPath)
{
if (stream == null)
throw new ArgumentNullException("stream");
_lastModified = lastModified;
_stream = stream;
}

public override Stream Open()
{
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
HttpContext.Current.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
HttpContext.Current.Response.Cache.SetMaxAge(TimeSpan.FromDays(7));
HttpContext.Current.Response.Cache.SetExpires(DateTime.Now.AddDays(7));
HttpContext.Current.Response.Cache.SetETag(GenerateETag(_lastModified.DateTime, DateTime.Now));
return _stream();
}

public override bool IsDirectory
{
get { return false; }
}

private static string GenerateETag(DateTime lastModified, DateTime now)
{
// Get 64-bit FILETIME stamp
long lastModFileTime = lastModified.ToFileTime();
long nowFileTime = now.ToFileTime();
string hexFileTime = lastModFileTime.ToString("X8", CultureInfo.InvariantCulture);

// Do what IIS does to determine if this is a weak ETag.
// Compare the last modified time to now and if the difference is
// less than or equal to 3 seconds, then it is weak
if ((nowFileTime - lastModFileTime) <= 30000000)
{
return "W/\"" + hexFileTime + "\"";
}
return "\"" + hexFileTime + "\"";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public override VirtualFile GetFile(string virtualPath)
return base.GetFile(virtualPath);

var fileSystemPath = RemovePathPrefix(path);
return new FileSystemVirtualFile(virtualPath, () => _fileSystem.Value.OpenFile(fileSystemPath));
return new FileSystemVirtualFile(virtualPath, _fileSystem.Value.GetLastModified(virtualPath), () => _fileSystem.Value.OpenFile(fileSystemPath));
}

private string RemovePathPrefix(string virtualPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
</ItemGroup>
<ItemGroup>
<Reference Include="AWSSDK.Core, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604, processorArchitecture=MSIL">
<HintPath>..\packages\AWSSDK.Core.3.3.103.29\lib\net45\AWSSDK.Core.dll</HintPath>
<HintPath>..\packages\AWSSDK.Core.3.7.0.44\lib\net45\AWSSDK.Core.dll</HintPath>
</Reference>
<Reference Include="AWSSDK.S3, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604, processorArchitecture=MSIL">
<HintPath>..\packages\AWSSDK.S3.3.3.104.17\lib\net45\AWSSDK.S3.dll</HintPath>
<HintPath>..\packages\AWSSDK.S3.3.7.1.14\lib\net45\AWSSDK.S3.dll</HintPath>
</Reference>
<Reference Include="LightInject, Version=5.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\LightInject.5.4.0\lib\net46\LightInject.dll</HintPath>
Expand Down Expand Up @@ -168,7 +168,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\AWSSDK.S3.3.3.104.17\analyzers\dotnet\cs\AWSSDK.S3.CodeAnalysis.dll" />
<Analyzer Include="..\packages\AWSSDK.S3.3.7.1.14\analyzers\dotnet\cs\AWSSDK.S3.CodeAnalysis.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Umbraco.SqlServerCE.4.0.0.1\build\Umbraco.SqlServerCE.targets" Condition="Exists('..\packages\Umbraco.SqlServerCE.4.0.0.1\build\Umbraco.SqlServerCE.targets')" />
Expand Down
Loading