This is an automated email from the ASF dual-hosted git repository.

snoopdave pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/roller.git


The following commit(s) were added to refs/heads/master by this push:
     new faafe379e Implement one-time-salt use and add comprehensive tests 
(#142)
faafe379e is described below

commit faafe379e353dc6a44d350734e4d8113d666a3be
Author: David M. Johnson <snoopd...@users.noreply.github.com>
AuthorDate: Sun Oct 6 16:45:44 2024 -0400

    Implement one-time-salt use and add comprehensive tests (#142)
    
    * Implement one-time-salt use and add comprehensive tests
    
    * Add test for ignored URLs
    
    * Remove unneeded mocking
    
    * Don't allow web analytics if weblog admins untrusted
    
    * Don't allow web analytics if weblog admins untrusted
---
 .../weblogger/pojos/wrapper/WeblogWrapper.java     |   3 +-
 .../weblogger/ui/core/filters/LoadSaltFilter.java  |  15 +-
 .../ui/core/filters/ValidateSaltFilter.java        |  42 ++---
 .../weblogger/ui/struts2/editor/WeblogConfig.java  |  15 +-
 .../main/resources/ApplicationResources.properties |   1 +
 .../webapp/WEB-INF/jsps/editor/WeblogConfig.jsp    |   4 +-
 .../ui/core/filters/LoadSaltFilterTest.java        |  88 ++++++++++
 .../ui/core/filters/ValidateSaltFilterTest.java    | 179 +++++++++++++++++++++
 8 files changed, 312 insertions(+), 35 deletions(-)

diff --git 
a/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogWrapper.java
 
b/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogWrapper.java
index afc297a29..71ff4d33e 100644
--- 
a/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogWrapper.java
+++ 
b/app/src/main/java/org/apache/roller/weblogger/pojos/wrapper/WeblogWrapper.java
@@ -141,10 +141,9 @@ public final class WeblogWrapper {
     }
 
     public String getAnalyticsCode() {
-        return this.pojo.getAnalyticsCode();
+        return 
HTMLSanitizer.conditionallySanitize(this.pojo.getAnalyticsCode());
     }
 
-
     public Boolean getEmailComments() {
         return this.pojo.getEmailComments();
     }
diff --git 
a/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilter.java
 
b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilter.java
index e6416ce96..b2e63915d 100644
--- 
a/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilter.java
+++ 
b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilter.java
@@ -37,13 +37,14 @@ public class LoadSaltFilter implements Filter {
         throws IOException, ServletException {
 
         HttpServletRequest httpReq = (HttpServletRequest) request;
-        RollerSession rses = RollerSession.getRollerSession(httpReq);
-        String userId = rses != null && rses.getAuthenticatedUser() != null ? 
rses.getAuthenticatedUser().getId() : "";
-
-        SaltCache saltCache = SaltCache.getInstance();
-        String salt = RandomStringUtils.random(20, 0, 0, true, true, null, new 
SecureRandom());
-        saltCache.put(salt, userId);
-        httpReq.setAttribute("salt", salt);
+        RollerSession rollerSession = RollerSession.getRollerSession(httpReq);
+        if (rollerSession != null) {
+            String userId = rollerSession.getAuthenticatedUser() != null ? 
rollerSession.getAuthenticatedUser().getId() : "";
+            SaltCache saltCache = SaltCache.getInstance();
+            String salt = RandomStringUtils.random(20, 0, 0, true, true, null, 
new SecureRandom());
+            saltCache.put(salt, userId);
+            httpReq.setAttribute("salt", salt);
+        }
 
         chain.doFilter(request, response);
     }
diff --git 
a/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilter.java
 
b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilter.java
index 275ccd328..9744551bc 100644
--- 
a/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilter.java
+++ 
b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilter.java
@@ -51,17 +51,31 @@ public class ValidateSaltFilter implements Filter {
             FilterChain chain) throws IOException, ServletException {
         HttpServletRequest httpReq = (HttpServletRequest) request;
 
-        if ("POST".equals(httpReq.getMethod()) && 
!isIgnoredURL(httpReq.getServletPath())) {
-            RollerSession rses = RollerSession.getRollerSession(httpReq);
-            String userId = rses != null && rses.getAuthenticatedUser() != 
null ? rses.getAuthenticatedUser().getId() : "";
+        String requestURL = httpReq.getRequestURL().toString();
+        String queryString = httpReq.getQueryString();
+        if (queryString != null) {
+            requestURL += "?" + queryString;
+        }
+
+        if ("POST".equals(httpReq.getMethod()) && !isIgnoredURL(requestURL)) {
+            RollerSession rollerSession = 
RollerSession.getRollerSession(httpReq);
+            if (rollerSession != null) {
+                String userId = rollerSession.getAuthenticatedUser() != null ? 
rollerSession.getAuthenticatedUser().getId() : "";
+
+                String salt = httpReq.getParameter("salt");
+                SaltCache saltCache = SaltCache.getInstance();
+                if (salt == null || !Objects.equals(saltCache.get(salt), 
userId)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Valid salt value not found on POST to URL : 
" + httpReq.getServletPath());
+                    }
+                    throw new ServletException("Security Violation");
+                }
 
-            String salt = httpReq.getParameter("salt");
-            SaltCache saltCache = SaltCache.getInstance();
-            if (salt == null || !Objects.equals(saltCache.get(salt), userId)) {
+                // Remove salt from cache after successful validation
+                saltCache.remove(salt);
                 if (log.isDebugEnabled()) {
-                    log.debug("Valid salt value not found on POST to URL : " + 
httpReq.getServletPath());
+                    log.debug("Salt used and invalidated: " + salt);
                 }
-                throw new ServletException("Security Violation");
             }
         }
 
@@ -70,8 +84,6 @@ public class ValidateSaltFilter implements Filter {
 
     @Override
     public void init(FilterConfig filterConfig) throws ServletException {
-
-        // Construct our list of ignored urls
         String urls = WebloggerConfig.getProperty("salt.ignored.urls");
         ignored = Set.of(StringUtils.stripAll(StringUtils.split(urls, ",")));
     }
@@ -82,16 +94,10 @@ public class ValidateSaltFilter implements Filter {
 
     /**
      * Checks if this is an ignored url defined in the salt.ignored.urls 
property
-     * @param theUrl the the url
+     * @param theUrl the url
      * @return true, if is ignored resource
      */
     private boolean isIgnoredURL(String theUrl) {
-        int i = theUrl.lastIndexOf('/');
-
-        // If it's not a resource then don't ignore it
-        if (i <= 0 || i == theUrl.length() - 1) {
-            return false;
-        }
-        return ignored.contains(theUrl.substring(i + 1));
+        return ignored.contains(theUrl);
     }
 }
\ No newline at end of file
diff --git 
a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/WeblogConfig.java
 
b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/WeblogConfig.java
index 912ffe122..0cfe9a352 100644
--- 
a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/WeblogConfig.java
+++ 
b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/WeblogConfig.java
@@ -29,6 +29,7 @@ import 
org.apache.roller.weblogger.business.plugins.PluginManager;
 import org.apache.roller.weblogger.business.WebloggerFactory;
 import org.apache.roller.weblogger.business.WeblogEntryManager;
 import org.apache.roller.weblogger.business.plugins.entry.WeblogEntryPlugin;
+import org.apache.roller.weblogger.config.WebloggerConfig;
 import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
 import org.apache.roller.weblogger.pojos.Weblog;
 import org.apache.roller.weblogger.pojos.WeblogCategory;
@@ -61,6 +62,8 @@ public class WeblogConfig extends UIAction {
     
     // list of available plugins
     private List<WeblogEntryPlugin> pluginsList = Collections.emptyList();
+
+    private final boolean weblogAdminsUntrusted = 
WebloggerConfig.getBooleanProperty("weblogAdminsUntrusted");
     
     
     public WeblogConfig() {
@@ -71,7 +74,7 @@ public class WeblogConfig extends UIAction {
 
     @Override
     public void myPrepare() {
-        
+
         try {
             WeblogEntryManager wmgr = 
WebloggerFactory.getWeblogger().getWeblogEntryManager();
             
@@ -88,10 +91,7 @@ public class WeblogConfig extends UIAction {
             // set plugins list
             PluginManager ppmgr = 
WebloggerFactory.getWeblogger().getPluginManager();
             Map<String, WeblogEntryPlugin> pluginsMap = 
ppmgr.getWeblogEntryPlugins(getActionWeblog());
-            List<WeblogEntryPlugin> plugins = new ArrayList<>();
-            for (WeblogEntryPlugin entryPlugin : pluginsMap.values()) {
-                plugins.add(entryPlugin);
-            }
+            List<WeblogEntryPlugin> plugins = new 
ArrayList<>(pluginsMap.values());
 
             // sort
             setPluginsList(plugins);
@@ -231,5 +231,8 @@ public class WeblogConfig extends UIAction {
     public void setPluginsList(List<WeblogEntryPlugin> pluginsList) {
         this.pluginsList = pluginsList;
     }
-    
+
+    public boolean getWeblogAdminsUntrusted() {
+        return weblogAdminsUntrusted;
+    }
 }
diff --git a/app/src/main/resources/ApplicationResources.properties 
b/app/src/main/resources/ApplicationResources.properties
index b318ff328..46c37454a 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -1759,6 +1759,7 @@ websiteSettings.formatting=Formatting
 websiteSettings.spamPrevention=Spam Prevention
 websiteSettings.ignoreUrls=List of words and regex expressions listed one per \
 line to be added to the banned words list used to check comments, trackbacks 
and referrers.
+websiteSettings.bannedWordsList=Words banned in comments (regex allowed)
 websiteSettings.acceptedBannedwordslist=Accepted {0} string and {1} regex 
banned-words list rules
 websiteSettings.error.processingBannedwordslist=Error processing banned-words 
list: {0}
 
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/WeblogConfig.jsp 
b/app/src/main/webapp/WEB-INF/jsps/editor/WeblogConfig.jsp
index 770e01ef7..c0437c721 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/WeblogConfig.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/WeblogConfig.jsp
@@ -130,11 +130,11 @@
     <h3><s:text name="websiteSettings.spamPrevention"/></h3>
 
     <s:textarea name="bean.bannedwordslist" rows="7" cols="40"
-                label="%{getText('websiteSettings.analyticsTrackingCode')}"/>
+                label="%{getText('websiteSettings.bannedWordsList')}"/>
 
     <%-- ***** Web analytics settings ***** --%>
 
-    <s:if test="getBooleanProp('analytics.code.override.allowed')">
+    <s:if test="getBooleanProp('analytics.code.override.allowed') && 
!weblogAdminsUntrusted">
         <h3><s:text name="configForm.webAnalytics"/></h3>
 
         <s:textarea name="bean.analyticsCode" rows="10" cols="70"
diff --git 
a/app/src/test/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilterTest.java
 
b/app/src/test/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilterTest.java
new file mode 100644
index 000000000..5ace927a2
--- /dev/null
+++ 
b/app/src/test/java/org/apache/roller/weblogger/ui/core/filters/LoadSaltFilterTest.java
@@ -0,0 +1,88 @@
+package org.apache.roller.weblogger.ui.core.filters;
+
+import org.apache.roller.weblogger.pojos.User;
+import org.apache.roller.weblogger.ui.core.RollerSession;
+import org.apache.roller.weblogger.ui.rendering.util.cache.SaltCache;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.MockitoAnnotations;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.mockito.Mockito.*;
+
+public class LoadSaltFilterTest {
+
+    private LoadSaltFilter filter;
+
+    @Mock
+    private HttpServletRequest request;
+
+    @Mock
+    private HttpServletResponse response;
+
+    @Mock
+    private FilterChain chain;
+
+    @Mock
+    private RollerSession rollerSession;
+
+    @Mock
+    private SaltCache saltCache;
+
+    @BeforeEach
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        filter = new LoadSaltFilter();
+    }
+
+    @Test
+    public void testDoFilterGeneratesSalt() throws Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(rollerSession);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+
+            when(rollerSession.getAuthenticatedUser()).thenReturn(new 
TestUser("userId"));
+
+            filter.doFilter(request, response, chain);
+
+            verify(request).setAttribute(eq("salt"), anyString());
+            verify(saltCache).put(anyString(), eq("userId"));
+            verify(chain).doFilter(request, response);
+        }
+    }
+
+    @Test
+    public void testDoFilterWithNullRollerSession() throws Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(null);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+
+            filter.doFilter(request, response, chain);
+
+            verify(request, never()).setAttribute(eq("salt"), anyString());
+            verify(saltCache, never()).put(anyString(), anyString());
+            verify(chain).doFilter(request, response);
+        }
+    }
+
+    private static class TestUser extends User {
+        private final String id;
+
+        TestUser(String id) {
+            this.id = id;
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+}
diff --git 
a/app/src/test/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilterTest.java
 
b/app/src/test/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilterTest.java
new file mode 100644
index 000000000..ab866d080
--- /dev/null
+++ 
b/app/src/test/java/org/apache/roller/weblogger/ui/core/filters/ValidateSaltFilterTest.java
@@ -0,0 +1,179 @@
+package org.apache.roller.weblogger.ui.core.filters;
+
+import org.apache.roller.weblogger.config.WebloggerConfig;
+import org.apache.roller.weblogger.pojos.User;
+import org.apache.roller.weblogger.ui.core.RollerSession;
+import org.apache.roller.weblogger.ui.rendering.util.cache.SaltCache;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.MockitoAnnotations;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+public class ValidateSaltFilterTest {
+
+    private ValidateSaltFilter filter;
+
+    @Mock
+    private HttpServletRequest request;
+
+    @Mock
+    private HttpServletResponse response;
+
+    @Mock
+    private FilterChain chain;
+
+    @Mock
+    private RollerSession rollerSession;
+
+    @Mock
+    private SaltCache saltCache;
+
+    @BeforeEach
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+        filter = new ValidateSaltFilter();
+    }
+
+    @Test
+    public void testDoFilterWithGetMethod() throws Exception {
+        when(request.getMethod()).thenReturn("GET");
+        StringBuffer requestURL = new 
StringBuffer("https://example.com/app/ignoredurl";);
+        when(request.getRequestURL()).thenReturn(requestURL);
+
+        filter.doFilter(request, response, chain);
+
+        verify(chain).doFilter(request, response);
+    }
+
+    @Test
+    public void testDoFilterWithPostMethodAndValidSalt() throws Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(rollerSession);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+
+            when(request.getMethod()).thenReturn("POST");
+            when(request.getParameter("salt")).thenReturn("validSalt");
+            when(saltCache.get("validSalt")).thenReturn("userId");
+            when(rollerSession.getAuthenticatedUser()).thenReturn(new 
TestUser("userId"));
+            StringBuffer requestURL = new 
StringBuffer("https://example.com/app/ignoredurl";);
+            when(request.getRequestURL()).thenReturn(requestURL);
+
+            filter.doFilter(request, response, chain);
+
+            verify(chain).doFilter(request, response);
+            verify(saltCache).remove("validSalt");
+        }
+    }
+
+    @Test
+    public void testDoFilterWithPostMethodAndInvalidSalt() throws Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(rollerSession);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+
+            when(request.getMethod()).thenReturn("POST");
+            when(request.getParameter("salt")).thenReturn("invalidSalt");
+            when(saltCache.get("invalidSalt")).thenReturn(null);
+            StringBuffer requestURL = new 
StringBuffer("https://example.com/app/ignoredurl";);
+            when(request.getRequestURL()).thenReturn(requestURL);
+
+            assertThrows(ServletException.class, () -> {
+                filter.doFilter(request, response, chain);
+            });
+        }
+    }
+
+    @Test
+    public void testDoFilterWithPostMethodAndMismatchedUserId() throws 
Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(rollerSession);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+
+            when(request.getMethod()).thenReturn("POST");
+            when(request.getParameter("salt")).thenReturn("validSalt");
+            when(saltCache.get("validSalt")).thenReturn("differentUserId");
+            when(rollerSession.getAuthenticatedUser()).thenReturn(new 
TestUser("userId"));
+            StringBuffer requestURL = new 
StringBuffer("https://example.com/app/ignoredurl";);
+            when(request.getRequestURL()).thenReturn(requestURL);
+
+            assertThrows(ServletException.class, () -> {
+                filter.doFilter(request, response, chain);
+            });
+        }
+    }
+
+    @Test
+    public void testDoFilterWithPostMethodAndNullRollerSession() throws 
Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(null);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+
+            when(request.getMethod()).thenReturn("POST");
+            when(request.getParameter("salt")).thenReturn("validSalt");
+            when(saltCache.get("validSalt")).thenReturn("");
+            StringBuffer requestURL = new 
StringBuffer("https://example.com/app/ignoredurl";);
+            when(request.getRequestURL()).thenReturn(requestURL);
+
+            filter.doFilter(request, response, chain);
+
+            verify(saltCache, never()).remove("validSalt");
+        }
+    }
+
+    @Test
+    public void testDoFilterWithIgnoredURL() throws Exception {
+        try (MockedStatic<RollerSession> mockedRollerSession = 
mockStatic(RollerSession.class);
+             MockedStatic<SaltCache> mockedSaltCache = 
mockStatic(SaltCache.class);
+             MockedStatic<WebloggerConfig> mockedWebloggerConfig = 
mockStatic(WebloggerConfig.class)) {
+
+            mockedRollerSession.when(() -> 
RollerSession.getRollerSession(request)).thenReturn(rollerSession);
+            mockedSaltCache.when(SaltCache::getInstance).thenReturn(saltCache);
+            mockedWebloggerConfig.when(() -> 
WebloggerConfig.getProperty("salt.ignored.urls"))
+                    
.thenReturn("https://example.com/app/ignoredurl?param1=value1&m2=value2";);
+
+            when(request.getMethod()).thenReturn("POST");
+            StringBuffer requestURL = new 
StringBuffer("https://example.com/app/ignoredurl";);
+            when(request.getRequestURL()).thenReturn(requestURL);
+            
when(request.getQueryString()).thenReturn("param1=value1&m2=value2");
+            when(request.getParameter("salt")).thenReturn(null);  // No salt 
provided
+
+            filter.init(mock(FilterConfig.class));
+            filter.doFilter(request, response, chain);
+
+            verify(chain).doFilter(request, response);
+            verify(saltCache, never()).get(anyString());
+            verify(saltCache, never()).remove(anyString());
+        }
+    }
+
+    private static class TestUser extends User {
+        private final String id;
+
+        TestUser(String id) {
+            this.id = id;
+        }
+
+        @Override
+        public String getId() {
+            return id;
+        }
+    }
+}

Reply via email to