diff --git a/README.md b/README.md index 932882b..71b2523 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet:%20Abstractions)](https://www.nuget.org/packages/GeekLearning.Storage/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=NuGet:%20FileSystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=NuGet:%20Azure%20Storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet)](https://www.nuget.org/packages/GeekLearning.Storage/) [![Build Status](https://geeklearning.visualstudio.com/_apis/public/build/definitions/f841b266-7595-4d01-9ee1-4864cf65aa73/27/badge)](#) # Geek Learning Cloud Storage Abstraction diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 0151860..0554c2f 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -182,6 +182,55 @@ public async Task SaveAsync(Stream data, IPrivateFileReference f return reference; } + public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) + { + var adHocPolicy = new SharedAccessBlobPolicy() + { + SharedAccessStartTime = policy.StartTime, + SharedAccessExpiryTime = policy.ExpiryTime, + Permissions = FromGenericToAzure(policy.Permissions), + }; + + return Task.FromResult(this.container.Value.GetSharedAccessSignature(adHocPolicy)); + } + + internal static SharedAccessBlobPermissions FromGenericToAzure(SharedAccessPermissions permissions) + { + var result = SharedAccessBlobPermissions.None; + + if (permissions.HasFlag(SharedAccessPermissions.Add)) + { + result |= SharedAccessBlobPermissions.Add; + } + + if (permissions.HasFlag(SharedAccessPermissions.Create)) + { + result |= SharedAccessBlobPermissions.Create; + } + + if (permissions.HasFlag(SharedAccessPermissions.Delete)) + { + result |= SharedAccessBlobPermissions.Delete; + } + + if (permissions.HasFlag(SharedAccessPermissions.List)) + { + result |= SharedAccessBlobPermissions.List; + } + + if (permissions.HasFlag(SharedAccessPermissions.Read)) + { + result |= SharedAccessBlobPermissions.Read; + } + + if (permissions.HasFlag(SharedAccessPermissions.Write)) + { + result |= SharedAccessBlobPermissions.Write; + } + + return result; + } + private async Task InternalGetAsync(IPrivateFileReference file, bool withMetadata = false) { var azureFile = file as Internal.AzureFileReference; diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index 9a0bf70..01f60c8 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -61,11 +61,6 @@ public Task UpdateAsync(Stream stream) return this.CloudBlob.UploadFromStreamAsync(stream); } - public Task GetExpirableUriAsync() - { - throw new NotImplementedException(); - } - public async Task ReadToStreamAsync(Stream targetStream) { await this.CloudBlob.DownloadRangeToStreamAsync(targetStream, null, null); @@ -88,5 +83,17 @@ public Task SavePropertiesAsync() { return this.propertiesLazy.Value.SaveAsync(); } + + public Task GetSharedAccessSignature(ISharedAccessPolicy policy) + { + var adHocPolicy = new SharedAccessBlobPolicy() + { + SharedAccessStartTime = policy.StartTime, + SharedAccessExpiryTime = policy.ExpiryTime, + Permissions = AzureStore.FromGenericToAzure(policy.Permissions), + }; + + return Task.FromResult(this.CloudBlob.GetSharedAccessSignature(adHocPolicy)); + } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 724cfd4..dfa731b 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -148,6 +148,11 @@ public async Task SaveAsync(Stream data, IPrivateFileReference f return fileReference; } + public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) + { + throw new NotSupportedException(); + } + private async Task InternalGetAsync(IPrivateFileReference file, bool withMetadata = false, bool checkIfExists = true) { var fileSystemFile = file as Internal.FileSystemFileReference; diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs index 6ed1d9a..71dab02 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs @@ -60,11 +60,6 @@ public Task DeleteAsync() return Task.FromResult(true); } - public Task GetExpirableUriAsync() - { - throw new NotImplementedException(); - } - public Task ReadAllBytesAsync() { return Task.FromResult(File.ReadAllBytes(this.FileSystemPath)); @@ -109,5 +104,10 @@ public Task SavePropertiesAsync() this, (this.Properties as FileSystemFileProperties).ExtendedProperties); } + + public Task GetSharedAccessSignature(ISharedAccessPolicy policy) + { + throw new NotSupportedException(); + } } } diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index 2e70721..d796858 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs index b89f712..2d344a0 100644 --- a/src/GeekLearning.Storage/IFileReference.cs +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -21,8 +21,8 @@ public interface IFileReference : IPrivateFileReference Task UpdateAsync(Stream stream); - Task GetExpirableUriAsync(); - Task SavePropertiesAsync(); + + Task GetSharedAccessSignature(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/ISharedAccessPolicy.cs b/src/GeekLearning.Storage/ISharedAccessPolicy.cs new file mode 100644 index 0000000..c701baf --- /dev/null +++ b/src/GeekLearning.Storage/ISharedAccessPolicy.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage +{ + using System; + + public interface ISharedAccessPolicy + { + DateTimeOffset? StartTime { get; } + + DateTimeOffset? ExpiryTime { get; } + + SharedAccessPermissions Permissions { get; } + } +} diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index eb869de..009147f 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -29,5 +29,7 @@ public interface IStore Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType); Task SaveAsync(Stream data, IPrivateFileReference file, string contentType); + + Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 8dd2508..6c85671 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -44,5 +44,7 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + + public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) => this.innerStore.GetSharedAccessSignatureAsync(policy); } } diff --git a/src/GeekLearning.Storage/SharedAccessPermissions.cs b/src/GeekLearning.Storage/SharedAccessPermissions.cs new file mode 100644 index 0000000..e06d14f --- /dev/null +++ b/src/GeekLearning.Storage/SharedAccessPermissions.cs @@ -0,0 +1,16 @@ +namespace GeekLearning.Storage +{ + using System; + + [Flags] + public enum SharedAccessPermissions + { + None = 0, + Read = 1, + Write = 2, + Delete = 4, + List = 8, + Add = 16, + Create = 32 + } +} diff --git a/src/GeekLearning.Storage/SharedAccessPolicy.cs b/src/GeekLearning.Storage/SharedAccessPolicy.cs new file mode 100644 index 0000000..5ad55b9 --- /dev/null +++ b/src/GeekLearning.Storage/SharedAccessPolicy.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage +{ + using System; + + public class SharedAccessPolicy : ISharedAccessPolicy + { + public DateTimeOffset? StartTime { get; set; } + + public DateTimeOffset? ExpiryTime { get; set; } + + public SharedAccessPermissions Permissions { get; set; } + } +} diff --git a/tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs b/tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs new file mode 100644 index 0000000..5a43bc2 --- /dev/null +++ b/tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs @@ -0,0 +1,68 @@ +namespace GeekLearning.Storage.Integration.Test +{ + using Storage; + using Microsoft.Extensions.DependencyInjection; + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using Xunit; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.Extensions.Options; + using GeekLearning.Storage.Azure.Configuration; + using System.Linq; + + [Collection(nameof(IntegrationCollection))] + [Trait("Operation", "SharedAccess"), Trait("Kind", "Integration")] + public class SharedAccessTests + { + private readonly StoresFixture storeFixture; + + public SharedAccessTests(StoresFixture fixture) + { + this.storeFixture = fixture; + } + + [Theory(DisplayName = nameof(StoreSharedAccess)), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] + public async Task StoreSharedAccess(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + var options = this.storeFixture.Services.GetRequiredService>(); + + var store = storageFactory.GetStore(storeName); + + options.Value.ParsedStores.TryGetValue(storeName, out var storeOptions); + + var sharedAccessSignature = await store.GetSharedAccessSignatureAsync(new SharedAccessPolicy + { + ExpiryTime = DateTime.UtcNow.AddHours(24), + Permissions = SharedAccessPermissions.List, + }); + + var account = CloudStorageAccount.Parse(storeOptions.ConnectionString); + + var accountSAS = new StorageCredentials(sharedAccessSignature); + var accountWithSAS = new CloudStorageAccount(accountSAS, account.Credentials.AccountName, endpointSuffix: null, useHttps: true); + var blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + var containerWithSAS = blobClientWithSAS.GetContainerReference(storeOptions.FolderName); + + BlobContinuationToken continuationToken = null; + List results = new List(); + + do + { + var response = await containerWithSAS.ListBlobsSegmentedAsync(continuationToken); + continuationToken = response.ContinuationToken; + results.AddRange(response.Results); + } + while (continuationToken != null); + + var filesFromStore = await store.ListAsync(null, false, false); + + Assert.Equal(filesFromStore.Length, results.OfType().Count()); + } + } +}