-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6bbc7e3
commit 9164509
Showing
2 changed files
with
113 additions
and
0 deletions.
There are no files selected for viewing
61 changes: 61 additions & 0 deletions
61
src/Tingle.Extensions.Primitives/Extensions/StringProtectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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."), | ||
}; | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
tests/Tingle.Extensions.Primitives.Tests/Extensions/StringProtectionExtensionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |