diff --git a/EnvelopeGenerator.Common/Constants.vb b/EnvelopeGenerator.Common/Constants.vb index 487a8a7a..d47d5bce 100644 --- a/EnvelopeGenerator.Common/Constants.vb +++ b/EnvelopeGenerator.Common/Constants.vb @@ -99,6 +99,12 @@ DocumentShared End Enum + Public Enum EncodeType + EnvelopeReceiver + EnvelopeReceiverReadOnly + Undefined + End Enum + #End Region #Region "Constants" diff --git a/EnvelopeGenerator.Extensions/DecodingExtensions.cs b/EnvelopeGenerator.Extensions/DecodingExtensions.cs new file mode 100644 index 00000000..1fa41809 --- /dev/null +++ b/EnvelopeGenerator.Extensions/DecodingExtensions.cs @@ -0,0 +1,148 @@ +using System.Text; +using static EnvelopeGenerator.Common.Constants; + +namespace EnvelopeGenerator.Extensions +{ + public static class DecodingExtensions + { + /// + /// Validates whether a given string is a correctly formatted Base-64 encoded string. + /// + /// + /// This method checks the string for proper Base-64 formatting, which includes validating + /// the length of the string (must be divisible by 4). It also checks each character to ensure + /// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding). + /// The method ensures that padding characters ('=') only appear at the end of the string and + /// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3, + /// or two '==' if the length % 4 is 2). + /// + /// The Base-64 encoded string to validate. + /// + /// true if the string is a valid Base-64 encoded string; otherwise, false. + /// + /// + /// + /// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk="; + /// bool isValid = IsValidBase64String(testString); + /// Console.WriteLine(isValid); // Output: true + /// + /// + public static bool IsBase64String(this string input) + { + // Check if the string is null or empty + if (string.IsNullOrEmpty(input)) + { + return false; + } + + // Replace valid base-64 padding + input = input.Trim(); + int mod4 = input.Length % 4; + if (mod4 > 0) + { + // Base-64 string lengths should be divisible by 4 + return false; + } + + // Check each character to ensure it is valid base-64 + foreach (char c in input) + { + if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=') + { + // Invalid character detected + return false; + } + } + + // Ensure no invalid padding scenarios exist + if (input.EndsWith("==") && (input.Length % 4 == 0) || + input.EndsWith("=") && (input.Length % 4 == 3)) + { + return true; + } + + return input.IndexOf('=') == -1; // No padding allowed except at the end + } + + public static bool TryDecode(this string encoded, out string[] decoded) + { + if (!encoded.IsBase64String()) + { + decoded = Array.Empty(); + return false; + } + byte[] bytes = Convert.FromBase64String(encoded); + string decodedString = Encoding.UTF8.GetString(bytes); + decoded = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); + return true; + } + + public static EncodeType GetEncodeType(this string[] decoded) => decoded.Length switch + { + 2 => EncodeType.EnvelopeReceiver, + 3 => long.TryParse(decoded[1], out var _) ? EncodeType.EnvelopeReceiverReadOnly : EncodeType.Undefined, + _ => EncodeType.Undefined, + }; + + public static (string? EnvelopeUuid, string? ReceiverSignature) ToEnvelopeReceiverId(this string[] decoded) + => decoded.GetEncodeType() == EncodeType.EnvelopeReceiver + ? (EnvelopeUuid: decoded[0], ReceiverSignature: decoded[1]) + : throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver. "); + + public static long ToReadOnlyId(this string[] decoded) + => decoded.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly + ? long.Parse(decoded[1]) + : throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver. "); + + /// + /// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature. + /// + /// The base64 encoded string containing the envelope UUID and receiver signature. + /// A tuple containing the envelope UUID and receiver signature. + public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId) + { + if (!envelopeReceiverId.IsBase64String()) + { + return (null, null); + } + byte[] bytes = Convert.FromBase64String(envelopeReceiverId); + string decodedString = Encoding.UTF8.GetString(bytes); + string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); + + if (parts.Length > 1) + return (EnvelopeUuid: parts[0], ReceiverSignature: parts[1]); + else + return (string.Empty, string.Empty); + } + + public static long? DecodeEnvelopeReceiverReadOnlyId(this string envelopeReceiverReadOnlyId) + { + if (!envelopeReceiverReadOnlyId.IsBase64String()) + { + return null; + } + byte[] bytes = Convert.FromBase64String(envelopeReceiverReadOnlyId); + string decodedString = System.Text.Encoding.UTF8.GetString(bytes); + string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); + + if (parts.Length > 2) + return long.TryParse(parts[1], out long readOnlyId) ? readOnlyId : null; + else + return null; + } + + /// + /// Gets the envelope UUID from the decoded envelope receiver ID. + /// + /// The base64 encoded string to decode. + /// The envelope UUID. + public static string? GetEnvelopeUuid(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().EnvelopeUuid; + + /// + /// Gets the receiver signature from the decoded envelope receiver ID. + /// + /// The base64 encoded string to decode. + /// The receiver signature. + public static string? GetReceiverSignature(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().ReceiverSignature; + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Extensions/EncodeType.cs b/EnvelopeGenerator.Extensions/EncodeType.cs deleted file mode 100644 index 41c706a7..00000000 --- a/EnvelopeGenerator.Extensions/EncodeType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace EnvelopeGenerator.Extensions -{ - public enum EncodeType - { - EnvelopeReceiver, - ReadOnly, - Undefined - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.Extensions/EncodingExtensions.cs b/EnvelopeGenerator.Extensions/EncodingExtensions.cs index f35c07d5..f2e0a1f8 100644 --- a/EnvelopeGenerator.Extensions/EncodingExtensions.cs +++ b/EnvelopeGenerator.Extensions/EncodingExtensions.cs @@ -8,155 +8,6 @@ namespace EnvelopeGenerator.Extensions /// public static class EncodingExtensions { - /// - /// Validates whether a given string is a correctly formatted Base-64 encoded string. - /// - /// - /// This method checks the string for proper Base-64 formatting, which includes validating - /// the length of the string (must be divisible by 4). It also checks each character to ensure - /// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding). - /// The method ensures that padding characters ('=') only appear at the end of the string and - /// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3, - /// or two '==' if the length % 4 is 2). - /// - /// The Base-64 encoded string to validate. - /// - /// true if the string is a valid Base-64 encoded string; otherwise, false. - /// - /// - /// - /// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk="; - /// bool isValid = IsValidBase64String(testString); - /// Console.WriteLine(isValid); // Output: true - /// - /// - public static bool IsBase64String(this string input) - { - // Check if the string is null or empty - if (string.IsNullOrEmpty(input)) - { - return false; - } - - // Replace valid base-64 padding - input = input.Trim(); - int mod4 = input.Length % 4; - if (mod4 > 0) - { - // Base-64 string lengths should be divisible by 4 - return false; - } - - // Check each character to ensure it is valid base-64 - foreach (char c in input) - { - if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=') - { - // Invalid character detected - return false; - } - } - - // Ensure no invalid padding scenarios exist - if (input.EndsWith("==") && (input.Length % 4 == 0) || - input.EndsWith("=") && (input.Length % 4 == 3)) - { - return true; - } - - return input.IndexOf('=') == -1; // No padding allowed except at the end - } - - public static bool TryDecode(this string encoded, out string[] decoded) - { - if (!encoded.IsBase64String()) - { - decoded = Array.Empty(); - return false; - } - byte[] bytes = Convert.FromBase64String(encoded); - string decodedString = Encoding.UTF8.GetString(bytes); - decoded = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); - return true; - } - - public static EncodeType GetEncodeType(this string[] decoded) => decoded.Length switch - { - 2 => EncodeType.EnvelopeReceiver, - 3 => long.TryParse(decoded[1], out var _) ? EncodeType.ReadOnly : EncodeType.Undefined, - _ => EncodeType.Undefined, - }; - - public static (string? EnvelopeUuid, string? ReceiverSignature) ToEnvelopeReceiverId(this string[] decoded) - => decoded.GetEncodeType() == EncodeType.EnvelopeReceiver - ? (EnvelopeUuid: decoded[0], ReceiverSignature: decoded[1]) - : throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver. "); - - public static long ToReadOnlyId(this string[] decoded) - => decoded.GetEncodeType() == EncodeType.ReadOnly - ? long.Parse(decoded[1]) - : throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver. "); - - /// - /// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature. - /// - /// The base64 encoded string containing the envelope UUID and receiver signature. - /// A tuple containing the envelope UUID and receiver signature. - public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId) - { - if (!envelopeReceiverId.IsBase64String()) - { - return (null, null); - } - byte[] bytes = Convert.FromBase64String(envelopeReceiverId); - string decodedString = Encoding.UTF8.GetString(bytes); - string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); - - if (parts.Length > 1) - return (EnvelopeUuid: parts[0], ReceiverSignature: parts[1]); - else - return (string.Empty, string.Empty); - } - - public static long? DecodeEnvelopeReceiverReadOnlyId(this string envelopeReceiverReadOnlyId) - { - if (!envelopeReceiverReadOnlyId.IsBase64String()) - { - return null; - } - byte[] bytes = Convert.FromBase64String(envelopeReceiverReadOnlyId); - string decodedString = System.Text.Encoding.UTF8.GetString(bytes); - string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); - - if (parts.Length > 2) - return long.TryParse(parts[1], out long readOnlyId) ? readOnlyId : null; - else - return null; - } - - /// - /// Gets the envelope UUID from the decoded envelope receiver ID. - /// - /// The base64 encoded string to decode. - /// The envelope UUID. - public static string? GetEnvelopeUuid(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().EnvelopeUuid; - - /// - /// Gets the receiver signature from the decoded envelope receiver ID. - /// - /// The base64 encoded string to decode. - /// The receiver signature. - public static string? GetReceiverSignature(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().ReceiverSignature; - - public static string EncodeEnvelopeReceiverId(this (string envelopeUuid, string receiverSignature) input) - { - string combinedString = $"{input.envelopeUuid}::{input.receiverSignature}"; - byte[] bytes = Encoding.UTF8.GetBytes(combinedString); - string base64String = Convert.ToBase64String(bytes); - - return base64String; - } - public static string EncodeEnvelopeReceiverId(this long readOnlyId) { string combinedString = $"{Random.Shared.Next()}::{readOnlyId}::{Random.Shared.Next()}"; @@ -166,38 +17,13 @@ namespace EnvelopeGenerator.Extensions return base64String; } - public static void LogEnvelopeError(this ILogger logger, string envelopeReceiverId, Exception? exception = null, string? message = null, params object?[] args) + public static string EncodeEnvelopeReceiverId(this (string envelopeUuid, string receiverSignature) input) { - var sb = new StringBuilder().AppendLine(envelopeReceiverId.DecodeEnvelopeReceiverId().ToTitle()); + string combinedString = $"{input.envelopeUuid}::{input.receiverSignature}"; + byte[] bytes = Encoding.UTF8.GetBytes(combinedString); + string base64String = Convert.ToBase64String(bytes); - if (message is not null) - sb.AppendLine(message); - - if(exception is null) - logger.Log(LogLevel.Error, sb.ToString(), args); - else - logger.Log(LogLevel.Error, exception, sb.AppendLine(exception.Message).ToString(), args); - } - - public static void LogEnvelopeError(this ILogger logger, string? uuid, string? signature = null, Exception? exception = null, string? message = null, params object?[] args) - { - var sb = new StringBuilder($"Envelope Uuid: {uuid}"); - - if(signature is not null) - sb.AppendLine().Append($"Receiver Signature: {signature}"); - - if (message is not null) - sb.AppendLine().Append(message); - - if (exception is null) - logger.Log(LogLevel.Error, sb.ToString(), args); - else - logger.Log(LogLevel.Error, exception, sb.ToString(), args); - } - - public static string ToTitle(this (string? UUID, string? Signature) envelopeReceiverTuple) - { - return $"UUID is {envelopeReceiverTuple.UUID} and signature is {envelopeReceiverTuple.Signature}"; + return base64String; } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj b/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj index 2f15281d..8467bc11 100644 --- a/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj +++ b/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj @@ -7,7 +7,13 @@ + + + + + + diff --git a/EnvelopeGenerator.Extensions/LoggerExtensions.cs b/EnvelopeGenerator.Extensions/LoggerExtensions.cs new file mode 100644 index 00000000..b1c56974 --- /dev/null +++ b/EnvelopeGenerator.Extensions/LoggerExtensions.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Logging; +using System.Text; + +namespace EnvelopeGenerator.Extensions +{ + public static class LoggerExtensions + { + public static void LogEnvelopeError(this ILogger logger, string envelopeReceiverId, Exception? exception = null, string? message = null, params object?[] args) + { + var sb = new StringBuilder().AppendLine(envelopeReceiverId.DecodeEnvelopeReceiverId().ToTitle()); + + if (message is not null) + sb.AppendLine(message); + + if (exception is null) + logger.Log(LogLevel.Error, sb.ToString(), args); + else + logger.Log(LogLevel.Error, exception, sb.AppendLine(exception.Message).ToString(), args); + } + + public static void LogEnvelopeError(this ILogger logger, string? uuid, string? signature = null, Exception? exception = null, string? message = null, params object?[] args) + { + var sb = new StringBuilder($"Envelope Uuid: {uuid}"); + + if (signature is not null) + sb.AppendLine().Append($"Receiver Signature: {signature}"); + + if (message is not null) + sb.AppendLine().Append(message); + + if (exception is null) + logger.Log(LogLevel.Error, sb.ToString(), args); + else + logger.Log(LogLevel.Error, exception, sb.ToString(), args); + } + + public static string ToTitle(this (string? UUID, string? Signature) envelopeReceiverTuple) + { + return $"UUID is {envelopeReceiverTuple.UUID} and signature is {envelopeReceiverTuple.Signature}"; + } + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/XSSExtensions.cs b/EnvelopeGenerator.Extensions/XSSExtensions.cs similarity index 94% rename from EnvelopeGenerator.Web/XSSExtensions.cs rename to EnvelopeGenerator.Extensions/XSSExtensions.cs index 9b414fd6..3649df9d 100644 --- a/EnvelopeGenerator.Web/XSSExtensions.cs +++ b/EnvelopeGenerator.Extensions/XSSExtensions.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Localization; using System.Text.Encodings.Web; -namespace EnvelopeGenerator.Web +namespace EnvelopeGenerator.Extensions { public static class XSSExtensions { diff --git a/EnvelopeGenerator.Web/Controllers/HomeController.cs b/EnvelopeGenerator.Web/Controllers/HomeController.cs index b987067d..3cedf1ac 100644 --- a/EnvelopeGenerator.Web/Controllers/HomeController.cs +++ b/EnvelopeGenerator.Web/Controllers/HomeController.cs @@ -17,6 +17,7 @@ using EnvelopeGenerator.Application.Resources; using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver; using EnvelopeGenerator.Domain.Entities; using System.Text.RegularExpressions; +using static EnvelopeGenerator.Common.Constants; namespace EnvelopeGenerator.Web.Controllers { @@ -60,7 +61,7 @@ namespace EnvelopeGenerator.Web.Controllers return this.ViewDocumentNotFound(); } - if(decoded.GetEncodeType() == EncodeType.ReadOnly) + if(decoded.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly) return Redirect($"{envelopeReceiverId}/ReadOnly"); ViewData["EnvelopeKey"] = envelopeReceiverId; diff --git a/EnvelopeGenerator.Web/Views/_ViewImports.cshtml b/EnvelopeGenerator.Web/Views/_ViewImports.cshtml index 1e3c2c8f..5a19cc58 100644 --- a/EnvelopeGenerator.Web/Views/_ViewImports.cshtml +++ b/EnvelopeGenerator.Web/Views/_ViewImports.cshtml @@ -1,6 +1,7 @@ @using EnvelopeGenerator.Web @using EnvelopeGenerator.Web.Models @using EnvelopeGenerator.Web.Sanitizers +@using EnvelopeGenerator.Extensions @using Microsoft.Extensions.Localization @using EnvelopeGenerator.Application.Resources @using Microsoft.Extensions.Options