Skip to content

Commit

Permalink
Import StringProtectionExtensions (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
mburumaxwell authored Mar 19, 2024
1 parent 6bbc7e3 commit c4b5075
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace System;

/// <summary>
/// Specifies the position to apply protection in <see cref="string"/> objects.
/// </summary>
public enum StringProtectionPosition
{
/// <summary>
/// Protect the start. Example result: <c>**********dttJAsQVU</c>
/// </summary>
Start,

/// <summary>
/// Protect the middle. Example result: <c>e0gNH**********AsQVU</c>
/// </summary>
Middle,

/// <summary>
/// Protect the end. Example result: <c>e0gNHBa90**********</c>
/// </summary>
End,
}

/// <summary>Extension methods for <see cref="string"/>.</summary>
public static partial class StringProtectionExtensions
{
/// <summary>
/// Protect a value such as an authentication key. Useful before serialization
/// </summary>
/// <param name="input"></param>
/// <param name="toKeep"></param>
/// <param name="position"></param>
/// <param name="replacementChar"></param>
/// <param name="replacementLength"></param>
/// <returns></returns>
public static string Protect(this string input,
float toKeep = 0.2f, /* 20% is a good rule of thumb */
StringProtectionPosition position = StringProtectionPosition.End,
char replacementChar = '*',
int? replacementLength = null)
{
ArgumentNullException.ThrowIfNull(input);

var lengthToKeep = Convert.ToInt32(input.Length * toKeep); // consider a minimum
var lengthToKeepHalf = lengthToKeep / 2;
var lengthToReplace = input.Length - (lengthToKeepHalf * 2);
if (replacementLength is not null)
{
replacementLength = replacementLength <= 0 ? input.Length : replacementLength;
lengthToReplace = Math.Min(replacementLength.Value, lengthToReplace);
}
var totalWidth = Math.Min(input.Length, lengthToKeep + lengthToReplace);
return position switch
{
StringProtectionPosition.Start => input[^lengthToKeep..].PadLeft(totalWidth, replacementChar),
StringProtectionPosition.End => input[..lengthToKeep].PadRight(totalWidth, replacementChar),
StringProtectionPosition.Middle => input[..lengthToKeepHalf] + new string(replacementChar, lengthToReplace) + input[^lengthToKeepHalf..],
_ => throw new NotSupportedException($"'{nameof(StringProtectionPosition)}.{position}' is not yet supported."),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
namespace Tingle.Extensions.Modeling.Tests;

public class StringProtectionExtensionsTests
{
[Theory]
[InlineData("EcsmGa/wXv/HlA==", "Ecs*************")]
[InlineData("U6G0be/Q5wR1nExscY6Rfg==", "U6G0b*******************")]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************")]
public void Protect_Works(string original, string expected)
{
Assert.Equal(expected, original.Protect());
}

[Theory]
[InlineData("EcsmGa/wXv/HlA==", "Ecs*************", 0.2f)]
[InlineData("U6G0be/Q5wR1nExscY6Rfg==", "U6G0b*******************", 0.2f)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************", 0.2f)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90CfdKbtcWg**************************", 0.4f)]
public void Protect_Works_Respects_Fraction(string original, string expected, float fraction)
{
Assert.Equal(expected, original.Protect(toKeep: fraction));
}

[Theory]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************", StringProtectionPosition.End)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "***********************************ttJAsQVU=", StringProtectionPosition.Start)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gN************************************QVU=", StringProtectionPosition.Middle)]
public void Protect_Respects_Position(string original, string expected, StringProtectionPosition position)
{
Assert.Equal(expected, original.Protect(position: position));
}

[Theory]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************", '*')]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 'x')]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90-----------------------------------", '-')]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", '$')]
public void Protect_Respects_ReplacementChar(string original, string expected, char replacementChar)
{
Assert.Equal(expected, original.Protect(replacementChar: replacementChar));
}

[Theory]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************", null)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************", 44)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90*****", 5)]
[InlineData("e0gNHBa90CfdKbtcWgksn52cvXoXMqCTaLdttJAsQVU=", "e0gNHBa90***********************************", 0)]
public void Protect_Respects_ReplacementLength(string original, string expected, int? replacementLength)
{
Assert.Equal(expected, original.Protect(replacementLength: replacementLength));
}
}

0 comments on commit c4b5075

Please sign in to comment.