Make ILoggerFactory an optional service on any DI-injected services
This commit is contained in:
parent
abf05e2856
commit
5d1a523682
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<OutputType>exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.DataProtection;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WindowsAzure.Storage;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace AzureBlob
|
||||
{
|
||||
|
|
@ -24,21 +25,19 @@ namespace AzureBlob
|
|||
container.CreateIfNotExistsAsync().GetAwaiter().GetResult();
|
||||
|
||||
// Configure
|
||||
using (var services = new ServiceCollection()
|
||||
.AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
|
||||
.AddDataProtection()
|
||||
.PersistKeysToAzureBlobStorage(container, "keys.xml")
|
||||
.Services
|
||||
.BuildServiceProvider())
|
||||
{
|
||||
// Run a sample payload
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddDataProtection()
|
||||
.PersistKeysToAzureBlobStorage(container, "keys.xml");
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var loggerFactory = services.GetService<LoggerFactory>();
|
||||
loggerFactory.AddConsole();
|
||||
|
||||
// Run a sample payload
|
||||
|
||||
var protector = services.GetDataProtector("sample-purpose");
|
||||
var protectedData = protector.Protect("Hello world!");
|
||||
Console.WriteLine(protectedData);
|
||||
var protector = services.GetDataProtector("sample-purpose");
|
||||
var protectedData = protector.Protect("Hello world!");
|
||||
Console.WriteLine(protectedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:2041/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"AzureBlob": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
|
||||
<OutputType>exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -14,25 +14,23 @@ namespace CustomEncryptorSample
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys");
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddDataProtection()
|
||||
using (var services = new ServiceCollection()
|
||||
.AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
|
||||
.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
|
||||
.UseXmlEncryptor(s => new CustomXmlEncryptor(s));
|
||||
.UseXmlEncryptor(s => new CustomXmlEncryptor(s))
|
||||
.Services.BuildServiceProvider())
|
||||
{
|
||||
var protector = services.GetDataProtector("SamplePurpose");
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var loggerFactory = services.GetRequiredService<LoggerFactory>();
|
||||
loggerFactory.AddConsole();
|
||||
// protect the payload
|
||||
var protectedPayload = protector.Protect("Hello World!");
|
||||
Console.WriteLine($"Protect returned: {protectedPayload}");
|
||||
|
||||
var protector = services.GetDataProtector("SamplePurpose");
|
||||
|
||||
// protect the payload
|
||||
var protectedPayload = protector.Protect("Hello World!");
|
||||
Console.WriteLine($"Protect returned: {protectedPayload}");
|
||||
|
||||
// unprotect the payload
|
||||
var unprotectedPayload = protector.Unprotect(protectedPayload);
|
||||
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
|
||||
// unprotect the payload
|
||||
var unprotectedPayload = protector.Unprotect(protectedPayload);
|
||||
Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:1398/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"CustomEncryptorSample": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
|
||||
<OutputType>exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ namespace KeyManagementSample
|
|||
{
|
||||
var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys");
|
||||
var serviceCollection = new ServiceCollection();
|
||||
var builder = serviceCollection.AddDataProtection()
|
||||
var builder = serviceCollection
|
||||
.AddDataProtection()
|
||||
// point at a specific folder and use DPAPI to encrypt keys
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(keysFolder));
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
|
|
@ -24,40 +25,41 @@ namespace KeyManagementSample
|
|||
builder.ProtectKeysWithDpapi();
|
||||
}
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
// perform a protect operation to force the system to put at least
|
||||
// one key in the key ring
|
||||
services.GetDataProtector("Sample.KeyManager.v1").Protect("payload");
|
||||
Console.WriteLine("Performed a protect operation.");
|
||||
|
||||
// get a reference to the key manager
|
||||
var keyManager = services.GetService<IKeyManager>();
|
||||
|
||||
// list all keys in the key ring
|
||||
var allKeys = keyManager.GetAllKeys();
|
||||
Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
|
||||
foreach (var key in allKeys)
|
||||
using (var services = serviceCollection.BuildServiceProvider())
|
||||
{
|
||||
Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
|
||||
}
|
||||
// perform a protect operation to force the system to put at least
|
||||
// one key in the key ring
|
||||
services.GetDataProtector("Sample.KeyManager.v1").Protect("payload");
|
||||
Console.WriteLine("Performed a protect operation.");
|
||||
|
||||
// revoke all keys in the key ring
|
||||
keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here.");
|
||||
Console.WriteLine("Revoked all existing keys.");
|
||||
// get a reference to the key manager
|
||||
var keyManager = services.GetService<IKeyManager>();
|
||||
|
||||
// add a new key to the key ring with immediate activation and a 1-month expiration
|
||||
keyManager.CreateNewKey(
|
||||
activationDate: DateTimeOffset.Now,
|
||||
expirationDate: DateTimeOffset.Now.AddMonths(1));
|
||||
Console.WriteLine("Added a new key.");
|
||||
// list all keys in the key ring
|
||||
var allKeys = keyManager.GetAllKeys();
|
||||
Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
|
||||
foreach (var key in allKeys)
|
||||
{
|
||||
Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
|
||||
}
|
||||
|
||||
// list all keys in the key ring
|
||||
allKeys = keyManager.GetAllKeys();
|
||||
Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
|
||||
foreach (var key in allKeys)
|
||||
{
|
||||
Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
|
||||
// revoke all keys in the key ring
|
||||
keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here.");
|
||||
Console.WriteLine("Revoked all existing keys.");
|
||||
|
||||
// add a new key to the key ring with immediate activation and a 1-month expiration
|
||||
keyManager.CreateNewKey(
|
||||
activationDate: DateTimeOffset.Now,
|
||||
expirationDate: DateTimeOffset.Now.AddMonths(1));
|
||||
Console.WriteLine("Added a new key.");
|
||||
|
||||
// list all keys in the key ring
|
||||
allKeys = keyManager.GetAllKeys();
|
||||
Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
|
||||
foreach (var key in allKeys)
|
||||
{
|
||||
Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:1396/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"KeyManagementSample": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,19 +17,18 @@ namespace Redis
|
|||
var redis = ConnectionMultiplexer.Connect("localhost:6379");
|
||||
|
||||
// Configure
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddDataProtection()
|
||||
.PersistKeysToRedis(redis, "DataProtection-Keys");
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var loggerFactory = services.GetService<LoggerFactory>();
|
||||
loggerFactory.AddConsole();
|
||||
|
||||
// Run a sample payload
|
||||
var protector = services.GetDataProtector("sample-purpose");
|
||||
var protectedData = protector.Protect("Hello world!");
|
||||
Console.WriteLine(protectedData);
|
||||
using (var services = new ServiceCollection()
|
||||
.AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
|
||||
.AddDataProtection()
|
||||
.PersistKeysToRedis(redis, "DataProtection-Keys")
|
||||
.Services
|
||||
.BuildServiceProvider())
|
||||
{
|
||||
// Run a sample payload
|
||||
var protector = services.GetDataProtector("sample-purpose");
|
||||
var protectedData = protector.Protect("Hello world!");
|
||||
Console.WriteLine(protectedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:2042/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Redis": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
|
||||
<OutputType>exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Cryptography;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel
|
||||
{
|
||||
|
|
@ -42,7 +43,7 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat
|
|||
|
||||
void IInternalAlgorithmConfiguration.Validate()
|
||||
{
|
||||
var factory = new AuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory());
|
||||
var factory = new AuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
|
||||
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
|
||||
var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this);
|
||||
try
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Cryptography;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel
|
||||
{
|
||||
|
|
@ -88,7 +89,7 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat
|
|||
/// </summary>
|
||||
void IInternalAlgorithmConfiguration.Validate()
|
||||
{
|
||||
var factory = new CngCbcAuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory());
|
||||
var factory = new CngCbcAuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
|
||||
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
|
||||
using (var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Cryptography;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel
|
||||
{
|
||||
|
|
@ -64,7 +65,7 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat
|
|||
/// </summary>
|
||||
void IInternalAlgorithmConfiguration.Validate()
|
||||
{
|
||||
var factory = new CngGcmAuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory());
|
||||
var factory = new CngGcmAuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
|
||||
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
|
||||
using (var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel
|
||||
{
|
||||
|
|
@ -66,7 +67,7 @@ namespace Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.Configurat
|
|||
/// </summary>
|
||||
void IInternalAlgorithmConfiguration.Validate()
|
||||
{
|
||||
var factory = new ManagedAuthenticatedEncryptorFactory(DataProtectionProviderFactory.GetDefaultLoggerFactory());
|
||||
var factory = new ManagedAuthenticatedEncryptorFactory(NullLoggerFactory.Instance);
|
||||
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
|
||||
using (var encryptor = factory.CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8), this))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.DataProtection.XmlEncryption;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Win32;
|
||||
|
||||
|
|
@ -206,7 +207,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = services.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
return new ConfigureOptions<KeyManagementOptions>(options =>
|
||||
{
|
||||
options.XmlRepository = new FileSystemXmlRepository(directory, loggerFactory);
|
||||
|
|
@ -236,7 +237,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = services.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
return new ConfigureOptions<KeyManagementOptions>(options =>
|
||||
{
|
||||
options.XmlRepository = new RegistryXmlRepository(registryKey, loggerFactory);
|
||||
|
|
@ -266,7 +267,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = services.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
return new ConfigureOptions<KeyManagementOptions>(options =>
|
||||
{
|
||||
options.XmlEncryptor = new CertificateXmlEncryptor(certificate, loggerFactory);
|
||||
|
|
@ -306,7 +307,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = services.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
var certificateResolver = services.GetRequiredService<ICertificateResolver>();
|
||||
return new ConfigureOptions<KeyManagementOptions>(options =>
|
||||
{
|
||||
|
|
@ -357,7 +358,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = services.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
return new ConfigureOptions<KeyManagementOptions>(options =>
|
||||
{
|
||||
CryptoUtil.AssertPlatformIsWindows();
|
||||
|
|
@ -419,7 +420,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(services =>
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = services.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
return new ConfigureOptions<KeyManagementOptions>(options =>
|
||||
{
|
||||
CryptoUtil.AssertPlatformIsWindows8OrLater();
|
||||
|
|
|
|||
|
|
@ -1,16 +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.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
internal static class DataProtectionProviderFactory
|
||||
{
|
||||
public static ILoggerFactory GetDefaultLoggerFactory()
|
||||
{
|
||||
return NullLoggerFactory.Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.DataProtection.XmlEncryption;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
|
|
@ -64,8 +65,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
private static void AddDataProtectionServices(IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<ILoggerFactory>(DataProtectionProviderFactory.GetDefaultLoggerFactory());
|
||||
|
||||
if (OSVersionUtil.IsWindows())
|
||||
{
|
||||
services.TryAddSingleton<RegistryPolicyResolver>();
|
||||
|
|
@ -88,10 +87,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
{
|
||||
var dpOptions = s.GetRequiredService<IOptions<DataProtectionOptions>>();
|
||||
var keyRingProvider = s.GetRequiredService<IKeyRingProvider>();
|
||||
var loggerFactory = s.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = s.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
|
||||
IDataProtectionProvider dataProtectionProvider = null;
|
||||
dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyRingProvider, loggerFactory);
|
||||
IDataProtectionProvider dataProtectionProvider = new KeyRingBasedDataProtectionProvider(keyRingProvider, loggerFactory);
|
||||
|
||||
// Link the provider to the supplied discriminator
|
||||
if (!string.IsNullOrEmpty(dpOptions.Value.ApplicationDiscriminator))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationM
|
|||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
|
|
@ -24,11 +25,23 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
private readonly KeyRingBasedDataProtectionProvider _dataProtectionProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ephemeral <see cref="IDataProtectionProvider"/>, optionally providing
|
||||
/// services (such as logging) for consumption by the provider.
|
||||
/// Creates an ephemeral <see cref="IDataProtectionProvider"/>.
|
||||
/// </summary>
|
||||
public EphemeralDataProtectionProvider()
|
||||
: this (NullLoggerFactory.Instance)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ephemeral <see cref="IDataProtectionProvider"/> with logging.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory" />.</param>
|
||||
public EphemeralDataProtectionProvider(ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
IKeyRingProvider keyringProvider;
|
||||
if (OSVersionUtil.IsWindows())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Builder;
|
|||
using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.Internal
|
||||
{
|
||||
|
|
@ -14,6 +15,10 @@ namespace Microsoft.AspNetCore.DataProtection.Internal
|
|||
private readonly IKeyRingProvider _keyRingProvider;
|
||||
private readonly ILogger<DataProtectionStartupFilter> _logger;
|
||||
|
||||
public DataProtectionStartupFilter(IKeyRingProvider keyRingProvider)
|
||||
: this(keyRingProvider, NullLoggerFactory.Instance)
|
||||
{ }
|
||||
|
||||
public DataProtectionStartupFilter(IKeyRingProvider keyRingProvider, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_keyRingProvider = keyRingProvider;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
|
|||
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.Internal
|
||||
|
|
@ -15,7 +16,18 @@ namespace Microsoft.AspNetCore.DataProtection.Internal
|
|||
private readonly RegistryPolicyResolver _registryPolicyResolver;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public KeyManagementOptionsSetup(ILoggerFactory loggerFactory) : this(loggerFactory, registryPolicyResolver: null)
|
||||
public KeyManagementOptionsSetup()
|
||||
: this(NullLoggerFactory.Instance, registryPolicyResolver: null)
|
||||
{
|
||||
}
|
||||
|
||||
public KeyManagementOptionsSetup(ILoggerFactory loggerFactory)
|
||||
: this(loggerFactory, registryPolicyResolver: null)
|
||||
{
|
||||
}
|
||||
|
||||
public KeyManagementOptionsSetup(RegistryPolicyResolver registryPolicyResolver)
|
||||
: this(NullLoggerFactory.Instance, registryPolicyResolver)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Cryptography;
|
|||
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
||||
|
|
@ -40,6 +41,10 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
/// </remarks>
|
||||
private readonly TimeSpan _maxServerToServerClockSkew;
|
||||
|
||||
public DefaultKeyResolver(IOptions<KeyManagementOptions> keyManagementOptions)
|
||||
: this(keyManagementOptions, NullLoggerFactory.Instance)
|
||||
{ }
|
||||
|
||||
public DefaultKeyResolver(IOptions<KeyManagementOptions> keyManagementOptions, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_keyPropagationWindow = keyManagementOptions.Value.KeyPropagationWindow;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||
using Microsoft.AspNetCore.Cryptography;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
||||
|
|
@ -16,7 +17,6 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
{
|
||||
private CacheableKeyRing _cacheableKeyRing;
|
||||
private readonly object _cacheableKeyRingLockObj = new object();
|
||||
private readonly ICacheableKeyRingProvider _cacheableKeyRingProvider;
|
||||
private readonly IDefaultKeyResolver _defaultKeyResolver;
|
||||
private readonly KeyManagementOptions _keyManagementOptions;
|
||||
private readonly IKeyManager _keyManager;
|
||||
|
|
@ -25,31 +25,31 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
public KeyRingProvider(
|
||||
IKeyManager keyManager,
|
||||
IOptions<KeyManagementOptions> keyManagementOptions,
|
||||
IDefaultKeyResolver defaultKeyResolver,
|
||||
ILoggerFactory loggerFactory)
|
||||
IDefaultKeyResolver defaultKeyResolver)
|
||||
: this(
|
||||
keyManager,
|
||||
keyManagementOptions,
|
||||
cacheableKeyRingProvider: null,
|
||||
defaultKeyResolver: defaultKeyResolver,
|
||||
loggerFactory: loggerFactory)
|
||||
defaultKeyResolver,
|
||||
NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public KeyRingProvider(
|
||||
IKeyManager keyManager,
|
||||
IOptions<KeyManagementOptions> keyManagementOptions,
|
||||
ICacheableKeyRingProvider cacheableKeyRingProvider,
|
||||
IDefaultKeyResolver defaultKeyResolver,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_keyManagementOptions = new KeyManagementOptions(keyManagementOptions.Value); // clone so new instance is immutable
|
||||
_keyManager = keyManager;
|
||||
_cacheableKeyRingProvider = cacheableKeyRingProvider ?? this;
|
||||
CacheableKeyRingProvider = this;
|
||||
_defaultKeyResolver = defaultKeyResolver;
|
||||
_logger = loggerFactory.CreateLogger<KeyRingProvider>();
|
||||
}
|
||||
|
||||
// for testing
|
||||
internal ICacheableKeyRingProvider CacheableKeyRingProvider { get; set; }
|
||||
|
||||
private CacheableKeyRing CreateCacheableKeyRingCore(DateTimeOffset now, IKey keyJustAdded)
|
||||
{
|
||||
// Refresh the list of all keys
|
||||
|
|
@ -183,7 +183,7 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
|
||||
try
|
||||
{
|
||||
newCacheableKeyRing = _cacheableKeyRingProvider.GetCacheableKeyRing(utcNow);
|
||||
newCacheableKeyRing = CacheableKeyRingProvider.GetCacheableKeyRing(utcNow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.DataProtection.KeyManagement.Internal;
|
|||
using Microsoft.AspNetCore.DataProtection.Repositories;
|
||||
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Win32;
|
||||
|
||||
|
|
@ -55,6 +56,15 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
|
||||
private CancellationTokenSource _cacheExpirationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="XmlKeyManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="keyManagementOptions">The <see cref="IOptions{KeyManagementOptions}"/> instance that provides the configuration.</param>
|
||||
/// <param name="activator">The <see cref="IActivator"/>.</param>
|
||||
public XmlKeyManager(IOptions<KeyManagementOptions> keyManagementOptions, IActivator activator)
|
||||
: this (keyManagementOptions, activator, NullLoggerFactory.Instance)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="XmlKeyManager"/>.
|
||||
/// </summary>
|
||||
|
|
@ -63,7 +73,7 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public XmlKeyManager(IOptions<KeyManagementOptions> keyManagementOptions, IActivator activator, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
_logger = _loggerFactory.CreateLogger<XmlKeyManager>();
|
||||
|
||||
KeyRepository = keyManagementOptions.Value.XmlRepository;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Cryptography;
|
|||
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
|
||||
using Microsoft.AspNetCore.DataProtection.Internal;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
|
|
@ -22,20 +21,17 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
{
|
||||
private readonly Func<RegistryKey> _getPolicyRegKey;
|
||||
private readonly IActivator _activator;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public RegistryPolicyResolver(IActivator activator, ILoggerFactory loggerFactory)
|
||||
public RegistryPolicyResolver(IActivator activator)
|
||||
{
|
||||
_getPolicyRegKey = () => Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\DotNetPackages\Microsoft.AspNetCore.DataProtection");
|
||||
_activator = activator;
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
internal RegistryPolicyResolver(RegistryKey policyRegKey, IActivator activator, ILoggerFactory loggerFactory)
|
||||
internal RegistryPolicyResolver(RegistryKey policyRegKey, IActivator activator)
|
||||
{
|
||||
_getPolicyRegKey = () => policyRegKey;
|
||||
_activator = activator;
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
// populates an options object from values stored in the registry
|
||||
|
|
@ -95,10 +91,8 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
/// <summary>
|
||||
/// Returns a <see cref="RegistryPolicy"/> from the default registry location.
|
||||
/// </summary>
|
||||
public static RegistryPolicy ResolveDefaultPolicy(IActivator activator, ILoggerFactory loggerFactory)
|
||||
{
|
||||
return new RegistryPolicyResolver(activator, loggerFactory).ResolvePolicy();
|
||||
}
|
||||
public static RegistryPolicy ResolveDefaultPolicy(IActivator activator)
|
||||
=> new RegistryPolicyResolver(activator).ResolvePolicy();
|
||||
|
||||
internal RegistryPolicy ResolvePolicy()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
private static readonly Regex _versionPattern = new Regex(@",\s?Version=[0-9]+(\.[0-9]+){0,3}", RegexOptions.Compiled, TimeSpan.FromSeconds(2));
|
||||
|
||||
public TypeForwardingActivator(IServiceProvider services)
|
||||
: this(services, DataProtectionProviderFactory.GetDefaultLoggerFactory())
|
||||
: this(services, NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,11 +79,6 @@
|
|||
"MemberId": "public .ctor(Microsoft.Win32.RegistryKey registryKey, System.IServiceProvider services)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public sealed class Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider : Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
|
||||
"MemberId": "public .ctor()",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public sealed class Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider : Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
|
||||
"MemberId": "public .ctor(System.IServiceProvider services)",
|
||||
|
|
@ -244,4 +239,4 @@
|
|||
"MemberId": "Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.IAuthenticatedEncryptor CreateEncryptor()",
|
||||
"Kind": "Addition"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
public void DifferentProvider_SamePurpose_DoesNotRoundTripData()
|
||||
{
|
||||
// Arrange
|
||||
var dataProtector1 = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("purpose");
|
||||
var dataProtector1 = new EphemeralDataProtectionProvider().CreateProtector("purpose");
|
||||
var dataProtector2 = new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("purpose");
|
||||
byte[] bytes = Encoding.UTF8.GetBytes("Hello there!");
|
||||
|
||||
|
|
|
|||
|
|
@ -89,8 +89,7 @@ namespace Microsoft.AspNetCore.DataProtection.Internal
|
|||
|
||||
var policyResolver = new RegistryPolicyResolver(
|
||||
registryKey,
|
||||
activator: SimpleActivator.DefaultWithoutServices,
|
||||
loggerFactory: NullLoggerFactory.Instance);
|
||||
activator: SimpleActivator.DefaultWithoutServices);
|
||||
|
||||
var setup = new KeyManagementOptionsSetup(NullLoggerFactory.Instance, policyResolver);
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
ShouldGenerateNewKey = false
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Act
|
||||
var cacheableKeyRing = keyRingProvider.GetCacheableKeyRing(now);
|
||||
|
||||
|
|
@ -597,9 +597,11 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
return new KeyRingProvider(
|
||||
keyManager: null,
|
||||
keyManagementOptions: Options.Create(options),
|
||||
cacheableKeyRingProvider: cacheableKeyRingProvider,
|
||||
defaultKeyResolver: null,
|
||||
loggerFactory: NullLoggerFactory.Instance);
|
||||
loggerFactory: NullLoggerFactory.Instance)
|
||||
{
|
||||
CacheableKeyRingProvider = cacheableKeyRingProvider
|
||||
};
|
||||
}
|
||||
|
||||
private static ICacheableKeyRingProvider CreateKeyRingProvider(IKeyManager keyManager, IDefaultKeyResolver defaultKeyResolver, KeyManagementOptions keyManagementOptions= null)
|
||||
|
|
@ -612,7 +614,6 @@ namespace Microsoft.AspNetCore.DataProtection.KeyManagement
|
|||
return new KeyRingProvider(
|
||||
keyManager: keyManager,
|
||||
keyManagementOptions: Options.Create(keyManagementOptions),
|
||||
cacheableKeyRingProvider: null,
|
||||
defaultKeyResolver: defaultKeyResolver,
|
||||
loggerFactory: NullLoggerFactory.Instance);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,8 +251,7 @@ namespace Microsoft.AspNetCore.DataProtection
|
|||
|
||||
var policyResolver = new RegistryPolicyResolver(
|
||||
registryKey,
|
||||
activator: SimpleActivator.DefaultWithoutServices,
|
||||
loggerFactory: NullLoggerFactory.Instance);
|
||||
activator: SimpleActivator.DefaultWithoutServices);
|
||||
|
||||
return policyResolver.ResolvePolicy();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
// 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.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
public class ServiceCollectionTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddsOptions()
|
||||
{
|
||||
var services = new ServiceCollection()
|
||||
.AddDataProtection()
|
||||
.Services
|
||||
.BuildServiceProvider();
|
||||
|
||||
Assert.NotNull(services.GetService<IOptions<DataProtectionOptions>>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotOverrideLogging()
|
||||
{
|
||||
var services1 = new ServiceCollection()
|
||||
.AddLogging()
|
||||
.AddDataProtection()
|
||||
.Services
|
||||
.BuildServiceProvider();
|
||||
|
||||
var services2 = new ServiceCollection()
|
||||
.AddDataProtection()
|
||||
.Services
|
||||
.AddLogging()
|
||||
.BuildServiceProvider();
|
||||
|
||||
Assert.Equal(
|
||||
services1.GetRequiredService<ILoggerFactory>().GetType(),
|
||||
services2.GetRequiredService<ILoggerFactory>().GetType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanResolveAllRegisteredServices()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection()
|
||||
.AddDataProtection()
|
||||
.Services;
|
||||
var services = serviceCollection.BuildServiceProvider(validateScopes: true);
|
||||
|
||||
Assert.Null(services.GetService<ILoggerFactory>());
|
||||
|
||||
foreach (var descriptor in serviceCollection)
|
||||
{
|
||||
if (descriptor.ServiceType.Assembly.GetName().Name == "Microsoft.Extensions.Options")
|
||||
{
|
||||
// ignore any descriptors added by the call to .AddOptions()
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.NotNull(services.GetService(descriptor.ServiceType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue