From 951fdc1a1b8504df28c0a9cf0aa82b4b62631e44 Mon Sep 17 00:00:00 2001 From: Michael Hamann <michael.hamann@xwiki.com> Date: Mon, 20 Jan 2025 16:42:00 +0100 Subject: [PATCH] XWIKI-22799: Make the required rights analyzers of the Cache, HTML and RAW macro case-insensitive * Ignore the case of parameter names. * Add tests. (cherry picked from commit 3d451e957fe2b14459e9ac64172b4a0e4c46971c) --- .../CacheMacroRequiredRightsAnalyzer.java | 7 +- .../CacheMacroRequiredRightsAnalyzerTest.java | 39 +++++++++-- .../HTMLMacroRequiredRightsAnalyzer.java | 15 +++-- .../macro/RawMacroRequiredRightsAnalyzer.java | 22 +++++-- .../HTMLMacroRequiredRightsAnalyzerTest.java | 65 +++++++++++++++++++ .../RawMacroRequiredRightsAnalyzerTest.java | 36 +++++++--- 6 files changed, 158 insertions(+), 26 deletions(-) diff --git a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/main/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzer.java b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/main/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzer.java index 725f03eeb2b..c2117b91643 100644 --- a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/main/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzer.java +++ b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/main/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzer.java @@ -23,8 +23,8 @@ import javax.inject.Singleton; import org.xwiki.component.annotation.Component; -import org.xwiki.platform.security.requiredrights.MacroRequiredRightsAnalyzer; import org.xwiki.platform.security.requiredrights.MacroRequiredRightReporter; +import org.xwiki.platform.security.requiredrights.MacroRequiredRightsAnalyzer; import org.xwiki.rendering.block.MacroBlock; /** @@ -43,7 +43,10 @@ public class CacheMacroRequiredRightsAnalyzer implements MacroRequiredRightsAnal @Override public void analyze(MacroBlock macroBlock, MacroRequiredRightReporter reporter) { - reporter.analyzeContent(macroBlock, macroBlock.getParameter("id")); + macroBlock.getParameters().entrySet().stream() + .filter(entry -> "id".equalsIgnoreCase(entry.getKey())) + .forEach(entry -> reporter.analyzeContent(macroBlock, entry.getValue())); + reporter.analyzeContent(macroBlock, macroBlock.getContent()); } } diff --git a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/test/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzerTest.java b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/test/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzerTest.java index 3a635a391ce..9060d7e29bd 100644 --- a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/test/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzerTest.java +++ b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-macros/xwiki-platform-rendering-macro-cache/src/test/java/org/xwiki/rendering/internal/macro/cache/CacheMacroRequiredRightsAnalyzerTest.java @@ -19,7 +19,14 @@ */ package org.xwiki.rendering.internal.macro.cache; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.xwiki.platform.security.requiredrights.MacroRequiredRightReporter; import org.xwiki.rendering.block.MacroBlock; import org.xwiki.test.junit5.mockito.ComponentTest; @@ -27,7 +34,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * Unit tests for {@link CacheMacroRequiredRightsAnalyzer}. @@ -40,14 +46,13 @@ class CacheMacroRequiredRightsAnalyzerTest @InjectMockComponents private CacheMacroRequiredRightsAnalyzer analyzer; - @Test - void analyze() + @ParameterizedTest + @MethodSource("idValuesProvider") + void analyze(String parameterName) { - MacroBlock macroBlock = mock(); String idValue = "idValue"; String contentValue = "contentValue"; - when(macroBlock.getParameter("id")).thenReturn(idValue); - when(macroBlock.getContent()).thenReturn(contentValue); + MacroBlock macroBlock = new MacroBlock("cache", Map.of(parameterName, idValue), contentValue, false); MacroRequiredRightReporter reporter = mock(); this.analyzer.analyze(macroBlock, reporter); @@ -55,4 +60,26 @@ void analyze() verify(reporter).analyzeContent(macroBlock, idValue); verify(reporter).analyzeContent(macroBlock, contentValue); } + + @Test + void analyzeAllInOne() + { + Map<String, String> parameters = idValuesProvider().collect(Collectors.toMap(Function.identity(), + Function.identity())); + String content = "content"; + MacroBlock macroBlock = new MacroBlock("cache", parameters, content, false); + MacroRequiredRightReporter reporter = mock(); + + this.analyzer.analyze(macroBlock, reporter); + + verify(reporter).analyzeContent(macroBlock, content); + for (String value : parameters.values()) { + verify(reporter).analyzeContent(macroBlock, value); + } + } + + static Stream<String> idValuesProvider() + { + return Stream.of("id", "ID", "Id", "iD"); + } } diff --git a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzer.java b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzer.java index 79737a8209e..165c20225c4 100644 --- a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzer.java +++ b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzer.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; @@ -67,11 +68,10 @@ public class HTMLMacroRequiredRightsAnalyzer implements MacroRequiredRightsAnaly @Override public void analyze(MacroBlock macroBlock, MacroRequiredRightReporter reporter) { - boolean wiki = Boolean.TRUE.equals(this.converter.convert(Boolean.class, macroBlock.getParameter("wiki"))); - String cleanParameter = macroBlock.getParameter("clean"); + boolean wiki = getBooleanParameterValues(macroBlock, "wiki").anyMatch(Boolean.TRUE::equals); + // Cleaning is enabled by default. - boolean clean = - cleanParameter == null || Boolean.TRUE.equals(this.converter.convert(Boolean.class, cleanParameter)); + boolean clean = getBooleanParameterValues(macroBlock, "clean").noneMatch(Boolean.FALSE::equals); if (wiki) { reporter.analyzeContent(macroBlock, macroBlock.getContent()); @@ -105,4 +105,11 @@ public void analyze(MacroBlock macroBlock, MacroRequiredRightReporter reporter) } } } + + private Stream<Boolean> getBooleanParameterValues(MacroBlock macroBlock, String parameterName) + { + return macroBlock.getParameters().entrySet().stream() + .filter(entry -> parameterName.equalsIgnoreCase(entry.getKey())) + .map(entry -> this.converter.convert(Boolean.class, entry.getValue())); + } } diff --git a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzer.java b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzer.java index e44b1abfa0b..f4a82ce026c 100644 --- a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzer.java +++ b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzer.java @@ -50,15 +50,25 @@ public class RawMacroRequiredRightsAnalyzer implements MacroRequiredRightsAnalyz @Override public void analyze(MacroBlock macroBlock, MacroRequiredRightReporter reporter) + { + // Check if any parameter that is equal to "syntax" ignoring case is an HTML syntax. + boolean isHTML = macroBlock.getParameters().entrySet().stream() + .filter(entry -> "syntax".equalsIgnoreCase(entry.getKey())) + .anyMatch(entry -> isHTMLSyntax(entry.getValue())); + + if (isHTML) { + reporter.report(macroBlock, List.of(MacroRequiredRight.SCRIPT), "rendering.macro.rawMacroRequiredRights"); + } + } + + private boolean isHTMLSyntax(String syntaxValue) { try { - SyntaxType syntax = this.syntaxRegistry.resolveSyntax(macroBlock.getParameter("syntax")).getType(); - if (SyntaxType.HTML_FAMILY_TYPES.contains(syntax)) { - reporter.report(macroBlock, List.of(MacroRequiredRight.SCRIPT), - "rendering.macro.rawMacroRequiredRights"); - } + SyntaxType syntax = this.syntaxRegistry.resolveSyntax(syntaxValue).getType(); + return SyntaxType.HTML_FAMILY_TYPES.contains(syntax); } catch (ParseException e) { - // Ignore, this should fail the macro or at least won't produce HTML output. + // Values that can't be parsed also won't be considered as HTML by the macro itself. + return false; } } } diff --git a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzerTest.java b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzerTest.java index c7d51927f77..8e29b277a1c 100644 --- a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzerTest.java +++ b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/HTMLMacroRequiredRightsAnalyzerTest.java @@ -21,10 +21,13 @@ import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.xwiki.platform.security.requiredrights.MacroRequiredRight; import org.xwiki.platform.security.requiredrights.MacroRequiredRightReporter; @@ -130,4 +133,66 @@ void analyzeWithoutParameters(String content, boolean scriptRequired) } verifyNoMoreInteractions(reporter); } + + /** + * Test the behavior when a parameter occurs several times but with different case. Ensure that the "most + * dangerous" value wins. + */ + @ParameterizedTest + @MethodSource("analyzeWithDuplicateParametersDataProvider") + void analyzeWithDuplicateParameters(Map<String, String> arguments, boolean wiki, boolean clean) + { + String content = "content"; + MacroBlock macroBlock = new MacroBlock(HTML_MACRO_ID, arguments, content, false); + + MacroRequiredRightReporter reporter = mock(); + this.htmlMacroRequiredRightsAnalyzer.analyze(macroBlock, reporter); + + if (wiki) { + verify(reporter).analyzeContent(macroBlock, content); + } + + if (!clean || wiki) { + ArgumentCaptor<List<MacroRequiredRight>> argumentCaptor = ArgumentCaptor.captor(); + verify(reporter).report(eq(macroBlock), argumentCaptor.capture(), anyString()); + List<MacroRequiredRight> requiredRights = argumentCaptor.getValue(); + if (wiki && clean) { + assertEquals(List.of(MacroRequiredRight.MAYBE_SCRIPT), requiredRights); + } else { + assertEquals(List.of(MacroRequiredRight.SCRIPT), requiredRights); + } + } else { + verifyNoMoreInteractions(reporter); + } + } + + private static Stream<Arguments> analyzeWithDuplicateParametersDataProvider() + { + return Stream.of( + Arguments.of(Map.of( + "wiki", Boolean.TRUE.toString(), + "WIKI", Boolean.FALSE.toString(), + "clean", Boolean.TRUE.toString(), + "CLEAN", Boolean.FALSE.toString() + ), true, false), + Arguments.of(Map.of( + "wiki", Boolean.FALSE.toString(), + "WIKI", Boolean.TRUE.toString(), + "clean", Boolean.FALSE.toString(), + "CLEAN", Boolean.TRUE.toString() + ), true, false), + Arguments.of(Map.of( + "wiKi", Boolean.TRUE.toString(), + "clEan", Boolean.FALSE.toString() + ), true, false), + Arguments.of(Map.of( + "wikI", Boolean.FALSE.toString(), + "Wiki", Boolean.FALSE.toString(), + "clean", Boolean.TRUE.toString() + ), false, true), + Arguments.of(Map.of( + "cleaN", Boolean.FALSE.toString() + ), false, false) + ); + } } diff --git a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzerTest.java b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzerTest.java index 5b10712f513..e25c9a07797 100644 --- a/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzerTest.java +++ b/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/test/java/org/xwiki/rendering/internal/macro/RawMacroRequiredRightsAnalyzerTest.java @@ -20,8 +20,10 @@ package org.xwiki.rendering.internal.macro; import java.util.List; +import java.util.Map; import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -35,6 +37,7 @@ import org.xwiki.test.junit5.mockito.InjectMockComponents; import org.xwiki.test.junit5.mockito.MockComponent; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -50,33 +53,50 @@ class RawMacroRequiredRightsAnalyzerTest { private static final String SYNTAX_PARAMETER = "syntax"; + private static final String PLAIN_VALUE = "plain"; + + private static final String HTML_VALUE = "html"; + @MockComponent private SyntaxRegistry syntaxRegistry; @InjectMockComponents private RawMacroRequiredRightsAnalyzer analyzer; + @BeforeEach + void setUp() throws ParseException + { + when(this.syntaxRegistry.resolveSyntax(anyString())).then(invocationOnMock -> { + String syntaxValue = invocationOnMock.getArgument(0); + return switch (syntaxValue) { + case PLAIN_VALUE -> Syntax.PLAIN_1_0; + case HTML_VALUE -> Syntax.HTML_5_0; + default -> throw new ParseException("Unknown syntax: " + syntaxValue); + }; + }); + } + private static Stream<Arguments> analyzeTestCases() { return Stream.of( - Arguments.of("plain", Syntax.PLAIN_1_0, null), - Arguments.of("html", Syntax.HTML_5_0, MacroRequiredRight.SCRIPT) + Arguments.of(Map.of(SYNTAX_PARAMETER, PLAIN_VALUE), null), + Arguments.of(Map.of(SYNTAX_PARAMETER, HTML_VALUE), MacroRequiredRight.SCRIPT), + Arguments.of(Map.of("sYnTaX", HTML_VALUE), MacroRequiredRight.SCRIPT), + Arguments.of(Map.of("sYntax", HTML_VALUE, SYNTAX_PARAMETER, PLAIN_VALUE), MacroRequiredRight.SCRIPT), + Arguments.of(Map.of("Syntax", PLAIN_VALUE, "SYNTAX", HTML_VALUE), MacroRequiredRight.SCRIPT), + Arguments.of(Map.of("syntaX", PLAIN_VALUE), null) ); } @ParameterizedTest @MethodSource("analyzeTestCases") - void analyze(String syntaxValue, Syntax expectedSyntax, MacroRequiredRight expectedRight) throws ParseException + void analyze(Map<String, String> parameters, MacroRequiredRight expectedRight) { - when(this.syntaxRegistry.resolveSyntax(syntaxValue)).thenReturn(expectedSyntax); - - MacroBlock macroBlock = mock(); - when(macroBlock.getParameter(SYNTAX_PARAMETER)).thenReturn(syntaxValue); + MacroBlock macroBlock = new MacroBlock("raw", parameters, false); MacroRequiredRightReporter reporter = mock(); this.analyzer.analyze(macroBlock, reporter); - verify(this.syntaxRegistry).resolveSyntax(syntaxValue); if (expectedRight != null) { verify(reporter).report(macroBlock, List.of(expectedRight), "rendering.macro.rawMacroRequiredRights"); } else { -- GitLab