diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 8e887e6f297..7ccfd53fd5b 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -34,6 +34,8 @@ What's changed since v1.31.0: - Fixed additional non-sensitive parameter name patterns by `Azure.Deployment.SecureParameter` by @BernieWhite. [#2528](https://github.com/Azure/PSRule.Rules.Azure/issues/2528) - Added support for configuration of the rule by setting `AZURE_DEPLOYMENT_NONSENSITIVE_PARAMETER_NAMES`. + - Fixed incorrect handling of expressions with contains with JValue string by @BernieWhite. + [#2531](https://github.com/Azure/PSRule.Rules.Azure/issues/2531) ## v1.31.0 diff --git a/src/PSRule.Rules.Azure/Data/Template/Functions.cs b/src/PSRule.Rules.Azure/Data/Template/Functions.cs index 2a3b8761007..17b1e7d405d 100644 --- a/src/PSRule.Rules.Azure/Data/Template/Functions.cs +++ b/src/PSRule.Rules.Azure/Data/Template/Functions.cs @@ -2177,8 +2177,8 @@ private static bool HasChild(object value, object child) { if (ExpressionHelpers.TryArray(value, out var array)) return Contains(array, child); - else if (value is string svalue) - return svalue.Contains(child.ToString()); + else if (ExpressionHelpers.TryString(value, out var s)) + return s.Contains(child.ToString()); else if (value is JObject jObject) return jObject.ContainsKeyInsensitive(child.ToString()); diff --git a/tests/PSRule.Rules.Azure.Tests/FunctionTests.cs b/tests/PSRule.Rules.Azure.Tests/FunctionTests.cs index 1b72c205a8c..a1a2ac54529 100644 --- a/tests/PSRule.Rules.Azure.Tests/FunctionTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/FunctionTests.cs @@ -97,6 +97,10 @@ public void Contains() // String Assert.True((bool)Functions.Contains(context, new object[] { "OneTwoThree", "e" })); Assert.False((bool)Functions.Contains(context, new object[] { "OneTwoThree", "z" })); + Assert.True((bool)Functions.Contains(context, new object[] { "abcd", new JValue("bc") })); + Assert.False((bool)Functions.Not(context, new object[] { Functions.Contains(context, new object[] { "abcd", new JValue("bc") }) })); + Assert.True((bool)Functions.Contains(context, new object[] { new JValue("abcd"), new JValue("bc") })); + Assert.False((bool)Functions.Not(context, new object[] { Functions.Contains(context, new object[] { new JValue("abcd"), new JValue("bc") }) })); // Object Assert.True((bool)Functions.Contains(context, new object[] { JObject.Parse("{ \"one\": \"a\", \"two\": \"b\", \"three\": \"c\" }"), "two" })); diff --git a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj index d9adfaca758..3b402468941 100644 --- a/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj +++ b/tests/PSRule.Rules.Azure.Tests/PSRule.Rules.Azure.Tests.csproj @@ -206,6 +206,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs index 0d8a38c0417..66daffa889f 100644 --- a/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/TemplateVisitorTests.cs @@ -932,6 +932,21 @@ public void MockWellKnownProperties() Assert.Equal("/subscriptions/ffffffff-ffff-ffff-ffff-ffffffffffff/providers/Microsoft.Web/locations/eastus/managedApis/servicebus", actual["properties"]["api"]["id"].Value()); } + [Fact] + public void ContainsWithJValue() + { + var resources = ProcessTemplate(GetSourcePath("Tests.Bicep.30.json"), null, out _); + Assert.Equal(3, resources.Length); + + var actual = resources[2]; + Assert.Equal("Microsoft.ManagedIdentity/userAssignedIdentities", actual["type"].Value()); + Assert.False(actual["properties"]["doesNotContain"].Value()); + Assert.True(actual["properties"]["doesContain"].Value()); + Assert.Equal(1, actual["properties"]["indexOfSubstring"].Value()); + Assert.Equal("abcd", actual["properties"]["stringToCheck"].Value()); + Assert.Equal("bc", actual["properties"]["stringToFind"].Value()); + } + #region Helper methods private static string GetSourcePath(string fileName) diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.30.bicep b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.30.bicep new file mode 100644 index 00000000000..db32c6acd71 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.30.bicep @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Community provided sample from: https://github.com/Azure/PSRule.Rules.Azure/issues/2531 + +var stringToCheck = 'abcd' +var stringToFind = 'bc' +var doesNotContain = !(contains(stringToCheck, stringToFind)) +var doesContain = contains(stringToCheck, stringToFind) +var indexOfSubstring = indexOf(stringToCheck, stringToFind) + +#disable-next-line BCP081 no-deployments-resources +resource taskDeployment 'Microsoft.Resources/deployments@2020-10-01' = { + name: 'name' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [ + { + apiVersion: '2019-12-01' + type: 'Microsoft.ManagedIdentity/userAssignedIdentities' + name: 'test' + properties: { + doesNotContain: doesNotContain + doesContain: doesContain + indexOfSubstring: indexOfSubstring + stringToCheck: stringToCheck + stringToFind: stringToFind + } + } + ] + } + } +} diff --git a/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.30.json b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.30.json new file mode 100644 index 00000000000..9d470deae38 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Tests.Bicep.30.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "15051303941734638046" + } + }, + "variables": { + "stringToCheck": "abcd", + "stringToFind": "bc", + "doesNotContain": "[not(contains(variables('stringToCheck'), variables('stringToFind')))]", + "doesContain": "[contains(variables('stringToCheck'), variables('stringToFind'))]", + "indexOfSubstring": "[indexOf(variables('stringToCheck'), variables('stringToFind'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-10-01", + "name": "name", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "apiVersion": "2019-12-01", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "test", + "properties": { + "doesNotContain": "[variables('doesNotContain')]", + "doesContain": "[variables('doesContain')]", + "indexOfSubstring": "[variables('indexOfSubstring')]", + "stringToCheck": "[variables('stringToCheck')]", + "stringToFind": "[variables('stringToFind')]" + } + } + ] + } + } + } + ] +} \ No newline at end of file