From d89849cc17533ab56354d9327a150d3223ce2aba Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 4 May 2021 16:16:31 +0200 Subject: [PATCH] Feature/hash functions (#691) * Hash function for scripts. * Liquid hash functions. --- .../Actions/Webhook/WebhookActionHandler.cs | 2 +- .../Extensions/StringJintExtension.cs | 28 ++++++++++++ .../Extensions/StringFluidExtension.cs | 15 ++++++- .../Schemas/MongoSchemasHash.cs | 2 +- .../DomainObject/AssetCommandMiddleware.cs | 2 +- .../src/Squidex.Infrastructure/RandomHash.cs | 45 +++++++++++++++++-- backend/src/Squidex.Web/Constants.cs | 2 +- .../Controllers/Apps/AppRolesController.cs | 2 +- .../Api/Controllers/Rules/RulesController.cs | 2 +- .../Scripting/JintScriptEngineHelperTests.cs | 34 ++++++++++++++ .../Templates/FluidTemplateEngineTests.cs | 30 +++++++++++++ 11 files changed, 153 insertions(+), 11 deletions(-) diff --git a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs index 06ef5909e..031a92efc 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs @@ -45,7 +45,7 @@ namespace Squidex.Extensions.Actions.Webhook requestBody = ToEnvelopeJson(@event); } - requestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64(); + requestSignature = $"{requestBody}{action.SharedSecret}".ToSha256Base64(); } var ruleDescription = $"Send event to webhook '{requestUrl}'"; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs index aac6514c6..d0c78c238 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs @@ -8,6 +8,7 @@ using System; using Jint; using Jint.Native; +using Squidex.Infrastructure; using Squidex.Text; namespace Squidex.Domain.Apps.Core.Scripting.Extensions @@ -16,6 +17,30 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions { private delegate JsValue StringSlugifyDelegate(string text, bool single = false); + private readonly Func sha256 = (text) => + { + try + { + return text.ToSha256(); + } + catch + { + return JsValue.Undefined; + } + }; + + private readonly Func md5 = (text) => + { + try + { + return text.ToMD5(); + } + catch + { + return JsValue.Undefined; + } + }; + private readonly StringSlugifyDelegate slugify = (text, single) => { try @@ -82,6 +107,9 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions { engine.SetValue("slugify", slugify); + engine.SetValue("sha256", sha256); + engine.SetValue("md5", md5); + engine.SetValue("toCamelCase", toCamelCase); engine.SetValue("toPascalCase", toPascalCase); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs index 49a2aa4d7..89e7bffb1 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs @@ -8,6 +8,7 @@ using Fluid; using Fluid.Values; using Newtonsoft.Json; +using Squidex.Infrastructure; using Squidex.Text; namespace Squidex.Domain.Apps.Core.Templates.Extensions @@ -51,11 +52,23 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions return FluidValue.Create(input.ToStringValue().Trim()); }; + private static readonly FilterDelegate MD5 = (input, arguments, context) => + { + return FluidValue.Create(input.ToStringValue().ToMD5()); + }; + + private static readonly FilterDelegate Sha256 = (input, arguments, context) => + { + return FluidValue.Create(input.ToStringValue().ToSha256()); + }; + public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy) { + TemplateContext.GlobalFilters.AddFilter("escape", Escape); TemplateContext.GlobalFilters.AddFilter("html2text", Html2Text); TemplateContext.GlobalFilters.AddFilter("markdown2text", Markdown2Text); - TemplateContext.GlobalFilters.AddFilter("escape", Escape); + TemplateContext.GlobalFilters.AddFilter("md5", MD5); + TemplateContext.GlobalFilters.AddFilter("sha256", Sha256); TemplateContext.GlobalFilters.AddFilter("slugify", Slugify); TemplateContext.GlobalFilters.AddFilter("trim", Trim); } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs index 9b674e0b5..08fc5833f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs @@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas sb.Append(';'); } - return sb.ToString().Sha256Base64(); + return sb.ToString().ToSha256Base64(); } finally { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs index e7c2cbb2d..7c1451a00 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs @@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject { var steamHash = hashStream.GetHashStringAndReset(); - return $"{steamHash}{file.FileName}{file.FileSize}".Sha256Base64(); + return $"{steamHash}{file.FileName}{file.FileSize}".ToSha256Base64(); } private async Task EnrichWithMetadataAsync(UploadAssetCommand command) diff --git a/backend/src/Squidex.Infrastructure/RandomHash.cs b/backend/src/Squidex.Infrastructure/RandomHash.cs index cefc156e4..31789d0a7 100644 --- a/backend/src/Squidex.Infrastructure/RandomHash.cs +++ b/backend/src/Squidex.Infrastructure/RandomHash.cs @@ -16,7 +16,7 @@ namespace Squidex.Infrastructure public static string New() { return Guid.NewGuid() - .ToString().Sha256Base64() + .ToString().ToSha256Base64() .ToLowerInvariant() .Replace("+", "x") .Replace("=", "x") @@ -28,12 +28,12 @@ namespace Squidex.Infrastructure return Guid.NewGuid().ToString().Replace("-", string.Empty); } - public static string Sha256Base64(this string value) + public static string ToSha256Base64(this string value) { - return Sha256Base64(Encoding.UTF8.GetBytes(value)); + return ToSha256Base64(Encoding.UTF8.GetBytes(value)); } - public static string Sha256Base64(this byte[] bytes) + public static string ToSha256Base64(this byte[] bytes) { using (var sha = SHA256.Create()) { @@ -44,5 +44,42 @@ namespace Squidex.Infrastructure return result; } } + + public static string ToSha256(this string value) + { + return value.ToHashed(SHA256.Create()); + } + + public static string ToSha256(this byte[] bytes) + { + return bytes.ToHashed(SHA256.Create()); + } + + public static string ToMD5(this string value) + { + return value.ToHashed(MD5.Create()); + } + + public static string ToMD5(this byte[] bytes) + { + return bytes.ToHashed(MD5.Create()); + } + + public static string ToHashed(this string value, HashAlgorithm algorithm) + { + return Encoding.UTF8.GetBytes(value).ToHashed(algorithm); + } + + public static string ToHashed(this byte[] bytes, HashAlgorithm algorithm) + { + using (algorithm) + { + var bytesHash = algorithm.ComputeHash(bytes); + + var result = Encoding.UTF8.GetString(bytesHash); + + return result; + } + } } } diff --git a/backend/src/Squidex.Web/Constants.cs b/backend/src/Squidex.Web/Constants.cs index d1b232fda..03fba81f6 100644 --- a/backend/src/Squidex.Web/Constants.cs +++ b/backend/src/Squidex.Web/Constants.cs @@ -38,7 +38,7 @@ namespace Squidex.Web public static readonly string InternalClientId = "squidex-internal"; - public static readonly string InternalClientSecret = "squidex-internal".Sha256Base64(); + public static readonly string InternalClientSecret = "squidex-internal".ToSha256Base64(); public static readonly string IdentityServerPrefix = "/identity-server"; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs index 0fc029db1..aa7ca80dd 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs @@ -78,7 +78,7 @@ namespace Squidex.Areas.Api.Controllers.Apps return permissionsProvider.GetPermissionsAsync(App); }); - Response.Headers[HeaderNames.ETag] = string.Concat(response).Sha256Base64(); + Response.Headers[HeaderNames.ETag] = string.Concat(response).ToSha256Base64(); return Ok(response); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index b15866212..483a41d91 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -70,7 +70,7 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiCosts(0)] public IActionResult GetActions() { - var etag = string.Concat(ruleRegistry.Actions.Select(x => x.Key)).Sha256Base64(); + var etag = string.Concat(ruleRegistry.Actions.Select(x => x.Key)).ToSha256Base64(); var response = Deferred.Response(() => { diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs index f8613c0bb..2c81252fe 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs @@ -184,6 +184,40 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting Assert.Equal("4-hauser", result); } + [Fact] + public void Should_compute_sha256_hash() + { + const string script = @" + return sha256(value); + "; + + var vars = new ScriptVars + { + ["value"] = "HelloWorld" + }; + + var result = sut.Execute(vars, script).ToString(); + + Assert.Equal("HelloWorld".ToSha256(), result); + } + + [Fact] + public void Should_compute_md5_hash() + { + const string script = @" + return md5(value); + "; + + var vars = new ScriptVars + { + ["value"] = "HelloWorld" + }; + + var result = sut.Execute(vars, script).ToString(); + + Assert.Equal("HelloWorld".ToMD5(), result); + } + [Fact] public async Task Should_throw_validation_exception_if_calling_reject() { diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Templates/FluidTemplateEngineTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Templates/FluidTemplateEngineTests.cs index adac6fdad..b929e53f4 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Templates/FluidTemplateEngineTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Templates/FluidTemplateEngineTests.cs @@ -190,6 +190,36 @@ namespace Squidex.Domain.Apps.Core.Operations.Templates Assert.Equal("10", result); } + [Fact] + public async Task Should_compute_md5_hash() + { + var template = "{{ e.text | md5 }}"; + + var value = new + { + text = "HelloWorld" + }; + + var result = await RenderAync(template, value); + + Assert.Equal("HelloWorld".ToMD5(), result); + } + + [Fact] + public async Task Should_compute_sha256_hash() + { + var template = "{{ e.text | sha256 }}"; + + var value = new + { + text = "HelloWorld" + }; + + var result = await RenderAync(template, value); + + Assert.Equal("HelloWorld".ToSha256(), result); + } + [Fact] public async Task Should_throw_exception_if_template_invalid() {