From 6142d9f3aa6519fd62883d561396f7102a9d5494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Fri, 28 Nov 2025 12:40:56 +0100 Subject: [PATCH 1/3] LPD-72782 Create a configuration to remove client's IP from Liferay object --- .../LiferayGlobalObjectConfiguration.java | 32 +++++++++++ ...ferayGlobalObjectPreAUIDynamicInclude.java | 54 +++++++++++++++++-- .../resources/content/Language.properties | 3 ++ 3 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/configuration/LiferayGlobalObjectConfiguration.java diff --git a/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/configuration/LiferayGlobalObjectConfiguration.java b/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/configuration/LiferayGlobalObjectConfiguration.java new file mode 100644 index 00000000000000..b5aabd9dacb794 --- /dev/null +++ b/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/configuration/LiferayGlobalObjectConfiguration.java @@ -0,0 +1,32 @@ +/** + * SPDX-FileCopyrightText: (c) 2025 Liferay, Inc. https://liferay.com + * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06 + */ + +package com.liferay.frontend.js.web.internal.configuration; + +import aQute.bnd.annotation.metatype.Meta; + +import com.liferay.portal.configuration.metatype.annotations.ExtendedObjectClassDefinition; + +/** + * @author Iván Zaera Avellón + */ +@ExtendedObjectClassDefinition( + category = "infrastructure", + scope = ExtendedObjectClassDefinition.Scope.COMPANY, strictScope = true +) +@Meta.OCD( + id = "com.liferay.frontend.js.web.internal.configuration.LiferayGlobalObjectConfiguration", + localization = "content/Language", + name = "liferay-global-object-configuration-name" +) +public interface LiferayGlobalObjectConfiguration { + + @Meta.AD( + deflt = "false", description = "disable-get-remote-methods-help", + name = "disable-get-remote-methods", required = false + ) + public boolean disableGetRemoteMethods(); + +} \ No newline at end of file diff --git a/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicInclude.java b/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicInclude.java index fa527e78f63680..76e40fc5e8abb7 100644 --- a/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicInclude.java +++ b/modules/apps/frontend-js/frontend-js-web/src/main/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicInclude.java @@ -6,11 +6,14 @@ package com.liferay.frontend.js.web.internal.servlet.taglib; import com.liferay.exportimport.kernel.staging.Staging; +import com.liferay.frontend.js.web.internal.configuration.LiferayGlobalObjectConfiguration; import com.liferay.layout.seo.kernel.LayoutSEOLink; import com.liferay.layout.seo.kernel.LayoutSEOLinkManager; import com.liferay.petra.string.CharPool; import com.liferay.petra.string.StringBundler; import com.liferay.petra.string.StringPool; +import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil; +import com.liferay.portal.configuration.module.configuration.ConfigurationProvider; import com.liferay.portal.kernel.content.security.policy.ContentSecurityPolicyNonceProviderUtil; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.feature.flag.FeatureFlag; @@ -23,6 +26,7 @@ import com.liferay.portal.kernel.model.LayoutTypePortlet; import com.liferay.portal.kernel.model.User; import com.liferay.portal.kernel.model.impl.VirtualLayout; +import com.liferay.portal.kernel.module.configuration.ConfigurationException; import com.liferay.portal.kernel.security.auth.AuthToken; import com.liferay.portal.kernel.security.permission.ActionKeys; import com.liferay.portal.kernel.service.permission.LayoutPermission; @@ -63,6 +67,7 @@ import java.text.Format; import java.text.SimpleDateFormat; +import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.TimeZone; @@ -87,6 +92,9 @@ public void include( HttpServletResponse httpServletResponse, String key) throws IOException { + LiferayGlobalObjectConfiguration liferayGlobalObjectConfiguration = + _getLiferayGlobalObjectConfiguration(httpServletRequest); + PrintWriter printWriter = httpServletResponse.getWriter(); printWriter.print(" _displayNames = new ConcurrentHashMap<>(); @Reference diff --git a/modules/apps/portal-language/portal-language-lang/src/main/resources/content/Language.properties b/modules/apps/portal-language/portal-language-lang/src/main/resources/content/Language.properties index 9fbef4107c7273..6cca39ab41bb8f 100644 --- a/modules/apps/portal-language/portal-language-lang/src/main/resources/content/Language.properties +++ b/modules/apps/portal-language/portal-language-lang/src/main/resources/content/Language.properties @@ -6224,6 +6224,8 @@ disable-as-a-collection-provider-help=Disabling this as a collection provider wi disable-caching=Disable Caching disable-document-recording=Disable Document Recording disable-forwarding=Disable Forwarding +disable-get-remote-methods=Disable getRemoteAddr() and getRemoteHost() methods +disable-get-remote-methods-help=If checked, the methods getRemoteAddr() and getRemoteHost() will be undefined within the Liferay.ThemeDisplay global object. This configuration is primarily implemented to mitigate privacy risks by avoiding the exposure of client IP/host information and to optimize performance by allowing public caching of pages for unauthenticated users. Note that checking this option will make custom code that relies on these methods fail. disable-globally=Disable Multi-Factor Authentication disable-globally-description=Disable multi-factor authentication system-wide. disable-inheritance-confirmation=Disable Inheritance Confirmation @@ -10783,6 +10785,7 @@ licenses-registered=Licenses Registered liferay=Liferay liferay-analytics-key=Liferay Analytics Key liferay-dxp-instance-has-to-be-connected-with-analytics-cloud-to-view-content-performance-metrics-and-build-a-successful-content-strategy=In order to view content performance metrics and build a successful content strategy, your Liferay DXP instance has to be connected with Liferay Analytics Cloud. +liferay-global-object-configuration-name=Liferay Global Object liferay-has-failed-to-connect-to-the-ldap-server=Liferay has failed to connect to the LDAP server. Please check your configuration and verify that the LDAP server is running. liferay-has-failed-to-connect-to-the-opensso-server=Liferay has failed to connect to the OpenSSO server. Please check your configuration and verify that the OpenSSO server is running. liferay-has-failed-to-connect-to-the-opensso-services=Liferay has failed to connect to the OpenSSO services. Please verify that the OpenSSO services are running. From 533ebee09c1c245a22c1fd012326bbd705bbe4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Fri, 28 Nov 2025 13:24:23 +0100 Subject: [PATCH 2/3] LPD-72782 Fix and improve test --- .../taglib/dependencies/liferay.js.tpl | 2 +- ...yGlobalObjectPreAUIDynamicIncludeTest.java | 191 ++++++++++++++---- .../taglib/dependencies/liferay_test.js.tpl | 1 - 3 files changed, 154 insertions(+), 40 deletions(-) diff --git a/modules/apps/frontend-js/frontend-js-web/src/main/resources/com/liferay/frontend/js/web/internal/servlet/taglib/dependencies/liferay.js.tpl b/modules/apps/frontend-js/frontend-js-web/src/main/resources/com/liferay/frontend/js/web/internal/servlet/taglib/dependencies/liferay.js.tpl index 5c270b37924256..cbbaa4cbeb6d0e 100644 --- a/modules/apps/frontend-js/frontend-js-web/src/main/resources/com/liferay/frontend/js/web/internal/servlet/taglib/dependencies/liferay.js.tpl +++ b/modules/apps/frontend-js/frontend-js-web/src/main/resources/com/liferay/frontend/js/web/internal/servlet/taglib/dependencies/liferay.js.tpl @@ -42,7 +42,7 @@ merge(target[key], source[key]); } -else { + else { Object.assign(target, { [key]: source[key] }); } } diff --git a/modules/apps/frontend-js/frontend-js-web/src/test/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicIncludeTest.java b/modules/apps/frontend-js/frontend-js-web/src/test/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicIncludeTest.java index b9adb4b72aa237..929a9c7ef02765 100644 --- a/modules/apps/frontend-js/frontend-js-web/src/test/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicIncludeTest.java +++ b/modules/apps/frontend-js/frontend-js-web/src/test/java/com/liferay/frontend/js/web/internal/servlet/taglib/LiferayGlobalObjectPreAUIDynamicIncludeTest.java @@ -6,10 +6,13 @@ package com.liferay.frontend.js.web.internal.servlet.taglib; import com.liferay.exportimport.kernel.staging.Staging; +import com.liferay.frontend.js.web.internal.configuration.LiferayGlobalObjectConfiguration; import com.liferay.layout.seo.kernel.LayoutSEOLink; import com.liferay.layout.seo.kernel.LayoutSEOLinkManager; import com.liferay.petra.string.StringBundler; import com.liferay.petra.string.StringPool; +import com.liferay.portal.configuration.module.configuration.ConfigurationProvider; +import com.liferay.portal.kernel.content.security.policy.ContentSecurityPolicyNonceProviderUtil; import com.liferay.portal.kernel.feature.flag.FeatureFlag; import com.liferay.portal.kernel.feature.flag.FeatureFlagManager; import com.liferay.portal.kernel.language.Language; @@ -46,8 +49,9 @@ import java.util.TimeZone; import java.util.function.Predicate; +import org.junit.After; import org.junit.Assert; -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; @@ -65,45 +69,32 @@ public class LiferayGlobalObjectPreAUIDynamicIncludeTest { public static final LiferayUnitTestRule liferayUnitTestRule = LiferayUnitTestRule.INSTANCE; - @Ignore + @Before + public void setUp() { + _browserSnifferUtilMockedStatic = _mockBrowserSnifferUtil(); + _contentSecurityPolicyNonceProviderUtilMockedStatic = + _mockContentSecurityPolicyNonceProviderUtil(); + _portalWebResourcesUtilMockedStatic = _mockPortalWebResourcesUtil(); + _shutdownUtilMockedStatic = _mockShutdownUtil(); + _timeMockedStatic = _mockTime(); + } + + @After + public void tearDown() { + _browserSnifferUtilMockedStatic.close(); + _contentSecurityPolicyNonceProviderUtilMockedStatic.close(); + _portalWebResourcesUtilMockedStatic.close(); + _shutdownUtilMockedStatic.close(); + _timeMockedStatic.close(); + } + @Test public void test() throws Exception { LiferayGlobalObjectPreAUIDynamicInclude liferayGlobalObjectPreAUIDynamicInclude = new LiferayGlobalObjectPreAUIDynamicInclude(); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_authToken", - _mockAuthToken()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_fastDateFormatFactory", - _mockFastDateFormatFactory()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_featureFlagManager", - _mockFeatureFlagManager()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_language", - _mockLanguage()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_layoutSEOLinkManager", - _mockLayoutSEOLinkManager()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_portal", _mockPortal()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_prefsProps", - Mockito.mock(PrefsProps.class)); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, "_staging", - _mockStaging()); - ReflectionTestUtil.setFieldValue( - liferayGlobalObjectPreAUIDynamicInclude, - "_uploadServletRequestConfigurationProvider", - Mockito.mock(UploadServletRequestConfigurationProvider.class)); - - _mockBrowserSnifferUtil(); - _mockPortalWebResourcesUtil(); - _mockShutdownUtil(); - _mockTime(); + _setupDefaultMocks(liferayGlobalObjectPreAUIDynamicInclude); MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); @@ -117,6 +108,33 @@ public void test() throws Exception { StringUtil.trim(mockHttpServletResponse.getContentAsString())); } + @Test + public void testDisableGetRemoteMethodsConfiguration() throws Exception { + LiferayGlobalObjectPreAUIDynamicInclude + liferayGlobalObjectPreAUIDynamicInclude = + new LiferayGlobalObjectPreAUIDynamicInclude(); + + _setupDefaultMocks(liferayGlobalObjectPreAUIDynamicInclude); + + ReflectionTestUtil.setFieldValue( + liferayGlobalObjectPreAUIDynamicInclude, "_configurationProvider", + _mockConfigurationProvider(true)); + + MockHttpServletResponse mockHttpServletResponse = + new MockHttpServletResponse(); + + liferayGlobalObjectPreAUIDynamicInclude.include( + _mockHttpServletRequest(), mockHttpServletResponse, + StringPool.BLANK); + + String contentAsString = mockHttpServletResponse.getContentAsString(); + + Assert.assertFalse( + contentAsString.contains("getRemoteAddr: () => '127.0.0.1',")); + Assert.assertFalse( + contentAsString.contains("getRemoteHost: () => '127.0.0.1',")); + } + private AuthToken _mockAuthToken() { AuthToken authToken = Mockito.mock(AuthToken.class); @@ -129,7 +147,7 @@ private AuthToken _mockAuthToken() { return authToken; } - private void _mockBrowserSnifferUtil() { + private MockedStatic _mockBrowserSnifferUtil() { MockedStatic browserSnifferUtilMockedStatic = Mockito.mockStatic(BrowserSnifferUtil.class); @@ -155,6 +173,52 @@ private void _mockBrowserSnifferUtil() { ).thenReturn( "42.0" ); + + return browserSnifferUtilMockedStatic; + } + + private ConfigurationProvider _mockConfigurationProvider( + boolean disableGetRemoteMethods) + throws Exception { + + ConfigurationProvider configurationProvider = Mockito.mock( + ConfigurationProvider.class); + + LiferayGlobalObjectConfiguration liferayGlobalObjectConfiguration = + Mockito.mock(LiferayGlobalObjectConfiguration.class); + + Mockito.when( + liferayGlobalObjectConfiguration.disableGetRemoteMethods() + ).thenReturn( + disableGetRemoteMethods + ); + + Mockito.when( + configurationProvider.getCompanyConfiguration( + Mockito.any(), Mockito.anyLong()) + ).thenReturn( + liferayGlobalObjectConfiguration + ); + + return configurationProvider; + } + + private MockedStatic + _mockContentSecurityPolicyNonceProviderUtil() { + + MockedStatic + contentSecurityPolicyNonceProviderUtilMockedStatic = + Mockito.mockStatic( + ContentSecurityPolicyNonceProviderUtil.class); + + contentSecurityPolicyNonceProviderUtilMockedStatic.when( + () -> ContentSecurityPolicyNonceProviderUtil.getNonceAttribute( + Mockito.any()) + ).thenReturn( + StringPool.BLANK + ); + + return contentSecurityPolicyNonceProviderUtilMockedStatic; } private FastDateFormatFactory _mockFastDateFormatFactory() { @@ -322,7 +386,7 @@ private Portal _mockPortal() throws Exception { return portal; } - private void _mockPortalWebResourcesUtil() { + private MockedStatic _mockPortalWebResourcesUtil() { MockedStatic portalWebResourcesUtilMockedStatic = Mockito.mockStatic( PortalWebResourcesUtil.class); @@ -333,9 +397,11 @@ private void _mockPortalWebResourcesUtil() { (Answer) invocationOnMock -> "/o/" + invocationOnMock.getArgument(0) ); + + return portalWebResourcesUtilMockedStatic; } - private void _mockShutdownUtil() { + private MockedStatic _mockShutdownUtil() { MockedStatic shutdownUtilMockedStatic = Mockito.mockStatic(ShutdownUtil.class); @@ -344,6 +410,8 @@ private void _mockShutdownUtil() { ).thenReturn( true ); + + return shutdownUtilMockedStatic; } private Staging _mockStaging() { @@ -484,7 +552,7 @@ private ThemeDisplay _mockThemeDisplay() { return themeDisplay; } - private void _mockTime() { + private MockedStatic