Skip to content

Commit

Permalink
#83
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebazzz committed Mar 27, 2019
1 parent 534a495 commit 8e1312e
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/App/App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

Expand Down
25 changes: 4 additions & 21 deletions src/App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,27 +90,13 @@ private static void AddOperatingSpecificConfigurationFolders([NotNull] this ICon
{
if (cfg == null) throw new ArgumentNullException(nameof(cfg));

const string appSpecificFolder = "financial-app";
const string configFileName = "config";
const string iniFileExt = "ini";
const string jsonFileExt = "json";

string MakeWin32FilePath(string extension)
string MakeFilePath(string extension)
{
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
appSpecificFolder,
Path.ChangeExtension(configFileName, extension)
);
}

string MakeUnixFilePath(string extension)
{
return Path.Combine(
"/etc",
appSpecificFolder,
Path.ChangeExtension(configFileName, extension)
);
return EmitConfigSearchMessage(EnvironmentPath.CreatePath(Path.ChangeExtension(configFileName, extension)));
}

string EmitConfigSearchMessage(string path)
Expand All @@ -122,12 +108,9 @@ string EmitConfigSearchMessage(string path)
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
cfg.AddJsonFile(EmitConfigSearchMessage(MakeWin32FilePath(jsonFileExt)), true);
cfg.AddIniFile(EmitConfigSearchMessage(MakeWin32FilePath(iniFileExt)), true);
break;
case PlatformID.Unix:
cfg.AddJsonFile(EmitConfigSearchMessage(MakeUnixFilePath(jsonFileExt)), true);
cfg.AddIniFile(EmitConfigSearchMessage(MakeUnixFilePath(iniFileExt)), true);
cfg.AddJsonFile(MakeFilePath(jsonFileExt), true);
cfg.AddIniFile(MakeFilePath(iniFileExt), true);
break;
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/App/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ namespace App

using Microsoft.AspNetCore.SpaServices.Webpack;
using Models.DTO.Services;
using Support.DataProtection;
using Support.Diagnostics;
using Support.Mapping;

Expand Down Expand Up @@ -86,7 +87,9 @@ public void ConfigureServices(IServiceCollection services)
{
// Note the possible dangers for HTTPS: https://docs.microsoft.com/en-us/aspnet/core/performance/response-compression?tabs=aspnetcore2x#compression-with-secure-protocol
opts.EnableForHttps = true;
});
});

services.AddConfiguredDataProtection(this.Configuration);

