Skip to content

Conversation

@i-a-m-s-k
Copy link
Contributor

@i-a-m-s-k i-a-m-s-k commented Nov 24, 2025

Removed XMLMap. Added Jackson based objects for implementation.

Summary by CodeRabbit

Release Notes

  • Chores

    • Modernized XML message handling for LTI protocol support using Jackson framework
    • Updated dependency management to support enhanced XML processing capabilities
    • Refactored internal message serialization and response generation architecture
  • Tests

    • Added comprehensive test coverage for XML message parsing and response generation
    • Expanded test suites for POX protocol handling and request validation

✏️ Tip: You can customize this high-level summary in your review settings.

@i-a-m-s-k i-a-m-s-k requested a review from csev November 24, 2025 20:02
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 24, 2025

Walkthrough

This pull request migrates the LTI module from POX XML handling using legacy utilities to a Jackson-based XML serialization framework. It replaces custom map-based XML construction with structured builder patterns, removes obsolete XML/JSON parsing utilities, and introduces comprehensive Jackson-annotated data model classes for POX envelope structures, LTI responses, members, results, and error handling.

Changes

Cohort / File(s) Change Summary
Maven Dependencies
lti/lti-blis/pom.xml, lti/tsugi-util/pom.xml
Added jackson-dataformat-xml dependency to enable Jackson-based XML serialization across the LTI modules.
Legacy Utilities (Deleted)
lti/tsugi-util/src/java/org/tsugi/lti/XMLMap.java, lti/tsugi-util/src/java/org/tsugi/json/IMSJSONRequest.java, lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequest.java
Removed legacy XML/JSON parsing and response generation utilities. XMLMap provided manual map-based XML conversion; IMSJSONRequest handled JSON requests; IMSPOXRequest handled POX message parsing with custom XML building.
Servlet Refactoring
lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java
Refactored to use Jackson-based XML serialization via builder patterns (MessageResponseBuilder, ReadResultResponse, etc.) instead of manual TreeMap/XMLMap construction. Updated method signatures to remove map parameters; introduced buildPoxBodyXml() helper and switched from IMSPOXRequest to IMSPOXRequestJackson.
POX Infrastructure (Jackson-based)
lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java, lti/tsugi-util/src/java/org/tsugi/pox/POXConstants.java, lti/tsugi-util/src/java/org/tsugi/pox/POXResponseBuilder.java, lti/tsugi-util/src/java/org/tsugi/pox/POXResponseFactory.java
Introduced new Jackson-based POX request/response handling. IMSPOXRequestJackson parses XML into POXEnvelopeRequest; POXConstants centralizes code definitions; POXResponseBuilder and POXResponseFactory provide fluent APIs for constructing and serializing POX responses.
Message/Response Builders
lti/tsugi-util/src/java/org/tsugi/lti/MessageResponseBuilder.java, lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java
Added builder utilities for message responses and POX parsing. MessageResponseBuilder provides fluent API for constructing MessageResponse objects with status, members, and results. POXJacksonParser offers parsing, validation, and response generation for POX messages.
Data Model Classes (POX/Response Structures)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeRequest.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeResponse.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeader.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeaderInfo.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseBody.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXStatusInfo.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinor.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinorField.java
Introduced Jackson XML-annotated data classes representing POX envelope structures (requests and responses), header/body information, and status/error codes. Enable type-safe deserialization and serialization of IMS POX messages.
Data Model Classes (Results)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Result.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultData.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultScore.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultRecord.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/SourcedGUID.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultRequest.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultResponse.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultRequest.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultResponse.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java
Added data model classes for LTI result operations (read, replace, delete). Provide Jackson XML bindings for result records, scores, data, and sourced identifiers, enabling structured handling of outcome management requests/responses.
Data Model Classes (Members & Groups)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/GroupSet.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/Groups.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/Membership.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/MembershipRecord.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipRequest.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipResponse.java
Introduced data model classes for roster operations. Represent member information, groups, and membership records with Jackson XML annotations for structured roster management.
Data Model Classes (Messages & Status)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/StatusInfo.java
Added message response and status information classes. Enable structured representation of LTI message responses with status codes and descriptions.
Data Model Classes (Metadata)
lti/tsugi-util/src/java/org/tsugi/lti/objects/AdminPeriod.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/TimeFrame.java, lti/tsugi-util/src/java/org/tsugi/lti/objects/Role.java
Added supporting metadata classes for administrative periods, time frames, and role definitions with Jackson XML bindings.
Test Classes
lti/tsugi-util/src/test/java/org/tsugi/lti/POXJacksonTest.java, lti/tsugi-util/src/test/java/org/tsugi/pox/IMSPOXRequestJacksonTest.java, lti/tsugi-util/src/test/java/org/tsugi/pox/POXResponseBuilderTest.java
Added comprehensive test coverage for POX parsing, request handling, and response building using JUnit, validating XML serialization/deserialization and error handling paths.

Suggested reviewers

  • Code maintainer responsible for LTI module architecture and XML handling patterns
  • QA or testing specialist familiar with POX message formats and IMS Global standards
  • Developer with experience in Jackson XML serialization and builder patterns
  • Reviewer responsible for servlet-level request/response handling in the Sakai LTI integration

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.12% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'SAK-51434 LTI Remove the XMLMap Library from LTI' accurately and clearly describes the primary objective: removing the XMLMap library and replacing it with Jackson-based XML processing throughout the LTI module.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java (1)

588-613: POX XML outcome flow: sourcedId path inconsistency and serialization behavior

Two points worth tightening up in the POX XML outcome flow:

  1. sourcedId path inconsistency (likely typo/pre‑existing bug)
    In doPostXml you extract the main sourcedid from bodyMap.get("/resultRecord/sourcedGUID/sourcedId"), but in processOutcomeXml you build the response’s Result.sourcedId from bodyMap.get("/resultRecord/result/sourcedId"). This path mismatch means the response’s sourcedId will typically be null even when the request carried a valid sourced GUID.

    Consider aligning sourced_id with the same key you use earlier:

  •    String sourced_id = bodyMap.get("/resultRecord/result/sourcedId");
    
  •    String sourced_id = bodyMap.get("/resultRecord/sourcedGUID/sourcedId");
    
    
    so the response echoes the same identifier.
    
    
  1. Serialization fallback on error
    buildPoxBodyXml returns an empty string when serialization fails, and pox.getResponseSuccess(message, bodyXml) will still build a success response. If you want tools to distinguish serialization failures from normal “empty body” outcomes, you might want to log at error and/or call doErrorXML instead when bodyXml ends up empty unexpectedly.

Overall, the move to POJO payloads plus Jackson serialization looks solid; these tweaks would just tighten correctness and observability.

Also applies to: 644-665, 799-901

🧹 Nitpick comments (18)
lti/tsugi-util/src/java/org/tsugi/lti/objects/TimeFrame.java (1)

