diff --git a/CapyKit/Attributes/ParameterAttribute.cs b/CapyKit/Attributes/ParameterAttribute.cs new file mode 100644 index 0000000..e2dc68b --- /dev/null +++ b/CapyKit/Attributes/ParameterAttribute.cs @@ -0,0 +1,108 @@ +namespace CapyKit.Attributes; + +/// Attribute describing parameters used in automatically generating search forms for paged models. +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ParameterAttribute : Attribute +{ + #region Members + + // + + #endregion Members + + #region Properties + + /// + /// Gets or sets the name parameter as passed in a POST method from the Browser. This can be hardcoded at the class level. + /// + /// The name of the parameter. + public string ParameterName { get; private set; } + + /// + /// Gets the description of the parameter. This serves as a human-readable explanation or label for the parameter's purpose or usage. + /// + /// The description text of the parameter. + public string Description { get; private set; } + + /// Gets or sets the FontAwesome icon name. + /// The FontAwesome icon name. + public string IconName { get; private set; } + + /// Gets or sets the ordinal position used when displaying the parameter. + /// The ordinal position. + public int Ordinal { get; private set; } + + #endregion Properties + + #region Constructors + + /// Constructor. + /// The name of the parameter. + public ParameterAttribute(string parameterName) + { + this.ParameterName = parameterName; + this.Description = string.Empty; + this.IconName = string.Empty; + this.Ordinal = 0; + } + + /// Constructor. + /// The name of the system. + /// Name of the resource that contains the description of + /// the parameter. + public ParameterAttribute(string systemName, string description) + { + this.ParameterName = systemName; + this.IconName = string.Empty; + this.Description = description; + this.Ordinal = 0; + } + + /// Constructor. + /// The name of the system. + /// Name of the resource that contains the description of + /// the parameter. + /// The FontAwesome icon name. + public ParameterAttribute(string systemName, string description, string iconName) + { + this.ParameterName = systemName; + this.Description = description; + this.IconName = iconName; + this.Ordinal = 0; + } + + /// Constructor. + /// The name of the system. + /// Name of the resource that contains the description of + /// the parameter. + /// The FontAwesome icon name. + /// The ordinal position. + public ParameterAttribute(string systemName, string description, string iconName, int ordinal) + { + this.ParameterName = systemName; + this.Description = description; + this.IconName = iconName; + this.Ordinal = ordinal; + } + + /// Constructor. + /// The name of the system. + /// Name of the resource that contains the description of + /// the parameter. + /// The ordinal position. + public ParameterAttribute(string systemName, string description, int ordinal) + { + this.ParameterName = systemName; + this.Description = description; + this.IconName = string.Empty; + this.Ordinal = ordinal; + } + + #endregion Constructors + + #region Methods + + // + + #endregion Methods +} \ No newline at end of file diff --git a/CapyKit/Color.cs b/CapyKit/Color.cs deleted file mode 100644 index 87a48ff..0000000 --- a/CapyKit/Color.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CapyKit -{ - /// An object representing a color. - public class Color - { - } -} diff --git a/CapyKit/EncryptedValue.cs b/CapyKit/EncryptedValue.cs index dff9016..20d49a5 100644 --- a/CapyKit/EncryptedValue.cs +++ b/CapyKit/EncryptedValue.cs @@ -6,6 +6,10 @@ using System.Threading.Tasks; namespace CapyKit { + /// + /// Holds a value that has been encrypted. + /// + /// public class EncryptedValue { #region Members @@ -16,6 +20,9 @@ namespace CapyKit #region Properties + /// + /// The encrypted value. + /// public T Value { get; set; } #endregion diff --git a/CapyKit/Helpers/EnumerationHelper.cs b/CapyKit/Helpers/EnumerationHelper.cs new file mode 100644 index 0000000..5017a8d --- /dev/null +++ b/CapyKit/Helpers/EnumerationHelper.cs @@ -0,0 +1,102 @@ +using CapyKit.Extensions; + +namespace CapyKit.Helpers; + +/// A class that contains methods for managing objects. +public static class EnumerationHelper +{ + #region Methods + + /// Gets enumeration value from its integer value. + /// Generic type parameter representing an enumeration. + /// The integer value. + /// The enumeration value. + public static T GetEnumerationValue(int value) where T : struct, Enum + { + if (value >= 0) + { + return (T)(object)value; + } + + return default(T); + } + + /// Gets enumeration value from a string value. + /// Generic type parameter represending an enumeration. + /// The string value. + /// The enumeration value. + public static T GetEnumerationValue(string value) where T : struct, Enum + { + foreach (var t in DeconstructNonDefault()) + { + var match = string.Equals(t.GetName(), value, StringComparison.OrdinalIgnoreCase) + || string.Equals(t.GetPrettyName(), value, StringComparison.OrdinalIgnoreCase) + || string.Equals(t.GetName(), value.Replace(" ", "")); + + if (match) + { + return t; + } + } + + return default(T); + } + + /// + /// An Enum extension method that deconstructs the given enumeration into a dictionary of its + /// integer value and its name. + /// + /// Generic type parameter. + /// + /// An enumerator that allows foreach to be used to process deconstruct in this collection. + /// + public static IEnumerable Deconstruct() where T : struct, Enum + { + var dictionary = new Dictionary(); + var enumerationType = typeof(T); + + foreach (var e in Enum.GetValues(enumerationType)) + { + yield return (T)e; + } + } + + /// + /// An Enum extension method that deconstructs the given enumeration into a dictionary of its + /// integer value and its name. + /// + /// Generic type parameter. + /// The enumeration to act on. + /// + /// An enumerator that allows foreach to be used to process deconstruct in this collection. + /// + public static IEnumerable Deconstruct(this T enumeration) where T : struct, Enum + { + return Deconstruct(); + } + + /// An Enum extension method that deconstruct non default. + /// Generic type parameter. + /// + /// An enumerator that allows foreach to be used to process deconstruct non default in this + /// collection. + /// + public static IEnumerable DeconstructNonDefault() where T : struct, Enum + { + return Deconstruct().Filter(item => item.GetValue() == 0); + } + + /// An Enum extension method that deconstruct non default. + /// Generic type parameter. + /// The enumeration to act on. + /// + /// An enumerator that allows foreach to be used to process deconstruct non default in this + /// collection. + /// + public static IEnumerable DeconstructNonDefault(this T enumeration) where T : struct, Enum + { + return DeconstructNonDefault(); + } + + #endregion Methods +} \ No newline at end of file diff --git a/CapyKit/Helpers/PropertyHelper.cs b/CapyKit/Helpers/PropertyHelper.cs new file mode 100644 index 0000000..694b5ac --- /dev/null +++ b/CapyKit/Helpers/PropertyHelper.cs @@ -0,0 +1,128 @@ +using System.Linq.Expressions; +using System.Reflection; +using CapyKit.Attributes; + +namespace CapyKit.Helpers; + +/// A class that contains methods for managing programatic access to C# proerties through reflection. +/// Generic type parameter. +public static class PropertyHelper +{ + /// + /// Gets the reflected value of an instanced + /// object. + /// + /// Type of the property. + /// The instanced object. + /// Name of the property. + /// The reflected property value. + public static TProperty GetPropertyValue(T obj, string propertyName) + { + return (TProperty)GetProperty(obj, propertyName).GetValue(obj); + } + + /// + /// Gets the reflected of an instanced + /// object. + /// + /// The instanced object. + /// Name of the property. + /// The reflected . + public static PropertyInfo GetProperty(T obj, string propertyName) + { + if (obj == null) + { + throw new ArgumentNullException("obj", + string.Format("The value provided for the property {0} was null.", propertyName)); + } + + return obj.GetType().GetProperties().Where(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + } + + /// Gets the reflected value of an instanced object property value as described by a . + /// Type of the property. + /// The instanced object. + /// The property selector function that identifies the property. + /// The reflected property value. + public static TProperty GetPropertyValue(T obj, Expression> selector) + { + return (TProperty)GetProperty(selector).GetValue(obj); + } + + /// + /// Gets the reflected of an object as + /// described by a . + /// + /// Thrown when the does + /// not return a + /// of type + /// . + /// Type of the property. + /// The property selector function that identifies the property. + /// The reflected . + public static PropertyInfo GetProperty(Expression> selector) + { + Expression body = selector; + + if (body is LambdaExpression) + { + body = ((LambdaExpression)body).Body; + } + + switch (body.NodeType) + { + case ExpressionType.MemberAccess: + return (PropertyInfo)((MemberExpression)body).Member; + + default: + throw new InvalidOperationException( + "The selector provided did not return a member or property accessor."); + } + } + + /// + /// Gets an attribute from a given property. + /// + /// Type of the property. + /// The property selector function that identifies the property. + /// The enumeration description of the property. + public static EnumerationDescriptionAttribute GetEnumerationDescription( + Expression> selector) + { + return GetPropertyCustomAttribute(selector); + } + + /// + /// Gets a attribute from the given property. + /// + /// Type of the property. + /// The property selector function that identifies the property. + /// The value format of the property. + public static ValueFormatAttribute GetValueFormat(Expression> selector) + { + return GetPropertyCustomAttribute(selector); + } + + /// + /// Gets a attribute from the given property. + /// + /// Type of the property. + /// The property selector function that identifies the property. + /// The paged link parameter of the property. + public static ParameterAttribute GetPagedLinkParameter(Expression> selector) + { + return GetPropertyCustomAttribute(selector); + } + + /// Gets custom from a given property. + /// Type of the attribute. + /// Type of the property. + /// The property selector function that identifies the property. + /// The custom of the property. + public static TAttribute GetPropertyCustomAttribute(Expression> selector) + where TAttribute : Attribute + { + return GetProperty(selector).GetCustomAttribute(typeof(TAttribute)) as TAttribute; + } +} \ No newline at end of file diff --git a/CapyKit/Helpers/SecurityHelper.cs b/CapyKit/Helpers/SecurityHelper.cs index 680bf47..af929e4 100644 --- a/CapyKit/Helpers/SecurityHelper.cs +++ b/CapyKit/Helpers/SecurityHelper.cs @@ -30,7 +30,7 @@ namespace CapyKit.Helpers #endregion Members #region Methods - + public static bool CompareHashedPassword(Password existingPassword, string password, string salt, params object[] args) where T : IPasswordAlgorithm @@ -71,7 +71,8 @@ namespace CapyKit.Helpers params object[] args) where T : IPasswordAlgorithm { - if (string.IsNullOrWhiteSpace(storedHash) || string.IsNullOrEmpty(password) || string.IsNullOrWhiteSpace(salt)) + if (string.IsNullOrWhiteSpace(storedHash) || string.IsNullOrEmpty(password) || + string.IsNullOrWhiteSpace(salt)) { return false; } @@ -88,48 +89,52 @@ namespace CapyKit.Helpers } /// - /// Compares a plaintext password with a persisted hash and salt. + /// Compares a hashed password with a plaintext password by using a specified hashing algorithm . + /// This method requires the existing hash, the plaintext password to compare, and the associated salt value. /// - /// The password hashing algorithm. - /// The persisted hash. + /// The type of the password hashing algorithm implementing . + /// The stored hash of the password as a byte array. /// The plaintext password to verify. - /// The persisted salt. - /// Arguments used to construct the password algorithm. - /// when the password matches the persisted hash. + /// The salt used during the password hashing process as a byte array. + /// Optional arguments required for constructing the password hashing algorithm instance. + /// + /// if the hashed password matches the stored hash, otherwise. + /// public static bool CompareHashedPassword(byte[] storedHash, string password, byte[] salt, params object[] args) where T : IPasswordAlgorithm { - if (storedHash == null || storedHash.Length == 0 || string.IsNullOrEmpty(password) || salt == null || salt.Length == 0) + if (storedHash == null || storedHash.Length == 0 || string.IsNullOrEmpty(password) || salt == null || + salt.Length == 0) { return false; } var candidatePassword = GetPassword(password, salt, args); return candidatePassword is not null && candidatePassword.Hash.Length == storedHash.Length && - CryptographicOperations.FixedTimeEquals(storedHash, candidatePassword.Hash); + CryptographicOperations.FixedTimeEquals(storedHash, candidatePassword.Hash); } - + public static bool CompareHashedPassword(Password existingPassword, string password, string salt, IPasswordAlgorithm algorithm, params object[] args) { var saltBytes = Convert.FromBase64String(salt); - + return CompareHashedPassword(existingPassword, password, saltBytes, algorithm, args); } /// /// Compares an unencrypted with a stored, encrypted . - /// This method uses the specified to retrieve the hashed version - /// of the and then compares it with the . + /// This method uses the specified password hashing algorithm of type to hash the + /// and then compares it with the . /// - /// The existing, encrypted password. - /// The unencrypted password to be compared. - /// The salt value used in password hashing. - /// The password hashing algorithm. - /// Additional arguments required for constructing the password algorithm instance. + /// The type of the password hashing algorithm, implementing . + /// The stored, encrypted password to compare against. + /// The plain text password to be compared. + /// The salt value used for hashing the plain text password. + /// Optional additional arguments required for initializing the password hashing algorithm. /// - /// if hash comparison succeeds, if it fails. + /// if the password hashes match, otherwise . /// public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt, IPasswordAlgorithm algorithm, params object[] args) @@ -172,7 +177,8 @@ namespace CapyKit.Helpers /// instance of created using any optional constructor arguments provided. /// /// - public static Password GetPassword(string password, int saltSize, params object[] args) where T : IPasswordAlgorithm + public static Password GetPassword(string password, int saltSize, params object[] args) + where T : IPasswordAlgorithm { var salt = GetRandomBytes(saltSize); return GetPassword(password, salt, args); @@ -298,8 +304,10 @@ namespace CapyKit.Helpers "No valid characters were provided, so all valid caharacters will be assumed."); validCharacters = new[] { - ValidCharacterCollection.Lowercase, ValidCharacterCollection.Uppercase, - ValidCharacterCollection.Numbers, ValidCharacterCollection.Special + ValidCharacterCollection.Lowercase, + ValidCharacterCollection.Uppercase, + ValidCharacterCollection.Numbers, + ValidCharacterCollection.Special }; } @@ -474,4 +482,4 @@ namespace CapyKit.Helpers [EnumerationDescriptionAttribute(SecurityHelper.SPECIAL_CHARACTERS)] Special, } -} +} \ No newline at end of file diff --git a/CapyKit/Password.cs b/CapyKit/Password.cs index e0288f0..76ba48e 100644 --- a/CapyKit/Password.cs +++ b/CapyKit/Password.cs @@ -67,6 +67,17 @@ namespace CapyKit this.Salt = salt; this.Algorithm = algorithm; } + + public Password(string password, string salt, IPasswordAlgorithm algorithm, params object[] args) + { + // We know there will always be a salt, so we can prepend it to h + var saltBytes = Convert.FromBase64String(salt); + var augmented = args.Prepend(saltBytes).ToArray(); + + this.Hash = algorithm.Encrypt(password, augmented); + this.Salt = saltBytes; + this.Algorithm = algorithm; + } #region Methods diff --git a/Docs/assets/favicon.svg b/Docs/assets/favicon.svg new file mode 100644 index 0000000..04fb406 --- /dev/null +++ b/Docs/assets/favicon.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + diff --git a/Docs/assets/logo.svg b/Docs/assets/logo.svg new file mode 100644 index 0000000..052bdfd --- /dev/null +++ b/Docs/assets/logo.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + diff --git a/Docs/docfx.json b/Docs/docfx.json index 6faf4ec..a2fbbe3 100644 --- a/Docs/docfx.json +++ b/Docs/docfx.json @@ -27,6 +27,13 @@ ] } ], + "resource": [ + { + "files": [ + "assets/**" + ] + } + ], "output": "_site", "template": [ "default", @@ -36,6 +43,8 @@ "globalMetadata": { "_appName": "CapyKit", "_appTitle": "CapyKit Documentation", + "_appLogoPath": "assets/logo.svg", + "_appFaviconPath": "assets/favicon.svg", "_enableSearch": true, "pdf": false }