services.AddMvc(options =>
{
Expand Down
167 changes: 167 additions & 0 deletions src/App/Support/DataProtection/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// ******************************************************************************
// © 2018 Sebastiaan Dammann | damsteen.nl
//
// File: : AppBuilderExtensions.cs
// Project : App
// ******************************************************************************

namespace App.Support.DataProtection {
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

internal static class ServiceCollectionExtensions {
public static void AddConfiguredDataProtection(this IServiceCollection services, IConfiguration configuration)
{
DataProtectionOptions dataProtectionOptions = configuration.GetValue<DataProtectionOptions>("DataProtection");

if (dataProtectionOptions == null)
{
// Automatic - let ASP.NET Core defaults in place
return;
}

IDataProtectionBuilder dataProtectionBuilder;
switch (dataProtectionOptions.StorageOptions?.Type ?? DataProtectionStorageType.InMemory)
{
case DataProtectionStorageType.InMemory:
dataProtectionBuilder = ConfigureForInMemory(services);
break;
case DataProtectionStorageType.File:
dataProtectionBuilder = ConfigureForFile(services, dataProtectionOptions);
break;
default:
throw new ArgumentOutOfRangeException();
}

switch (dataProtectionOptions.Protection)
{
case DataProtectionProtectionType.None:
break;
case DataProtectionProtectionType.Certificate:
ConfigureForCertificate(dataProtectionBuilder, dataProtectionOptions);
break;
case DataProtectionProtectionType.Windows:
ConfigureWindowProtection(dataProtectionBuilder);
break;
default:
throw new ArgumentOutOfRangeException();
}

if (dataProtectionOptions.Lifetime != null)
{
dataProtectionBuilder.SetDefaultKeyLifetime(dataProtectionOptions.Lifetime.Value);
}
}

private static void ConfigureWindowProtection(IDataProtectionBuilder dataProtectionBuilder) => dataProtectionBuilder.ProtectKeysWithDpapi();

private static IDataProtectionBuilder ConfigureForInMemory(IServiceCollection services) => services.AddConfiguredDataProtection().UseEphemeralDataProtectionProvider();

private static IDataProtectionBuilder ConfigureForFile(IServiceCollection services, DataProtectionOptions dataProtectionOptions)
{
IDataProtectionBuilder builder = services.AddConfiguredDataProtection();
DataProtectionStorageOptions storageOptions = dataProtectionOptions.File;

if (storageOptions == null)
{
throw new InvalidOperationException($"{nameof(DataProtectionOptions)}:{nameof(dataProtectionOptions.File)} not set");
}

if (storageOptions.Path == "auto")
{
storageOptions.Path = EnvironmentPath.CreatePath("key-store");
}

DirectoryInfo directory = new DirectoryInfo(storageOptions.Path);
directory.Create();

builder.PersistKeysToFileSystem(
directory
);

return builder;
}

private static void ConfigureForCertificate(IDataProtectionBuilder builder, DataProtectionOptions dataProtectionOptions)
{
CertificateDataProtectionOptions certificateOptions = dataProtectionOptions.Certificate;

if (certificateOptions == null)
{
throw new InvalidOperationException($"{nameof(DataProtectionOptions)}:{nameof(dataProtectionOptions.Certificate)} not set");
}

certificateOptions.Validate();

X509Certificate2 certificate = new X509Certificate2(certificateOptions.CertificatePath, certificateOptions.Password);

builder.ProtectKeysWithCertificate(
certificate
);
}

private static IDataProtectionBuilder AddConfiguredDataProtection(this IServiceCollection services)
{
return services.AddDataProtection(options => options.ApplicationDiscriminator = "financial-app");
}
}

internal sealed class DataProtectionOptions
{
public DataProtectionProtectionType Protection { get; set; } = DataProtectionProtectionType.None;

public DataProtectionStorageOptions File { get;set; }
public CertificateDataProtectionOptions Certificate { get;set; }

public DataProtectionStorageOptions StorageOptions { get; set; }

public TimeSpan? Lifetime { get; set; }
}

internal sealed class DataProtectionStorageOptions
{
public string Path { get; set; }

public DataProtectionStorageType Type { get; set; } = DataProtectionStorageType.InMemory;
}

internal sealed class CertificateDataProtectionOptions
{
/// <summary>
/// Path to PFX certificate
/// </summary>
public string CertificatePath { get; set; }

public string Password { get; set; }

public void Validate()
{
if (String.IsNullOrEmpty(this.CertificatePath) || !File.Exists(this.CertificatePath))
{
throw new InvalidOperationException($"Certificate path '{this.CertificatePath}' does not exist");
}

if (String.IsNullOrEmpty(this.Password))
{
throw new InvalidOperationException("No password has been given for certificate");
}
}
}

internal enum DataProtectionStorageType
{
InMemory,
File
}

internal enum DataProtectionProtectionType
{
None,
Certificate,
Windows
}
}
47 changes: 47 additions & 0 deletions src/App/Support/EnvironmentPath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ******************************************************************************
// © 2018 Sebastiaan Dammann | damsteen.nl
//
// File: : EnvironmentPath.cs
// Project : App
// ******************************************************************************

namespace App.Support {
using System;
using System.IO;

public static class EnvironmentPath {
private const string AppSpecificFolder = "financial-app";

public static string CreatePath(string subPath)
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
return MakeWin32FilePath(subPath);
case PlatformID.Unix:
return MakeUnixFilePath(subPath);

default:
throw new InvalidOperationException($"Unsupported platform family '{Environment.OSVersion.Platform}' of {Environment.OSVersion}");
}
}

private static string MakeWin32FilePath(string subPath)
{
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
AppSpecificFolder,
subPath
);
}

private static string MakeUnixFilePath(string subPath)
{
return Path.Combine(
"/etc",
AppSpecificFolder,
subPath
);
}
}
}

0 comments on commit 8e1312e

Please sign in to comment.