Skip to content
This repository has been archived by the owner on Jul 5, 2023. It is now read-only.

Feature/hashedtext type #126

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace Nox.Types.EntityFramework.Types;


public class HashedTextConverter : ValueConverter<HashedText, string>
{
public HashedTextConverter() : base(hashedText => hashedText.Value, hashedTextValue => HashedText.From(hashedTextValue)) { }
}

74 changes: 69 additions & 5 deletions src/Nox.Types/Types/HashedText/HashedText.cs
rochar marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,9 +1,73 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Nox.Types;

/// <summary>
/// Represents a Nox <see cref="HashedText"/> type and value object.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
public sealed class HashedText : ValueObject<uint, HashedText>
/// <summary>
/// Represents a Nox <see cref="HashedText"/> type and value object.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
danield-ap marked this conversation as resolved.
Show resolved Hide resolved
public sealed class HashedText : ValueObject<string, HashedText>
{
public HashedText() : base() { Value = string.Empty; }

private HashedTextTypeOptions _hashedTextTypeOptions = new();

public static HashedText From(string value, HashedTextTypeOptions options)
{
options ??= new HashedTextTypeOptions();

var newObject = new HashedText
{
Value = HashText(value, options),
_hashedTextTypeOptions = options
};

var validationResult = newObject.Validate();

if (!validationResult.IsValid)
{
throw new TypeValidationException(validationResult.Errors);
}

return newObject;
}

new public static HashedText From(string value)
=> From(value, new HashedTextTypeOptions());

public bool Equals(string value)
{
if (value == null) throw new ArgumentNullException("value", "Text to hash cannot be null.");

// check if value is already hashed
if(value.Equals(Value)) return true;

string hashedText = HashText(value, _hashedTextTypeOptions);

return hashedText.Equals(Value);
}

private static string HashText(string plainText, HashedTextTypeOptions hashedTextTypeOptions)
{
string hashedText = string.Empty;
using (var hasher = CreateHasher(hashedTextTypeOptions.HashingAlgorithm))
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes($"{plainText}{hashedTextTypeOptions.Salt}");
byte[] hashBytes = hasher.ComputeHash(plainTextBytes);
hashedText = Convert.ToBase64String(hashBytes);
}

return hashedText;
}


static HashAlgorithm CreateHasher(HashingAlgorithm hashAlgorithm)
{
HashAlgorithm hasher = HashAlgorithm.Create(hashAlgorithm.ToString());

return hasher ?? throw new CryptographicException("Invalid hash algorithm");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Nox.Types;

public class HashedTextTypeOptions
{
public HashingAlgorithm HashingAlgorithm { get; set; } = HashingAlgorithm.SHA256;
public string Salt { get; set; } = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

namespace Nox.Types;

public enum HashingAlgorithm
{
SHA256,
SHA512
}
65 changes: 64 additions & 1 deletion tests/Nox.Types.Tests/Types/HashedText/HashedTextTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,74 @@
// ReSharper disable once CheckNamespace
using System.Security.Cryptography;

namespace Nox.Types.Tests.Types;

public class HashedTextTests
{

rochar marked this conversation as resolved.
Show resolved Hide resolved
[Fact]
public void Nox_HashedText_Constructor_WithoutOptions_ReturnsHashedValue()
rochar marked this conversation as resolved.
Show resolved Hide resolved
{
string text = "Text to hash";
var hashedText = HashedText.From(text);

Assert.NotNull(hashedText);
Assert.NotNull(hashedText.Value);
Assert.NotEqual(text, hashedText.Value);
}


[Fact]
public void Nox_HashedText_Constructor_WithOptions_ReturnsHashedValue()
{
string text = "Text to hash";
string textHashedExpected = string.Empty;

using (var sha = SHA512.Create())
{
byte[] textData = System.Text.Encoding.UTF8.GetBytes(text);
byte[] hash = sha.ComputeHash(textData);
textHashedExpected = Convert.ToBase64String(hash);
}

var hashedText = HashedText.From(text, new HashedTextTypeOptions() { HashingAlgorithm = HashingAlgorithm.SHA512, Salt="" });

Assert.Equal(textHashedExpected, hashedText.Value);
}

[Fact]
public void Nox_HashedText_Equal_CompareHashedValues_NoSalting()
{
string text = "Text to hash";
string textHashedExpected = string.Empty;

using (var sha = SHA512.Create())
{
byte[] textData = System.Text.Encoding.UTF8.GetBytes(text);
byte[] hash = sha.ComputeHash(textData);
textHashedExpected = Convert.ToBase64String(hash);
}

var hashedText = HashedText.From(text, new HashedTextTypeOptions() { HashingAlgorithm = HashingAlgorithm.SHA512, Salt="" });

Assert.True(hashedText.Equals(textHashedExpected));
}

[Fact]
public void Nox_HashedText_Equals_ReturnsTrue()
{
string text = "Text to hash";
var hashedText = HashedText.From(text);

Assert.True(hashedText.Equals(text));
}

[Fact]
public void When_Create_Should()
public void Nox_HashedText_Equals_ReturnsFalse()
{
string text = "Text to hash";
var hashedText = HashedText.From($"{text} 1");

Assert.False(hashedText.Equals(text));
}
}