From 67c8f3467793b3544f8677e6f981145d7be1350d Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Sat, 29 Aug 2026 08:54:32 -0400 Subject: [PATCH] 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 --- .../roller/weblogger/util/WSSEUtilities.java | 83 ---------------- .../atomprotocol/RollerAtomHandler.java | 63 ++---------- .../resources/ApplicationResources.properties | 2 +- .../ApplicationResources_ja.properties | 2 +- .../ApplicationResources_zh_CN.properties | 3 +- .../atomprotocol/RollerAtomHandlerTest.java | 95 +++++++++++++++++++ 6 files changed, 105 insertions(+), 143 deletions(-) delete mode 100644 app/src/main/java/org/apache/roller/weblogger/util/WSSEUtilities.java create mode 100644 app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandlerTest.java 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 e63032b442..0000000000 --- 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 55e1c576ef..a48d8aec71 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 @@ -29,7 +29,6 @@ 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 +36,6 @@ 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 +116,15 @@ public RollerAtomHandler(HttpServletRequest request, HttpServletResponse respons roller = WebloggerFactory.getWeblogger(); String userName; - if ("oauth".equals(WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"))) { + String authenticationMethod = + WebloggerRuntimeConfig.getProperty("webservices.atomPubAuth"); + 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; authentication denied"); + userName = null; } if (userName != null) { @@ -410,53 +408,6 @@ public static boolean canView(User u, Weblog website) { //-------------------------------------------------------------- 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. */ diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties index 66072c23f0..e8438ae0d7 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 8802be030d..34af433211 100644 --- a/app/src/main/resources/ApplicationResources_ja.properties +++ b/app/src/main/resources/ApplicationResources_ja.properties @@ -1383,7 +1383,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 93ac844968..8fc1e1ad2b 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 @@ -1878,4 +1878,3 @@ user.account.activation.mail.subject=Roller\uFF1A\u4F60\u7684\u8D26\u53F7\u6FC0\ user.account.activation.mail.content=

\u8981\u6FC0\u6D3B\u4F60\u7684Roller\u8D26\u6237[{1}]\uFF0C\u8BF7\u70B9\u51FB\u4EE5\u4E0B\u94FE\u63A5\uFF1A

{2}

- 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 0000000000..bbb250cbdc --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandlerTest.java @@ -0,0 +1,95 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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); + + 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 factory = mockStatic(WebloggerFactory.class); + MockedStatic 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"); + } + } +}