-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
155 additions
and
117 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace OrchardCore.DataProtection.Azure; | ||
|
||
public class BlobOptions | ||
{ | ||
public string ConnectionString { get; set; } | ||
|
||
public string ContainerName { get; set; } = "dataprotection"; | ||
|
||
public string BlobName { get; set; } | ||
|
||
public bool CreateContainer { get; set; } = true; | ||
} |
109 changes: 109 additions & 0 deletions
109
src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/BlobOptionsSetup.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Azure.Storage.Blobs; | ||
using Azure.Storage.Blobs.Models; | ||
using Fluid; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using OrchardCore.Environment.Shell; | ||
using OrchardCore.Environment.Shell.Configuration; | ||
|
||
namespace OrchardCore.DataProtection.Azure; | ||
|
||
public class BlobOptionsSetup | ||
{ | ||
private readonly FluidParser _fluidParser = new(); | ||
|
||
private readonly IShellConfiguration _configuration; | ||
private readonly ShellOptions _shellOptions; | ||
private readonly ShellSettings _shellSettings; | ||
private readonly ILogger _logger; | ||
|
||
public BlobOptionsSetup( | ||
IShellConfiguration configuration, | ||
IOptions<ShellOptions> shellOptions, | ||
ShellSettings shellSettings, | ||
ILogger<BlobOptionsSetup> logger) | ||
{ | ||
_configuration = configuration; | ||
_shellOptions = shellOptions.Value; | ||
_shellSettings = shellSettings; | ||
_logger = logger; | ||
} | ||
|
||
public async Task ConfigureAsync(BlobOptions options) | ||
{ | ||
_configuration.Bind("OrchardCore_DataProtection_Azure", options); | ||
await ConfigureContainerNameAsync(options); | ||
await ConfigureBlobNameAsync(options); | ||
} | ||
|
||
private async Task ConfigureContainerNameAsync(BlobOptions options) | ||
{ | ||
try | ||
{ | ||
// Use Fluid directly as the service provider has not been built. | ||
var templateOptions = new TemplateOptions(); | ||
templateOptions.MemberAccessStrategy.Register<ShellSettings>(); | ||
var templateContext = new TemplateContext(templateOptions); | ||
templateContext.SetValue("ShellSettings", _shellSettings); | ||
|
||
var template = _fluidParser.Parse(options.ContainerName); | ||
|
||
// Container name must be lowercase. | ||
var containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); | ||
options.ContainerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogCritical(e, "Unable to parse data protection connection string."); | ||
throw; | ||
} | ||
|
||
if (options.CreateContainer) | ||
{ | ||
try | ||
{ | ||
_logger.LogDebug("Testing data protection container {ContainerName} existence", options.ContainerName); | ||
var blobContainer = new BlobContainerClient(options.ConnectionString, options.ContainerName); | ||
var response = await blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); | ||
_logger.LogDebug("Data protection container {ContainerName} created.", options.ContainerName); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogCritical(e, "Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); | ||
throw; | ||
} | ||
} | ||
} | ||
|
||
private async Task ConfigureBlobNameAsync(BlobOptions options) | ||
{ | ||
if (string.IsNullOrEmpty(options.BlobName)) | ||
{ | ||
options.BlobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; | ||
|
||
return; | ||
} | ||
|
||
try | ||
{ | ||
// Use Fluid directly as the service provider has not been built. | ||
var templateOptions = new TemplateOptions(); | ||
var templateContext = new TemplateContext(templateOptions); | ||
templateOptions.MemberAccessStrategy.Register<ShellSettings>(); | ||
templateContext.SetValue("ShellSettings", _shellSettings); | ||
|
||
var template = _fluidParser.Parse(options.BlobName); | ||
|
||
var blobName = await template.RenderAsync(templateContext, NullEncoder.Default); | ||
options.BlobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogCritical(e, "Unable to parse data protection blob name."); | ||
throw; | ||
} | ||
} | ||
} |
151 changes: 34 additions & 117 deletions
151
src/OrchardCore.Modules/OrchardCore.DataProtection.Azure/Startup.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,56 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Azure.Storage.Blobs; | ||
using Azure.Storage.Blobs.Models; | ||
using Fluid; | ||
using Microsoft.AspNetCore.DataProtection; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using OrchardCore.Environment.Shell; | ||
using OrchardCore.Environment.Shell.Configuration; | ||
using OrchardCore.Modules; | ||
|
||
namespace OrchardCore.DataProtection.Azure | ||
namespace OrchardCore.DataProtection.Azure; | ||
|
||
public class Startup : StartupBase | ||
{ | ||
public class Startup : StartupBase | ||
private readonly IShellConfiguration _configuration; | ||
private readonly ILogger _logger; | ||
|
||
public Startup(IShellConfiguration configuration, ILogger<Startup> logger) | ||
{ | ||
private readonly ShellOptions _shellOptions; | ||
private readonly ShellSettings _shellSettings; | ||
private readonly ILogger _logger; | ||
_configuration = configuration; | ||
_logger = logger; | ||
} | ||
|
||
// Local instance since it can be discarded once the startup is over. | ||
private readonly FluidParser _fluidParser = new(); | ||
// Assume that this module will override default configuration, so set the Order to a value above the default. | ||
public override int Order => 10; | ||
|
||
public Startup( | ||
IOptions<ShellOptions> shellOptions, | ||
ShellSettings shellSettings, | ||
ILogger<Startup> logger) | ||
{ | ||
_shellOptions = shellOptions.Value; | ||
_shellSettings = shellSettings; | ||
_logger = logger; | ||
} | ||
public override void ConfigureServices(IServiceCollection services) | ||
{ | ||
var connectionString = _configuration.GetValue<string>("OrchardCore_DataProtection_Azure:ConnectionString"); | ||
|
||
public override void ConfigureServices(IServiceCollection services) | ||
if (!string.IsNullOrWhiteSpace(connectionString)) | ||
{ | ||
services.Initialize(async sp => | ||
{ | ||
var configuration = sp.GetRequiredService<IShellConfiguration>(); | ||
|
||
var connectionString = configuration.GetValue<string>("OrchardCore_DataProtection_Azure:ConnectionString"); | ||
|
||
if (!string.IsNullOrWhiteSpace(connectionString)) | ||
services | ||
.AddSingleton(new BlobOptions()) | ||
.AddTransient<BlobOptionsSetup>() | ||
.AddDataProtection() | ||
.PersistKeysToAzureBlobStorage(sp => | ||
{ | ||
var containerName = await GetBlobContainerNameAsync(configuration, connectionString); | ||
|
||
services.AddDataProtection() | ||
.PersistKeysToAzureBlobStorage(connectionString, containerName, await GetBlobNameAsync(configuration)); | ||
} | ||
else | ||
{ | ||
_logger.LogCritical("No connection string was supplied for OrchardCore.DataProtection.Azure. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); | ||
} | ||
}); | ||
} | ||
|
||
private async Task<string> GetBlobContainerNameAsync(IShellConfiguration configuration, string connectionString) | ||
{ | ||
var containerName = configuration.GetValue("OrchardCore_DataProtection_Azure:ContainerName", "dataprotection"); | ||
|
||
// Use Fluid directly as the service provider has not been built. | ||
try | ||
{ | ||
var templateOptions = new TemplateOptions(); | ||
templateOptions.MemberAccessStrategy.Register<ShellSettings>(); | ||
var templateContext = new TemplateContext(templateOptions); | ||
templateContext.SetValue("ShellSettings", _shellSettings); | ||
|
||
var template = _fluidParser.Parse(containerName); | ||
var options = sp.GetRequiredService<BlobOptions>(); | ||
return new BlobClient( | ||
options.ConnectionString, | ||
options.ContainerName, | ||
options.BlobName); | ||
}); | ||
|
||
// container name must be lowercase | ||
containerName = template.Render(templateContext, NullEncoder.Default).ToLower(); | ||
containerName = containerName.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogCritical(e, "Unable to parse data protection connection string."); | ||
throw; | ||
} | ||
|
||
var createContainer = configuration.GetValue("OrchardCore_DataProtection_Azure:CreateContainer", true); | ||
if (createContainer) | ||
services.Initialize(async sp => | ||
{ | ||
try | ||
{ | ||
_logger.LogDebug("Testing data protection container {ContainerName} existence", containerName); | ||
var _blobContainer = new BlobContainerClient(connectionString, containerName); | ||
var response = await _blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); | ||
_logger.LogDebug("Data protection container {ContainerName} created.", containerName); | ||
} | ||
catch (Exception) | ||
{ | ||
_logger.LogCritical("Unable to connect to Azure Storage to configure data protection storage. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); | ||
|
||
throw; | ||
} | ||
} | ||
|
||
return containerName; | ||
var options = sp.GetRequiredService<BlobOptions>(); | ||
var setup = sp.GetRequiredService<BlobOptionsSetup>(); | ||
await setup.ConfigureAsync(options); | ||
}); | ||
} | ||
|
||
private async Task<string> GetBlobNameAsync(IShellConfiguration configuration) | ||
else | ||
{ | ||
var blobName = configuration.GetValue<string>("OrchardCore_DataProtection_Azure:BlobName"); | ||
|
||
if (string.IsNullOrEmpty(blobName)) | ||
{ | ||
blobName = $"{_shellOptions.ShellsContainerName}/{_shellSettings.Name}/DataProtectionKeys.xml"; | ||
} | ||
else | ||
{ | ||
try | ||
{ | ||
// Use Fluid directly as the service provider has not been built. | ||
var templateOptions = new TemplateOptions(); | ||
var templateContext = new TemplateContext(templateOptions); | ||
templateOptions.MemberAccessStrategy.Register<ShellSettings>(); | ||
templateContext.SetValue("ShellSettings", _shellSettings); | ||
|
||
var template = _fluidParser.Parse(blobName); | ||
|
||
blobName = await template.RenderAsync(templateContext, NullEncoder.Default); | ||
blobName = blobName.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogCritical(e, "Unable to parse data protection blob name."); | ||
throw; | ||
} | ||
} | ||
|
||
return blobName; | ||
_logger.LogCritical("No connection string was supplied for OrchardCore.DataProtection.Azure. Ensure that an application setting containing a valid Azure Storage connection string is available at `Modules:OrchardCore.DataProtection.Azure:ConnectionString`."); | ||
} | ||
|
||
// Assume that this module will override default configuration, so set the Order to a value above the default. | ||
public override int Order => 10; | ||
} | ||
} |