7-21: TimeFrame POJO looks consistent; Lombok @ToString is redundant

The field set and XML names look coherent for a simple envelope object; nothing blocking here. Minor nit: @Data already includes @ToString, so keeping both is redundant. You can drop the explicit @ToString here (and in similar classes) if you prefer to keep Lombok usage minimal and consistent.

lti/tsugi-util/src/java/org/tsugi/lti/objects/AdminPeriod.java (1)

7-15: AdminPeriod is fine; consider dropping redundant @ToString

This is a straightforward value object and fits cleanly with the surrounding types. As with TimeFrame, @Data already generates toString(), so the separate @ToString annotation is redundant and can be removed for simplicity if desired.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Role.java (1)

7-27: Role structure and XML mapping look good; @ToString is optional

The Role model ties together the expected fields (including TimeFrame) and the XML element names are consistent. Only minor nit: @Data already includes @ToString, so you can safely remove the explicit @ToString annotation if you want to reduce Lombok noise.

lti/tsugi-util/src/java/org/tsugi/lti/objects/StatusInfo.java (1)

7-21: StatusInfo XML mapping is correct; consider enums as an optional refinement

The four fields are correctly mapped to the expected XML element names and integrate cleanly with MessageResponse. As an optional future improvement, codeMajor and severity could be modeled as enums to restrict values to the spec-defined set, but the current String-based approach is perfectly acceptable and keeps the model simple.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Membership.java (1)

1-27: Membership mapping is coherent; consider creditHours type

The field and XML element names are consistent with the surrounding POX DTOs, and the members collection mapping (no wrapper, member elements) matches the pattern used elsewhere. One design consideration: if upstream systems ever send fractional credit hours, you may want BigDecimal or Double instead of Integer for creditHours; otherwise Integer is fine.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseBody.java (1)

13-30: Confirm rawContent serialization matches legacy POX body expectations

The typed response mappings (replace/read/delete result and membership) look good and mirror POXRequestBody. One potential subtle point is rawContent: with @JacksonXmlProperty(localName = "rawContent") it will serialize as a <rawContent>...</rawContent> child element, and Jackson will escape any XML markup in the String. If callers (for example, IMSPOXRequestJacksonTest.testGetResponseSuccess) expect a fragment like <test>body</test> to appear directly under <imsx_POXBody> rather than wrapped/escaped, please double‑check how bodyString/rawContent are being used in POXJacksonParser / the response factory and adjust the mapping or builder logic if necessary.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1)

9-30: Unify NON_NULL handling at class level and include rawContent

You can simplify and make behavior more consistent by moving @JsonInclude(Include.NON_NULL) to the class and dropping the per‑field annotations, which will also cover rawContent so you do not emit empty <rawContent/> elements.

