improvements
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
125
certmgr/Core/Converters/Impl/CertStoreStorageContextConverter.cs
Normal file
125
certmgr/Core/Converters/Impl/CertStoreStorageContextConverter.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
using CertMgr.Core.Log;
|
||||||
|
using CertMgr.Core.Storage;
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Converters.Impl;
|
||||||
|
|
||||||
|
internal sealed class CertStoreStorageContextConverter : StorageContextConverter<CertStoreStorageContext>
|
||||||
|
{
|
||||||
|
protected override Task<CertStoreStorageContext?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> storageDefinition = rawValue.AsSpan();
|
||||||
|
|
||||||
|
TryGetCertStore(storageDefinition, out CertStoreStorageContext? context);
|
||||||
|
|
||||||
|
return Task.FromResult(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetCertStore(ReadOnlySpan<char> storageDefinition, [NotNullWhen(true)] out CertStoreStorageContext? context)
|
||||||
|
{
|
||||||
|
// expecting 'storageDefinition' is something like:
|
||||||
|
// <store-location>|<store-name>|<flags>
|
||||||
|
// where <store-location> can be:
|
||||||
|
// - 'user'
|
||||||
|
// - 'machine'
|
||||||
|
// where '<store-name> can be:
|
||||||
|
// - 'my'
|
||||||
|
// - 'root'
|
||||||
|
// where <flags> can be:
|
||||||
|
// - 'default'
|
||||||
|
// - 'exportable'
|
||||||
|
|
||||||
|
int firstSplitIndex = storageDefinition.IndexOf(Separator);
|
||||||
|
if (firstSplitIndex != -1)
|
||||||
|
{
|
||||||
|
// there is a splitter. On the left side of it there must be <store-location>. On the right there is a <store-name>
|
||||||
|
|
||||||
|
ReadOnlySpan<char> locationSpan = storageDefinition.Slice(0, firstSplitIndex);
|
||||||
|
int secondSplitIndex = storageDefinition.Slice(firstSplitIndex + 1).IndexOf(Separator) + locationSpan.Length + 1;
|
||||||
|
ReadOnlySpan<char> nameSpan = storageDefinition.Slice(firstSplitIndex + 1, secondSplitIndex - firstSplitIndex - 1);
|
||||||
|
ReadOnlySpan<char> flagsSpan = storageDefinition.Slice(secondSplitIndex + 1);
|
||||||
|
|
||||||
|
CertStoreLocation? storeLocation = null;
|
||||||
|
CertStoreName? storeName = null;
|
||||||
|
X509KeyStorageFlags? flags = null;
|
||||||
|
if (locationSpan.Equals("user", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeLocation = CertStoreLocation.User;
|
||||||
|
}
|
||||||
|
else if (locationSpan.Equals("machine", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeLocation = CertStoreLocation.Machine;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLog.Error("{0}: Unsupported store-location '{1}' for storage '{2}'", GetType().ToString(false), locationSpan.ToString(), typeof(CertStoreStorage).ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameSpan.Equals("my", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeName = CertStoreName.My;
|
||||||
|
}
|
||||||
|
else if (nameSpan.Equals("root", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeName = CertStoreName.Root;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLog.Error("{0}: Unsupported store-name '{1}' for storage '{2}'", GetType().ToString(false), nameSpan.ToString(), typeof(CertStoreStorage).ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flagsSpan.Length > 0)
|
||||||
|
{
|
||||||
|
MemoryExtensions.SpanSplitEnumerator<char> enu = flagsSpan.Split(',');
|
||||||
|
while (enu.MoveNext())
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> currentExt = flagsSpan[enu.Current].Trim();
|
||||||
|
if (currentExt.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (flags.HasValue)
|
||||||
|
{
|
||||||
|
flags |= X509KeyStorageFlags.DefaultKeySet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
flags = X509KeyStorageFlags.DefaultKeySet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (currentExt.Equals("exportable", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (flags.HasValue)
|
||||||
|
{
|
||||||
|
flags |= X509KeyStorageFlags.Exportable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
flags = X509KeyStorageFlags.Exportable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLog.Error("{0}: cert storage-flags not defined", GetType().ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storeLocation.HasValue && storeName.HasValue)
|
||||||
|
{
|
||||||
|
context = new CertStoreStorageContext(storeLocation.Value, storeName.Value, flags.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
99
certmgr/Core/Converters/Impl/FileStorageContextConverter.cs
Normal file
99
certmgr/Core/Converters/Impl/FileStorageContextConverter.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using CertMgr.Core.Log;
|
||||||
|
using CertMgr.Core.Storage;
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Converters.Impl;
|
||||||
|
|
||||||
|
internal class FileStorageContextConverter : StorageContextConverter<FileStorageContext>
|
||||||
|
{
|
||||||
|
protected override Task<FileStorageContext?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> storageDefinition = rawValue.AsSpan();
|
||||||
|
|
||||||
|
TryGetFileStore(storageDefinition, out FileStorageContext? context);
|
||||||
|
|
||||||
|
return Task.FromResult(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetFileStore(ReadOnlySpan<char> storageDefinition, [NotNullWhen(true)] out FileStorageContext? context)
|
||||||
|
{
|
||||||
|
// expecting that 'storageDefinition' is something like:
|
||||||
|
// <store-mode>|<file-path>
|
||||||
|
// or
|
||||||
|
// |<file-path>
|
||||||
|
// or
|
||||||
|
// <file-path>
|
||||||
|
//
|
||||||
|
// where <store-mode> can be:
|
||||||
|
// 'o' or 'overwrite' or 'overwriteornew'
|
||||||
|
// or
|
||||||
|
// 'a' or 'append' or 'appendornew'
|
||||||
|
// or
|
||||||
|
// 'c' or 'create' or 'createnew'
|
||||||
|
//
|
||||||
|
// where <file-path> can be:
|
||||||
|
// 'c:\path\myfile.txt'
|
||||||
|
// or
|
||||||
|
// '/path/myfile.txt'
|
||||||
|
// or
|
||||||
|
// './path/myfile.txt'
|
||||||
|
// in addition - <file-path> can be either quoted or double-quoted
|
||||||
|
|
||||||
|
context = null;
|
||||||
|
|
||||||
|
FileStoreMode defaultStoreMode = FileStoreMode.OverwriteOrNew;
|
||||||
|
FileStoreMode storeMode;
|
||||||
|
ReadOnlySpan<char> filename;
|
||||||
|
|
||||||
|
int firstSplitIndex = storageDefinition.IndexOf(Separator);
|
||||||
|
if (firstSplitIndex != -1)
|
||||||
|
{
|
||||||
|
// there is a splitter. On the left side of it there must be <store-mode>. On the right there is a <file-path>
|
||||||
|
|
||||||
|
ReadOnlySpan<char> firstPart = storageDefinition.Slice(0, firstSplitIndex);
|
||||||
|
filename = storageDefinition.Slice(firstSplitIndex + 1);
|
||||||
|
|
||||||
|
if (firstPart.Length == 0)
|
||||||
|
{
|
||||||
|
storeMode = defaultStoreMode;
|
||||||
|
}
|
||||||
|
else if (Enum.TryParse(firstPart, true, out FileStoreMode tmpMode))
|
||||||
|
{
|
||||||
|
storeMode = tmpMode;
|
||||||
|
}
|
||||||
|
else if (firstPart.Equals("w", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("overwrite", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeMode = FileStoreMode.OverwriteOrNew;
|
||||||
|
}
|
||||||
|
else if (firstPart.Equals("c", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("createnew", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeMode = FileStoreMode.Create;
|
||||||
|
}
|
||||||
|
else if (firstPart.Equals("a", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("append", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeMode = FileStoreMode.AppendOrNew;
|
||||||
|
}
|
||||||
|
else if (firstPart.Equals("o", StringComparison.OrdinalIgnoreCase) || firstPart.Equals("open", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeMode = FileStoreMode.Open;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// it is not store-mode or there is a typo or unsupported value
|
||||||
|
CLog.Error(string.Format("{0}: Unsupported store-mode '{1}' for storage '{2}'", GetType().ToString(false), firstPart.ToString()), typeof(FileStorage).ToString(false));
|
||||||
|
storeMode = defaultStoreMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no split-char => just filename
|
||||||
|
storeMode = defaultStoreMode;
|
||||||
|
filename = storageDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = new FileStorageContext(filename.ToString(), storeMode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
certmgr/Core/Converters/Impl/StorageContextConverter.cs
Normal file
8
certmgr/Core/Converters/Impl/StorageContextConverter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Converters.Impl;
|
||||||
|
|
||||||
|
public abstract class StorageContextConverter<T> : ValueConverter<T> where T : StorageContext
|
||||||
|
{
|
||||||
|
protected const char Separator = '|';
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
/*using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
using CertMgr.Core.Log;
|
using CertMgr.Core.Log;
|
||||||
using CertMgr.Core.Storage;
|
using CertMgr.Core.Storage;
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
|
|
||||||
namespace CertMgr.Core.Converters.Impl;
|
namespace CertMgr.Core.Converters.Impl;
|
||||||
|
|
||||||
@@ -31,6 +33,13 @@ internal sealed class StorageConverter : ValueConverter<IStorage>
|
|||||||
storage = EmptyStorage.Empty;
|
storage = EmptyStorage.Empty;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "certstore":
|
||||||
|
case "cert-store":
|
||||||
|
if (!TryGetCertStore(storageDefinition, out storage))
|
||||||
|
{
|
||||||
|
storage = EmptyStorage.Empty;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
storage = EmptyStorage.Empty;
|
storage = EmptyStorage.Empty;
|
||||||
break;
|
break;
|
||||||
@@ -104,7 +113,7 @@ internal sealed class StorageConverter : ValueConverter<IStorage>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// it is not store-mode or there is a typo or unsupported value
|
// it is not store-mode or there is a typo or unsupported value
|
||||||
CLog.Error(string.Concat("Unsupported store-mode '", firstPart, "'"));
|
CLog.Error(string.Format("{0}: Unsupported store-mode '{1}' for storage '{2}'", GetType().ToString(false), firstPart.ToString()), typeof(FileStorage).ToString(false));
|
||||||
storeMode = defaultStoreMode;
|
storeMode = defaultStoreMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,4 +127,69 @@ internal sealed class StorageConverter : ValueConverter<IStorage>
|
|||||||
storage = new FileStorage(filename.ToString(), storeMode);
|
storage = new FileStorage(filename.ToString(), storeMode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryGetCertStore(ReadOnlySpan<char> storageDefinition, [NotNullWhen(true)] out IStorage? storage)
|
||||||
|
{
|
||||||
|
// expecting 'storageDefinition' is something like:
|
||||||
|
// <store-location>|<store-name>
|
||||||
|
// where <store-location> can be:
|
||||||
|
// - 'user'
|
||||||
|
// - 'machine'
|
||||||
|
// where '<store-name> can be:
|
||||||
|
// - 'my'
|
||||||
|
// - 'root'
|
||||||
|
|
||||||
|
int firstSplitIndex = storageDefinition.IndexOf(Separator);
|
||||||
|
if (firstSplitIndex != -1)
|
||||||
|
{
|
||||||
|
// there is a splitter. On the left side of it there must be <store-location>. On the right there is a <store-name>
|
||||||
|
|
||||||
|
ReadOnlySpan<char> locationSpan = storageDefinition.Slice(0, firstSplitIndex);
|
||||||
|
ReadOnlySpan<char> nameSpan = storageDefinition.Slice(firstSplitIndex + 1);
|
||||||
|
|
||||||
|
StoreLocation? storeLocation = null;
|
||||||
|
StoreName? storeName = null;
|
||||||
|
if (locationSpan.Equals("user", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeLocation = StoreLocation.CurrentUser;
|
||||||
}
|
}
|
||||||
|
else if (locationSpan.Equals("machine", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeLocation = StoreLocation.LocalMachine;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLog.Error(string.Format("{0}: Unsupported store-location '{1}' for storage '{2}'", GetType().ToString(false), locationSpan.ToString()), typeof(CertStoreStorage).ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameSpan.Equals("my", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeName = StoreName.My;
|
||||||
|
}
|
||||||
|
else if (nameSpan.Equals("root", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
storeName = StoreName.Root;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLog.Error(string.Format("{0}: Unsupported store-name '{1}' for storage '{2}'", GetType().ToString(false), nameSpan.ToString()), typeof(CertStoreStorage).ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storeLocation.HasValue && storeName.HasValue)
|
||||||
|
{
|
||||||
|
storage = new CertStoreStorage(storeLocation.Value, storeName.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
47
certmgr/Core/Converters/Impl/StorageKindConverter.cs
Normal file
47
certmgr/Core/Converters/Impl/StorageKindConverter.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Converters.Impl;
|
||||||
|
|
||||||
|
internal class StorageKindConverter : ValueConverter<IStorage>
|
||||||
|
{
|
||||||
|
// private const char Separator = '|';
|
||||||
|
|
||||||
|
protected override Task<IStorage?> DoConvertAsync(string rawValue, Type targetType, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> storageTypeSpan = rawValue.AsSpan();
|
||||||
|
|
||||||
|
// int storageTypeSplitIndex = rawSpan.IndexOf(Separator);
|
||||||
|
// if (storageTypeSplitIndex == -1)
|
||||||
|
// {
|
||||||
|
// return Task.FromResult((IStorage?)EmptyStorage.Empty);
|
||||||
|
// }
|
||||||
|
|
||||||
|
IStorage? storage;
|
||||||
|
|
||||||
|
// ReadOnlySpan<char> storageTypeSpan = rawSpan.Slice(0, storageTypeSplitIndex);
|
||||||
|
// ReadOnlySpan<char> storageDefinition = rawSpan.Slice(storageTypeSplitIndex + 1);
|
||||||
|
switch (storageTypeSpan)
|
||||||
|
{
|
||||||
|
case "file":
|
||||||
|
storage = new FileStorage();
|
||||||
|
// if (!TryGetFileStore(storageDefinition, out storage))
|
||||||
|
// {
|
||||||
|
// storage = EmptyStorage.Empty;
|
||||||
|
// }
|
||||||
|
break;
|
||||||
|
case "certstore":
|
||||||
|
case "cert-store":
|
||||||
|
storage = new CertStoreStorage();
|
||||||
|
//if (!TryGetCertStore(storageDefinition, out storage))
|
||||||
|
//{
|
||||||
|
// storage = EmptyStorage.Empty;
|
||||||
|
//}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
storage = EmptyStorage.Empty;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult((IStorage?)storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ public sealed class TimeSpanConverter : ValueConverter<TimeSpan?>
|
|||||||
result = TimeSpan.FromDays(value);
|
result = TimeSpan.FromDays(value);
|
||||||
break;
|
break;
|
||||||
case 'y':
|
case 'y':
|
||||||
result = TimeSpan.FromDays(value * 365);
|
result = TimeSpan.FromDays(value * 365 + (value / 4));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
result = null;
|
result = null;
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ namespace CertMgr.Core.Jobs;
|
|||||||
|
|
||||||
public abstract class JobBase<TSettings> where TSettings : JobSettings, new()
|
public abstract class JobBase<TSettings> where TSettings : JobSettings, new()
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
protected JobBase()
|
protected JobBase()
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
{
|
{
|
||||||
|
// 'Settings' is set via reflection
|
||||||
Name = JobUtils.GetJobName(GetType());
|
Name = JobUtils.GetJobName(GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,16 +105,44 @@ internal sealed class PropertyDescriptor
|
|||||||
_propertyInfo.SetValue(settings, _settingAttribute.Default);
|
_propertyInfo.SetValue(settings, _settingAttribute.Default);
|
||||||
succeeded = true;
|
succeeded = true;
|
||||||
}
|
}
|
||||||
|
else if (typeInfo.IsElementTypeNullable)
|
||||||
|
{
|
||||||
|
_propertyInfo.SetValue(settings, _settingAttribute.Default);
|
||||||
|
succeeded = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CLog.Error("Default value for argument '{0}' is specified, but its type is '{1}' instead of expected '{2}'", SettingName, _settingAttribute.Default?.GetType().ToString(false) ?? "<null>", typeInfo.ElementType.ToString(false));
|
CLog.Error("Default value for argument '{0}' is specified, but its type is '{1}' instead of expected '{2}'", SettingName, _settingAttribute.Default?.GetType().ToString(false) ?? "<null>", typeInfo.ElementType.ToString(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AsyncResult<object?> GetDefaultValue(JobSettings settings)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (_settingAttribute.Default != null)
|
||||||
|
{
|
||||||
|
Utils.TypeInfo typeInfo = TypeUtils.UnwrapCollection(_propertyInfo.PropertyType);
|
||||||
|
if (_settingAttribute.Default.GetType() == typeInfo.ElementType)
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else if (typeInfo.IsElementTypeNullable)
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLog.Error("Default value for argument '{0}' is specified, but its type is '{1}' instead of expected '{2}'", SettingName, _settingAttribute.Default?.GetType().ToString(false) ?? "<null>", typeInfo.ElementType.ToString(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AsyncResult<object?>(success, _settingAttribute.Default);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("property '{0}', type = {1}, is-mandatory = {2}, validator = {3}, converter = {4}", PropertyName, PropertyTypeInfo.SourceType.ToString(false), IsMandatory ? "yes" : "no", CustomValidator?.GetType().ToString(false) ?? "<null>", CustomConverter?.GetType().ToString(false) ?? "<null>");
|
return string.Format("property '{0}', type = {1}, is-mandatory = {2}, validator = {3}, converter = {4}", PropertyName, PropertyTypeInfo.SourceType.ToString(false), IsMandatory ? "yes" : "no", CustomValidator?.GetType().ToString(false) ?? "<null>", CustomConverter?.GetType().ToString(false) ?? "<null>");
|
||||||
|
|||||||
@@ -31,24 +31,20 @@ internal sealed class SettingsBuilder
|
|||||||
|
|
||||||
foreach (PropertyDescriptor descriptor in GetPropertiesWithSettingAttribute())
|
foreach (PropertyDescriptor descriptor in GetPropertiesWithSettingAttribute())
|
||||||
{
|
{
|
||||||
AsyncResult<object?> setPropertyResult = await SetPropertyValueAsync(settings, descriptor, cancellationToken).ConfigureAwait(false);
|
AsyncResult<object?> result = await GetValueAsync(descriptor, settings, cancellationToken);
|
||||||
if (setPropertyResult.IsSuccess)
|
|
||||||
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
try
|
await ValidatePropertyAsync(result.Value, descriptor, settings, cancellationToken);
|
||||||
{
|
|
||||||
IValueValidator? validator = descriptor.CustomValidator;
|
descriptor.SetValue(settings, result.Value);
|
||||||
if (validator != null)
|
|
||||||
{
|
|
||||||
IValidationResult valres = await validator.ValidateAsync(setPropertyResult.Value, cancellationToken).ConfigureAwait(false);
|
|
||||||
settings.ValidationResults.Add(valres);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
CLog.Error(e, "Failed to validate property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
|
||||||
throw new ConsoleToolsException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AsyncResult<object?> setPropertyResult = await SetPropertyValueAsync(settings, descriptor, cancellationToken).ConfigureAwait(false);
|
||||||
|
// if (setPropertyResult.IsSuccess)
|
||||||
|
// {
|
||||||
|
// await ValidatePropertyAsync(setPropertyResult.Value, descriptor, settings, cancellationToken);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
@@ -91,6 +87,37 @@ internal sealed class SettingsBuilder
|
|||||||
return new AsyncResult<object?>(valueSet, convertedValue);
|
return new AsyncResult<object?>(valueSet, convertedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<AsyncResult<object?>> GetValueAsync(PropertyDescriptor descriptor, JobSettings settings, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
AsyncResult<object?> result;
|
||||||
|
|
||||||
|
if (TryGetRawArgument(descriptor, out RawArgument? rawArg))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await ConvertRawValueAsync(descriptor, rawArg, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CLog.Error(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
||||||
|
throw new ConsoleToolsException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (descriptor.IsMandatory)
|
||||||
|
{
|
||||||
|
ValidationResult valres = new ValidationResult(descriptor.SettingName, false, "Mandatory argument is missing");
|
||||||
|
settings.ValidationResults.Add(valres);
|
||||||
|
CLog.Error("mandatory argument '{0}' is missing", descriptor.SettingName);
|
||||||
|
result = new AsyncResult<object?>(false, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = descriptor.GetDefaultValue(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryGetRawArgument(PropertyDescriptor descriptor, [NotNullWhen(true)] out RawArgument? rawArg)
|
private bool TryGetRawArgument(PropertyDescriptor descriptor, [NotNullWhen(true)] out RawArgument? rawArg)
|
||||||
{
|
{
|
||||||
rawArg = null;
|
rawArg = null;
|
||||||
@@ -243,4 +270,22 @@ internal sealed class SettingsBuilder
|
|||||||
|
|
||||||
return convertedValue != null;
|
return convertedValue != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ValidatePropertyAsync(object? value, PropertyDescriptor descriptor, JobSettings settings, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IValueValidator? validator = descriptor.CustomValidator;
|
||||||
|
if (validator != null)
|
||||||
|
{
|
||||||
|
IValidationResult valres = await validator.ValidateAsync(value, cancellationToken).ConfigureAwait(false);
|
||||||
|
settings.ValidationResults.Add(valres);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CLog.Error(e, "Failed to validate property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
||||||
|
throw new ConsoleToolsException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
certmgr/Core/Storage/CertStoreLocation.cs
Normal file
9
certmgr/Core/Storage/CertStoreLocation.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public enum CertStoreLocation
|
||||||
|
{
|
||||||
|
User = StoreLocation.CurrentUser,
|
||||||
|
Machine = StoreLocation.LocalMachine
|
||||||
|
}
|
||||||
9
certmgr/Core/Storage/CertStoreName.cs
Normal file
9
certmgr/Core/Storage/CertStoreName.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public enum CertStoreName
|
||||||
|
{
|
||||||
|
My = StoreName.My,
|
||||||
|
Root = StoreName.Root,
|
||||||
|
}
|
||||||
100
certmgr/Core/Storage/CertStoreStorage.cs
Normal file
100
certmgr/Core/Storage/CertStoreStorage.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public sealed class CertStoreStorage : Storage<CertStoreStorageContext>
|
||||||
|
{
|
||||||
|
private static StoreResult<X509Certificate2> NotFoundResult = new StoreResult<X509Certificate2>(null, false);
|
||||||
|
|
||||||
|
protected override async Task<StoreResult> DoReadAsync(Stream target, CertStoreStorageContext context, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
StoreResult<X509Certificate2> result = NotFoundResult;
|
||||||
|
|
||||||
|
using (X509Store store = OpenCertStore(context, OpenFlags.ReadWrite))
|
||||||
|
{
|
||||||
|
foreach (X509Certificate2 cert in store.Certificates)
|
||||||
|
{
|
||||||
|
if (await context.Filter.IsMatchAsync(cert, cancellationToken))
|
||||||
|
{
|
||||||
|
result = new StoreResult<X509Certificate2>(cert, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<StoreResult> DoWriteAsync(Stream source, CertStoreStorageContext context, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
X509KeyStorageFlags flags = X509KeyStorageFlags.EphemeralKeySet;
|
||||||
|
|
||||||
|
await using (MemoryStream ms = await CopyAsync(source, cancellationToken))
|
||||||
|
{
|
||||||
|
using (X509Certificate2 cert = LoadCertificate(ms.GetBuffer(), context.Password, flags))
|
||||||
|
{
|
||||||
|
using (X509Store store = OpenCertStore(context, OpenFlags.ReadWrite))
|
||||||
|
{
|
||||||
|
AddToCertStore(store, cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StoreResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MemoryStream> CopyAsync(Stream source, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
await source.CopyToAsync(ms).ConfigureAwait(false);
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new StorageException(e, "{0}: Failure while reading from stream (source stream type = '{1}')", GetType().ToString(false), source.GetType().ToString(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509Certificate2 LoadCertificate(byte[] data, string password, X509KeyStorageFlags flags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
X509Certificate2 cert = X509CertificateLoader.LoadPkcs12(data, password, flags);
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new StorageException(e, "{0}: Failure while loading certificate from specified source", GetType().ToString(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509Store OpenCertStore(CertStoreStorageContext context, OpenFlags flags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
X509Store store = new X509Store((StoreName)context.Name, (StoreLocation)context.Location);
|
||||||
|
store.Open(flags);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new StorageException(e, "{0}: Failure while opening cert-store (name = '{0}', location = '{1}', open-mode = '{2}'", context.Name, context.Location, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToCertStore(X509Store store, X509Certificate2 cert)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
store.Add(cert);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new StorageException(e, "{0}: Failure while adding cert to cert-store (name = '{0}', location = '{1}', cert-subj = '{2}', cert-thumbprint = '{3}'", store.Name, store.Location, cert.Subject, cert.Thumbprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
certmgr/Core/Storage/CertStoreStorageContext.cs
Normal file
42
certmgr/Core/Storage/CertStoreStorageContext.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public sealed class CertStoreStorageContext : StorageContext
|
||||||
|
{
|
||||||
|
public CertStoreStorageContext(CertStoreLocation storeLocation, CertStoreName storeName, X509KeyStorageFlags flags)
|
||||||
|
{
|
||||||
|
Location = storeLocation;
|
||||||
|
Name = storeName;
|
||||||
|
Flags = flags;
|
||||||
|
Filter = Filter<X509Certificate2>.NotMatch;
|
||||||
|
Password = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertStoreStorageContext(CertStoreLocation storeLocation, CertStoreName storeName, X509KeyStorageFlags flags, IFilter<X509Certificate2> filter)
|
||||||
|
{
|
||||||
|
Location = storeLocation;
|
||||||
|
Name = storeName;
|
||||||
|
Flags = flags;
|
||||||
|
Filter = filter;
|
||||||
|
Password = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertStoreLocation Location { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
public CertStoreName Name { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
public X509KeyStorageFlags Flags { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
public IFilter<X509Certificate2> Filter { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
public string Password { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("{0}/{1}, flags = {2}, filter-type = {3}", Location, Name, Flags, Filter?.GetType().ToString(false) ?? "<null>");
|
||||||
|
}
|
||||||
|
}
|
||||||
27
certmgr/Core/Storage/CertificateFilter.cs
Normal file
27
certmgr/Core/Storage/CertificateFilter.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public static class CertificateFilter
|
||||||
|
{
|
||||||
|
private abstract class CertFilter : Filter<X509Certificate2>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ByThumbprintFilter : CertFilter
|
||||||
|
{
|
||||||
|
public ByThumbprintFilter(string thumbprint)
|
||||||
|
{
|
||||||
|
Thumbprint = thumbprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Thumbprint { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
protected override Task<bool> DoIsMatchAsync(X509Certificate2 value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
bool result = string.Equals(value.Thumbprint, Thumbprint, StringComparison.OrdinalIgnoreCase);
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace CertMgr.Core.Storage;
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
public sealed class EmptyStorage : Storage
|
public sealed class EmptyStorage : Storage<StorageContext>
|
||||||
{
|
{
|
||||||
public static readonly IStorage Empty = new EmptyStorage();
|
public static readonly IStorage Empty = new EmptyStorage();
|
||||||
|
|
||||||
@@ -8,16 +8,12 @@ public sealed class EmptyStorage : Storage
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWrite => true;
|
protected override Task<StoreResult> DoWriteAsync(Stream source, StorageContext context, CancellationToken cancellationToken)
|
||||||
|
|
||||||
public override bool CanRead => true;
|
|
||||||
|
|
||||||
protected override Task<StoreResult> DoWriteAsync(Stream source, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
return Task.FromResult(StoreResult.CreateSuccess());
|
return Task.FromResult(StoreResult.CreateSuccess());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<StoreResult> DoReadAsync(Stream target, CancellationToken cancellationToken)
|
protected override Task<StoreResult> DoReadAsync(Stream target, StorageContext context, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult(StoreResult.CreateSuccess());
|
return Task.FromResult(StoreResult.CreateSuccess());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,10 @@
|
|||||||
using CertMgr.Core.Utils;
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
namespace CertMgr.Core.Storage;
|
public sealed class FileStorage : Storage<FileStorageContext>
|
||||||
|
|
||||||
public sealed class FileStorage : Storage
|
|
||||||
{
|
{
|
||||||
private readonly string _fileFullPath;
|
protected override async Task<StoreResult> DoWriteAsync(Stream source, FileStorageContext context, CancellationToken cancellationToken)
|
||||||
private readonly FileStoreMode _mode;
|
|
||||||
|
|
||||||
public FileStorage(string fileFullPath, FileStoreMode mode)
|
|
||||||
{
|
{
|
||||||
_fileFullPath = fileFullPath;
|
using (FileStream fs = new FileStream(context.Path, (FileMode)context.Mode, FileAccess.Write, FileShare.None))
|
||||||
_mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite => _mode.IsAnyOf(FileStoreMode.AppendOrNew, FileStoreMode.Create, FileStoreMode.OverwriteOrNew);
|
|
||||||
|
|
||||||
public override bool CanRead => _mode == FileStoreMode.Open;
|
|
||||||
|
|
||||||
protected override async Task<StoreResult> DoWriteAsync(Stream source, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
using (FileStream fs = new FileStream(_fileFullPath, (FileMode)_mode, FileAccess.Write, FileShare.None))
|
|
||||||
{
|
{
|
||||||
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
|
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -27,18 +12,13 @@ public sealed class FileStorage : Storage
|
|||||||
return StoreResult.CreateSuccess();
|
return StoreResult.CreateSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<StoreResult> DoReadAsync(Stream target, CancellationToken cancellationToken)
|
protected override async Task<StoreResult> DoReadAsync(Stream target, FileStorageContext context, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using (FileStream fs = new FileStream(_fileFullPath, (FileMode)_mode, FileAccess.Read, FileShare.Read))
|
using (FileStream fs = new FileStream(context.Path, (FileMode)context.Mode, FileAccess.Read, FileShare.Read))
|
||||||
{
|
{
|
||||||
await fs.CopyToAsync(target, cancellationToken).ConfigureAwait(false);
|
await fs.CopyToAsync(target, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return StoreResult.CreateSuccess();
|
return StoreResult.CreateSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format("{0}: mode = '{1}', path = '{2}'", GetType().Name, _mode, _fileFullPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
certmgr/Core/Storage/FileStorageContext.cs
Normal file
16
certmgr/Core/Storage/FileStorageContext.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public sealed class FileStorageContext : StorageContext
|
||||||
|
{
|
||||||
|
public FileStorageContext(string fileFullPath, FileStoreMode mode)
|
||||||
|
{
|
||||||
|
Path = fileFullPath;
|
||||||
|
Mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
public FileStoreMode Mode { [DebuggerStepThrough] get; }
|
||||||
|
}
|
||||||
38
certmgr/Core/Storage/Filter.cs
Normal file
38
certmgr/Core/Storage/Filter.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
|
|
||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public abstract class Filter<T> : IFilter<T>
|
||||||
|
{
|
||||||
|
public static readonly IFilter<T> IsMatch = new EmptyFilter(true);
|
||||||
|
public static readonly IFilter<T> NotMatch = new EmptyFilter(false);
|
||||||
|
|
||||||
|
public Task<bool> IsMatchAsync(T value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return DoIsMatchAsync(value, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task<bool> DoIsMatchAsync(T value, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
private sealed class EmptyFilter : Filter<T>
|
||||||
|
{
|
||||||
|
internal EmptyFilter(bool result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Result { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
protected override Task<bool> DoIsMatchAsync(T value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("{0}: is-match = {1}", GetType().ToString(false), Result ? "yes" : "no");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
certmgr/Core/Storage/IFilter.cs
Normal file
6
certmgr/Core/Storage/IFilter.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public interface IFilter<T>
|
||||||
|
{
|
||||||
|
Task<bool> IsMatchAsync(T value, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
public interface IStorage
|
public interface IStorage
|
||||||
{
|
{
|
||||||
Task<StoreResult> WriteAsync(Stream source, CancellationToken cancellationToken);
|
Task<StoreResult> WriteAsync(Stream source, StorageContext context, CancellationToken cancellationToken);
|
||||||
|
|
||||||
// Task<StoreResult> WriteFromAsync<TResult>(StorageAdapter<TResult> adapter, CancellationToken cancellationToken);
|
Task<StoreResult> ReadAsync(Stream target, StorageContext context, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<StoreResult> ReadAsync(Stream target, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
// Task<StoreResult<T>> ReadToAsync<T>(StorageAdapter<T> adapter, CancellationToken cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,15 @@
|
|||||||
|
|
||||||
namespace CertMgr.Core.Storage;
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
public abstract class Storage : IStorage
|
public abstract class Storage<T> : IStorage where T : StorageContext
|
||||||
{
|
{
|
||||||
|
public async Task<StoreResult> WriteAsync(Stream source, StorageContext context, CancellationToken cancellationToken)
|
||||||
public virtual bool CanRead => false;
|
|
||||||
|
|
||||||
public virtual bool CanWrite => false;
|
|
||||||
|
|
||||||
public async Task<StoreResult> WriteAsync(Stream source, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
if (!CanWrite)
|
T typedContext = GetTypedContext(context);
|
||||||
{
|
|
||||||
throw new StorageException("Cannot write. Storage not writable");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await DoWriteAsync(source, cancellationToken);
|
return await DoWriteAsync(source, typedContext, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -27,18 +19,15 @@ public abstract class Storage : IStorage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task<StoreResult> DoWriteAsync(Stream source, CancellationToken cancellationToken);
|
protected abstract Task<StoreResult> DoWriteAsync(Stream source, T context, CancellationToken cancellationToken);
|
||||||
|
|
||||||
public async Task<StoreResult> ReadAsync(Stream target, CancellationToken cancellationToken)
|
public async Task<StoreResult> ReadAsync(Stream target, StorageContext context, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!CanRead)
|
T typedContext = GetTypedContext(context);
|
||||||
{
|
|
||||||
throw new StorageException("Cannot read. Storage not readable");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await DoReadAsync(target, cancellationToken);
|
return await DoReadAsync(target, typedContext, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -47,37 +36,26 @@ public abstract class Storage : IStorage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task<StoreResult> DoReadAsync(Stream target, CancellationToken cancellationToken);
|
protected abstract Task<StoreResult> DoReadAsync(Stream target, T context, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/*public async Task<StoreResult> WriteFromAsync<TResult>(StorageAdapter<TResult> adapter, CancellationToken cancellationToken)
|
private T GetTypedContext(StorageContext context)
|
||||||
{
|
{
|
||||||
|
T typedContext;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await adapter.WriteAsync(this, cancellationToken).ConfigureAwait(false);
|
typedContext = (T)context;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
StorageException ex = new StorageException(e, "Failed to write to storage of type '{0}' adapter of type '{1}'", GetType().ToString(false), adapter.GetType().ToString(false));
|
throw new StorageException(e, "Faile to convert type {0} to {1} in storage of type {2}", context.GetType().ToString(false), typeof(T).ToString(false), GetType().ToString(false));
|
||||||
return StoreResult.CreateFailure(ex);
|
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
/*public async Task<StoreResult<TResult>> ReadToAsync<TResult>(StorageAdapter<TResult> adapter, CancellationToken cancellationToken)
|
return typedContext;
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
TResult result = await adapter.ReadAsync(this, cancellationToken).ConfigureAwait(false);
|
|
||||||
return new StoreResult<TResult>(result, true);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
StorageException ex = new StorageException(e, "Failed to read as type '{0}' from storage of type '{1}' using adapter of type '{1}'", typeof(TResult).ToString(false), GetType().ToString(false), adapter.GetType().ToString(false));
|
|
||||||
return new StoreResult<TResult>(default, false, ex);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("{0}: can-read = {1}, can-write = {2}", GetType().Name, CanRead ? "yes" : "no", CanWrite ? "yes" : "no");
|
return string.Format("{0}", GetType().ToString(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
certmgr/Core/Storage/StorageContext.cs
Normal file
5
certmgr/Core/Storage/StorageContext.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
public class StorageContext
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ public sealed class TypeInfo
|
|||||||
SourceType = sourceType;
|
SourceType = sourceType;
|
||||||
IsCollection = isCollection;
|
IsCollection = isCollection;
|
||||||
ElementType = elementType;
|
ElementType = elementType;
|
||||||
|
|
||||||
|
IsElementTypeNullable = Nullable.GetUnderlyingType(ElementType) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type SourceType { [DebuggerStepThrough] get; }
|
public Type SourceType { [DebuggerStepThrough] get; }
|
||||||
@@ -17,6 +19,8 @@ public sealed class TypeInfo
|
|||||||
|
|
||||||
public Type ElementType { [DebuggerStepThrough] get; }
|
public Type ElementType { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
public bool IsElementTypeNullable { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("is-collection = {0}, source-type = '{1}', element-type = '{2}'", IsCollection ? "yes" : "no", SourceType.ToString(false), ElementType.ToString(false));
|
return string.Format("is-collection = {0}, source-type = '{1}', element-type = '{2}'", IsCollection ? "yes" : "no", SourceType.ToString(false), ElementType.ToString(false));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using CertMgr.Core.Attributes;
|
|||||||
using CertMgr.Core.Converters.Impl;
|
using CertMgr.Core.Converters.Impl;
|
||||||
using CertMgr.Core.Jobs;
|
using CertMgr.Core.Jobs;
|
||||||
using CertMgr.Core.Storage;
|
using CertMgr.Core.Storage;
|
||||||
|
using CertMgr.Core.Utils;
|
||||||
using CertMgr.Core.Validation;
|
using CertMgr.Core.Validation;
|
||||||
|
|
||||||
namespace CertMgr.Jobs;
|
namespace CertMgr.Jobs;
|
||||||
@@ -52,20 +53,102 @@ public sealed class CertificateSettings : JobSettings
|
|||||||
[Setting("is-certificate-authority", Default = false, AlternateNames = ["isca"])]
|
[Setting("is-certificate-authority", Default = false, AlternateNames = ["isca"])]
|
||||||
public bool IsCertificateAuthority { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
public bool IsCertificateAuthority { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
[Setting("issuer-certificate", Converter = typeof(StorageConverter))]
|
[Setting("issuer-kind", Converter = typeof(StorageKindConverter))]
|
||||||
public IStorage? Issuer { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
public IStorage? Issuer { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
[Setting("issuer-file", Converter = typeof(FileStorageContextConverter))]
|
||||||
|
public FileStorageContext? IssuerFileContext { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
[Setting("issuer-certstore", Converter = typeof(CertStoreStorageContextConverter))]
|
||||||
|
public CertStoreStorageContext? IssuerCertStoreContext { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
// [Setting("issuer-certificate", Converter = typeof(StorageConverter))]
|
||||||
|
// public IStorage? Issuer { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
[Setting("issuer-password", IsSecret = true)]
|
[Setting("issuer-password", IsSecret = true)]
|
||||||
public string? IssuerPassword { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
public string? IssuerPassword { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
[Setting("storage", IsMandatory = true, Converter = typeof(StorageConverter))]
|
[Setting("validity-period", Default = "1y", Converter = typeof(TimeSpanConverter))]
|
||||||
public IStorage? Storage { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
public TimeSpan? ValidityPeriod { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
[Setting("password", IsSecret = true)]
|
// [Setting("certificate-target", IsMandatory = true, Converter = typeof(StorageConverter))]
|
||||||
|
// public IStorage? Storage { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
[Setting("target-password", IsSecret = true)]
|
||||||
public string? Password { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
public string? Password { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
[Setting("validity-period", Default = "365d", Converter = typeof(TimeSpanConverter))]
|
[Setting("target-kind", Converter = typeof(StorageKindConverter))]
|
||||||
public TimeSpan? ValidityPeriod { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
public IStorage? Target { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
[Setting("target-file", Converter = typeof(FileStorageContextConverter))]
|
||||||
|
public FileStorageContext? TargetFileContext { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
[Setting("target-certstore", Converter = typeof(CertStoreStorageContextConverter))]
|
||||||
|
public CertStoreStorageContext? TargetCertStoreContext { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||||
|
|
||||||
|
public StorageContext GetTargetContext()
|
||||||
|
{
|
||||||
|
if (Target == null)
|
||||||
|
{
|
||||||
|
throw new JobException("Target storage is not properly configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageContext? ctx;
|
||||||
|
|
||||||
|
switch (Target)
|
||||||
|
{
|
||||||
|
case FileStorage:
|
||||||
|
ctx = TargetFileContext;
|
||||||
|
break;
|
||||||
|
case CertStoreStorage:
|
||||||
|
if (TargetCertStoreContext != null && Password != null)
|
||||||
|
{
|
||||||
|
TargetCertStoreContext.Password = Password;
|
||||||
|
}
|
||||||
|
ctx = TargetCertStoreContext;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ctx = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx == null)
|
||||||
|
{
|
||||||
|
throw new JobException("context for target storage of type '{0}' is not properly set", Target.GetType().ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageContext GetIssuerContext()
|
||||||
|
{
|
||||||
|
if (Issuer == null)
|
||||||
|
{
|
||||||
|
throw new JobException("Issuer storage is not properly configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageContext? ctx;
|
||||||
|
|
||||||
|
switch (Issuer)
|
||||||
|
{
|
||||||
|
case FileStorage:
|
||||||
|
ctx = IssuerFileContext;
|
||||||
|
break;
|
||||||
|
case CertStoreStorage:
|
||||||
|
ctx = IssuerCertStoreContext;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ctx = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx == null)
|
||||||
|
{
|
||||||
|
throw new JobException("context for issuer storage of type '{0}' is not properly set", Issuer.GetType().ToString(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
protected override Task DoValidateAsync(ValidationResults results, CancellationToken cancellationToken)
|
protected override Task DoValidateAsync(ValidationResults results, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -92,9 +175,24 @@ public sealed class CertificateSettings : JobSettings
|
|||||||
results.AddInvalid(nameof(HashAlgorithm), "value value must be specified: '{0}'", HashAlgorithm?.ToString() ?? "<null>");
|
results.AddInvalid(nameof(HashAlgorithm), "value value must be specified: '{0}'", HashAlgorithm?.ToString() ?? "<null>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Storage == null)
|
if (Target == null)
|
||||||
{
|
{
|
||||||
results.AddInvalid(nameof(Storage), "must be specified");
|
results.AddInvalid(nameof(Target), "must be specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Target is FileStorage)
|
||||||
|
{
|
||||||
|
if (TargetFileContext is null)
|
||||||
|
{
|
||||||
|
results.AddInvalid(nameof(TargetFileContext), "value must be specified when target is '{0}'", Target.GetType().ToString(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Target is CertStoreStorage)
|
||||||
|
{
|
||||||
|
if (TargetCertStoreContext is null)
|
||||||
|
{
|
||||||
|
results.AddInvalid(nameof(TargetCertStoreContext), "value must be specified when target is '{0}'", Target.GetType().ToString(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|||||||
@@ -24,18 +24,20 @@ public sealed class CreateCertificateJob : Job<CertificateSettings>
|
|||||||
CertificateManager cm = new CertificateManager();
|
CertificateManager cm = new CertificateManager();
|
||||||
using (X509Certificate2 cert = await cm.CreateAsync(cgcs, gs, cancellationToken).ConfigureAwait(false))
|
using (X509Certificate2 cert = await cm.CreateAsync(cgcs, gs, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
if (Settings.Storage != null)
|
if (Settings.Target != null)
|
||||||
{
|
{
|
||||||
|
StorageContext ctx = Settings.GetTargetContext();
|
||||||
|
|
||||||
byte[] data = cert.Export(X509ContentType.Pfx, Settings.Password);
|
byte[] data = cert.Export(X509ContentType.Pfx, Settings.Password);
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (MemoryStream ms = new MemoryStream())
|
||||||
{
|
{
|
||||||
await ms.WriteAsync(data, cancellationToken);
|
await ms.WriteAsync(data, cancellationToken);
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
StoreResult writeResult = await Settings.Storage.WriteAsync(ms, cancellationToken).ConfigureAwait(false);
|
StoreResult writeResult = await Settings.Target.WriteAsync(ms, ctx, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!writeResult.IsSuccess)
|
if (!writeResult.IsSuccess)
|
||||||
{
|
{
|
||||||
throw new JobException(writeResult.Exception, "Failed to write create certificate to target storage (type = '{0}', storage = '{1}')", Settings.Storage.GetType().ToString(false), Settings.Storage.ToString());
|
throw new JobException(writeResult.Exception, "Failed to write create certificate to target storage (type = '{0}', context = '{1}')", Settings.Target.GetType().ToString(false), ctx.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,9 +82,10 @@ public sealed class CreateCertificateJob : Job<CertificateSettings>
|
|||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (MemoryStream ms = new MemoryStream())
|
||||||
{
|
{
|
||||||
|
StorageContext ctx = Settings.GetIssuerContext();
|
||||||
// X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet;
|
// X509KeyStorageFlags flags = X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet;
|
||||||
X509KeyStorageFlags flags = X509KeyStorageFlags.DefaultKeySet;
|
X509KeyStorageFlags flags = X509KeyStorageFlags.DefaultKeySet;
|
||||||
await Settings.Issuer.ReadAsync(ms, cancellationToken).ConfigureAwait(false);
|
await Settings.Issuer.ReadAsync(ms, ctx, cancellationToken).ConfigureAwait(false);
|
||||||
cgcs.Issuer = X509CertificateLoader.LoadPkcs12(ms.GetBuffer(), Settings.IssuerPassword, flags);
|
cgcs.Issuer = X509CertificateLoader.LoadPkcs12(ms.GetBuffer(), Settings.IssuerPassword, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
args = [
|
args = [
|
||||||
"--job=create-certificate",
|
"--job=create-certificate",
|
||||||
"--issuer-certificate=file|o|c:\\friend2.pfx",
|
"--issuer-kind=file",
|
||||||
|
"--issuer-file=o|c:\\friend2.pfx",
|
||||||
"--issuer-password=aaa",
|
"--issuer-password=aaa",
|
||||||
"--subject=CN=hello",
|
"--subject=CN=hello",
|
||||||
"--san=world",
|
"--san=world",
|
||||||
@@ -16,8 +17,21 @@ internal static class Program
|
|||||||
"--san=IP:192.168.131.1",
|
"--san=IP:192.168.131.1",
|
||||||
"--algorithm=ecdsa",
|
"--algorithm=ecdsa",
|
||||||
"--ecdsa-curve=p384",
|
"--ecdsa-curve=p384",
|
||||||
"--storage=file|w|c:\\mycert-ecdsa.pfx",
|
"--validity-period=2d",
|
||||||
"--validity-period=2d" ];
|
|
||||||
|
// "--certificate-target=file|w|c:\\mycert-ecdsa.pfx",
|
||||||
|
// "--certificate-password=aaa",
|
||||||
|
|
||||||
|
// "--target-kind=file",
|
||||||
|
// "--target-file=w|c:\\mycert-ecdsa.pfx",
|
||||||
|
// "--target-password=aaa",
|
||||||
|
|
||||||
|
"--target-kind=certstore",
|
||||||
|
"--target-certstore=machine|my|exportable",
|
||||||
|
"--target-password=aaa"
|
||||||
|
];
|
||||||
|
|
||||||
|
// --certificate-target=certstore|machine|my
|
||||||
|
|
||||||
// args = [
|
// args = [
|
||||||
// "--job=create-certificate",
|
// "--job=create-certificate",
|
||||||
@@ -29,10 +43,12 @@ internal static class Program
|
|||||||
// "--san=IP:192.168.131.1",
|
// "--san=IP:192.168.131.1",
|
||||||
// "--algorithm=rsa",
|
// "--algorithm=rsa",
|
||||||
// "--rsa-key-size=2048",
|
// "--rsa-key-size=2048",
|
||||||
// "--storage=file|w|c:\\friend-rsa.pfx",
|
// "--validity-period=2d",
|
||||||
// "--validity-period=2d" ];
|
// "--certificate-target=file|w|c:\\friend-rsa.pfx",
|
||||||
|
// "--certificate-password=aaa"
|
||||||
|
// ];
|
||||||
|
|
||||||
using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
JobExecutor executor = new JobExecutor();
|
JobExecutor executor = new JobExecutor();
|
||||||
int errorLevel = await executor.ExecuteAsync(args, cts.Token).ConfigureAwait(false);
|
int errorLevel = await executor.ExecuteAsync(args, cts.Token).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -9,4 +9,8 @@
|
|||||||
<RootNamespace>CertMgr</RootNamespace>
|
<RootNamespace>CertMgr</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Core\Storage\Filters\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
20
certmgrTest/CertStoreStorageTest.cs
Normal file
20
certmgrTest/CertStoreStorageTest.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using CertMgr.Core.Storage;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace certmgrTest;
|
||||||
|
|
||||||
|
public class CertStoreStorageTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task First()
|
||||||
|
{
|
||||||
|
using (MemoryStream target = new MemoryStream())
|
||||||
|
{
|
||||||
|
CertStoreStorage storage = new CertStoreStorage();
|
||||||
|
CertStoreStorageContext context = new CertStoreStorageContext(CertStoreLocation.User, CertStoreName.My, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.DefaultKeySet);
|
||||||
|
context.Password = "aaa";
|
||||||
|
await storage.ReadAsync(target, context, CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
certmgrTest/PropertyDescriptorTest.cs
Normal file
82
certmgrTest/PropertyDescriptorTest.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using CertMgr.Core;
|
||||||
|
using CertMgr.Core.Attributes;
|
||||||
|
using CertMgr.Core.Jobs;
|
||||||
|
using CertMgr.Core.Validation;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace certmgrTest;
|
||||||
|
|
||||||
|
public class PropertyDescriptorTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void First()
|
||||||
|
{
|
||||||
|
PropertyDescriptor? descriptor = GetProperty(typeof(TestSettings), nameof(TestSettings.StringItems));
|
||||||
|
|
||||||
|
if (descriptor == null)
|
||||||
|
{
|
||||||
|
throw new Exception(string.Format("property not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
IValueValidator? validator = descriptor.CustomValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PropertyDescriptor? GetProperty(Type settingsType, string name)
|
||||||
|
{
|
||||||
|
PropertyDescriptor? result = null;
|
||||||
|
|
||||||
|
foreach (PropertyInfo propertyInfo in settingsType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (propertyInfo.Name != name)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SettingAttribute? settingAttribute = propertyInfo.GetCustomAttribute<SettingAttribute>();
|
||||||
|
if (settingAttribute is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new PropertyDescriptor(propertyInfo, settingAttribute, settingsType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestSettings : JobSettings
|
||||||
|
{
|
||||||
|
[Setting("string-names", Validator = typeof(StringItemValidator))]
|
||||||
|
public IReadOnlyCollection<string>? StringItems { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StringItemValidator : IValueValidator<string>, IValueValidator<IEnumerable<string>>
|
||||||
|
{
|
||||||
|
public StringItemValidator(string settingName)
|
||||||
|
{
|
||||||
|
ValueName = settingName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ValueName { [DebuggerStepThrough] get; }
|
||||||
|
|
||||||
|
Task<IValidationResult> IValueValidator<string>.ValidateAsync(string? settingValue, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, true, "OK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task<IValidationResult> IValueValidator.ValidateAsync(object? value, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, true, "OK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task<IValidationResult> IValueValidator<IEnumerable<string>>.ValidateAsync(IEnumerable<string>? settingValue, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ public class SubjectValidatorTest
|
|||||||
public async Task First(string subj, bool expectedSuccess)
|
public async Task First(string subj, bool expectedSuccess)
|
||||||
{
|
{
|
||||||
SubjectValidator validator = new SubjectValidator("TestSetting");
|
SubjectValidator validator = new SubjectValidator("TestSetting");
|
||||||
ValidationResult result = await validator.ValidateAsync(subj, CancellationToken.None);
|
IValidationResult result = await validator.ValidateAsync(subj, CancellationToken.None);
|
||||||
Assert.That(result.IsValid, Is.EqualTo(expectedSuccess), result.Justification);
|
Assert.That(result.IsValid, Is.EqualTo(expectedSuccess), result.Justification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user