From 760efe303f2e909be5a97150ca16f73428d1c7f6 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 12 Aug 2018 19:09:13 +0000 Subject: [PATCH 1/4] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 28 ++++++++++++++-------------- korebuild-lock.txt | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b2bb6f5bcc..06597fadcb 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,21 +3,21 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 2.2.0-preview1-20180731.1 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-20180807.2 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 2.3.2 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 3.14.2 2.0.9 2.1.2 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index b6efc7cfcb..29a57027f1 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180731.1 -commithash:29fde58465439f4bb9df40830635ed758e063daf +version:2.2.0-preview1-20180807.2 +commithash:11495dbd236104434e08cb1152fcb58cf2a20923 From c119bee8efb1632cfc3ff864ba2be704349efd26 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Tue, 21 Aug 2018 13:33:49 -0700 Subject: [PATCH 2/4] Update package branding for 2.2.0-preview2 --- build/dependencies.props | 2 +- version.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 06597fadcb..9798c889a4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -33,6 +33,6 @@ 2.3.1 2.4.0 - + diff --git a/version.props b/version.props index 44985cedb3..15637ba785 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ 2.2.0 - preview1 + preview2 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000 From 8f7d995508df0c4466b3ebc1bfbe1d215ce43565 Mon Sep 17 00:00:00 2001 From: "dan.s.ward" Date: Wed, 29 Aug 2018 17:04:37 -0400 Subject: [PATCH 3/4] Added Entity Framework Core backed IXmlRepository with tests and sample (#303) --- DataProtection.sln | 33 +++++++++ build/dependencies.props | 2 + .../DataProtectionKeyContext.cs | 21 ++++++ .../EntityFrameworkCore.csproj | 18 +++++ samples/EntityFrameworkCore/Program.cs | 33 +++++++++ .../ConfigureKeyManagementOptions.cs | 22 ++++++ .../DataProtectionKey.cs | 31 +++++++++ ...tyFrameworkCoreDataProtectionExtensions.cs | 46 +++++++++++++ .../EntityFrameworkCoreXmlRepository.cs | 69 +++++++++++++++++++ .../IDataProtectionKeyContext.cs | 18 +++++ .../LoggingExtensions.cs | 31 +++++++++ ....DataProtection.EntityFrameworkCore.csproj | 24 +++++++ .../DataProtectionEntityFrameworkTests.cs | 68 ++++++++++++++++++ .../DataProtectionKeyContext.cs | 14 ++++ ...oreDataProtectionBuilderExtensionsTests.cs | 26 +++++++ ...Protection.EntityFrameworkCore.Test.csproj | 15 ++++ 16 files changed, 471 insertions(+) create mode 100644 samples/EntityFrameworkCore/DataProtectionKeyContext.cs create mode 100644 samples/EntityFrameworkCore/EntityFrameworkCore.csproj create mode 100644 samples/EntityFrameworkCore/Program.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj create mode 100644 test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionKeyContext.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs create mode 100644 test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj diff --git a/DataProtection.sln b/DataProtection.sln index c08ab6a1ce..7b22058b82 100644 --- a/DataProtection.sln +++ b/DataProtection.sln @@ -77,6 +77,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureKeyVault", "samples\Az EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test", "test\Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test\Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test.csproj", "{C85ED942-8121-453F-8308-9DB730843B63}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test", "test\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj", "{06728BF2-C5EB-44C7-9F30-14FAA5649E14}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore", "samples\EntityFrameworkCore\EntityFrameworkCore.csproj", "{E837A2E3-FC93-494C-8689-5AF9C6802AD7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore", "src\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj", "{3E4CA7FE-741B-4C78-A775-220E0E3C1B03}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -265,6 +271,30 @@ Global {C85ED942-8121-453F-8308-9DB730843B63}.Release|Any CPU.Build.0 = Release|Any CPU {C85ED942-8121-453F-8308-9DB730843B63}.Release|x86.ActiveCfg = Release|Any CPU {C85ED942-8121-453F-8308-9DB730843B63}.Release|x86.Build.0 = Release|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|x86.ActiveCfg = Debug|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|x86.Build.0 = Debug|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|Any CPU.Build.0 = Release|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|x86.ActiveCfg = Release|Any CPU + {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|x86.Build.0 = Release|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|x86.Build.0 = Debug|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|Any CPU.Build.0 = Release|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|x86.ActiveCfg = Release|Any CPU + {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|x86.Build.0 = Release|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|x86.ActiveCfg = Debug|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|x86.Build.0 = Debug|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|Any CPU.Build.0 = Release|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|x86.ActiveCfg = Release|Any CPU + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -293,6 +323,9 @@ Global {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA} {295E8539-5450-4764-B3F5-51F968628022} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} {C85ED942-8121-453F-8308-9DB730843B63} = {60336AB3-948D-4D15-A5FB-F32A2B91E814} + {06728BF2-C5EB-44C7-9F30-14FAA5649E14} = {60336AB3-948D-4D15-A5FB-F32A2B91E814} + {E837A2E3-FC93-494C-8689-5AF9C6802AD7} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} + {3E4CA7FE-741B-4C78-A775-220E0E3C1B03} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DD305D75-BD1B-43AE-BF04-869DA6A0858F} diff --git a/build/dependencies.props b/build/dependencies.props index 9798c889a4..06b0858ebe 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -8,6 +8,8 @@ 2.2.0-preview1-34967 2.2.0-preview1-34967 2.3.2 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 2.2.0-preview1-34967 2.2.0-preview1-34967 2.2.0-preview1-34967 diff --git a/samples/EntityFrameworkCore/DataProtectionKeyContext.cs b/samples/EntityFrameworkCore/DataProtectionKeyContext.cs new file mode 100644 index 0000000000..a84031ae50 --- /dev/null +++ b/samples/EntityFrameworkCore/DataProtectionKeyContext.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore +{ + class DataProtectionKeyContext : DbContext, IDataProtectionKeyContext + { + public DataProtectionKeyContext(DbContextOptions options) : base(options) { } + public DbSet DataProtectionKeys { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + optionsBuilder.UseInMemoryDatabase("DataProtection_EntityFrameworkCore"); + optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + optionsBuilder.EnableSensitiveDataLogging(); + } + } +} diff --git a/samples/EntityFrameworkCore/EntityFrameworkCore.csproj b/samples/EntityFrameworkCore/EntityFrameworkCore.csproj new file mode 100644 index 0000000000..212ec9d566 --- /dev/null +++ b/samples/EntityFrameworkCore/EntityFrameworkCore.csproj @@ -0,0 +1,18 @@ + + + + exe + net461;netcoreapp2.1 + + + + + + + + + + + + + diff --git a/samples/EntityFrameworkCore/Program.cs b/samples/EntityFrameworkCore/Program.cs new file mode 100644 index 0000000000..9e8a0d5ee1 --- /dev/null +++ b/samples/EntityFrameworkCore/Program.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; + +namespace EntityFrameworkCore +{ + class Program + { + static void Main(string[] args) + { + // Configure + using (var services = new ServiceCollection() + .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug)) + .AddDbContext() + .AddDataProtection() + .PersistKeysToDbContext() + .SetDefaultKeyLifetime(TimeSpan.FromDays(7)) + .Services + .BuildServiceProvider(validateScopes: true)) + { + // Run a sample payload + var protector = services.GetDataProtector("sample-purpose"); + var protectedData = protector.Protect("Hello world!"); + Console.WriteLine(protectedData); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs new file mode 100644 index 0000000000..246aa3c1e5 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Repositories; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore +{ + internal class ConfigureKeyManagementOptions : IConfigureOptions + { + private readonly IServiceProvider _serviceProvider; + + public ConfigureKeyManagementOptions(IServiceProvider serviceProvider) + => _serviceProvider = serviceProvider; + + public void Configure(KeyManagementOptions options) + => options.XmlRepository = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs new file mode 100644 index 0000000000..b13a9fbd60 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore +{ + /// + /// Code first model used by . + /// + public class DataProtectionKey + { + /// + /// The entity identifier of the . + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + /// + /// The friendly name of the . + /// + public string FriendlyName { get; set; } + + /// + /// The XML representation of the . + /// + public string Xml { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs new file mode 100644 index 0000000000..a3577f0e6d --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.DataProtection.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore +{ + /// + /// Extension method class for configuring instances of + /// + public static class EntityFrameworkCoreDataProtectionExtensions + { + /// + /// Configures the data protection system to persist keys to an EntityFrameworkCore datastore + /// + /// The instance to modify. + /// The value . + public static IDataProtectionBuilder PersistKeysToDbContext(this IDataProtectionBuilder builder) + where TContext : DbContext, IDataProtectionKeyContext + { + var services = builder.Services; + + services.AddScoped>( + provider => new Func( + () => provider.CreateScope().ServiceProvider.GetService())); + + services.AddScoped(provider => + { + var scope = provider.CreateScope(); + return new EntityFrameworkCoreXmlRepository( + contextFactory: scope.ServiceProvider.GetRequiredService>(), + loggerFactory: scope.ServiceProvider.GetService()); + }); + + services.AddTransient, ConfigureKeyManagementOptions>(); + + return builder; + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs new file mode 100644 index 0000000000..6720c400b8 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore +{ + /// + /// An backed by an EntityFrameworkCore datastore. + /// + public class EntityFrameworkCoreXmlRepository : IXmlRepository + where TContext : DbContext, IDataProtectionKeyContext + { + private readonly ILoggerFactory _loggerFactory; + private readonly Func _contextFactory; + + private ILogger> _logger => _loggerFactory?.CreateLogger>(); + + private TContext _context => _contextFactory?.Invoke(); + + /// + /// Creates a new instance of the . + /// + /// The factory method that creates a context to store instances of + /// The . + public EntityFrameworkCoreXmlRepository(Func contextFactory, ILoggerFactory loggerFactory = null) + { + _contextFactory = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory)); + _loggerFactory = loggerFactory; + } + + /// + public virtual IReadOnlyCollection GetAllElements() + => _context?.Set()?.AsNoTracking().Select(key => TryParseKeyXml(key.Xml)).ToList().AsReadOnly(); + + /// + public void StoreElement(XElement element, string friendlyName) + { + var newKey = new DataProtectionKey() + { + FriendlyName = friendlyName, + Xml = element.ToString(SaveOptions.DisableFormatting) + }; + var context = _context; + context?.Set()?.Add(newKey); + _logger?.LogSavingKeyToDbContext(friendlyName, typeof(TContext).Name); + context?.SaveChanges(); + } + + private XElement TryParseKeyXml(string xml) + { + try + { + return XElement.Parse(xml); + } + catch (Exception e) + { + _logger?.LogExceptionWhileParsingKeyXml(xml, e); + return null; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs new file mode 100644 index 0000000000..39998d2a79 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore +{ + /// + /// Interface used to store instances of in a + /// + public interface IDataProtectionKeyContext + { + /// + /// A collection of + /// + DbSet DataProtectionKeys { get; } + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs new file mode 100644 index 0000000000..d0aeb09271 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.Logging +{ + internal static class LoggingExtensions + { + private static readonly Action _anExceptionOccurredWhileParsingKeyXml; + private static readonly Action _savingKeyToDbContext; + + static LoggingExtensions() + { + _anExceptionOccurredWhileParsingKeyXml = LoggerMessage.Define( + eventId: 1, + logLevel: LogLevel.Warning, + formatString: "An exception occurred while parsing the key xml '{Xml}'."); + _savingKeyToDbContext = LoggerMessage.Define( + eventId: 2, + logLevel: LogLevel.Debug, + formatString: "Saving key '{FriendlyName}' to '{DbContext}'."); + } + + public static void LogExceptionWhileParsingKeyXml(this ILogger logger, string keyXml, Exception exception) + => _anExceptionOccurredWhileParsingKeyXml(logger, keyXml, exception); + + public static void LogSavingKeyToDbContext(this ILogger logger, string friendlyName, string contextName) + => _savingKeyToDbContext(logger, friendlyName, contextName, null); + } +} diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj new file mode 100644 index 0000000000..966b1bfc78 --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj @@ -0,0 +1,24 @@ + + + + EntityFramworkCore storage support as key store. + $(ExperimentalVersionPrefix) + $(ExperimentalVersionSuffix) + false + $(ExperimentalPackageVersion) + netstandard2.0 + true + true + aspnetcore;dataprotection;entityframeworkcore + false + + + + + + + + + + + diff --git a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs new file mode 100644 index 0000000000..31034c7f4c --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Xml.Linq; +using Xunit; + +namespace Microsoft.AspNetCore.DataProtection +{ + public class DataProtectionEntityFrameworkTests + { + [Fact] + public void CreateRepository_ThrowsIf_ContextIsNull() + { + Assert.Throws(() => new EntityFrameworkCoreXmlRepository(null)); + } + + [Fact] + public void StoreElement_PersistsData() + { + var element = XElement.Parse(""); + var friendlyName = "Element1"; + var key = new DataProtectionKey() { FriendlyName = friendlyName, Xml = element.ToString() }; + using (var context = BuildDataProtectionKeyContext(nameof(StoreElement_PersistsData))) + { + var service = new EntityFrameworkCoreXmlRepository(() => context); + service.StoreElement(element, friendlyName); + } + // Use a separate instance of the context to verify correct data was saved to database + using (var context = BuildDataProtectionKeyContext(nameof(StoreElement_PersistsData))) + { + Assert.Equal(1, context.DataProtectionKeys.Count()); + Assert.Equal(key.FriendlyName, context.DataProtectionKeys.Single()?.FriendlyName); + Assert.Equal(key.Xml, context.DataProtectionKeys.Single()?.Xml); + } + } + + [Fact] + public void GetAllElements_ReturnsAllElements() + { + var element1 = XElement.Parse(""); + var element2 = XElement.Parse(""); + using (var context = BuildDataProtectionKeyContext(nameof(GetAllElements_ReturnsAllElements))) + { + var service = new EntityFrameworkCoreXmlRepository(() => context); + service.StoreElement(element1, "element1"); + service.StoreElement(element2, "element2"); + } + // Use a separate instance of the context to verify correct data was saved to database + using (var context = BuildDataProtectionKeyContext(nameof(GetAllElements_ReturnsAllElements))) + { + var service = new EntityFrameworkCoreXmlRepository(() => context); + var elements = service.GetAllElements(); + Assert.Equal(2, elements.Count); + } + } + + private DbContextOptions BuildDbContextOptions(string databaseName) + => new DbContextOptionsBuilder().UseInMemoryDatabase(databaseName: databaseName).Options; + + private DataProtectionKeyContext BuildDataProtectionKeyContext(string databaseName) + => new DataProtectionKeyContext(BuildDbContextOptions(databaseName)); + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionKeyContext.cs b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionKeyContext.cs new file mode 100644 index 0000000000..96151de0bb --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionKeyContext.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test +{ + class DataProtectionKeyContext : DbContext, IDataProtectionKeyContext + { + public DataProtectionKeyContext(DbContextOptions options) : base(options) { } + + public DbSet DataProtectionKeys { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs new file mode 100644 index 0000000000..d04ccdde88 --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test +{ + public class EntityFrameworkCoreDataProtectionBuilderExtensionsTests + { + [Fact] + public void PersistKeysToEntityFrameworkCore_UsesEntityFrameworkCoreXmlRepository() + { + var serviceCollection = new ServiceCollection(); + serviceCollection + .AddDbContext() + .AddDataProtection() + .PersistKeysToDbContext(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var keyManagementOptions = serviceProvider.GetRequiredService>(); + Assert.IsType>(keyManagementOptions.Value.XmlRepository); + } + } +} diff --git a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj new file mode 100644 index 0000000000..ed07b79f25 --- /dev/null +++ b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj @@ -0,0 +1,15 @@ + + + + $(StandardTestTfms) + + + + + + + + + + + From 7520ffa0efd04c18630c2612855cb590a94cc69a Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 29 Aug 2018 14:33:57 -0700 Subject: [PATCH 4/4] Fix up service scoping in the EF Core xml repository and update package version to 2.2 --- DataProtection.sln | 22 +- .../DataProtectionKeyContext.cs | 21 -- .../EntityFrameworkCoreSample.csproj} | 0 .../Program.cs | 25 ++- samples/Redis/Program.cs | 2 +- .../ConfigureKeyManagementOptions.cs | 22 -- .../DataProtectionKey.cs | 5 - ...tyFrameworkCoreDataProtectionExtensions.cs | 25 +-- .../EntityFrameworkCoreXmlRepository.cs | 56 +++-- ....DataProtection.EntityFrameworkCore.csproj | 11 +- .../baseline.netcore.json | 203 ++++++++++++++++++ .../DataProtectionEntityFrameworkTests.cs | 51 ++--- ...oreDataProtectionBuilderExtensionsTests.cs | 2 +- 13 files changed, 310 insertions(+), 135 deletions(-) delete mode 100644 samples/EntityFrameworkCore/DataProtectionKeyContext.cs rename samples/{EntityFrameworkCore/EntityFrameworkCore.csproj => EntityFrameworkCoreSample/EntityFrameworkCoreSample.csproj} (100%) rename samples/{EntityFrameworkCore => EntityFrameworkCoreSample}/Program.cs (56%) delete mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs create mode 100644 src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json diff --git a/DataProtection.sln b/DataProtection.sln index 7b22058b82..7fb7eb0592 100644 --- a/DataProtection.sln +++ b/DataProtection.sln @@ -79,10 +79,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataPr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test", "test\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj", "{06728BF2-C5EB-44C7-9F30-14FAA5649E14}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore", "samples\EntityFrameworkCore\EntityFrameworkCore.csproj", "{E837A2E3-FC93-494C-8689-5AF9C6802AD7}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore", "src\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj", "{3E4CA7FE-741B-4C78-A775-220E0E3C1B03}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCoreSample", "samples\EntityFrameworkCoreSample\EntityFrameworkCoreSample.csproj", "{22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -279,14 +279,6 @@ Global {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|Any CPU.Build.0 = Release|Any CPU {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|x86.ActiveCfg = Release|Any CPU {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|x86.Build.0 = Release|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|x86.ActiveCfg = Debug|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Debug|x86.Build.0 = Debug|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|Any CPU.Build.0 = Release|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|x86.ActiveCfg = Release|Any CPU - {E837A2E3-FC93-494C-8689-5AF9C6802AD7}.Release|x86.Build.0 = Release|Any CPU {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -295,6 +287,14 @@ Global {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|Any CPU.Build.0 = Release|Any CPU {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|x86.ActiveCfg = Release|Any CPU {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|x86.Build.0 = Release|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|x86.ActiveCfg = Debug|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|x86.Build.0 = Debug|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|Any CPU.Build.0 = Release|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|x86.ActiveCfg = Release|Any CPU + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -324,8 +324,8 @@ Global {295E8539-5450-4764-B3F5-51F968628022} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} {C85ED942-8121-453F-8308-9DB730843B63} = {60336AB3-948D-4D15-A5FB-F32A2B91E814} {06728BF2-C5EB-44C7-9F30-14FAA5649E14} = {60336AB3-948D-4D15-A5FB-F32A2B91E814} - {E837A2E3-FC93-494C-8689-5AF9C6802AD7} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} {3E4CA7FE-741B-4C78-A775-220E0E3C1B03} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA} + {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DD305D75-BD1B-43AE-BF04-869DA6A0858F} diff --git a/samples/EntityFrameworkCore/DataProtectionKeyContext.cs b/samples/EntityFrameworkCore/DataProtectionKeyContext.cs deleted file mode 100644 index a84031ae50..0000000000 --- a/samples/EntityFrameworkCore/DataProtectionKeyContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace EntityFrameworkCore -{ - class DataProtectionKeyContext : DbContext, IDataProtectionKeyContext - { - public DataProtectionKeyContext(DbContextOptions options) : base(options) { } - public DbSet DataProtectionKeys { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder.UseInMemoryDatabase("DataProtection_EntityFrameworkCore"); - optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - optionsBuilder.EnableSensitiveDataLogging(); - } - } -} diff --git a/samples/EntityFrameworkCore/EntityFrameworkCore.csproj b/samples/EntityFrameworkCoreSample/EntityFrameworkCoreSample.csproj similarity index 100% rename from samples/EntityFrameworkCore/EntityFrameworkCore.csproj rename to samples/EntityFrameworkCoreSample/EntityFrameworkCoreSample.csproj diff --git a/samples/EntityFrameworkCore/Program.cs b/samples/EntityFrameworkCoreSample/Program.cs similarity index 56% rename from samples/EntityFrameworkCore/Program.cs rename to samples/EntityFrameworkCoreSample/Program.cs index 9e8a0d5ee1..d4e978a7b8 100644 --- a/samples/EntityFrameworkCore/Program.cs +++ b/samples/EntityFrameworkCoreSample/Program.cs @@ -1,27 +1,35 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System; -namespace EntityFrameworkCore +namespace EntityFrameworkCoreSample { class Program { static void Main(string[] args) { // Configure - using (var services = new ServiceCollection() + var services = new ServiceCollection() .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug)) - .AddDbContext() + .AddDbContext(o => + { + o.UseInMemoryDatabase("DataProtection_EntityFrameworkCore"); + o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + o.EnableSensitiveDataLogging(); + }) .AddDataProtection() .PersistKeysToDbContext() .SetDefaultKeyLifetime(TimeSpan.FromDays(7)) .Services - .BuildServiceProvider(validateScopes: true)) + .BuildServiceProvider(validateScopes: true); + + using(services) { // Run a sample payload var protector = services.GetDataProtector("sample-purpose"); @@ -30,4 +38,11 @@ namespace EntityFrameworkCore } } } + + class DataProtectionKeyContext : DbContext, IDataProtectionKeyContext + { + public DataProtectionKeyContext(DbContextOptions options) : base(options) { } + + public DbSet DataProtectionKeys { get; set; } + } } diff --git a/samples/Redis/Program.cs b/samples/Redis/Program.cs index f8f213cfad..aa1cdf5164 100644 --- a/samples/Redis/Program.cs +++ b/samples/Redis/Program.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using StackExchange.Redis; -namespace Redis +namespace RedisSample { public class Program { diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs deleted file mode 100644 index 246aa3c1e5..0000000000 --- a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/ConfigureKeyManagementOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.AspNetCore.DataProtection.Repositories; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System; - -namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore -{ - internal class ConfigureKeyManagementOptions : IConfigureOptions - { - private readonly IServiceProvider _serviceProvider; - - public ConfigureKeyManagementOptions(IServiceProvider serviceProvider) - => _serviceProvider = serviceProvider; - - public void Configure(KeyManagementOptions options) - => options.XmlRepository = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService(); - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs index b13a9fbd60..c236d5cb89 100644 --- a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore { /// @@ -14,8 +11,6 @@ namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore /// /// The entity identifier of the . /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } /// diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs index a3577f0e6d..ff24b58eb9 100644 --- a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs @@ -1,15 +1,15 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using System; -namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore +namespace Microsoft.AspNetCore.DataProtection { /// /// Extension method class for configuring instances of @@ -24,22 +24,15 @@ namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore public static IDataProtectionBuilder PersistKeysToDbContext(this IDataProtectionBuilder builder) where TContext : DbContext, IDataProtectionKeyContext { - var services = builder.Services; - - services.AddScoped>( - provider => new Func( - () => provider.CreateScope().ServiceProvider.GetService())); - - services.AddScoped(provider => + builder.Services.AddSingleton>(services => { - var scope = provider.CreateScope(); - return new EntityFrameworkCoreXmlRepository( - contextFactory: scope.ServiceProvider.GetRequiredService>(), - loggerFactory: scope.ServiceProvider.GetService()); + var loggerFactory = services.GetService() ?? NullLoggerFactory.Instance; + return new ConfigureOptions(options => + { + options.XmlRepository = new EntityFrameworkCoreXmlRepository(services, loggerFactory); + }); }); - services.AddTransient, ConfigureKeyManagementOptions>(); - return builder; } } diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs index 6720c400b8..62250cf3ef 100644 --- a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.DataProtection.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore { @@ -17,40 +18,51 @@ namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore public class EntityFrameworkCoreXmlRepository : IXmlRepository where TContext : DbContext, IDataProtectionKeyContext { - private readonly ILoggerFactory _loggerFactory; - private readonly Func _contextFactory; - - private ILogger> _logger => _loggerFactory?.CreateLogger>(); - - private TContext _context => _contextFactory?.Invoke(); + private readonly IServiceProvider _services; + private readonly ILogger _logger; /// /// Creates a new instance of the . /// - /// The factory method that creates a context to store instances of + /// /// The . - public EntityFrameworkCoreXmlRepository(Func contextFactory, ILoggerFactory loggerFactory = null) + public EntityFrameworkCoreXmlRepository(IServiceProvider services, ILoggerFactory loggerFactory) { - _contextFactory = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory)); - _loggerFactory = loggerFactory; + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger>(); + _services = services ?? throw new ArgumentNullException(nameof(services)); } /// public virtual IReadOnlyCollection GetAllElements() - => _context?.Set()?.AsNoTracking().Select(key => TryParseKeyXml(key.Xml)).ToList().AsReadOnly(); + { + using (var scope = _services.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + return context.DataProtectionKeys.AsNoTracking().Select(key => TryParseKeyXml(key.Xml)).ToList().AsReadOnly(); + } + } /// public void StoreElement(XElement element, string friendlyName) { - var newKey = new DataProtectionKey() + using (var scope = _services.CreateScope()) { - FriendlyName = friendlyName, - Xml = element.ToString(SaveOptions.DisableFormatting) - }; - var context = _context; - context?.Set()?.Add(newKey); - _logger?.LogSavingKeyToDbContext(friendlyName, typeof(TContext).Name); - context?.SaveChanges(); + var context = scope.ServiceProvider.GetRequiredService(); + var newKey = new DataProtectionKey() + { + FriendlyName = friendlyName, + Xml = element.ToString(SaveOptions.DisableFormatting) + }; + + context.DataProtectionKeys.Add(newKey); + _logger.LogSavingKeyToDbContext(friendlyName, typeof(TContext).Name); + context.SaveChanges(); + } } private XElement TryParseKeyXml(string xml) diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj index 966b1bfc78..e1715d94f2 100644 --- a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj @@ -1,16 +1,11 @@  - EntityFramworkCore storage support as key store. - $(ExperimentalVersionPrefix) - $(ExperimentalVersionSuffix) - false - $(ExperimentalPackageVersion) + Support for storing keys using Entity Framework Core. netstandard2.0 true true aspnetcore;dataprotection;entityframeworkcore - false @@ -21,4 +16,8 @@ + + + + diff --git a/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json new file mode 100644 index 0000000000..9a9a7ebc1c --- /dev/null +++ b/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json @@ -0,0 +1,203 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCoreDataProtectionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "PersistKeysToDbContext", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder" + } + ], + "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TContext", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.EntityFrameworkCore.DbContext", + "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.IDataProtectionKeyContext" + ] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Id", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Id", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FriendlyName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FriendlyName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Xml", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Xml", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.EntityFrameworkCoreXmlRepository", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetAllElements", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyCollection", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StoreElement", + "Parameters": [ + { + "Name": "element", + "Type": "System.Xml.Linq.XElement" + }, + { + "Name": "friendlyName", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "services", + "Type": "System.IServiceProvider" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TContext", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.EntityFrameworkCore.DbContext", + "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.IDataProtectionKeyContext" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.IDataProtectionKeyContext", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_DataProtectionKeys", + "Parameters": [], + "ReturnType": "Microsoft.EntityFrameworkCore.DbSet", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs index 31034c7f4c..c298d8e64f 100644 --- a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/DataProtectionEntityFrameworkTests.cs @@ -1,12 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; -using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test; -using Microsoft.EntityFrameworkCore; using System; using System.Linq; using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.DataProtection @@ -16,7 +18,7 @@ namespace Microsoft.AspNetCore.DataProtection [Fact] public void CreateRepository_ThrowsIf_ContextIsNull() { - Assert.Throws(() => new EntityFrameworkCoreXmlRepository(null)); + Assert.Throws(() => new EntityFrameworkCoreXmlRepository(null, null)); } [Fact] @@ -25,13 +27,13 @@ namespace Microsoft.AspNetCore.DataProtection var element = XElement.Parse(""); var friendlyName = "Element1"; var key = new DataProtectionKey() { FriendlyName = friendlyName, Xml = element.ToString() }; - using (var context = BuildDataProtectionKeyContext(nameof(StoreElement_PersistsData))) - { - var service = new EntityFrameworkCoreXmlRepository(() => context); + + var services = GetServices(nameof(StoreElement_PersistsData)); + var service = new EntityFrameworkCoreXmlRepository(services, NullLoggerFactory.Instance); service.StoreElement(element, friendlyName); - } + // Use a separate instance of the context to verify correct data was saved to database - using (var context = BuildDataProtectionKeyContext(nameof(StoreElement_PersistsData))) + using (var context = services.CreateScope().ServiceProvider.GetRequiredService< DataProtectionKeyContext>()) { Assert.Equal(1, context.DataProtectionKeys.Count()); Assert.Equal(key.FriendlyName, context.DataProtectionKeys.Single()?.FriendlyName); @@ -44,25 +46,24 @@ namespace Microsoft.AspNetCore.DataProtection { var element1 = XElement.Parse(""); var element2 = XElement.Parse(""); - using (var context = BuildDataProtectionKeyContext(nameof(GetAllElements_ReturnsAllElements))) - { - var service = new EntityFrameworkCoreXmlRepository(() => context); - service.StoreElement(element1, "element1"); - service.StoreElement(element2, "element2"); - } + + var services = GetServices(nameof(GetAllElements_ReturnsAllElements)); + var service1 = CreateRepo(services); + service1.StoreElement(element1, "element1"); + service1.StoreElement(element2, "element2"); + // Use a separate instance of the context to verify correct data was saved to database - using (var context = BuildDataProtectionKeyContext(nameof(GetAllElements_ReturnsAllElements))) - { - var service = new EntityFrameworkCoreXmlRepository(() => context); - var elements = service.GetAllElements(); - Assert.Equal(2, elements.Count); - } + var service2 = CreateRepo(services); + var elements = service2.GetAllElements(); + Assert.Equal(2, elements.Count); } - private DbContextOptions BuildDbContextOptions(string databaseName) - => new DbContextOptionsBuilder().UseInMemoryDatabase(databaseName: databaseName).Options; + private EntityFrameworkCoreXmlRepository CreateRepo(IServiceProvider services) + => new EntityFrameworkCoreXmlRepository(services, NullLoggerFactory.Instance); - private DataProtectionKeyContext BuildDataProtectionKeyContext(string databaseName) - => new DataProtectionKeyContext(BuildDbContextOptions(databaseName)); + private IServiceProvider GetServices(string dbName) + => new ServiceCollection() + .AddDbContext(o => o.UseInMemoryDatabase(dbName)) + .BuildServiceProvider(validateScopes: true); } } diff --git a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs index d04ccdde88..55b67d98e3 100644 --- a/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test/EntityFrameworkCoreDataProtectionBuilderExtensionsTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test .AddDbContext() .AddDataProtection() .PersistKeysToDbContext(); - var serviceProvider = serviceCollection.BuildServiceProvider(); + var serviceProvider = serviceCollection.BuildServiceProvider(validateScopes: true); var keyManagementOptions = serviceProvider.GetRequiredService>(); Assert.IsType>(keyManagementOptions.Value.XmlRepository); }