This is an automated email from the ASF dual-hosted git repository.
mridulpathak pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push:
new 2d5c3bdf70 Fixed: REST refresh-token endpoint now rejects
disabled/deleted accounts (#1646)
2d5c3bdf70 is described below
commit 2d5c3bdf70a20d52fec954cee5436d1788feec98
Author: Mridul Pathak <[email protected]>
AuthorDate: Mon Aug 17 10:32:47 2026 +0530
Fixed: REST refresh-token endpoint now rejects disabled/deleted accounts
(#1646)
AuthenticationResource.refreshToken() minted new access/refresh tokens
directly from a validated JWT's claims, with no check that the UserLogin still
existed or was active — so disabling or deleting an account did not revoke a
holder's ability to keep renewing API tokens with a refresh token issued
beforehand. It also swallowed refresh-token validation errors instead of
returning 401. Both are fixed: the userLoginId is now looked up and confirmed
active via the same LoginWorker.isUser [...]
Thanks: Pavel Kohout, Aisle Research for reporting.
---
.../ws/rs/resources/AuthenticationResource.java | 44 ++++++++++++++++++++--
1 file changed, 41 insertions(+), 3 deletions(-)
diff --git
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/AuthenticationResource.java
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/AuthenticationResource.java
index 799c88346f..5fa0c35d8d 100644
---
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/AuthenticationResource.java
+++
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/AuthenticationResource.java
@@ -20,11 +20,17 @@ package org.apache.ofbiz.ws.rs.resources;
import java.util.Map;
+import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.UtilMisc;
+import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.entity.Delegator;
+import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityUtilProperties;
+import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.webapp.control.JWTManager;
+import org.apache.ofbiz.webapp.control.LoginWorker;
import org.apache.ofbiz.ws.rs.annotation.AuthToken;
import org.apache.ofbiz.ws.rs.util.RestApiUtil;
@@ -53,6 +59,8 @@ import jakarta.ws.rs.core.Response;
@Tag(name = "Authentication Token Generating Resource", description =
"Intended to provide generation of authentication tokens.")
public class AuthenticationResource {
+ private static final String MODULE =
AuthenticationResource.class.getName();
+
@Context
private ServletContext servletContext;
@@ -146,12 +154,17 @@ public class AuthenticationResource {
httpRequest.setAttribute("dispatcher", delegator);
Map<String, Object> claims =
JWTManager.validateRefreshToken(delegator, refreshToken);
- // Fetch delegator, dispatcher, and userLogin
- if (claims.containsKey("errorMessage")) {
- System.out.println("Error with JWT token: ");
+ if (claims.containsKey(ModelService.ERROR_MESSAGE)) {
+ return
RestApiUtil.error(Response.Status.UNAUTHORIZED.getStatusCode(),
Response.Status.UNAUTHORIZED.getReasonPhrase(),
+ "Unauthorized: " + claims.get(ModelService.ERROR_MESSAGE));
}
String userLoginId = (String) claims.get("userLoginId");
+ GenericValue userLogin = getActiveUserLogin(delegator, userLoginId);
+ if (userLogin == null) {
+ return
RestApiUtil.error(Response.Status.UNAUTHORIZED.getStatusCode(),
Response.Status.UNAUTHORIZED.getReasonPhrase(),
+ "Unauthorized: Invalid refresh token.");
+ }
String newAccessToken = JWTManager.createJwt(delegator,
UtilMisc.toMap("userLoginId", userLoginId));
String newRefreshToken = JWTManager.createRefreshToken(delegator,
userLoginId);
@@ -161,4 +174,29 @@ public class AuthenticationResource {
return RestApiUtil.success("Token refreshed.", tokenPayload);
}
+
+ /**
+ * Looks up the {@code UserLogin} named by a validated refresh token's
claims and confirms
+ * the account is still present and active, so a disabled or deleted
account cannot keep
+ * renewing tokens on the strength of a refresh token issued before the
account changed.
+ * @param delegator the delegator
+ * @param userLoginId the userLoginId claim from a cryptographically
validated refresh token
+ * @return the active userLogin, or {@code null} if it is missing,
deleted, or disabled
+ */
+ private GenericValue getActiveUserLogin(Delegator delegator, String
userLoginId) {
+ if (UtilValidate.isEmpty(userLoginId)) {
+ return null;
+ }
+ GenericValue userLogin;
+ try {
+ userLogin =
EntityQuery.use(delegator).from("UserLogin").where("userLoginId",
userLoginId).queryOne();
+ } catch (GenericEntityException e) {
+ Debug.logError(e, "Unable to get UserLogin information from
refresh token: " + e.getMessage(), MODULE);
+ return null;
+ }
+ if (userLogin == null || !LoginWorker.isUserLoginActive(userLogin)) {
+ return null;
+ }
+ return userLogin;
+ }
}