fixes, improvements
This commit is contained in:
4
certmgr/AssemblyInfo.cs
Normal file
4
certmgr/AssemblyInfo.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("certmgrTest")]
|
||||
@@ -34,9 +34,9 @@ public sealed class CertificateSettings
|
||||
|
||||
public X509KeyUsageFlags KeyUsage { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
public Task<ValidationResults> ValidateAsync(CancellationToken cancellationToken)
|
||||
public Task<IValidationResult> ValidateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ValidationResults results = new ValidationResults();
|
||||
ValidationResults results = new ValidationResults(nameof(CertificateSettings));
|
||||
|
||||
if (string.IsNullOrEmpty(SubjectName))
|
||||
{
|
||||
@@ -48,7 +48,7 @@ public sealed class CertificateSettings
|
||||
results.AddInvalid(nameof(ValidityPeriod), "must be greater than 1sec");
|
||||
}
|
||||
|
||||
return Task.FromResult(results);
|
||||
return Task.FromResult((IValidationResult)results);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -6,7 +6,7 @@ using CertMgr.Core.Validation;
|
||||
|
||||
namespace CertMgr.CertGen.Utils;
|
||||
|
||||
public sealed class SubjectValidator : ISettingValidator<string>
|
||||
public sealed class SubjectValidator : IValueValidator<string>
|
||||
{
|
||||
// the list contains most used attributes, but more of them exists. Extend the list if needed.
|
||||
private static readonly HashSet<string> AllowedAttributes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -19,16 +19,16 @@ public sealed class SubjectValidator : ISettingValidator<string>
|
||||
|
||||
public SubjectValidator(string settingName)
|
||||
{
|
||||
SettingName = settingName;
|
||||
ValueName = settingName;
|
||||
}
|
||||
|
||||
public string SettingName { [DebuggerStepThrough] get; }
|
||||
public string ValueName { [DebuggerStepThrough] get; }
|
||||
|
||||
public Task<ValidationResult> ValidateAsync(string? settingValue, CancellationToken cancellationToken)
|
||||
public Task<IValidationResult> ValidateAsync(string? settingValue, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(settingValue))
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, "must not be null"));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, "must not be null"));
|
||||
}
|
||||
|
||||
try
|
||||
@@ -39,18 +39,18 @@ public sealed class SubjectValidator : ISettingValidator<string>
|
||||
|
||||
if (!EnsureAllowedAttributes(normalizedSubject, out string? error))
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, error));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, error));
|
||||
}
|
||||
|
||||
return Task.FromResult(new ValidationResult(SettingName, true, "success"));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, true, "success"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, "invalid value: '{0}'. Exception = {1}: {2}", settingValue ?? "<null>", e.GetType().Name, e.Message));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, "invalid value: '{0}'. Exception = {1}: {2}", settingValue ?? "<null>", e.GetType().Name, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<ValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken)
|
||||
public Task<IValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValidateAsync(value as string, cancellationToken);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public sealed class SubjectValidator : ISettingValidator<string>
|
||||
break;
|
||||
}
|
||||
|
||||
// reached start or RDN (relative distinguished name)
|
||||
// reached start of RDN (relative distinguished name)
|
||||
// note that single RDN might contain multiple values (rare cases), in such a case they are split by '+' (e.g. 'CN=pankrac + OU=machinists')
|
||||
while (currentIndex < totalLength)
|
||||
{
|
||||
@@ -161,7 +161,7 @@ public sealed class SubjectValidator : ISettingValidator<string>
|
||||
continue;
|
||||
}
|
||||
|
||||
// end of RDN, break to proceed with outer while-loop on next RDN (if available)
|
||||
// end of RDN - break inner while-loop to proceed with outer while-loop on next RDN (if available)
|
||||
if (currentIndex < totalLength && normalizedSubject[currentIndex] == ',')
|
||||
{
|
||||
currentIndex++;
|
||||
@@ -240,7 +240,7 @@ public sealed class SubjectValidator : ISettingValidator<string>
|
||||
|
||||
if (firstArc < 2 && secondArc > 39)
|
||||
{
|
||||
errorMessage = string.Format("If first arc has value < 2 then second arc must be from range [0..39] (first arc value = '{0}', second arc value = '{1}')", firstArc, secondArc);
|
||||
errorMessage = string.Format("If first arc has value < 2 then second arc must be from range [0;39] (first arc value = '{0}', second arc value = '{1}')", firstArc, secondArc);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ public sealed class SettingAttribute : Attribute
|
||||
{
|
||||
Name = name;
|
||||
IsMandatory = false;
|
||||
IsSecret = false;
|
||||
AlternateNames = Array.Empty<string>();
|
||||
}
|
||||
|
||||
@@ -17,6 +18,8 @@ public sealed class SettingAttribute : Attribute
|
||||
|
||||
public bool IsMandatory { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
public bool IsSecret { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
public Type? Validator { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
public Type? Converter { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
@@ -25,6 +28,6 @@ public sealed class SettingAttribute : Attribute
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("name = '{0}', is-mandatory = {1}, validator = {2}, converter = {3}", Name, IsMandatory ? "yes" : "no", Validator?.GetType().Name ?? "<not-set>", Converter?.GetType().Name ?? "<not-set>");
|
||||
return string.Format("name = '{0}', is-mandatory = {1}, is-secret = {2}, validator = {3}, converter = {4}", Name, IsMandatory ? "yes" : "no", IsSecret ? "yes" : "no", Validator?.GetType().Name ?? "<not-set>", Converter?.GetType().Name ?? "<not-set>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ internal sealed class JobExecutor
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sb.AppendFormat("\t- {0}: {1}", vr.PropertyName, vr.Justification);
|
||||
sb.AppendFormat("\t- {0}: {1}", vr.ValueName, vr.Justification);
|
||||
sb.AppendLine();
|
||||
}
|
||||
throw new CertMgrException(sb.ToString());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using CertMgr.Core.Utils;
|
||||
using CertMgr.Core.Validation;
|
||||
|
||||
namespace CertMgr.Core.Jobs;
|
||||
@@ -8,7 +9,7 @@ public abstract class JobSettings
|
||||
{
|
||||
protected JobSettings()
|
||||
{
|
||||
ValidationResults = new ValidationResults();
|
||||
ValidationResults = new ValidationResults(GetType().ToString(false));
|
||||
}
|
||||
|
||||
public ValidationResults ValidationResults { [DebuggerStepThrough] get; }
|
||||
|
||||
146
certmgr/Core/PropertyDescriptor.cs
Normal file
146
certmgr/Core/PropertyDescriptor.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
using CertMgr.Core.Attributes;
|
||||
using CertMgr.Core.Converters;
|
||||
using CertMgr.Core.Jobs;
|
||||
using CertMgr.Core.Log;
|
||||
using CertMgr.Core.Utils;
|
||||
using CertMgr.Core.Validation;
|
||||
|
||||
namespace CertMgr.Core;
|
||||
|
||||
internal sealed class PropertyDescriptor
|
||||
{
|
||||
private readonly PropertyInfo _propertyInfo;
|
||||
private readonly SettingAttribute _settingAttribute;
|
||||
private readonly Type _settingsType;
|
||||
|
||||
internal PropertyDescriptor(PropertyInfo propertyInfo, SettingAttribute settingAttribute, Type settingsType)
|
||||
{
|
||||
_propertyInfo = propertyInfo;
|
||||
_settingAttribute = settingAttribute;
|
||||
_settingsType = settingsType;
|
||||
|
||||
PropertyTypeInfo = TypeUtils.UnwrapCollection(propertyInfo.PropertyType);
|
||||
}
|
||||
|
||||
public string SettingName => _settingAttribute.Name;
|
||||
|
||||
public string[] SettingAlternateNames => _settingAttribute.AlternateNames;
|
||||
|
||||
public bool IsMandatory => _settingAttribute.IsMandatory;
|
||||
|
||||
public bool IsSecret => _settingAttribute.IsSecret;
|
||||
|
||||
public string PropertyName => _propertyInfo.Name;
|
||||
|
||||
public Utils.TypeInfo PropertyTypeInfo { [DebuggerStepThrough] get; }
|
||||
|
||||
public Type? ValidatorType => _settingAttribute.Validator;
|
||||
|
||||
public IValueValidator? CustomValidator
|
||||
{
|
||||
get
|
||||
{
|
||||
IValueValidator? validator = null;
|
||||
|
||||
if (_settingAttribute.Validator != null)
|
||||
{
|
||||
validator = (IValueValidator?)Activator.CreateInstance(_settingAttribute.Validator, [PropertyName]);
|
||||
|
||||
if (validator == null)
|
||||
{
|
||||
CLog.Error("Failed to create instance of value-validator of type '{0}' for property '{1}' in class '{2}'", _settingAttribute.Validator.ToString(false), PropertyName, _settingsType.ToString(false));
|
||||
throw new CertMgrException("Failed to create instance of value-validator of type '{0}' for property '{1}' in class '{2}'", _settingAttribute.Validator.ToString(false), PropertyName, _settingsType.ToString(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
||||
public IValueConverter? CustomConverter
|
||||
{
|
||||
get
|
||||
{
|
||||
IValueConverter? converter = null;
|
||||
|
||||
Type? valueConverter = _settingAttribute.Converter;
|
||||
if (valueConverter != null)
|
||||
{
|
||||
if (typeof(IValueConverter).IsAssignableFrom(valueConverter))
|
||||
{
|
||||
converter = (IValueConverter?)Activator.CreateInstance(valueConverter);
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Argument '{0}' has converter specified but its type doesn't implement '{1}' and cannot be used", _settingAttribute.Name, typeof(IValueConverter));
|
||||
}
|
||||
}
|
||||
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue(JobSettings settings, object? value)
|
||||
{
|
||||
_propertyInfo.SetValue(settings, value);
|
||||
}
|
||||
|
||||
public bool TrySetDefaultValue(JobSettings settings)
|
||||
{
|
||||
bool succeeded = false;
|
||||
|
||||
if (_settingAttribute.Default != null)
|
||||
{
|
||||
Utils.TypeInfo typeInfo = TypeUtils.UnwrapCollection(_propertyInfo.PropertyType);
|
||||
if (_settingAttribute.Default.GetType() == typeInfo.ElementType)
|
||||
{
|
||||
_propertyInfo.SetValue(settings, _settingAttribute.Default);
|
||||
succeeded = 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 succeeded;
|
||||
}
|
||||
|
||||
/// <summary>Returns types that validator handles. Usually there is just one type, but class can inherit from multiple ISettingValidator<T> types.</summary>
|
||||
/// <param name="validatorType">Type used to create instance of a type</param>
|
||||
/// <returns>List of types handled by validator.</returns>
|
||||
private static IReadOnlyList<Type> GetValidatedTypes(Type validatorType)
|
||||
{
|
||||
if (validatorType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(validatorType));
|
||||
}
|
||||
|
||||
List<Type> validatedTypes = new List<Type>();
|
||||
|
||||
if (validatorType.IsInterface && validatorType.IsGenericType && validatorType.GetGenericTypeDefinition() == typeof(IValueValidator<>))
|
||||
{
|
||||
Type candidate = validatorType.GetGenericArguments()[0];
|
||||
validatedTypes.Add(candidate);
|
||||
}
|
||||
|
||||
foreach (Type iface in validatorType.GetInterfaces())
|
||||
{
|
||||
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IValueValidator<>))
|
||||
{
|
||||
Type candidate = iface.GetGenericArguments()[0];
|
||||
if (!validatedTypes.Contains(candidate))
|
||||
{
|
||||
validatedTypes.Add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validatedTypes;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
@@ -26,6 +25,66 @@ internal sealed class SettingsBuilder
|
||||
_settingsType = settingsType;
|
||||
}
|
||||
|
||||
public async Task<JobSettings> LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
JobSettings settings = CreateSettingsInstance();
|
||||
|
||||
foreach (PropertyDescriptor descriptor in GetPropertiesWithSettingAttribute())
|
||||
{
|
||||
AsyncResult<object?> setPropertyResult = await SetPropertyValueAsync(settings, descriptor, cancellationToken).ConfigureAwait(false);
|
||||
if (setPropertyResult.IsSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
IValueValidator? validator = descriptor.CustomValidator;
|
||||
if (validator != null)
|
||||
{
|
||||
IValidationResult valres = await validator.ValidateAsync(setPropertyResult.Value, cancellationToken).ConfigureAwait(false);
|
||||
settings.ValidationResults.Add(valres);
|
||||
|
||||
// IReadOnlyList<Type> validatedTypes = GetValidatedTypes(descriptor.ValidatorType);
|
||||
//
|
||||
// if (descriptor.PropertyTypeInfo.IsCollection)
|
||||
// {
|
||||
// Utils.TypeInfo validatedTypeInfo = TypeUtils.UnwrapCollection(validatedTypes[0]);
|
||||
// if (validatedTypeInfo.IsCollection)
|
||||
// {
|
||||
// // validator validates collection => send whole collection as argument to ValidateAsync(..)
|
||||
// ValidationResult valres = await validator.ValidateAsync(setPropertyResult.Value, cancellationToken).ConfigureAwait(false);
|
||||
// settings.ValidationResults.Add(valres);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // validator validates elements => send items one by one to ValidateAsync(..)
|
||||
// if (setPropertyResult.Value is IList list)
|
||||
// {
|
||||
// foreach (object value in list)
|
||||
// {
|
||||
// ValidationResult valres = await validator.ValidateAsync(value, cancellationToken).ConfigureAwait(false);
|
||||
// settings.ValidationResults.Add(valres);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // setting is not a collection. lets assume the validator doesn't validate collection as well
|
||||
// ValidationResult 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 CertMgrException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", descriptor.PropertyName, descriptor.PropertyTypeInfo.SourceType.ToString(false), _settingsType.ToString(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Type> GetValidatedTypes(Type validatorType)
|
||||
{
|
||||
if (validatorType == null)
|
||||
@@ -35,7 +94,7 @@ internal sealed class SettingsBuilder
|
||||
|
||||
List<Type> hits = new List<Type>();
|
||||
|
||||
if (validatorType.IsInterface && validatorType.IsGenericType && validatorType.GetGenericTypeDefinition() == typeof(ISettingValidator<>))
|
||||
if (validatorType.IsInterface && validatorType.IsGenericType && validatorType.GetGenericTypeDefinition() == typeof(IValueValidator<>))
|
||||
{
|
||||
Type candidate = validatorType.GetGenericArguments()[0];
|
||||
hits.Add(candidate);
|
||||
@@ -43,7 +102,7 @@ internal sealed class SettingsBuilder
|
||||
|
||||
foreach (Type iface in validatorType.GetInterfaces())
|
||||
{
|
||||
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(ISettingValidator<>))
|
||||
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IValueValidator<>))
|
||||
{
|
||||
Type candidate = iface.GetGenericArguments()[0];
|
||||
if (!hits.Contains(candidate))
|
||||
@@ -56,120 +115,52 @@ internal sealed class SettingsBuilder
|
||||
return hits;
|
||||
}
|
||||
|
||||
private async Task<AsyncResult<object?>> SetPropertyValueAsync(JobSettings settings, PropertyInfo propertyInfo, TypeInfo propertyType, SettingAttribute settingAttribute, CancellationToken cancellationToken)
|
||||
private async Task<AsyncResult<object?>> SetPropertyValueAsync(JobSettings settings, PropertyDescriptor descriptor, CancellationToken cancellationToken)
|
||||
{
|
||||
object? convertedValue = null;
|
||||
bool valueSet = false;
|
||||
|
||||
if (TryGetRawArgument(settingAttribute, out RawArgument? rawArg))
|
||||
if (TryGetRawArgument(descriptor, out RawArgument? rawArg))
|
||||
{
|
||||
try
|
||||
{
|
||||
AsyncResult<object?> conversionResult = await ConvertRawValueAsync(settingAttribute, rawArg, propertyType.IsCollection, propertyInfo.PropertyType, propertyType.ElementType, cancellationToken).ConfigureAwait(false);
|
||||
AsyncResult<object?> conversionResult = await ConvertRawValueAsync(descriptor, rawArg, cancellationToken).ConfigureAwait(false);
|
||||
if (conversionResult.IsSuccess)
|
||||
{
|
||||
propertyInfo.SetValue(settings, conversionResult.Value);
|
||||
descriptor.SetValue(settings, conversionResult.Value);
|
||||
convertedValue = conversionResult.Value;
|
||||
valueSet = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CLog.Error(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", propertyInfo.Name, propertyInfo.PropertyType.ToString(false), _settingsType.ToString(false));
|
||||
throw new CertMgrException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", propertyInfo.Name, propertyInfo.PropertyType.ToString(false), _settingsType.ToString(false));
|
||||
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 CertMgrException(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 (settingAttribute.IsMandatory)
|
||||
else if (descriptor.IsMandatory)
|
||||
{
|
||||
ValidationResult valres = new ValidationResult(settingAttribute.Name, false, "Mandatory argument is missing");
|
||||
ValidationResult valres = new ValidationResult(descriptor.SettingName, false, "Mandatory argument is missing");
|
||||
settings.ValidationResults.Add(valres);
|
||||
CLog.Error("mandatory argument '{0}' is missing", settingAttribute.Name);
|
||||
}
|
||||
else if (settingAttribute.Default != null)
|
||||
{
|
||||
TypeInfo typeInfo = UnwrapCollection(propertyInfo.PropertyType);
|
||||
if (settingAttribute.Default.GetType() == typeInfo.ElementType)
|
||||
{
|
||||
propertyInfo.SetValue(settings, settingAttribute.Default);
|
||||
CLog.Error("mandatory argument '{0}' is missing", descriptor.SettingName);
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Default value for argument '{0}' is specified, but its type is '{1}' instead of expected '{2}'", settingAttribute.Name, settingAttribute.Default?.GetType().ToString(false) ?? "<null>", typeInfo.ElementType.ToString(false));
|
||||
descriptor.TrySetDefaultValue(settings);
|
||||
}
|
||||
|
||||
}
|
||||
return new AsyncResult<object?>(valueSet, convertedValue);
|
||||
}
|
||||
|
||||
public async Task<JobSettings> LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
JobSettings settings = CreateSettingsInstance();
|
||||
|
||||
foreach ((PropertyInfo propertyInfo, SettingAttribute settingAttribute) in GetPropertiesWithSettingAttribute())
|
||||
{
|
||||
TypeInfo propertyType = UnwrapCollection(propertyInfo.PropertyType);
|
||||
|
||||
AsyncResult<object?> setPropertyResult = await SetPropertyValueAsync(settings, propertyInfo, propertyType, settingAttribute, cancellationToken).ConfigureAwait(false);
|
||||
if (setPropertyResult.IsSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (settingAttribute.Validator != null)
|
||||
{
|
||||
IReadOnlyList<Type> validatedTypes = GetValidatedTypes(settingAttribute.Validator);
|
||||
|
||||
ISettingValidator? validatorInst = (ISettingValidator?)Activator.CreateInstance(settingAttribute.Validator, [settingAttribute.Name]);
|
||||
|
||||
if (propertyType.IsCollection)
|
||||
{
|
||||
TypeInfo validatedTypeInfo = UnwrapCollection(validatedTypes[0]);
|
||||
if (validatedTypeInfo.IsCollection)
|
||||
{
|
||||
// validator validates collection => send whole collection as argument to ValidateAsync(..)
|
||||
ValidationResult valres = await validatorInst.ValidateAsync(setPropertyResult.Value, cancellationToken).ConfigureAwait(false);
|
||||
settings.ValidationResults.Add(valres);
|
||||
}
|
||||
else
|
||||
{
|
||||
// validator validates elements => send items one by one to ValidateAsync(..)
|
||||
if (setPropertyResult.Value is IList list)
|
||||
{
|
||||
foreach (object value in list)
|
||||
{
|
||||
ValidationResult valres = await validatorInst.ValidateAsync(value, cancellationToken).ConfigureAwait(false);
|
||||
settings.ValidationResults.Add(valres);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// setting is not a collection. lets assume the validator doesn't validate collection as well
|
||||
ValidationResult valres = await validatorInst.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}'", propertyInfo.Name, propertyInfo.PropertyType.ToString(false), _settingsType.ToString(false));
|
||||
throw new CertMgrException(e, "Failed to process property '{0}' (of type '{1}') in settings of type '{2}'", propertyInfo.Name, propertyInfo.PropertyType.ToString(false), _settingsType.ToString(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private bool TryGetRawArgument(SettingAttribute settingAttribute, [NotNullWhen(true)] out RawArgument? rawArg)
|
||||
private bool TryGetRawArgument(PropertyDescriptor descriptor, [NotNullWhen(true)] out RawArgument? rawArg)
|
||||
{
|
||||
rawArg = null;
|
||||
|
||||
if (!_rawArgs.TryGet(settingAttribute.Name, out rawArg))
|
||||
if (!_rawArgs.TryGet(descriptor.SettingName, out rawArg))
|
||||
{
|
||||
if (settingAttribute.AlternateNames != null)
|
||||
if (descriptor.SettingAlternateNames != null)
|
||||
{
|
||||
foreach (string altName in settingAttribute.AlternateNames)
|
||||
foreach (string altName in descriptor.SettingAlternateNames)
|
||||
{
|
||||
if (_rawArgs.TryGet(altName, out rawArg))
|
||||
{
|
||||
@@ -182,7 +173,7 @@ internal sealed class SettingsBuilder
|
||||
return rawArg != null;
|
||||
}
|
||||
|
||||
private IEnumerable<(PropertyInfo, SettingAttribute)> GetPropertiesWithSettingAttribute()
|
||||
private IEnumerable<PropertyDescriptor> GetPropertiesWithSettingAttribute()
|
||||
{
|
||||
foreach (PropertyInfo propertyInfo in _settingsType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
@@ -192,7 +183,7 @@ internal sealed class SettingsBuilder
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return (propertyInfo, settingAttribute);
|
||||
yield return new PropertyDescriptor(propertyInfo, settingAttribute, _settingsType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,21 +211,21 @@ internal sealed class SettingsBuilder
|
||||
return settings;
|
||||
}
|
||||
|
||||
private async Task<AsyncResult<object?>> ConvertRawValueAsync(SettingAttribute settingAttribute, RawArgument rawArg, bool isCollection, Type collectionType, Type elementType, CancellationToken cancellationToken)
|
||||
private async Task<AsyncResult<object?>> ConvertRawValueAsync(PropertyDescriptor descriptor, RawArgument rawArg, CancellationToken cancellationToken)
|
||||
{
|
||||
bool success = false;
|
||||
object? convertedValue = null;
|
||||
|
||||
IValueConverter? customConverter = GetCustomConverter(settingAttribute);
|
||||
IValueConverter? customConverter = descriptor.CustomConverter;
|
||||
|
||||
if (isCollection)
|
||||
if (descriptor.PropertyTypeInfo.IsCollection)
|
||||
{
|
||||
Type listType = typeof(List<>).MakeGenericType(elementType);
|
||||
Type listType = typeof(List<>).MakeGenericType(descriptor.PropertyTypeInfo.ElementType);
|
||||
IList values = (IList)Activator.CreateInstance(listType)!;
|
||||
|
||||
foreach (string rawValue in rawArg.Values)
|
||||
{
|
||||
AsyncResult<object?> converted = await ConvertValueAsync(rawValue, customConverter, elementType, cancellationToken).ConfigureAwait(false);
|
||||
AsyncResult<object?> converted = await ConvertValueAsync(rawValue, customConverter, descriptor.PropertyTypeInfo.ElementType, cancellationToken).ConfigureAwait(false);
|
||||
if (converted.IsSuccess)
|
||||
{
|
||||
values.Add(converted.Value);
|
||||
@@ -245,7 +236,7 @@ internal sealed class SettingsBuilder
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncResult<object?> converted = await ConvertValueAsync(rawArg.Values.First(), customConverter, elementType, cancellationToken).ConfigureAwait(false);
|
||||
AsyncResult<object?> converted = await ConvertValueAsync(rawArg.Values.First(), customConverter, descriptor.PropertyTypeInfo.ElementType, cancellationToken).ConfigureAwait(false);
|
||||
if (converted.IsSuccess)
|
||||
{
|
||||
convertedValue = converted.Value;
|
||||
@@ -253,11 +244,11 @@ internal sealed class SettingsBuilder
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Cannot convert value '{0}' of argument '{1}' to type '{2}'", rawArg.Values.First(), settingAttribute.Name, elementType.ToString(false));
|
||||
CLog.Error("Cannot convert value '{0}' of argument '{1}' to type '{2}'", rawArg.Values.First(), descriptor.SettingName, descriptor.PropertyTypeInfo.ElementType.ToString(false));
|
||||
}
|
||||
}
|
||||
|
||||
return new AsyncResult<object?>(success, convertedValue);
|
||||
return AsyncResult<object?>.Create(success, convertedValue);
|
||||
}
|
||||
|
||||
private async Task<AsyncResult<object?>> ConvertValueAsync(string rawValue, IValueConverter? customConverter, Type elementType, CancellationToken cancellationToken)
|
||||
@@ -330,26 +321,6 @@ internal sealed class SettingsBuilder
|
||||
return fallback;
|
||||
}*/
|
||||
|
||||
private IValueConverter? GetCustomConverter(SettingAttribute settingAttribute)
|
||||
{
|
||||
IValueConverter? customConverter = null;
|
||||
|
||||
Type? valueConverter = settingAttribute.Converter;
|
||||
if (valueConverter != null)
|
||||
{
|
||||
if (typeof(IValueConverter).IsAssignableFrom(valueConverter))
|
||||
{
|
||||
customConverter = (IValueConverter?)Activator.CreateInstance(valueConverter);
|
||||
}
|
||||
else
|
||||
{
|
||||
CLog.Error("Argument '{0}' has converter specified but its type doesn't implement '{1}' and cannot be used", settingAttribute.Name, typeof(IValueConverter));
|
||||
}
|
||||
}
|
||||
|
||||
return customConverter;
|
||||
}
|
||||
|
||||
private bool TryConvertValue(string rawValue, Type targetType, out object? convertedValue)
|
||||
{
|
||||
convertedValue = null;
|
||||
@@ -386,52 +357,4 @@ internal sealed class SettingsBuilder
|
||||
|
||||
return convertedValue != null;
|
||||
}
|
||||
|
||||
private static TypeInfo UnwrapCollection(Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return new TypeInfo(false, type);
|
||||
}
|
||||
|
||||
Type testedType = type;
|
||||
Type? underlying = Nullable.GetUnderlyingType(type);
|
||||
if (underlying != null)
|
||||
{
|
||||
testedType = underlying;
|
||||
}
|
||||
|
||||
if (testedType.IsArray)
|
||||
{
|
||||
return new TypeInfo(true, testedType.GetElementType()!);
|
||||
}
|
||||
|
||||
if (testedType.IsGenericType && testedType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return new TypeInfo(true, testedType.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
foreach (Type i in testedType.GetInterfaces())
|
||||
{
|
||||
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return new TypeInfo(true, i.GetGenericArguments()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return new TypeInfo(false, type);
|
||||
}
|
||||
|
||||
private sealed class TypeInfo
|
||||
{
|
||||
public TypeInfo(bool isCollection, Type elementType)
|
||||
{
|
||||
IsCollection = isCollection;
|
||||
ElementType = elementType;
|
||||
}
|
||||
|
||||
public bool IsCollection { [DebuggerStepThrough] get; }
|
||||
|
||||
public Type ElementType { [DebuggerStepThrough] get; }
|
||||
}
|
||||
}
|
||||
|
||||
24
certmgr/Core/Utils/TypeInfo.cs
Normal file
24
certmgr/Core/Utils/TypeInfo.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CertMgr.Core.Utils;
|
||||
|
||||
public sealed class TypeInfo
|
||||
{
|
||||
public TypeInfo(Type sourceType, bool isCollection, Type elementType)
|
||||
{
|
||||
SourceType = sourceType;
|
||||
IsCollection = isCollection;
|
||||
ElementType = elementType;
|
||||
}
|
||||
|
||||
public Type SourceType { [DebuggerStepThrough] get; }
|
||||
|
||||
public bool IsCollection { [DebuggerStepThrough] get; }
|
||||
|
||||
public Type ElementType { [DebuggerStepThrough] get; }
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
74
certmgr/Core/Utils/TypeUtils.cs
Normal file
74
certmgr/Core/Utils/TypeUtils.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
namespace CertMgr.Core.Utils;
|
||||
|
||||
public static class TypeUtils
|
||||
{
|
||||
public static TypeInfo UnwrapCollection(Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return new TypeInfo(type, false, type);
|
||||
}
|
||||
|
||||
Type testedType = type;
|
||||
Type? underlying = Nullable.GetUnderlyingType(type);
|
||||
if (underlying != null)
|
||||
{
|
||||
testedType = underlying;
|
||||
}
|
||||
|
||||
if (testedType.IsArray)
|
||||
{
|
||||
return new TypeInfo(type, true, testedType.GetElementType()!);
|
||||
}
|
||||
|
||||
if (testedType.IsGenericType && testedType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return new TypeInfo(type, true, testedType.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
foreach (Type i in testedType.GetInterfaces())
|
||||
{
|
||||
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return new TypeInfo(type, true, i.GetGenericArguments()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return new TypeInfo(type, false, type);
|
||||
}
|
||||
|
||||
public static bool IsCollection(Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type testedType = type;
|
||||
Type? underlying = Nullable.GetUnderlyingType(type);
|
||||
if (underlying != null)
|
||||
{
|
||||
testedType = underlying;
|
||||
}
|
||||
|
||||
if (testedType.IsArray)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (testedType.IsGenericType && testedType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (Type i in testedType.GetInterfaces())
|
||||
{
|
||||
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public interface ISettingValidator
|
||||
{
|
||||
string SettingName { [DebuggerStepThrough] get; }
|
||||
|
||||
Task<ValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public interface ISettingValidator<T> : ISettingValidator
|
||||
{
|
||||
Task<ValidationResult> ValidateAsync(T? settingValue, CancellationToken cancellationToken);
|
||||
}
|
||||
10
certmgr/Core/Validation/IValidationResult.cs
Normal file
10
certmgr/Core/Validation/IValidationResult.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public interface IValidationResult
|
||||
{
|
||||
string ValueName { get; }
|
||||
|
||||
string Justification { get; }
|
||||
|
||||
bool IsValid { get; }
|
||||
}
|
||||
10
certmgr/Core/Validation/IValueValidator.cs
Normal file
10
certmgr/Core/Validation/IValueValidator.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public interface IValueValidator
|
||||
{
|
||||
string ValueName { [DebuggerStepThrough] get; }
|
||||
|
||||
Task<IValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken);
|
||||
}
|
||||
6
certmgr/Core/Validation/IValueValidatorT.cs
Normal file
6
certmgr/Core/Validation/IValueValidatorT.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public interface IValueValidator<T> : IValueValidator
|
||||
{
|
||||
Task<IValidationResult> ValidateAsync(T? settingValue, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public abstract class SettingValidator<T> : ISettingValidator<T>
|
||||
{
|
||||
protected SettingValidator(string settingName)
|
||||
{
|
||||
SettingName = settingName;
|
||||
}
|
||||
|
||||
public string SettingName { [DebuggerStepThrough] get; }
|
||||
|
||||
public abstract Task<ValidationResult> ValidateAsync(T? settingValue, CancellationToken cancellationToken);
|
||||
|
||||
public Task<ValidationResult> ValidateAsync(object? settingValue, CancellationToken cancellationToken)
|
||||
{
|
||||
T? typedValue;
|
||||
try
|
||||
{
|
||||
typedValue = (T?)settingValue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new CertMgrException(e, "SettingValidator of type '{0}' failed to convert value of type '{1}' to type '{2}' (setting-name = '{3}')", GetType().ToString(false), settingValue?.GetType().ToString(false) ?? "<null>", typeof(T).ToString(false), SettingName);
|
||||
}
|
||||
|
||||
return ValidateAsync(typedValue, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,22 @@ namespace CertMgr.Core.Validation;
|
||||
|
||||
public sealed class StringValidator
|
||||
{
|
||||
public sealed class IsNotNull : ISettingValidator<string>
|
||||
public sealed class IsNotNullValidator : IValueValidator<string>
|
||||
{
|
||||
public IsNotNull(string settingName)
|
||||
public IsNotNullValidator(string valueName)
|
||||
{
|
||||
SettingName = settingName;
|
||||
ValueName = valueName;
|
||||
}
|
||||
|
||||
public string SettingName { [DebuggerStepThrough] get; }
|
||||
public string ValueName { [DebuggerStepThrough] get; }
|
||||
|
||||
public Task<ValidationResult> ValidateAsync(string? value, CancellationToken cancellationToken)
|
||||
public Task<IValidationResult> ValidateAsync(string? value, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidationResult result = new ValidationResult(SettingName, value != null, "value is null");
|
||||
IValidationResult result = new ValidationResult(ValueName, value != null, "value is null");
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<ValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken)
|
||||
public Task<IValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValidateAsync(value as string, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public sealed class ValidationResult
|
||||
public class ValidationResult : IValidationResult
|
||||
{
|
||||
public ValidationResult(string propertyName, bool isValid, string justificationFormat, params object?[] justificationArgs)
|
||||
public ValidationResult(string valueName, bool isValid, string justificationFormat, params object?[] justificationArgs)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
ValueName = valueName;
|
||||
Justification = StringFormatter.Format(justificationFormat, justificationArgs);
|
||||
IsValid = isValid;
|
||||
}
|
||||
|
||||
public string PropertyName { [DebuggerStepThrough] get; }
|
||||
public string ValueName { [DebuggerStepThrough] get; }
|
||||
|
||||
public string Justification { [DebuggerStepThrough] get; }
|
||||
|
||||
public bool IsValid { [DebuggerStepThrough] get; }
|
||||
public virtual bool IsValid { [DebuggerStepThrough] get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}: {1} => {2}", PropertyName, IsValid ? "valid" : "not valid", Justification);
|
||||
return string.Format("{0}: is-valid = {1} => '{2}'", ValueName, IsValid ? "yes" : "no", Justification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,40 +2,41 @@
|
||||
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public sealed class ValidationResults : IReadOnlyCollection<ValidationResult>
|
||||
public sealed class ValidationResults : ValidationResult, IReadOnlyCollection<IValidationResult>
|
||||
{
|
||||
private readonly List<ValidationResult> _results;
|
||||
private readonly List<IValidationResult> _results;
|
||||
|
||||
internal ValidationResults()
|
||||
internal ValidationResults(string valueName)
|
||||
: base(valueName, true, "See subresults")
|
||||
{
|
||||
_results = new List<ValidationResult>();
|
||||
_results = new List<IValidationResult>();
|
||||
}
|
||||
|
||||
public int Count => _results.Count;
|
||||
|
||||
public bool IsValid => _results.All(res => res.IsValid);
|
||||
public override bool IsValid => _results.All(res => res.IsValid);
|
||||
|
||||
internal void Add(ValidationResult result)
|
||||
internal void Add(IValidationResult result)
|
||||
{
|
||||
_results.Add(result);
|
||||
}
|
||||
|
||||
internal void Add(IReadOnlyCollection<ValidationResult> results)
|
||||
internal void Add(IReadOnlyCollection<IValidationResult> results)
|
||||
{
|
||||
_results.AddRange(results);
|
||||
}
|
||||
|
||||
internal void AddValid(string propertyName, string justificationFormat, params object?[] justificationArgs)
|
||||
internal void AddValid(string valueName, string justificationFormat, params object?[] justificationArgs)
|
||||
{
|
||||
Add(new ValidationResult(propertyName, true, justificationFormat, justificationArgs));
|
||||
Add(new ValidationResult(valueName, true, justificationFormat, justificationArgs));
|
||||
}
|
||||
|
||||
internal void AddInvalid(string propertyName, string justificationFormat, params object?[] justificationArgs)
|
||||
internal void AddInvalid(string valueName, string justificationFormat, params object?[] justificationArgs)
|
||||
{
|
||||
Add(new ValidationResult(propertyName, false, justificationFormat, justificationArgs));
|
||||
Add(new ValidationResult(valueName, false, justificationFormat, justificationArgs));
|
||||
}
|
||||
|
||||
public IEnumerator<ValidationResult> GetEnumerator()
|
||||
public IEnumerator<IValidationResult> GetEnumerator()
|
||||
{
|
||||
return _results.GetEnumerator();
|
||||
}
|
||||
@@ -47,6 +48,6 @@ public sealed class ValidationResults : IReadOnlyCollection<ValidationResult>
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("is-valid = {0}, total-count = {1}, invalid-count = {2}", IsValid ? "yes" : "no", _results.Count, _results.Where(r => !r.IsValid).Count());
|
||||
return string.Format("{0}: is-valid = {1}, total-count = {2}, invalid-count = {3}", ValueName, IsValid ? "yes" : "no", _results.Count, _results.Where(r => !r.IsValid).Count());
|
||||
}
|
||||
}
|
||||
|
||||
32
certmgr/Core/Validation/ValueValidatorT.cs
Normal file
32
certmgr/Core/Validation/ValueValidatorT.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using CertMgr.Core.Utils;
|
||||
|
||||
namespace CertMgr.Core.Validation;
|
||||
|
||||
public abstract class ValueValidator<T> : IValueValidator<T>
|
||||
{
|
||||
protected ValueValidator(string valueName)
|
||||
{
|
||||
ValueName = valueName;
|
||||
}
|
||||
|
||||
public string ValueName { [DebuggerStepThrough] get; }
|
||||
|
||||
public abstract Task<IValidationResult> ValidateAsync(T? value, CancellationToken cancellationToken);
|
||||
|
||||
public Task<IValidationResult> ValidateAsync(object? value, CancellationToken cancellationToken)
|
||||
{
|
||||
T? typedValue;
|
||||
try
|
||||
{
|
||||
typedValue = (T?)value;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new CertMgrException(e, "'{0}': failed to convert value of type '{1}' to type '{2}' (value-name = '{3}')", GetType().ToString(false), value?.GetType().ToString(false) ?? "<null>", typeof(T).ToString(false), ValueName);
|
||||
}
|
||||
|
||||
return ValidateAsync(typedValue, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public sealed class CertificateSettings : JobSettings
|
||||
// - DNS:hostname (i.e. with 'DNS:' prefix)
|
||||
// - IP:ip-address (i.e. with 'IP:' prefix)
|
||||
// other types (URI, UPN, email) are not supported
|
||||
[Setting("subject-alternate-name", AlternateNames = ["san"], Validator = typeof(SubjectAlternateNameValidator))]
|
||||
[Setting("subject-alternate-name", AlternateNames = ["san"], Validator = typeof(SubjectAlternateNamesValidator))]
|
||||
public IReadOnlyCollection<string>? SubjectAlternateNames { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("algorithm", Default = CertificateAlgorithm.ECDsa, Converter = typeof(EnumConverter))]
|
||||
@@ -49,13 +49,13 @@ public sealed class CertificateSettings : JobSettings
|
||||
[Setting("issuer-certificate", Converter = typeof(StorageConverter))]
|
||||
public IStorage? Issuer { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("issuer-password")]
|
||||
[Setting("issuer-password", IsSecret = true)]
|
||||
public string? IssuerPassword { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("storage", IsMandatory = true, Converter = typeof(StorageConverter))]
|
||||
public IStorage? Storage { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("password")]
|
||||
[Setting("password", IsSecret = true)]
|
||||
public string? Password { [DebuggerStepThrough] get; [DebuggerStepThrough] set; }
|
||||
|
||||
[Setting("validity-period", Default = "365d", Converter = typeof(TimeSpanConverter))]
|
||||
|
||||
@@ -41,6 +41,7 @@ public sealed class CreateCertificateJob : Job<CertificateSettings>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return CreateSuccess("Certificate was successfully created");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,33 +3,33 @@ using CertMgr.Core.Validation;
|
||||
|
||||
namespace CertMgr.Jobs;
|
||||
|
||||
internal sealed class SubjectAlternateNameValidator : SettingValidator<string>
|
||||
internal sealed class SubjectAlternateNameValidator : ValueValidator<string>
|
||||
{
|
||||
public SubjectAlternateNameValidator(string settingName)
|
||||
: base(settingName)
|
||||
public SubjectAlternateNameValidator(string valueName)
|
||||
: base(valueName)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<ValidationResult> ValidateAsync(string? settingValue, CancellationToken cancellationToken)
|
||||
public override Task<IValidationResult> ValidateAsync(string? value, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(settingValue))
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, "value must not be empty"));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, "value must not be empty"));
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> span = settingValue.AsSpan();
|
||||
ReadOnlySpan<char> span = value.AsSpan();
|
||||
if (span.StartsWith("DNS:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!NetUtils.IsValidDns(span.Slice(4)))
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, "value '{0}' is not valid DNS name", span.Slice(4).ToString()));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, "value '{0}' is not valid DNS name", span.Slice(4).ToString()));
|
||||
}
|
||||
}
|
||||
else if (span.StartsWith("IP:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!NetUtils.IsValidIPAny(span.Slice(3)))
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, "value '{0}' is not valid IP address", span.Slice(3).ToString()));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, "value '{0}' is not valid IP address", span.Slice(3).ToString()));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -37,10 +37,10 @@ internal sealed class SubjectAlternateNameValidator : SettingValidator<string>
|
||||
// fallback to dns name as other alt-names (UPN, email, URI..) are not supported
|
||||
if (!NetUtils.IsValidDns(span))
|
||||
{
|
||||
return Task.FromResult(new ValidationResult(SettingName, false, "value '{0}' is not valid DNS name (no prefix)", span.Slice(4).ToString()));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, false, "value '{0}' is not valid DNS name (no prefix)", span.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(new ValidationResult(SettingName, true, "valid"));
|
||||
return Task.FromResult((IValidationResult)new ValidationResult(ValueName, true, "valid"));
|
||||
}
|
||||
}
|
||||
|
||||
32
certmgr/Jobs/SubjectAlternateNamesValidator.cs
Normal file
32
certmgr/Jobs/SubjectAlternateNamesValidator.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using CertMgr.Core.Validation;
|
||||
|
||||
namespace CertMgr.Jobs;
|
||||
|
||||
internal sealed class SubjectAlternateNamesValidator : ValueValidator<IEnumerable<string>>
|
||||
{
|
||||
public SubjectAlternateNamesValidator(string valueName)
|
||||
: base(valueName)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<IValidationResult> ValidateAsync(IEnumerable<string>? values, CancellationToken cancellationToken)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
return new ValidationResult(ValueName, false, "collection is null");
|
||||
}
|
||||
|
||||
ValidationResults results = new ValidationResults(ValueName);
|
||||
|
||||
int index = 0;
|
||||
foreach (string value in values)
|
||||
{
|
||||
SubjectAlternateNameValidator validator = new SubjectAlternateNameValidator(ValueName + "[" + index + "]");
|
||||
IValidationResult subresult = await validator.ValidateAsync(value, cancellationToken).ConfigureAwait(false);
|
||||
results.Add(subresult);
|
||||
index++;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user