-@Data
-@ToString
-public class POXRequestBody {
+@Data
+@ToString
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class POXRequestBody {
@@
-    @JacksonXmlProperty(localName = "replaceResultRequest")
-    @JsonInclude(JsonInclude.Include.NON_NULL)
-    private ReplaceResultRequest replaceResultRequest;
+    @JacksonXmlProperty(localName = "replaceResultRequest")
+    private ReplaceResultRequest replaceResultRequest;
@@
-    @JacksonXmlProperty(localName = "readResultRequest")
-    @JsonInclude(JsonInclude.Include.NON_NULL)
-    private ReadResultRequest readResultRequest;
+    @JacksonXmlProperty(localName = "readResultRequest")
+    private ReadResultRequest readResultRequest;
@@
-    @JacksonXmlProperty(localName = "deleteResultRequest")
-    @JsonInclude(JsonInclude.Include.NON_NULL)
-    private DeleteResultRequest deleteResultRequest;
+    @JacksonXmlProperty(localName = "deleteResultRequest")
+    private DeleteResultRequest deleteResultRequest;
@@
-    @JacksonXmlProperty(localName = "readMembershipRequest")
-    @JsonInclude(JsonInclude.Include.NON_NULL)
-    private ReadMembershipRequest readMembershipRequest;
+    @JacksonXmlProperty(localName = "readMembershipRequest")
+    private ReadMembershipRequest readMembershipRequest;
@@
-    @JacksonXmlProperty(localName = "rawContent")
-    private String rawContent;
+    @JacksonXmlProperty(localName = "rawContent")
+    private String rawContent;
lti/tsugi-util/src/java/org/tsugi/lti/MessageResponseBuilder.java (2)

34-46: Avoid magic strings for StatusInfo; prefer shared constants or enums

withStatusInfo plus the success() and error() helpers currently rely on hard‑coded strings like "Success", "Fail", "Status", "Error", "fullsuccess". If these values are constrained by a spec or reused elsewhere, consider centralizing them as constants (or an enum) to prevent drift and typos across the codebase.

 public class MessageResponseBuilder {
-    
+
+    // Example: centralize commonly used status values
+    public static final String CODE_MAJOR_SUCCESS = "Success";
+    public static final String CODE_MAJOR_FAIL = "Fail";
+    public static final String SEVERITY_STATUS = "Status";
+    public static final String SEVERITY_ERROR = "Error";
+    public static final String CODE_MINOR_FULLSUCCESS = "fullsuccess";
@@
-    public static MessageResponseBuilder success() {
-        return new MessageResponseBuilder()
-            .withStatusInfo("Success", "Status", "fullsuccess", null);
-    }
+    public static MessageResponseBuilder success() {
+        return new MessageResponseBuilder()
+            .withStatusInfo(CODE_MAJOR_SUCCESS, SEVERITY_STATUS, CODE_MINOR_FULLSUCCESS, null);
+    }
@@
-    public static MessageResponseBuilder error(String description) {
-        return new MessageResponseBuilder()
-            .withStatusInfo("Fail", "Error", null, description);
-    }
+    public static MessageResponseBuilder error(String description) {
+        return new MessageResponseBuilder()
+            .withStatusInfo(CODE_MAJOR_FAIL, SEVERITY_ERROR, null, description);
+    }

Also applies to: 99-107


57-73: Skip creating an empty <result> when both score and data are null

withResult currently creates a Result and attaches it even when both resultScore and resultData are null, which can yield an empty <result/> element in the XML. If that is not required by the consumers, consider early‑returning in that case.

-    public MessageResponseBuilder withResult(String resultScore, String resultData) {
-        Result result = new Result();
-        
-        if (resultScore != null) {
-            ResultScore score = new ResultScore();
-            score.setTextString(resultScore);
-            result.setResultScore(score);
-        }
-        
-        if (resultData != null) {
-            ResultData data = new ResultData();
-            data.setText(resultData);
-            result.setResultData(data);
-        }
-        
-        response.setResult(result);
-        return this;
-    }
+    public MessageResponseBuilder withResult(String resultScore, String resultData) {
+        if (resultScore == null && resultData == null) {
+            return this;
+        }
+
+        Result result = new Result();
+
+        if (resultScore != null) {
+            ResultScore score = new ResultScore();
+            score.setTextString(resultScore);
+            result.setResultScore(score);
+        }
+
+        if (resultData != null) {
+            ResultData data = new ResultData();
+            data.setText(resultData);
+            result.setResultData(data);
+        }
+
+        response.setResult(result);
+        return this;
+    }
lti/tsugi-util/src/test/java/org/tsugi/pox/POXResponseBuilderTest.java (1)

16-223: Tests are comprehensive; consider future structure-aware assertions

The test suite covers the main builder paths (majors, severities, minor codes, null/empty body, XML header trimming) and should guard against most regressions. If you ever need stricter guarantees, you might consider parsing the XML and asserting on specific elements/attributes instead of contains(...), but that’s optional given the current scope.

lti/tsugi-util/src/java/org/tsugi/pox/POXResponseBuilder.java (1)

26-32: Static XmlMapper is currently unused

xmlMapper is configured but never used in this class; all XML output is generated by buildManualXml. You may want to either:

  • Remove the mapper for now, or
  • Wire buildAsXml() to actually serialize the POXEnvelopeResponse via Jackson instead of manual string building.

This is non-blocking but would reduce confusion for future maintainers.

lti/tsugi-util/src/java/org/tsugi/pox/POXConstants.java (1)

3-141: Centralize usage of POX constants to avoid drift

This class is a good single source of truth for POX codes and validation. However, IMSPOXRequestJackson (and POXResponseFactory via its private validators) still define and use their own validMajor/validSeverity/validMinor arrays and related constants.

To prevent future divergence, consider refactoring those classes to delegate to POXConstants (e.g., POXConstants.VALID_MAJOR, POXConstants.isValidMajorCode(...)) instead of maintaining parallel definitions.

lti/tsugi-util/src/java/org/tsugi/pox/POXResponseFactory.java (1)

237-286: Leverage POXConstants for validation instead of IMSPOXRequestJackson.validXXX arrays

validateResponseParameters and the private isValidMajorCode/isValidSeverity/isValidMinorCode helpers currently iterate over IMSPOXRequestJackson.validMajor, validSeverity, and validMinor. Since POXConstants provides VALID_MAJOR, VALID_SEVERITY, VALID_MINOR, and corresponding isValid* helpers, you can simplify and avoid duplicating allowed values:

-    private static boolean isValidMajorCode(String major) {
-        for (String validMajor : IMSPOXRequestJackson.validMajor) {
-            if (validMajor.equals(major)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    private static boolean isValidMajorCode(String major) {
+        return POXConstants.isValidMajorCode(major);
+    }
@@
-    private static boolean isValidSeverity(String severity) {
-        for (String validSeverity : IMSPOXRequestJackson.validSeverity) {
-            if (validSeverity.equals(severity)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    private static boolean isValidSeverity(String severity) {
+        return POXConstants.isValidSeverity(severity);
+    }
@@
-    private static boolean isValidMinorCode(String minor) {
-        for (String validMinor : IMSPOXRequestJackson.validMinor) {
-            if (validMinor.equals(minor)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    private static boolean isValidMinorCode(String minor) {
+        return POXConstants.isValidMinorCode(minor);
+    }

This keeps all code lists and validation rules in one place.

lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java (1)

195-205: SHA‑1 usage is per OAuth 1.0a but worth documenting

Static analysis flags MessageDigest.getInstance("SHA-1") as a weak hash. In this method, the digest is used only to compute oauth_body_hash for HMAC-SHA1 requests and is paired with an HMAC-based OAuth 1.0a signature:

MessageDigest md = MessageDigest.getInstance("SHA-1");
if ("HMAC-SHA256".equals(oauth_signature_method)) {
    md = MessageDigest.getInstance("SHA-256");
}

Given protocol compatibility, you likely must continue supporting SHA‑1 here for oauth_signature_method = HMAC-SHA1. To make this clearer and help future reviewers/tools, consider:

  • Adding a short comment noting that SHA‑1 is required by the OAuth 1.0a body-hash specification in this context.
  • Encouraging new integrations to prefer HMAC-SHA256 where possible.

No immediate code change is strictly necessary, but documenting the rationale will reduce recurring false positives.

lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java (2)

121-138: Prefer POXConstants.OPERATION_* instead of hard‑coded operation strings

getOperation returns string literals like "replaceResultRequest" while getOperationInfo uses POXConstants.OPERATION_*. For consistency and typo‑safety, both helpers should use POXConstants.OPERATION_* everywhere:

-        if (body.getReplaceResultRequest() != null) {
-            return "replaceResultRequest";
-        } else if (body.getReadResultRequest() != null) {
-            return "readResultRequest";
-        } else if (body.getDeleteResultRequest() != null) {
-            return "deleteResultRequest";
-        } else if (body.getReadMembershipRequest() != null) {
-            return "readMembershipRequest";
-        }
+        if (body.getReplaceResultRequest() != null) {
+            return POXConstants.OPERATION_REPLACE_RESULT;
+        } else if (body.getReadResultRequest() != null) {
+            return POXConstants.OPERATION_READ_RESULT;
+        } else if (body.getDeleteResultRequest() != null) {
+            return POXConstants.OPERATION_DELETE_RESULT;
+        } else if (body.getReadMembershipRequest() != null) {
+            return POXConstants.OPERATION_READ_MEMBERSHIP;
+        }

This keeps all operation identifiers centralized in POXConstants.

Also applies to: 473-514


61-86: Consider reusing a shared XmlMapper instead of constructing per‑call

Both parseRequest and parseResponse create and configure a new XmlMapper on every invocation. For a servlet‑style environment this adds unnecessary object churn; using a single, statically configured XmlMapper (similar to ServiceServlet.POX_XML_MAPPER) would be cheaper and still thread‑safe once configuration is complete.

If you align the configuration with the rest of the POX stack (e.g., FAIL_ON_UNKNOWN_PROPERTIES=false, setDefaultUseWrapper(false)), you can centralize it in a static field and reuse it across all parse methods.

Also applies to: 94-113

lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java (2)

119-126: Shared POX_XML_MAPPER + buildPoxBodyXml is a good, thread‑safe centralization

The static, pre‑configured XmlMapper combined with buildPoxBodyXml gives you a single place to control POX body serialization (no XML declaration, no wrappers, empty‑bean tolerant). This is a solid pattern for a servlet.

You might later consider reusing this same mapper in debugging (the pretty‑print block in doPostXml) to keep configuration fully consistent, but that is not required for correctness.

Also applies to: 903-913


128-145: The verification confirms the original review comment's observation. I can now generate the rewritten response:


Set HTTP status code and content type in doError to ensure error responses are properly signaled to LTI tool consumers

The doError method now centralizes error XML generation via MessageResponseBuilder, but does not set HTTP status codes or content type. Currently, error responses are returned with the default HTTP 200 status, which may confuse LTI tool consumers expecting error status codes.

The codebase shows inconsistency: early guard-clause errors (lines 185, 640) set SC_FORBIDDEN directly and return, while business logic errors call doError without setting status. To maintain consistency and ensure clients properly recognize errors at the HTTP layer, add status code and content type to doError:

response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType("application/xml");
response.setCharacterEncoding("UTF-8");

Alternatively, if different error types warrant different status codes (e.g., SC_INTERNAL_SERVER_ERROR for exception-based errors vs. SC_BAD_REQUEST for validation), move status setting to call sites where the error context is known.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efc337f and f724ef5.

📒 Files selected for processing (51)
  • lti/lti-blis/pom.xml (1 hunks)
  • lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java (23 hunks)
  • lti/tsugi-util/pom.xml (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/json/IMSJSONRequest.java (0 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/MessageResponseBuilder.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/XMLMap.java (0 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/AdminPeriod.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/GroupSet.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Groups.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Membership.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/MembershipRecord.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinor.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinorField.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeRequest.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeResponse.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseBody.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeader.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeaderInfo.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXStatusInfo.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipRequest.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipResponse.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultRequest.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultResponse.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultRequest.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultResponse.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Result.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultData.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultRecord.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultScore.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Role.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/SourcedGUID.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/StatusInfo.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/TimeFrame.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequest.java (0 hunks)
  • lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/pox/POXConstants.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/pox/POXResponseBuilder.java (1 hunks)
  • lti/tsugi-util/src/java/org/tsugi/pox/POXResponseFactory.java (1 hunks)
  • lti/tsugi-util/src/test/java/org/tsugi/lti/POXJacksonTest.java (1 hunks)
  • lti/tsugi-util/src/test/java/org/tsugi/pox/IMSPOXRequestJacksonTest.java (1 hunks)
  • lti/tsugi-util/src/test/java/org/tsugi/pox/POXResponseBuilderTest.java (1 hunks)
💤 Files with no reviewable changes (3)
  • lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequest.java
  • lti/tsugi-util/src/java/org/tsugi/json/IMSJSONRequest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/XMLMap.java
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.{html,jsp,vm,xhtml,xml}: Use Bootstrap 5.2 as the preferred UI framework for styling in Sakai frontends
Leverage Bootstrap 5 components for consistent UI/UX

Files:

  • lti/tsugi-util/pom.xml
  • lti/lti-blis/pom.xml
**/*.{html,jsp,vm,xhtml,xml,css,scss}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Ensure all UI components work across different screen sizes (Responsive Design)

Files:

  • lti/tsugi-util/pom.xml
  • lti/lti-blis/pom.xml
**/*.{js,html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Use the web components in the webcomponents/ directory when possible in Sakai frontends

Files:

  • lti/tsugi-util/pom.xml
  • lti/lti-blis/pom.xml
**/*.{html,jsp,jspx,xml,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer kebab-case for values of HTML class and id attributes

Files:

  • lti/tsugi-util/pom.xml
  • lti/lti-blis/pom.xml
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseBody.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultRequest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultResponse.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultRequest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Role.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipResponse.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Groups.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/AdminPeriod.java
  • lti/tsugi-util/src/test/java/org/tsugi/lti/POXJacksonTest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultScore.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinorField.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeader.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeaderInfo.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipRequest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeRequest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/TimeFrame.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java
  • lti/tsugi-util/src/java/org/tsugi/lti/MessageResponseBuilder.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/SourcedGUID.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinor.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultData.java
  • lti/tsugi-util/src/java/org/tsugi/pox/POXResponseBuilder.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Result.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/MembershipRecord.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/Membership.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/StatusInfo.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultResponse.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeResponse.java
  • lti/tsugi-util/src/test/java/org/tsugi/pox/POXResponseBuilderTest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/POXStatusInfo.java
  • lti/tsugi-util/src/java/org/tsugi/pox/POXConstants.java
  • lti/tsugi-util/src/java/org/tsugi/pox/POXResponseFactory.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultRecord.java
  • lti/tsugi-util/src/test/java/org/tsugi/pox/IMSPOXRequestJacksonTest.java
  • lti/tsugi-util/src/java/org/tsugi/lti/objects/GroupSet.java
  • lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java
  • lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java
  • lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java
🧠 Learnings (1)
📚 Learning: 2025-09-28T04:09:37.259Z
Learnt from: csev
Repo: sakaiproject/sakai PR: 14094
File: lti/lti-common/src/java/org/sakaiproject/lti/util/SakaiLTIUtil.java:1439-1447
Timestamp: 2025-09-28T04:09:37.259Z
Learning: csev prefers to keep POJO adapter methods thin/minimal during the transition phase in SAK-51970. The long-term plan is to eventually flip roles where POJOs become primary implementations and Map versions become thin adapters, then get deprecated and removed. This transition will happen over multiple commits over time.

Applied to files:

  • lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java
🧬 Code graph analysis (26)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseBody.java (3)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java (1)
  • Data (12-17)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1)
  • Data (9-31)
lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultRequest.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1)
  • Data (9-31)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java (1)
  • Data (9-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultResponse.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java (1)
  • Data (12-17)
lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipResponse.java (3)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java (1)
  • Data (9-42)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Membership.java (1)
  • Data (9-28)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MembershipRecord.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Groups.java (4)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java (1)
  • Data (7-19)
lti/tsugi-util/src/java/org/tsugi/lti/objects/GroupSet.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java (1)
  • Data (9-42)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java (1)
  • Data (9-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeader.java (3)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeaderInfo.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeRequest.java (4)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeResponse.java (1)
  • JacksonXmlRootElement (8-18)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1)
  • Data (9-31)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/TimeFrame.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/AdminPeriod.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinor.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinorField.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultData.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1)
  • Data (8-24)
lti/tsugi-util/src/java/org/tsugi/pox/POXResponseBuilder.java (1)
lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java (1)
  • Slf4j (44-552)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Result.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1)
  • Data (8-24)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Membership.java (4)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java (1)
  • Data (7-19)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Groups.java (1)
  • Data (9-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java (1)
  • Data (9-42)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java (1)
  • Data (9-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/StatusInfo.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1)
  • Data (8-24)
lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultResponse.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java (1)
  • Data (12-17)
lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1)
  • Data (8-24)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeResponse.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeRequest.java (1)
  • JacksonXmlRootElement (8-18)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (3)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestBody.java (1)
  • Data (9-31)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseBody.java (1)
  • Data (9-31)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXStatusInfo.java (4)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinor.java (1)
  • Data (9-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinorField.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)
  • Data (7-16)
lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultRecord.java (2)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)
  • Data (7-13)
lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java (1)
  • Data (12-17)
lti/tsugi-util/src/test/java/org/tsugi/pox/IMSPOXRequestJacksonTest.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java (1)
  • POXJacksonParser (21-576)
lti/tsugi-util/src/java/org/tsugi/lti/objects/GroupSet.java (1)
lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java (1)
  • Data (7-19)
lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java (1)
lti/tsugi-util/src/java/org/tsugi/pox/POXConstants.java (1)
  • POXConstants (3-142)
🪛 ast-grep (0.40.0)
lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java

[warning] 195-195: Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic signature. Instead, use PBKDF2 for password hashing or SHA256 or SHA512 for other hash function applications.
Context: MessageDigest.getInstance("SHA-1")
Note: [CWE-328] Use of Weak Hash. [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures

(use-of-sha1-java)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: maven-build
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
🔇 Additional comments (36)
lti/tsugi-util/src/java/org/tsugi/lti/objects/GroupSet.java (1)

7-15: GroupSet DTO mapping looks correct and consistent with Group

The GroupSet class is a clean Lombok DTO with id and title correctly exposed via @JacksonXmlProperty, matching the way Group references it; no issues spotted.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultResponse.java (1)

9-15: ReadResultResponse XML model is well-structured

The root element name and single result payload field match the expected read-result response shape and are consistent with the surrounding Result/MessageResponse models.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultScore.java (1)

7-15: ResultScore DTO matches expected XML element structure

The language and textString fields are correctly exposed as XML elements and the class is a straightforward Lombok DTO; this aligns well with typical LIS/LTI resultScore structures.

lti/tsugi-util/pom.xml (1)

64-67: Version is centrally managed and properly aligned—no issues found

Verification confirms the dependency follows the correct pattern: all Jackson modules in lti/tsugi-util/pom.xml (including the new jackson-dataformat-xml) are versionless declarations that inherit from the parent BOM's central version definition (${sakai.jackson.version} in master/pom.xml). This approach is consistent across the codebase's 17 pom.xml files using this dependency. No version conflicts or misalignments exist.

lti/lti-blis/pom.xml (1)

65-68: Verified: Jackson version alignment is consistent across modules

The addition of jackson-dataformat-xml to lti/lti-blis follows the established codebase pattern. All 17 modules declaring this dependency (including the newly modified lti/tsugi-util) omit explicit versions and inherit from master/pom.xml's centralized version management (sakai.jackson.version=2.19.2). Version consistency is maintained through the parent pom chain: lti-blis → lti → master. No alignment issues detected.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipRequest.java (1)

1-12: ReadMembershipRequest DTO looks consistent and minimal

Single sourcedId field with XML mapping is clear and aligns with the other request holder classes in this PR. No changes needed.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultData.java (1)

1-12: ResultData is a straightforward text holder

Simple text field with matching XML element name; this is clear and adequate for embedding result payload text. No changes needed.

lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultResponse.java (1)

1-17: DeleteResultResponse root wrapper looks appropriate

Using deleteResultResponse as the XML root with an empty body matches the typical pattern for delete POX operations; documentation is clear. No further changes needed.

lti/tsugi-util/src/java/org/tsugi/lti/objects/SourcedGUID.java (1)

1-12: SourcedGUID DTO matches expected structure

The class cleanly models a sourcedId container for use inside result records; naming and XML mapping look consistent. No issues.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Group.java (1)

1-18: Group structure is clear and aligns with related classes

id, title, and set (via GroupSet) give a straightforward representation of a group; XML element names and types are consistent with the surrounding grouping model. No changes suggested.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadResultRequest.java (1)

1-12: ReadResultRequest matches the other result request DTOs

Using a single resultRecord field here keeps the request shape consistent with DeleteResultRequest and the POX body mappings shown in the snippets. Looks good as-is.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ReadMembershipResponse.java (1)

1-12: ReadMembershipResponse composition looks correct

Wrapping a single membershipRecord here cleanly exposes the MembershipRecord tree for POX responses. Naming and mapping are consistent with the other response DTOs.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Groups.java (1)

1-16: Groups POJO and XML mapping look consistent with surrounding model

The Jackson + Lombok setup is straightforward and matches the existing Members/Member pattern; the useWrapping = false configuration is appropriate for an unwrapped <group> list inside <groups>. No changes needed.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Members.java (1)

1-16: Members container matches expected unwrapped list pattern

The Members class mirrors the Groups pattern with an unwrapped list of <member> elements, which is appropriate for the POX-style XML structure. Looks good as-is.

lti/tsugi-util/src/java/org/tsugi/lti/objects/DeleteResultRequest.java (1)

1-13: DeleteResultRequest is a clean, minimal wrapper for resultRecord

The class is a simple Lombok/Jackson POJO with @JacksonXmlProperty(localName = "resultRecord"), which fits the POX request body pattern. No issues from this snippet.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Result.java (1)

1-20: Double‑check XML element names against the existing LTI/POX schema

The Result POJO wiring (sourcedId, resultScore, resultData) is structurally sound and integrates cleanly with MessageResponse. Since these localName values are part of the external XML contract, please confirm they exactly match the prior XMLMap-based payloads / IMS schema (especially sourcedId casing) before relying on this in production.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultRequest.java (1)

1-13: ReplaceResultRequest mirrors DeleteResultRequest and looks correct

The class is a minimal Jackson/Lombok wrapper around ResultRecord with localName = "resultRecord", consistent with the delete request pattern. No changes needed from this snippet.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ReplaceResultResponse.java (1)

1-12: LGTM! Clean implementation of empty response payload.

The class correctly represents an empty response body for LTI replace result operations. The Jackson XML annotations are properly configured, and the structure aligns with similar response classes like DeleteResultResponse in the codebase.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeRequest.java (1)

1-18: LGTM! Proper POX envelope structure.

The class correctly defines the IMS POX envelope request structure with appropriate Jackson XML annotations and namespace declarations. The structure mirrors the response envelope pattern and properly encapsulates the header and body components.

lti/tsugi-util/src/java/org/tsugi/lti/objects/MembershipRecord.java (1)

1-16: LGTM! Clean data holder implementation.

The class appropriately encapsulates membership record data with proper Jackson XML property mappings. The structure is straightforward and well-suited for XML serialization/deserialization of LTI membership information.

lti/tsugi-util/src/java/org/tsugi/lti/objects/Member.java (1)

1-42: LGTM! Comprehensive member data model.

The class provides a well-structured representation of LTI member information with appropriate Jackson XML annotations for all fields. The field names follow LTI conventions (e.g., user_id, person_name_given, lis_result_sourcedid), and the nested Groups relationship is properly defined.

lti/tsugi-util/src/java/org/tsugi/lti/objects/MessageResponse.java (1)

1-24: LGTM! Well-structured message response model.

The class properly defines the LTI message response structure with all necessary fields for conveying response data, including members, results, and status information. The Jackson XML annotations are correctly configured for root element serialization.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeaderInfo.java (1)

1-16: LGTM! Proper header information structure.

The class correctly defines the POX request header information with appropriate IMS namespace declarations. The version and message identifier fields are essential for POX envelope handling and are properly annotated.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinor.java (1)

1-16: LGTM! Correct list wrapper configuration.

The class properly configures Jackson XML annotations to serialize multiple imsx_codeMinorField entries without a wrapper element using useWrapping = false. This ensures the XML structure matches IMS specifications for POX code minor details.

lti/tsugi-util/src/test/java/org/tsugi/lti/POXJacksonTest.java (1)

1-333: LGTM! Comprehensive test coverage.

The test class provides excellent coverage of the POXJacksonParser functionality:

  • Request/response parsing with valid, null, empty, and invalid inputs
  • Operation type detection and extraction
  • Header and body information retrieval
  • Response creation for various status types (success, failure, fatal, unsupported)
  • Edge cases including empty bodies, unknown operations, and special character escaping
  • Performance validation

All variable declarations use explicit types as required by the coding guidelines. The test structure is well-organized with clear assertions and descriptive test names.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXRequestHeader.java (1)

7-12: POXRequestHeader POJO and XML mapping look correct

The Lombok + @JacksonXmlProperty setup cleanly models the request header wrapper and matches the IMS POX element name/namespace; no issues spotted.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeader.java (1)

7-17: Response header mapping is consistent and well-documented

The class-level Javadoc and the responseHeaderInfo XML mapping align with the POX envelope design and other header/info types; implementation looks solid.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXCodeMinorField.java (1)

7-15: POXCodeMinorField correctly models minor field entries

Field names and Jackson XML mappings match the IMS codeMinor element structure; this is a clean, minimal POJO for minor fields.

lti/tsugi-util/src/test/java/org/tsugi/pox/IMSPOXRequestJacksonTest.java (1)

13-245: Comprehensive coverage of Jackson-based POX request/response behavior

These tests exercise constructor validity, header/body extraction, response generation (success/failure/unsupported/fatal), minor code handling, and edge-case XML inputs, giving good confidence in the new Jackson-backed POX flow. The substring assertions on response XML also help guard against regressions in the serialized format.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXResponseHeaderInfo.java (1)

7-18: POXResponseHeaderInfo structure aligns with request header and status model

Version, messageIdentifier, and typed statusInfo are mapped cleanly to their IMS elements, matching the pattern used by POXRequestHeaderInfo and the broader POX status objects.

lti/tsugi-util/src/java/org/tsugi/lti/objects/ResultRecord.java (1)

7-15: ResultRecord XML mapping looks consistent and focused

The DTO cleanly models sourcedGUID and result with appropriate Jackson XML property names, matching how DeleteResultRequest embeds it. No issues from a mapping or structure standpoint.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXStatusInfo.java (1)

9-29: POXStatusInfo matches IMS POX structure and namespace usage

The fields and Jackson XML annotations (including the shared OMS namespace and POXCodeMinor nesting) are consistent with the other POX* objects and look correct.

lti/tsugi-util/src/java/org/tsugi/lti/objects/POXEnvelopeResponse.java (1)

8-17: POXEnvelopeResponse is correctly wired to header/body in IMS namespace

The root element, namespace, and header/body mappings mirror POXEnvelopeRequest and are suitable for use by the POX response builder/factory code.

lti/tsugi-util/src/java/org/tsugi/pox/POXResponseBuilder.java (1)

135-171: The review comment is based on incorrect assumptions about StringEscapeUtils behavior and does not identify a real NPE vulnerability.

After examining the code:

  • StringEscapeUtils.escapeXml11(null) returns null rather than throwing a NullPointerException
  • messageId is already guarded in the build() method (line 136-137)
  • major and severity have initialized default values (lines 36-37)
  • While description and operation can be null, passing them to escapeXml11() will not cause an NPE—the method will simply return null

No NullPointerException will occur at runtime, regardless of whether these fields are null. The suggested fix is unnecessary for preventing NPE.

Likely an incorrect or invalid review comment.

lti/lti-blis/src/java/org/sakaiproject/lti/ServiceServlet.java (2)

449-586: Roster response migration to MessageResponseBuilder + POJOs looks correct and keeps adapter thin

The new processRoster builds Member and Group POJOs and feeds them to MessageResponseBuilder, instead of mutating a generic map. Role mapping, outcomes handling, and group assignment logic remain unchanged and the adapter stays fairly thin, which matches the stated transition strategy.

No functional issues spotted here; this is a good incremental refactor.


375-447: Response shape is spec-compliant; no changes needed

The current implementation correctly follows the LTI Outcomes Management specification:

  • Read operations (basic-lis-readresult): Include <result> with grade/comment data ✓
  • Write operations (basic-lis-deleteresult, basic-lis-replaceresult, basic-lis-updateresult, basic-lis-createresult): Omit <result> element; status/success conveyed in header only ✓

Per the LTI spec, write operation responses contain an empty body element (e.g., <deleteResultResponse/>) with success/failure status in the header (imsx_statusInfo). The result element is reserved for read operations to return the grade value.

Comment on lines +297 to +369
public static String createResponse(String description, String major, String severity,
String messageId, String operation, Properties minorCodes,
String bodyString) {
if (major == null) major = MAJOR_FAILURE;
if (severity == null && MAJOR_PROCESSING.equals(major)) severity = SEVERITY_STATUS;
if (severity == null && MAJOR_SUCCESS.equals(major)) severity = SEVERITY_STATUS;
if (severity == null) severity = SEVERITY_ERROR;
if (messageId == null) {
Date dt = new Date();
messageId = "" + dt.getTime();
}

StringBuilder minorString = new StringBuilder();
if (minorCodes != null && minorCodes.size() > 0) {
minorString.append("\n <imsx_codeMinor>\n");
for (Object okey : minorCodes.keySet()) {
String key = (String) okey;
String value = minorCodes.getProperty(key);
if (key == null || value == null) continue;

minorString.append(" <imsx_codeMinorField>\n");
minorString.append(" <imsx_codeMinorFieldName>");
minorString.append(StringEscapeUtils.escapeXml11(key));
minorString.append("</imsx_codeMinorFieldName>\n");
minorString.append(" <imsx_codeMinorFieldValue>");
minorString.append(StringEscapeUtils.escapeXml11(value));
minorString.append("</imsx_codeMinorFieldValue>\n");
minorString.append(" </imsx_codeMinorField>\n");
}
minorString.append(" </imsx_codeMinor>");
}

if (bodyString == null) bodyString = "";

if (bodyString.startsWith("<?xml")) {
int pos = bodyString.indexOf("<", 1);
if (pos > 0) bodyString = bodyString.substring(pos);
}
bodyString = bodyString.trim();
String newLine = "";
if (bodyString.length() > 0) newLine = "\n";

return String.format(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<imsx_POXEnvelopeResponse xmlns=\"http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0\">\n" +
" <imsx_POXHeader>\n" +
" <imsx_POXResponseHeaderInfo>\n" +
" <imsx_version>V1.0</imsx_version>\n" +
" <imsx_messageIdentifier>%s</imsx_messageIdentifier>\n" +
" <imsx_statusInfo>\n" +
" <imsx_codeMajor>%s</imsx_codeMajor>\n" +
" <imsx_severity>%s</imsx_severity>\n" +
" <imsx_description>%s</imsx_description>\n" +
" <imsx_messageRefIdentifier>%s</imsx_messageRefIdentifier>\n" +
" <imsx_operationRefIdentifier>%s</imsx_operationRefIdentifier>" +
"%s\n"+
" </imsx_statusInfo>\n" +
" </imsx_POXResponseHeaderInfo>\n" +
" </imsx_POXHeader>\n" +
" <imsx_POXBody>\n" +
"%s%s"+
" </imsx_POXBody>\n" +
"</imsx_POXEnvelopeResponse>",
StringEscapeUtils.escapeXml11(messageId),
StringEscapeUtils.escapeXml11(major),
StringEscapeUtils.escapeXml11(severity),
StringEscapeUtils.escapeXml11(description),
StringEscapeUtils.escapeXml11(messageId),
StringEscapeUtils.escapeXml11(operation),
StringEscapeUtils.escapeXml11(minorString.toString()),
bodyString, newLine
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Minor-code XML block is being double‑escaped, breaking <imsx_codeMinor> markup

minorString is built as XML (with its contents already XML‑escaped), but then the whole block is passed through StringEscapeUtils.escapeXml11 before formatting into the response. This will escape the tags themselves (e.g. <imsx_codeMinor>&lt;imsx_codeMinor&gt;), so consumers will never see proper <imsx_codeMinor> elements.

You should inject the block as raw XML while continuing to escape only the inner key/value content:

-            "%s\n"+ 
+            "%s\n"+ 
             "      </imsx_statusInfo>\n" +
@@
-            StringEscapeUtils.escapeXml11(operation), 
-            StringEscapeUtils.escapeXml11(minorString.toString()), 
+            StringEscapeUtils.escapeXml11(operation), 
+            minorString.toString(), 
             bodyString, newLine

The key and value are already safely escaped when building minorString.

[scratchpad_start]
[done]
Critical XML escaping bug identified and fix suggested.
[scratchpad_end]

🤖 Prompt for AI Agents
In lti/tsugi-util/src/java/org/tsugi/lti/POXJacksonParser.java around lines
297-369, the XML block built in minorString is being double-escaped by
StringEscapeUtils.escapeXml11 when injected into the final String.format call,
which turns tags like <imsx_codeMinor> into &lt;imsx_codeMinor&gt;; to fix, stop
escaping the whole minorString (it already contains escaped key/value content)
and pass minorString.toString() directly to the formatter (or an empty string
when no minor codes) so the imsx_codeMinor markup is emitted as raw XML while
keeping inner key/value escape intact.

Comment on lines +127 to +173
@SuppressWarnings("deprecation")
public void loadFromRequest(HttpServletRequest request) {
String contentType = request.getContentType();
String baseContentType;

try {
MimeType mimeType = new MimeType(contentType);
baseContentType = mimeType.getBaseType();
} catch (MimeTypeParseException e) {
errorMessage = "Unable to parse mime type";
log.info("{}\n{}", errorMessage, contentType);
return;
}

if (!"application/xml".equals(baseContentType)) {
errorMessage = "Content Type must be application/xml";
log.info("{}\n{}", errorMessage, contentType);
return;
}

header = request.getHeader("Authorization");
oauth_body_hash = null;
if (header != null) {
if (header.startsWith("OAuth ")) header = header.substring(5);
String[] parms = header.split(",");
for (String parm : parms) {
parm = parm.trim();
if (parm.startsWith("oauth_body_hash=")) {
String[] pieces = parm.split("\"");
oauth_body_hash = URLDecoder.decode(pieces[1]);
}
if (parm.startsWith("oauth_consumer_key=")) {
String[] pieces = parm.split("\"");
oauth_consumer_key = URLDecoder.decode(pieces[1]);
}
if (parm.startsWith("oauth_signature_method=")) {
String[] pieces = parm.split("\"");
oauth_signature_method = URLDecoder.decode(pieces[1]);
}
}
}

if (oauth_body_hash == null) {
errorMessage = "Did not find oauth_body_hash";
log.info("{}\n{}", errorMessage, header);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Harden content-type handling and consider reducing Authorization header logging

In loadFromRequest:

  • String contentType = request.getContentType(); can legally return null. Passing that into new MimeType(contentType) risks a NullPointerException rather than a clean error path.
  • On certain failures you log the full Authorization header (log.info("{}\n{}", errorMessage, header);). That header typically contains oauth_consumer_key, oauth_signature, and other OAuth parameters; while not raw secrets, logging them in full can still be sensitive.

You can tighten both behaviors with a small change:

     public void loadFromRequest(HttpServletRequest request) {
         String contentType = request.getContentType();
-        String baseContentType;
+        String baseContentType;
 
-        try {
+        if (contentType == null) {
+            errorMessage = "Content Type must be application/xml";
+            log.info("{}", errorMessage);
+            return;
+        }
+
+        try {
             MimeType mimeType = new MimeType(contentType);
             baseContentType = mimeType.getBaseType();
         } catch (MimeTypeParseException e) {
             errorMessage = "Unable to parse mime type";
-            log.info("{}\n{}", errorMessage, contentType);
+            log.info("{} ({})", errorMessage, contentType);
             return;
         }
@@
-        if (oauth_body_hash == null) {
-            errorMessage = "Did not find oauth_body_hash";
-            log.info("{}\n{}", errorMessage, header);
-            return;
-        }
+        if (oauth_body_hash == null) {
+            errorMessage = "Did not find oauth_body_hash";
+            // Consider logging only that the header was present/missing, not its full value
+            log.info("{}", errorMessage);
+            return;
+        }

This avoids a potential NPE on missing content type and reduces sensitive header exposure in logs.


I'll verify this review comment by examining the actual file and checking the validity of the concerns raised.


🏁 Script executed:

#!/bin/bash
# Find and examine the file in question
fd -type f -name "IMSPOXRequestJackson.java" -exec cat {} \;

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Check for other instances of getContentType handling in the codebase
rg -n "getContentType" --type java -A 5 -B 2

Length of output: 266216


🏁 Script executed:

#!/bin/bash
# Look for Authorization header handling patterns
rg -n "Authorization" --type java -A 3 -B 2

Length of output: 269810


🏁 Script executed:

#!/bin/bash
# Check for similar OAuth/sensitive data logging
rg -n "oauth_" --type java -C 3

Length of output: 109947


🌐 Web query:

HttpServletRequest.getContentType() null behavior Java

💡 Result:

Short answer: getContentType() returns null when the request has no known MIME type (i.e., there is no Content-Type header or the container can't determine it). Use a null-safe check (and string contains/prefix check for multipart) instead of assuming a non-null value. [1][2]

Example pattern:
if (request.getContentType() != null && request.getContentType().toLowerCase().contains("multipart/form-data")) { ... }

Sources:
[1] ServletRequest.getContentType() Javadoc — "Returns the MIME type ... or null if the type is not known."
[2] Common guidance (StackOverflow) — request.getContentType() may be null; check for null and use contains/startsWith for multipart/form-data.


🌐 Web query:

OAuth 1.0a RFC content type requirements

💡 Result:

Short answer — OAuth 1.0a (RFC 5849) requires form-url-encoded bodies for OAuth protocol parameters:

  • Request body parameters are only included in the signature/source if all of these hold: the entity is single-part, it follows the encoding rules of application/x-www-form-urlencoded, and the Content-Type header is set to application/x-www-form-urlencoded. (RFC 5849 §3.4.1.3). [1]

  • Token responses (request_token / access_token) are returned in the HTTP response body using the application/x-www-form-urlencoded content type. (RFC 5849 §2 and §2.1 examples / §3.4.1.3). [1][2]

Implication: do not treat JSON or multipart/form-data bodies as OAuth parameter sources unless they are encoded and labelled as application/x-www-form-urlencoded. [1]

Sources:
[1] RFC 5849 (The OAuth 1.0 Protocol), see §3.4.1.3 and examples.
[2] RFC 5849 (token response examples showing Content-Type: application/x-www-form-urlencoded).


Harden content-type handling and avoid logging full Authorization header

  • In loadFromRequest() (lti/tsugi-util/src/java/org/tsugi/pox/IMSPOXRequestJackson.java): request.getContentType() can be null — check for null before creating new MimeType(contentType) and return a clear error path to avoid an NPE.
  • Do not log the full Authorization header on errors (it contains oauth_* parameters); log only that the header was present/missing or log a sanitized summary (avoid printing the raw header value).

Comment on lines +132 to +157
public static String createFatalResponse(String description, String messageId) {
if (messageId == null) {
messageId = String.valueOf(new Date().getTime());
}

return String.format(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<imsx_POXEnvelopeResponse xmlns=\"http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0\">\n" +
" <imsx_POXHeader>\n" +
" <imsx_POXResponseHeaderInfo>\n" +
" <imsx_version>V1.0</imsx_version>\n" +
" <imsx_messageIdentifier>%s</imsx_messageIdentifier>\n" +
" <imsx_statusInfo>\n" +
" <imsx_codeMajor>failure</imsx_codeMajor>\n" +
" <imsx_severity>error</imsx_severity>\n" +
" <imsx_description>%s</imsx_description>\n" +
" <imsx_operationRefIdentifier>%s</imsx_operationRefIdentifier>" +
" </imsx_statusInfo>\n" +
" </imsx_POXResponseHeaderInfo>\n" +
" </imsx_POXHeader>\n" +
" <imsx_POXBody/>\n" +
"</imsx_POXEnvelopeResponse>",
messageId,
description,
messageId
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Escape fatal-response fields and avoid reusing messageId as operationRefIdentifier

In createFatalResponse, the XML is constructed with raw messageId and description:

"            <imsx_messageIdentifier>%s</imsx_messageIdentifier>\n" + 
...
"                <imsx_description>%s</imsx_description>\n" +
"                <imsx_operationRefIdentifier>%s</imsx_operationRefIdentifier>" + 
...
messageId, 
description,
messageId

Two issues:

  1. No XML escaping – if description (or messageId) ever contains <, &, or quotes, the response becomes malformed XML and could potentially surface unsanitized text back to a tool.
  2. Reusing messageId as operationRefIdentifier – semantically, imsx_operationRefIdentifier is supposed to reference the operation, not the message ID. In IMSPOXRequestJackson.getFatalResponse(...) the default is "unknown", so this is at least inconsistent.

At minimum, the fields should be escaped and messageId reused consistently:

+import org.apache.commons.text.StringEscapeUtils;
@@
     public static String createFatalResponse(String description, String messageId) {
         if (messageId == null) {
             messageId = String.valueOf(new Date().getTime());
         }
-        
-        return String.format(
+        String safeMessageId = StringEscapeUtils.escapeXml11(messageId);
+        String safeDescription = StringEscapeUtils.escapeXml11(description == null ? "" : description);
+
+        return String.format(
@@
-            "</imsx_POXEnvelopeResponse>",
-            messageId, 
-            description,
-            messageId
+            "</imsx_POXEnvelopeResponse>",
+            safeMessageId, 
+            safeDescription,
+            safeMessageId
         );
     }

Separately, you may want to decide whether imsx_operationRefIdentifier should actually be a fixed value like "unknown" (to mirror IMSPOXRequestJackson.getFatalResponse) instead of the message ID, but that’s a behavioral choice.


I'll verify the concerns raised in this review comment by examining the codebase. Let me start by investigating the actual implementation, dependencies, and related code patterns.


---

🏁 Script executed:

```shell
#!/bin/bash
# 1. Find and examine POXResponseFactory.java
fd -t f -i "POXResponseFactory.java" --exec cat {} \;

Length of output: 10839


🏁 Script executed:

#!/bin/bash
# 2. Search for pom.xml and check for commons-text dependency
fd -t f "pom.xml" --exec grep -l "commons-text\|commons.text" {} \;

Length of output: 823


🏁 Script executed:

#!/bin/bash
# 3. Find IMSPOXRequestJackson to verify getFatalResponse behavior
fd -t f -i "IMSPOXRequestJackson.java" --exec cat {} \;

Length of output: 22316


🏁 Script executed:

#!/bin/bash
# 4. Search for XML escaping patterns in the codebase
rg -n "escapeXml|StringEscapeUtils|XMLUtils" --type java -C 2

Length of output: 152066


🏁 Script executed:

#!/bin/bash
# 5. Find other places where XML is constructed manually (potential patterns to compare)
rg -n "String\.format.*xml|<.*%s" --type java -C 2 | head -100

Length of output: 12628


Add XML escaping to POXResponseFactory.createFatalResponse

The createFatalResponse method constructs XML with raw, unescaped messageId and description values:

"            <imsx_messageIdentifier>%s</imsx_messageIdentifier>\n" + 
...
"                <imsx_description>%s</imsx_description>\n" +
"                <imsx_operationRefIdentifier>%s</imsx_operationRefIdentifier>"

This is unsafe. If either parameter contains XML special characters (<, &, quotes), the response becomes malformed XML. Related methods in the codebase (POXResponseBuilder and IMSPOXRequestJackson) already escape such values using StringEscapeUtils.escapeXml11(). Apply the same pattern here.

Additionally, the method reuses messageId for both imsx_messageIdentifier and imsx_operationRefIdentifier. While semantically imsx_operationRefIdentifier should reference an operation (not a message), this is a design decision—the diff in the review addresses the critical escaping issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant