You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2026/09/05 20:51:25 UTC

(roller) branch master updated: Retire the WSSE AtomPub authentication mode (#166)

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 2bffdea23 Retire the WSSE AtomPub authentication mode (#166)
2bffdea23 is described below

commit 2bffdea23b08b31265eaf3d43c382ea6907049e9
Author: David M. Johnson <sn...@users.noreply.github.com>
AuthorDate: Sat Sep 5 16:51:19 2026 -0400

    Retire the WSSE AtomPub authentication mode (#166)
    
    * Retire the WSSE AtomPub authentication mode
    
    WSSE has no other production caller; AtomPub retains Basic and OAuth authentication.
    
    Claude-Session: https://claude.ai/code/session_01A1fhY1E2PCFU6UAPXu2WtV
    
    * Restore and normalize AtomPub authentication dispatch
---
 .../roller/weblogger/util/WSSEUtilities.java       |  83 -------------
 .../atomprotocol/RollerAtomHandler.java            |  71 +++--------
 .../main/resources/ApplicationResources.properties |   2 +-
 .../resources/ApplicationResources_ja.properties   |   2 +-
 .../ApplicationResources_zh_CN.properties          |   2 +-
 .../atomprotocol/RollerAtomHandlerTest.java        | 137 +++++++++++++++++++++
 6 files changed, 154 insertions(+), 143 deletions(-)

diff --git a/app/src/main/java/org/apache/roller/weblogger/util/WSSEUtilities.java b/app/src/main/java/org/apache/roller/weblogger/util/WSSEUtilities.java
deleted file mode 100644
index e63032b44..000000000
--- a/app/src/main/java/org/apache/roller/weblogger/util/WSSEUtilities.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2005, Dave Johnson
- * 
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.roller.weblogger.util;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.apache.commons.codec.binary.Base64;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Utilties to support WSSE authentication.
- * @author Dave Johnson
- */
-public class WSSEUtilities {
-    public static synchronized String generateDigest(
-            byte[] nonce, byte[] created, byte[] password) {
-        String result = null;
-        try {
-            MessageDigest digester = MessageDigest.getInstance("SHA");
-            digester.reset();
-            digester.update(nonce);
-            digester.update(created);
-            digester.update(password);
-            byte[] digest = digester.digest();
-            result = base64Encode(digest);
-        }
-        catch (NoSuchAlgorithmException e) {
-            result = null;
-        }
-        return result;
-    }
-    public static byte[] base64Decode(String value) throws IOException {
-        return Base64.decodeBase64(value.getBytes(UTF_8));
-    }
-    public static String base64Encode(byte[] value) {
-        return new String(Base64.encodeBase64(value));
-    }
-    public static String generateWSSEHeader(String userName, String password) 
-    throws UnsupportedEncodingException {  
-       
-        byte[] nonceBytes = Long.toString(new Date().getTime()).getBytes();
-        String nonce = WSSEUtilities.base64Encode(nonceBytes);
-        
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-        String created = sdf.format(new Date());
-        
-        String digest = WSSEUtilities.generateDigest(
-                nonceBytes, created.getBytes(UTF_8), password.getBytes(UTF_8));
-        
-        StringBuilder header = new StringBuilder("UsernameToken Username=\"");
-        header.append(userName);
-        header.append("\", ");
-        header.append("PasswordDigest=\"");
-        header.append(digest);
-        header.append("\", ");
-        header.append("Nonce=\"");
-        header.append(nonce);
-        header.append("\", ");
-        header.append("Created=\"");
-        header.append(created);
-        header.append("\"");
-        return header.toString();
-    }
-}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java
index 55e1c576e..7f9bc76fe 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java
@@ -19,6 +19,7 @@ package org.apache.roller.weblogger.webservices.atomprotocol;
 import com.rometools.propono.atom.common.Categories;
 import com.rometools.propono.atom.server.AtomRequest;
 import java.util.StringTokenizer;
+import java.util.Locale;
 import javax.servlet.http.HttpServletRequest;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.logging.Log;
@@ -29,7 +30,6 @@ import org.apache.roller.weblogger.business.WebloggerFactory;
 import org.apache.roller.weblogger.pojos.User;
 import org.apache.roller.weblogger.pojos.WeblogEntry;
 import org.apache.roller.weblogger.pojos.Weblog;
-import org.apache.roller.weblogger.util.WSSEUtilities;
 import com.rometools.propono.atom.common.AtomService;
 import com.rometools.propono.atom.server.AtomException;
 import com.rometools.propono.atom.server.AtomHandler;
@@ -37,7 +37,6 @@ import com.rometools.propono.atom.server.AtomMediaResource;
 import com.rometools.propono.atom.server.AtomNotFoundException;
 import com.rometools.rome.feed.atom.Entry;
 import com.rometools.rome.feed.atom.Feed;
-import java.nio.charset.StandardCharsets;
 import javax.servlet.http.HttpServletResponse;
 import net.oauth.OAuthAccessor;
 import net.oauth.OAuthMessage;
@@ -118,15 +117,19 @@ public class RollerAtomHandler implements AtomHandler {
         roller = WebloggerFactory.getWeblogger();
 
         String userName;
-        if ("oauth".equals(WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"))) {
+        String authenticationMethod = WebloggerRuntimeConfig
+                .getProperty("webservices.atomPubAuth");
+        if (authenticationMethod != null) {
+            authenticationMethod = authenticationMethod.trim().toLowerCase(Locale.ROOT);
+        }
+        if ("oauth".equals(authenticationMethod)) {
             userName = authenticationOAUTH(request, response);
-
-        } else if ("wsse".equals(WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"))) {
-            userName = authenticateWSSE(request);
-
-        } else {
-            // default to basic
+        } else if ("basic".equals(authenticationMethod)) {
             userName = authenticateBASIC(request);
+        } else {
+            log.warn("Unsupported AtomPub authentication method '" + authenticationMethod
+                    + "'; expected 'basic' or 'oauth'. Authentication denied.");
+            userName = null;
         }
 
         if (userName != null) {
@@ -410,53 +413,6 @@ public class RollerAtomHandler implements AtomHandler {
 
     //-------------------------------------------------------------- authentication
 
-    /**
-     * Perform WSSE authentication based on information in request.
-     * Will not work if Weblogger password encryption is turned on.
-     */
-    protected String authenticateWSSE(HttpServletRequest request) {
-        String wsseHeader = request.getHeader("X-WSSE");
-        String ret = null;
-        if (wsseHeader == null) {
-            return ret;
-        }
-        String userName = null;
-        String created = null;
-        String nonce = null;
-        String passwordDigest = null;
-        String[] tokens = wsseHeader.split(",");
-        for (int i = 0; i < tokens.length; i++) {
-            int index = tokens[i].indexOf('=');
-            if (index != -1) {
-                String key = tokens[i].substring(0, index).trim();
-                String value = tokens[i].substring(index + 1).trim();
-                value = value.replace("\"", "");
-                if (key.startsWith("UsernameToken")) {
-                    userName = value;
-                } else if (key.equalsIgnoreCase("nonce")) {
-                    nonce = value;
-                } else if (key.equalsIgnoreCase("passworddigest")) {
-                    passwordDigest = value;
-                } else if (key.equalsIgnoreCase("created")) {
-                    created = value;
-                }
-            }
-        }
-        String digest = null;
-        try {
-            User inUser = roller.getUserManager().getUserByUserName(userName);
-            digest = WSSEUtilities.generateDigest(WSSEUtilities.base64Decode(nonce),
-                    created.getBytes(StandardCharsets.UTF_8),
-                    inUser.getPassword().getBytes(StandardCharsets.UTF_8));
-            if (digest.equals(passwordDigest)) {
-                ret = userName;
-            }
-        } catch (Exception e) {
-            log.error("During wsseAuthenticataion: " + e.getMessage(), e);
-        }
-        return ret;
-    }
-
     /**
      * BASIC authentication.
      */
@@ -478,7 +434,8 @@ public class RollerAtomHandler implements AtomHandler {
                             User inUser = roller.getUserManager().getUserByUserName(userID);
                             if (inUser.getEnabled()) {
                                 String password = userPass.substring(p+1);
-                                valid = RollerContext.getPasswordEncoder().matches(password, user.getPassword());
+                                valid = RollerContext.getPasswordEncoder()
+                                        .matches(password, inUser.getPassword());
                             }
                         }
                     }
diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties
index 2a9b78639..f12276c39 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -338,7 +338,7 @@ configForm.editorPages=Editor Pages
 
 configForm.webServicesSettings=Web Services Settings
 configForm.enableAtomPub=Enable Atom Publishing Protocol
-configForm.AtomPubAuth=AtomPub authentication (basic, oauth, or wsse)
+configForm.AtomPubAuth=AtomPub authentication (basic or oauth)
 configForm.enableXmlRpc=Enable Blogger / MetaWeblog API
 
 configForm.weblogSettings=Weblog Rendering Settings
diff --git a/app/src/main/resources/ApplicationResources_ja.properties b/app/src/main/resources/ApplicationResources_ja.properties
index 599a609e2..0998a8052 100644
--- a/app/src/main/resources/ApplicationResources_ja.properties
+++ b/app/src/main/resources/ApplicationResources_ja.properties
@@ -1370,7 +1370,7 @@ pingTargetAdd.subtitle=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u8FFD\u52A0
 planetSubscription.feedUrl=\u30CB\u30E5\u30FC\u30B9\u30D5\u30A3\u30FC\u30C9URL
 mediaFileSuccess.noEnclosure=\u30A8\u30F3\u30AF\u30ED\u30FC\u30B8\u30E3\u306A\u3057
 userRegister.tip.ready=\u3088\u308D\u3057\u3051\u308C\u3070\u3001\u4EE5\u4E0B\u306E\u30DC\u30BF\u30F3\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002
-configForm.AtomPubAuth=AtomPub\u8A8D\u8A3C (basic, oauth, \u307E\u305F\u306F wsse)
+configForm.AtomPubAuth=AtomPub\u8A8D\u8A3C (basic \u307E\u305F\u306F oauth)
 ConfigForm.proxyPort=Feed fetcher\u304C\u4F7F\u7528\u3059\u308B\u30D7\u30ED\u30AD\u30B7\u306E\u30DD\u30FC\u30C8
 mediaFileView.searchTitle=\u691C\u7D22\u7D50\u679C
 weblogEdit.enclosureLength=\u9577\u3055
diff --git a/app/src/main/resources/ApplicationResources_zh_CN.properties b/app/src/main/resources/ApplicationResources_zh_CN.properties
index 1000dd54d..ec1f5748c 100644
--- a/app/src/main/resources/ApplicationResources_zh_CN.properties
+++ b/app/src/main/resources/ApplicationResources_zh_CN.properties
@@ -337,7 +337,7 @@ configForm.editorPages=\u7F16\u8F91\u5668\u9875\u9762
 
 configForm.webServicesSettings=Web\u670D\u52A1\u8BBE\u7F6E
 configForm.enableAtomPub=\u542F\u7528 Atom \u53D1\u5E03\u534F\u8BAE
-configForm.AtomPubAuth=AtomPub \u8BA4\u8BC1\u65B9\u5F0F (basic / oauth / wsse)
+configForm.AtomPubAuth=AtomPub \u8BA4\u8BC1\u65B9\u5F0F (basic / oauth)
 configForm.enableXmlRpc=\u542F\u7528 Blogger / MetaWeblog API
 
 configForm.weblogSettings=\u535A\u5BA2\u663E\u793A\u8BBE\u7F6E
diff --git a/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandlerTest.java b/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandlerTest.java
new file mode 100644
index 000000000..899a06f43
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandlerTest.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0.
+ */
+package org.apache.roller.weblogger.webservices.atomprotocol;
+
+import org.apache.roller.weblogger.business.URLStrategy;
+import org.apache.roller.weblogger.business.UserManager;
+import org.apache.roller.weblogger.business.Weblogger;
+import org.apache.roller.weblogger.business.WebloggerFactory;
+import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
+import org.apache.roller.weblogger.pojos.User;
+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.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.time.Instant;
+import java.util.Base64;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import org.apache.roller.weblogger.ui.core.RollerContext;
+
+class RollerAtomHandlerTest {
+
+    private static final String USER_NAME = "alice";
+    private static final String PASSWORD = "test-password";
+
+    @Mock
+    private HttpServletRequest request;
+
+    @Mock
+    private HttpServletResponse response;
+
+    @Mock
+    private Weblogger weblogger;
+
+    @Mock
+    private UserManager userManager;
+
+    @Mock
+    private URLStrategy urlStrategy;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        MockitoAnnotations.openMocks(this);
+
+        User user = new User();
+        user.setUserName(USER_NAME);
+        user.setPassword(PASSWORD);
+        user.setEnabled(true);
+
+        when(weblogger.getUserManager()).thenReturn(userManager);
+        when(weblogger.getUrlStrategy()).thenReturn(urlStrategy);
+        when(urlStrategy.getAtomProtocolURL(true)).thenReturn("https://example.test/app");
+        when(userManager.getUserByUserName(USER_NAME)).thenReturn(user);
+    }
+
+    @Test
+    void wsseAuthenticationModeIsRejected() throws Exception {
+        String created = Instant.now().toString();
+        byte[] nonce = "test-nonce".getBytes(StandardCharsets.UTF_8);
+        MessageDigest digester = MessageDigest.getInstance("SHA-1");
+        digester.update(nonce);
+        digester.update(created.getBytes(StandardCharsets.UTF_8));
+        digester.update(PASSWORD.getBytes(StandardCharsets.UTF_8));
+        String digest = Base64.getEncoder().encodeToString(digester.digest());
+        when(request.getHeader("X-WSSE")).thenReturn(
+                "UsernameToken Username=\"" + USER_NAME
+                + "\", PasswordDigest=\"" + digest
+                + "\", Nonce=\"" + Base64.getEncoder().encodeToString(nonce)
+                + "\", Created=\"" + created + "\"");
+
+        try (MockedStatic<WebloggerFactory> factory = mockStatic(WebloggerFactory.class);
+             MockedStatic<WebloggerRuntimeConfig> config = mockStatic(WebloggerRuntimeConfig.class)) {
+            factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger);
+            config.when(() -> WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"))
+                    .thenReturn("wsse");
+
+            RollerAtomHandler handler = new RollerAtomHandler(request, response);
+
+            assertNull(handler.getAuthenticatedUsername());
+            verify(request, never()).getHeader("Authorization");
+        }
+    }
+
+    @Test
+    void basicAuthenticationAcceptsNormalizedMethodAndUsesResolvedUser() {
+        String credentials = Base64.getEncoder().encodeToString(
+                (USER_NAME + ":" + PASSWORD).getBytes(StandardCharsets.UTF_8));
+        when(request.getHeader("Authorization")).thenReturn("Basic " + credentials);
+        PasswordEncoder encoder = mock(PasswordEncoder.class);
+        when(encoder.matches(PASSWORD, PASSWORD)).thenReturn(true);
+
+        try (MockedStatic<WebloggerFactory> factory = mockStatic(WebloggerFactory.class);
+             MockedStatic<WebloggerRuntimeConfig> config = mockStatic(WebloggerRuntimeConfig.class);
+             MockedStatic<RollerContext> context = mockStatic(RollerContext.class)) {
+            factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger);
+            config.when(() -> WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"))
+                    .thenReturn("  BaSiC  ");
+            context.when(RollerContext::getPasswordEncoder).thenReturn(encoder);
+
+            RollerAtomHandler handler = new RollerAtomHandler(request, response);
+
+            assertNotNull(handler.getAuthenticatedUsername());
+        }
+    }
+
+    @Test
+    void nullAuthenticationMethodIsRejected() {
+        try (MockedStatic<WebloggerFactory> factory = mockStatic(WebloggerFactory.class);
+             MockedStatic<WebloggerRuntimeConfig> config = mockStatic(WebloggerRuntimeConfig.class)) {
+            factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger);
+            config.when(() -> WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"))
+                    .thenReturn(null);
+
+            RollerAtomHandler handler = new RollerAtomHandler(request, response);
+
+            assertNull(handler.getAuthenticatedUsername());
+            verify(request, never()).getHeader("Authorization");
+        }
+    }
+}