From 368ef4574e51b95d63fcdac6b066b6c9ccc15d61 Mon Sep 17 00:00:00 2001 From: Mridul Pathak Date: Tue, 15 Sep 2026 18:04:21 +0530 Subject: [PATCH 1/2] Fixed: ModelApiReader resolved omitted auth/publish attributes to false, opposite of rest-api.xsd's declared default ModelApiReader parsed an omitted auth or publish attribute via Boolean.parseBoolean(""), which resolves to false, while rest-api.xsd declares both attributes default="true". A .rest.xml resource or operation that omits auth therefore registered without the @Secured binding OFBizApiConfig applies based on isAuth(), contrary to what the schema documents. OpenApiResource (the OpenAPI schema endpoint) also had no @Secured binding of its own, independent of any .rest.xml auth setting. ModelApiReader now defaults both attributes to true when omitted, matching the XSD, and OpenApiResource is now bound with @Secured like other protected resources. --- .../ofbiz/ws/rs/model/ModelApiReader.java | 23 +++++++++++++++---- .../ws/rs/resources/OpenApiResource.java | 2 ++ .../ofbiz/ws/rs/model/ModelApiReaderTest.java | 10 ++++---- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java index a6aab6c294e..bec41a8739a 100644 --- a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java +++ b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/model/ModelApiReader.java @@ -61,7 +61,7 @@ public static ModelApi getModelApi(final File apiDef) { api.setName(UtilXml.checkEmpty(docElement.getAttribute("name")).intern()); api.setDescription(UtilXml.checkEmpty(docElement.getAttribute("description")).intern()); api.setPath(UtilXml.checkEmpty(docElement.getAttribute("path")).intern()); - api.setPublish(Boolean.parseBoolean(UtilXml.checkEmpty(docElement.getAttribute("publish")).intern())); + api.setPublish(parseBooleanDefaultTrue(docElement.getAttribute("publish"))); for (Element resourceEle : UtilXml.childElementList(docElement, "resource")) { createModelResource(resourceEle, api); } @@ -107,8 +107,8 @@ private static ModelResource buildResource(Element resourceEle) { .description(UtilXml.checkEmpty(resourceEle.getAttribute("description")).intern()) .displayName(UtilXml.checkEmpty(resourceEle.getAttribute("displayName")).intern()) .path(UtilXml.checkEmpty(resourceEle.getAttribute("path")).intern()) - .publish(Boolean.parseBoolean(UtilXml.checkEmpty(resourceEle.getAttribute("publish")).intern())) - .auth(Boolean.parseBoolean(UtilXml.checkEmpty(resourceEle.getAttribute("auth")).intern())) + .publish(parseBooleanDefaultTrue(resourceEle.getAttribute("publish"))) + .auth(parseBooleanDefaultTrue(resourceEle.getAttribute("auth"))) .primaryPermission(UtilXml.checkEmpty(resourceEle.getAttribute("primaryPermission")).intern()) .mainAction(UtilXml.checkEmpty(resourceEle.getAttribute("mainAction")).intern()) .customHeaders(UtilXml.checkEmpty(resourceEle.getAttribute("customHeaders")).intern()); @@ -125,7 +125,7 @@ private static void createOperations(Element resourceEle, ModelResource resource .produces(UtilXml.checkEmpty(operationEle.getAttribute("produces")).intern()) .consumes(UtilXml.checkEmpty(operationEle.getAttribute("consumes")).intern()) .description(UtilXml.checkEmpty(operationEle.getAttribute("description")).intern()) - .auth(Boolean.parseBoolean(UtilXml.checkEmpty(operationEle.getAttribute("auth")).intern())) + .auth(parseBooleanDefaultTrue(operationEle.getAttribute("auth"))) .primaryPermission(UtilXml.checkEmpty(operationEle.getAttribute("primaryPermission"), resourceEle.getAttribute("primaryPermission")).intern()) .mainAction(UtilXml.checkEmpty(operationEle.getAttribute("mainAction"), @@ -144,6 +144,21 @@ private static void createOperations(Element resourceEle, ModelResource resource } + /** + * Parses an {@code xs:boolean}-typed attribute that {@code rest-api.xsd} declares with + * {@code default="true"} (currently {@code publish} and {@code auth}). DOM's + * {@code Element.getAttribute} returns {@code ""} for an attribute that is absent from the + * source XML, so the omitted case must be defaulted explicitly rather than handed to + * {@link Boolean#parseBoolean(String)}, which would otherwise resolve it to {@code false}. + * + * @param attributeValue the raw attribute value, or {@code ""} if the attribute was omitted + * @return {@code true} if the attribute was omitted or explicitly {@code "true"}; {@code false} otherwise + */ + private static boolean parseBooleanDefaultTrue(String attributeValue) { + String value = UtilXml.checkEmpty(attributeValue).intern(); + return value.isEmpty() || Boolean.parseBoolean(value); + } + private static void createQueryParams(Element operationEle, ModelOperation operation) { for (Element queryParamEle : UtilXml.childElementList(operationEle, "queryParam")) { ModelQueryParam qp = new ModelQueryParam() diff --git a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java index 1ad95f3560b..ba3dc30ecd3 100644 --- a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java +++ b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java @@ -23,6 +23,7 @@ import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.ws.rs.annotation.Secured; import org.apache.ofbiz.ws.rs.openapi.OFBizOpenApiReader; import org.apache.ofbiz.ws.rs.openapi.OFBizResourceScanner; @@ -53,6 +54,7 @@ import jakarta.ws.rs.core.UriInfo; @Path("/openapi.{type:json|yaml}") +@Secured public final class OpenApiResource { @Context private ServletConfig config; diff --git a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/model/ModelApiReaderTest.java b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/model/ModelApiReaderTest.java index 5cb0f398dae..6d21ed7e6fb 100644 --- a/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/model/ModelApiReaderTest.java +++ b/framework/rest-api/src/test/java/org/apache/ofbiz/ws/rs/model/ModelApiReaderTest.java @@ -19,7 +19,6 @@ package org.apache.ofbiz.ws.rs.model; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -67,15 +66,14 @@ void testGetModelApiParsesTopLevelAttributes() throws IOException { } @Test - void testGetModelApiMissingPublishAttributeDefaultsToFalse() throws IOException { - // No "publish" attribute at all -> UtilXml.checkEmpty(...) yields "", - // and Boolean.parseBoolean("") is false. This locks in that implicit - // default so a change in checkEmpty/parseBoolean behavior is caught. + void testGetModelApiMissingPublishAttributeDefaultsToTrue() throws IOException { + // No "publish" attribute at all -> rest-api.xsd declares this attribute + // default="true", so an omitted attribute must resolve to true. File file = writeXml(""); ModelApi api = ModelApiReader.getModelApi(file); - assertFalse(api.isPublish()); + assertTrue(api.isPublish()); } @Test From 3018510609058021f4ae5e9b36ed6c46f33609eb Mon Sep 17 00:00:00 2001 From: Mridul Pathak Date: Wed, 16 Sep 2026 16:28:46 +0530 Subject: [PATCH 2/2] Reverted: OpenApiResource no longer requires @Secured Gating GET /rest/openapi.json behind auth conflicts with how Swagger UI's own Authorize flow works: it assumes the spec is viewable before a client has a token, since that's where a developer discovers how to obtain one in the first place. Leaving OpenApiResource unauthenticated, as it was before this fix, keeps that flow intact. --- .../java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java index ba3dc30ecd3..1ad95f3560b 100644 --- a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java +++ b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/resources/OpenApiResource.java @@ -23,7 +23,6 @@ import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; -import org.apache.ofbiz.ws.rs.annotation.Secured; import org.apache.ofbiz.ws.rs.openapi.OFBizOpenApiReader; import org.apache.ofbiz.ws.rs.openapi.OFBizResourceScanner; @@ -54,7 +53,6 @@ import jakarta.ws.rs.core.UriInfo; @Path("/openapi.{type:json|yaml}") -@Secured public final class OpenApiResource { @Context private ServletConfig config;