-
Notifications
You must be signed in to change notification settings - Fork 161
Retire the WSSE AtomPub authentication mode #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,15 +29,13 @@ | |
| 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; | ||
| 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 +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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now the only password-based AtomPub path, and it doesn't work: |
||
| log.warn("Unsupported AtomPub authentication method; authentication denied"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For an upgraded install that still has wsse persisted in |
||
| 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. | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This covers the wsse-is-denied case, but nothing asserts that basic still authenticates through the Authorization header (which would have caught the NPE above), or that |
||
| 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"); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
webservices.atomPubAuthis a free-text runtime property (a plain textbox on the config page), and the old code fell through to Basic for anything that wasn't oauth or wsse. An exact, case-sensitive match means an admin who typed Basic or left trailing whitespace is now denied. Atrim()andtoLowerCase()before the comparisons keeps the fail-closed behavior for genuinely unknown values without punishing that.