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");
+ }
+ }
+}