Conversation
WalkthroughIntroduces a new Ballerina utilities module with project configuration, JWT validation for OAuth client assertions, and an HTTP proxy service that forwards FHIR requests with ASG-based token authentication, supporting both JWT and standard OAuth flows. Changes
Sequence DiagramsequenceDiagram
participant Client as HTTP Client
participant ProxySvc as Proxy Service
participant Validator as JWT Validator
participant ASG as ASG (OAuth/JWKS)
participant FHIR as FHIR Server
Client->>ProxySvc: POST /token (with client_assertion)
activate ProxySvc
ProxySvc->>Validator: isValidAssertion(assertion)
activate Validator
Validator->>ASG: Fetch JWKS from advancedConfigurations
activate ASG
ASG-->>Validator: JWKS URI & Details
deactivate ASG
Validator->>ASG: Validate JWT signature with JWKS
ASG-->>Validator: Valid, subject (clientId)
Validator->>ASG: Fetch OIDC inbound protocol (clientSecret)
ASG-->>Validator: clientSecret
Validator-->>ProxySvc: ClientAssertionValidationResponse (isValid, clientId, clientSecret)
deactivate Validator
alt Validation Success
ProxySvc->>ProxySvc: Rebuild token request payload (strip assertion fields)
ProxySvc->>ProxySvc: Add Basic Auth header (clientId:clientSecret)
ProxySvc->>ASG: Forward to ASG token endpoint
ASG-->>ProxySvc: Token response
ProxySvc->>ProxySvc: Enrich response (smart_style_url, patient_banner, fhiruser)
ProxySvc-->>Client: 200 + enriched token
else Validation Failure
ProxySvc-->>Client: 400/401 + error JSON
end
deactivate ProxySvc
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (3)
utilities/proxy_service.bal (2)
61-61: Typo in comment: "pwt key" should be "JWT".📝 Proposed fix
- // Handle pwt key jwt assertions. + // Handle private key JWT assertions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@utilities/proxy_service.bal` at line 61, Replace the comment "// Handle pwt key jwt assertions." with the corrected phrasing to fix the typo (e.g., "// Handle JWT key assertions." or "// Handle JWT assertions.") so it correctly references "JWT"; update the comment near the same location where that exact string appears to ensure clarity.
165-170: Unused functioncreateUnauthorizedResponse.This function is defined but never called anywhere in the codebase. Consider removing it or documenting its intended use.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@utilities/proxy_service.bal` around lines 165 - 170, The function createUnauthorizedResponse is defined but never used; either remove this unused isolated function to avoid dead code or wire it into the code paths that generate 401 responses (replace ad-hoc creation of a 401 http:Response with a call to createUnauthorizedResponse from the authentication/authorization logic). Locate the isolated function createUnauthorizedResponse and either delete it or replace instances where unauthorized responses are constructed with calls to createUnauthorizedResponse (or document its intended public API use if you intend to keep it).utilities/constants.bal (1)
19-31: Inconsistent naming convention for configurable variables.Some variables use
camelCase(e.g.,fhirServerUrl,adminAppClientId) while others usesnake_case(e.g.,smart_style_url,need_patient_banner,patient_id). Consider standardizing to one convention for maintainability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@utilities/constants.bal` around lines 19 - 31, The configurable variables use mixed naming conventions; rename the snake_case configurables (smart_style_url, need_patient_banner, patient_id) to camelCase (e.g., smartStyleUrl, needPatientBanner, patientId) to match existing camelCase names like fhirServerUrl and adminAppClientId, update all references/usages (imports, assignments, env mappings, tests) to the new identifiers, and ensure any external config keys or environment variable bindings are adjusted/documented to reflect the new names so compilation and runtime config resolution continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@utilities/constants.bal`:
- Around line 19-21: The three configurable variables fhirServerUrl,
asgServerUrl, and orgResolverServiceUrl currently default to empty strings which
can cause silent failures when used by the HTTP client (see proxy_service.bal);
add startup validation where configuration is loaded (or in init/start function
used by proxy_service.bal) to check these variables are non-empty and either
assign safe defaults or fail-fast: log a clear error including the variable name
(fhirServerUrl, asgServerUrl, orgResolverServiceUrl) and abort startup (or
throw) if any required URL is missing so downstream HTTP client initialization
does not proceed with an empty value.
In `@utilities/jwtValidator.bal`:
- Around line 72-79: The code extracts jwksUri from additionalSpProperties into
the jwksUri variable but does not handle the case where no "jwksURI" key is
found; after the foreach over additionalSpProperties, add an explicit check that
jwksUri is not empty and if it is, return or throw a clear, recoverable error or
log and abort JWT validation (e.g., return an error from the surrounding
validation function), ensuring callers receive a descriptive message like
"missing jwksURI in additionalSpProperties" so downstream JWT validation logic
fails with a clear reason; reference the jwksUri variable and the
additionalSpProperties loop to locate where to add this check.
- Around line 71-73: The code accesses appData.applications[0] without checking
that the array has elements, which can cause an index-out-of-bounds panic;
update the logic in utilities/jwtValidator.bal (where appId, jwksUri and
additionalSpProperties are read from appData.applications[0]) to first verify
appData.applications is non-nil and has length > 0, and handle the empty case by
returning/throwing an appropriate error or logging and using safe defaults
instead of indexing into [0].
- Around line 65-66: The ASG API filter string passed to asgAdminClient->get is
malformed; update the request in utilities/jwtValidator.bal where
asgAdminClient->get(...) is called (the call that uses jwtSubject) to build a
properly quoted/encoded filter: use filter=clientId co "${jwtSubject}" (or
URL-encode as filter=clientId%20co%20%22${jwtSubject}%22) and remove the stray
`=` before &attributes so the query reads ...filter=clientId co
"${jwtSubject}"&attributes=advancedConfigurations.
In `@utilities/proxy_service.bal`:
- Around line 21-30: Module-level check expressions for fhirClient,
asgAdminClient, and asgClient can panic during module init if
fhirServerUrl/asgServerUrl are invalid; move their creation into a startup/init
function that validates the URLs and handles errors instead of using top-level
check. Create an init function (e.g., initHttpClients or a module init() that
returns error) that validates fhirServerUrl and asgServerUrl, constructs the
http:Client instances (for fhirClient, asgAdminClient with its auth map, and
asgClient) and returns or assigns them on success, and logs/returns a proper
error on failure so initialization doesn’t panic. Ensure you reference and
replace the existing module-level finals fhirClient, asgAdminClient, and
asgClient to be initialized by that function.
- Line 35: The conditional if path[path.length() -1] == "authorize" can panic
when path is empty; update the check to first ensure the array has at least one
element (e.g., path.length() > 0) before indexing, so change the condition to
combine a length check with the existing last-element comparison (reference the
use of the path variable and this if expression) to avoid index-out-of-bounds.
- Line 52: The POST token endpoint currently reads the last path segment using
path[path.length() - 1] without ensuring the path array is non-empty; update the
POST handler to first check that path.length() > 0 (or path.length() != 0)
before accessing the last element and only then compare it to "token" (i.e.,
replace if path[path.length() - 1] == "token" with a guarded check), using the
same bounds-check approach as the GET handler to avoid panics when path is
empty.
- Around line 86-91: Rebuild the form payload using URL-encoded keys and values:
import the ballerina/url module and, inside the payload reconstruction (the loop
that builds payloadParts), apply url:encode to both key and value before
concatenation so each entry becomes url:encode(key) + "=" + url:encode(value);
then join with "&" (keep using payloadParts and string:join) so special
characters like &, = and spaces are properly encoded and the payload remains
valid.
- Around line 205-209: The current contentType handling in the block referencing
contentType, payload, httpRequest.getTextPayload(), and
httpRequest.getJsonPayload() only differentiates urlencoded vs JSON and will try
JSON for all other types; update it to branch on more content-type families:
treat types that contain "json" or "+json" (e.g., application/fhir+json) with
httpRequest.getJsonPayload(), treat types that contain "xml" or "+xml" (e.g.,
application/fhir+xml or application/xml) with httpRequest.getTextPayload(),
handle multipart/form-data (and other binary/multipart types) using the
appropriate binary/form retrieval method (e.g., getBytePayload()/form-data
accessor) rather than JSON, and fallback to getTextPayload() for unknown types
so non-JSON/XML payloads don't get JSON-parsed and fail.
---
Nitpick comments:
In `@utilities/constants.bal`:
- Around line 19-31: The configurable variables use mixed naming conventions;
rename the snake_case configurables (smart_style_url, need_patient_banner,
patient_id) to camelCase (e.g., smartStyleUrl, needPatientBanner, patientId) to
match existing camelCase names like fhirServerUrl and adminAppClientId, update
all references/usages (imports, assignments, env mappings, tests) to the new
identifiers, and ensure any external config keys or environment variable
bindings are adjusted/documented to reflect the new names so compilation and
runtime config resolution continue to work.
In `@utilities/proxy_service.bal`:
- Line 61: Replace the comment "// Handle pwt key jwt assertions." with the
corrected phrasing to fix the typo (e.g., "// Handle JWT key assertions." or "//
Handle JWT assertions.") so it correctly references "JWT"; update the comment
near the same location where that exact string appears to ensure clarity.
- Around line 165-170: The function createUnauthorizedResponse is defined but
never used; either remove this unused isolated function to avoid dead code or
wire it into the code paths that generate 401 responses (replace ad-hoc creation
of a 401 http:Response with a call to createUnauthorizedResponse from the
authentication/authorization logic). Locate the isolated function
createUnauthorizedResponse and either delete it or replace instances where
unauthorized responses are constructed with calls to createUnauthorizedResponse
(or document its intended public API use if you intend to keep it).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
utilities/.gitignoreutilities/Ballerina.tomlutilities/constants.balutilities/jwtValidator.balutilities/proxy_service.bal
| configurable string fhirServerUrl = ""; | ||
| configurable string asgServerUrl = ""; | ||
| configurable string orgResolverServiceUrl = ""; |
There was a problem hiding this comment.
Empty string defaults for required URLs may cause silent failures.
fhirServerUrl, asgServerUrl, and orgResolverServiceUrl default to empty strings. If not configured, HTTP client initialization (in proxy_service.bal) will fail or behave unexpectedly. Consider adding validation at startup or providing meaningful defaults.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/constants.bal` around lines 19 - 21, The three configurable
variables fhirServerUrl, asgServerUrl, and orgResolverServiceUrl currently
default to empty strings which can cause silent failures when used by the HTTP
client (see proxy_service.bal); add startup validation where configuration is
loaded (or in init/start function used by proxy_service.bal) to check these
variables are non-empty and either assign safe defaults or fail-fast: log a
clear error including the variable name (fhirServerUrl, asgServerUrl,
orgResolverServiceUrl) and abort startup (or throw) if any required URL is
missing so downstream HTTP client initialization does not proceed with an empty
value.
| AsgAppResponse|http:ClientError appData = | ||
| asgAdminClient->get(string `api/server/v1/applications?filter=clientId+co+${jwtSubject}=&attributes=advancedConfigurations`); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for similar API filter patterns in the codebase
rg -n "api/server/v1/applications" --type-add 'bal:*.bal' --type balRepository: wso2/reference-implementation-cms0057f
Length of output: 381
🏁 Script executed:
#!/bin/bash
# Also look for other ASG API calls that might show the expected pattern
rg -n "asgAdminClient" -A 2 -B 2 --type-add 'bal:*.bal' --type balRepository: wso2/reference-implementation-cms0057f
Length of output: 1406
🏁 Script executed:
#!/bin/bash
# Search for any filter patterns in the codebase
rg -n "filter=" --type-add 'bal:*.bal' --type balRepository: wso2/reference-implementation-cms0057f
Length of output: 249
🏁 Script executed:
#!/bin/bash
# Read the jwtValidator.bal file to understand the context
wc -l utilities/jwtValidator.balRepository: wso2/reference-implementation-cms0057f
Length of output: 112
🌐 Web query:
Asgardeo ASG API SCIM filter syntax clientId
💡 Result:
In Asgardeo’s SCIM2 endpoints, filtering is done with the standard SCIM query parameter:
GET .../scim2/Users?filter=<SCIM-filter-expression>(wso2.com)
SCIM filter syntax (RFC 7644)
A filter expression is generally:
<attrPath> <op> <value>- Logical composition:
(<expr>) and|or (<expr>), plusnot (...)(datatracker.ietf.org)
Supported operators include: eq, ne, co, sw, ew, gt, lt, ge, le, and pr (present). (datatracker.ietf.org)
Examples:
filter=userName eq "bjensen"(datatracker.ietf.org)filter=title pr and userType eq "Employee"(datatracker.ietf.org)
About filtering by clientId
clientId is not a standard SCIM User attribute, so you can only filter by clientId if it exists as an attribute on the SCIM resource you’re querying (e.g., via a custom schema/extension). In that case, you’d filter using its SCIM attribute path (optionally fully-qualified with the schema URN), e.g.:
filter=urn:your:custom:schema:clientId eq "abc123"(pattern per RFC 7644) (datatracker.ietf.org)
If you tell me which Asgardeo endpoint you’re calling (e.g., /scim2/Users, /scim2/Groups, Roles, or a non-SCIM “applications” endpoint) and where clientId appears in the response payload, I can give the exact filter string.
Citations:
- 1: https://wso2.com/asgardeo/docs/guides/organization-management/onboard-org-admins/self-service-approach/?utm_source=openai
- 2: https://datatracker.ietf.org/doc/html/rfc7644?utm_source=openai
- 3: https://datatracker.ietf.org/doc/html/rfc7644?utm_source=openai
- 4: https://datatracker.ietf.org/doc/html/rfc7644?utm_source=openai
- 5: https://datatracker.ietf.org/doc/html/rfc7644?utm_source=openai
- 6: https://datatracker.ietf.org/doc/html/rfc7644?utm_source=openai
🌐 Web query:
Asgardeo api/server/v1/applications filter parameter syntax
💡 Result:
For GET .../api/server/v1/applications, the filter query param uses a SCIM-like expression:
- Shape:
<attribute> <operator> <value> [and <attribute> <operator> <value> ...] - Example (by name):
filter=name eq <B2B-application-name>(shown in Asgardeo docs) [1] - Another example of chaining with
and(same syntax style, shown for Roles):filter=displayName eq <admin-role-name> and audience.value eq <role-audience-value>[1]
Practical tips:
- URL-encode spaces:
?filter=name%20eq%20MyApp - If your value contains spaces/special chars, quote it and URL-encode quotes, e.g.
?filter=name%20eq%20%22My%20App%22(works with the same grammar shown above) [1]
Sources: Asgardeo endpoint context (/api/server/v1/applications) [2], and the docs example showing the filter grammar in use [1].
Citations:
- 1: https://wso2.com/asgardeo/docs/guides/organization-management/onboard-org-admins/self-service-approach/
- 2: https://wso2.com/asgardeo/docs/apis/?utm_source=openai
Fix the ASG API filter syntax.
The filter expression filter=clientId+co+${jwtSubject}=&attributes=advancedConfigurations has incorrect syntax. Per SCIM RFC 7644 and Asgardeo API documentation, filter values must be properly quoted. The trailing = before &attributes is a syntax error. The filter should be:
filter=clientId co "${jwtSubject}"&attributes=advancedConfigurations
Or with URL encoding:
filter=clientId%20co%20%22${jwtSubject}%22&attributes=advancedConfigurations
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/jwtValidator.bal` around lines 65 - 66, The ASG API filter string
passed to asgAdminClient->get is malformed; update the request in
utilities/jwtValidator.bal where asgAdminClient->get(...) is called (the call
that uses jwtSubject) to build a properly quoted/encoded filter: use
filter=clientId co "${jwtSubject}" (or URL-encode as
filter=clientId%20co%20%22${jwtSubject}%22) and remove the stray `=` before
&attributes so the query reads ...filter=clientId co
"${jwtSubject}"&attributes=advancedConfigurations.
| string appId = appData.applications[0].id; | ||
| string jwksUri = ""; | ||
| json[] additionalSpProperties = appData.applications[0].advancedConfigurations.additionalSpProperties; |
There was a problem hiding this comment.
Missing bounds check before array access.
Accessing appData.applications[0] without verifying the array is non-empty will cause an index-out-of-bounds panic if no application matches the filter.
🐛 Proposed fix to add bounds check
+ if appData.applications.length() == 0 {
+ string errorMessage = "No application found for clientId: " + jwtSubject;
+ return logAndThrowException(errorMessage, "invalid_client");
+ }
string appId = appData.applications[0].id;
string jwksUri = "";
json[] additionalSpProperties = appData.applications[0].advancedConfigurations.additionalSpProperties;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| string appId = appData.applications[0].id; | |
| string jwksUri = ""; | |
| json[] additionalSpProperties = appData.applications[0].advancedConfigurations.additionalSpProperties; | |
| if appData.applications.length() == 0 { | |
| string errorMessage = "No application found for clientId: " + jwtSubject; | |
| return logAndThrowException(errorMessage, "invalid_client"); | |
| } | |
| string appId = appData.applications[0].id; | |
| string jwksUri = ""; | |
| json[] additionalSpProperties = appData.applications[0].advancedConfigurations.additionalSpProperties; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/jwtValidator.bal` around lines 71 - 73, The code accesses
appData.applications[0] without checking that the array has elements, which can
cause an index-out-of-bounds panic; update the logic in
utilities/jwtValidator.bal (where appId, jwksUri and additionalSpProperties are
read from appData.applications[0]) to first verify appData.applications is
non-nil and has length > 0, and handle the empty case by returning/throwing an
appropriate error or logging and using safe defaults instead of indexing into
[0].
| string jwksUri = ""; | ||
| json[] additionalSpProperties = appData.applications[0].advancedConfigurations.additionalSpProperties; | ||
| foreach json item in additionalSpProperties { | ||
| if (check item.name).toString() == "jwksURI" { | ||
| jwksUri = (check item.value).toString(); | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
Handle missing JWKS URI gracefully.
If no jwksURI property exists in additionalSpProperties, jwksUri remains empty and subsequent JWT validation will fail with an unclear error. Consider explicit error handling.
🛡️ Proposed fix to validate JWKS URI
foreach json item in additionalSpProperties {
if (check item.name).toString() == "jwksURI" {
jwksUri = (check item.value).toString();
break;
}
}
+ if jwksUri == "" {
+ string errorMessage = "JWKS URI not configured for clientId: " + jwtSubject;
+ return logAndThrowException(errorMessage, "invalid_client");
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/jwtValidator.bal` around lines 72 - 79, The code extracts jwksUri
from additionalSpProperties into the jwksUri variable but does not handle the
case where no "jwksURI" key is found; after the foreach over
additionalSpProperties, add an explicit check that jwksUri is not empty and if
it is, return or throw a clear, recoverable error or log and abort JWT
validation (e.g., return an error from the surrounding validation function),
ensuring callers receive a descriptive message like "missing jwksURI in
additionalSpProperties" so downstream JWT validation logic fails with a clear
reason; reference the jwksUri variable and the additionalSpProperties loop to
locate where to add this check.
| final http:Client fhirClient = check new (fhirServerUrl); | ||
| final http:Client asgAdminClient = check new (asgServerUrl, | ||
| auth = { | ||
| tokenUrl: tokenEp, | ||
| clientId: adminAppClientId, | ||
| clientSecret: adminAppClientSecret, | ||
| scopes: "internal_application_mgt_view" | ||
| } | ||
| ); | ||
| final http:Client asgClient = check new (asgServerUrl); |
There was a problem hiding this comment.
Module-level check expressions will panic on initialization failure.
If fhirServerUrl or asgServerUrl are empty/invalid, the module initialization will panic with an unrecoverable error. Consider wrapping client creation in a function with proper error handling or validating configuration at startup.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/proxy_service.bal` around lines 21 - 30, Module-level check
expressions for fhirClient, asgAdminClient, and asgClient can panic during
module init if fhirServerUrl/asgServerUrl are invalid; move their creation into
a startup/init function that validates the URLs and handles errors instead of
using top-level check. Create an init function (e.g., initHttpClients or a
module init() that returns error) that validates fhirServerUrl and asgServerUrl,
constructs the http:Client instances (for fhirClient, asgAdminClient with its
auth map, and asgClient) and returns or assigns them on success, and
logs/returns a proper error on failure so initialization doesn’t panic. Ensure
you reference and replace the existing module-level finals fhirClient,
asgAdminClient, and asgClient to be initialized by that function.
| service / on new http:Listener(proxyServerPort) { | ||
|
|
||
| isolated resource function get [string... path](http:Request httpRequest) returns http:Response|error { | ||
| if path[path.length() -1] == "authorize" { |
There was a problem hiding this comment.
Potential index-out-of-bounds if path array is empty.
Accessing path[path.length() - 1] will panic if the path array is empty. Add a bounds check.
🐛 Proposed fix
isolated resource function get [string... path](http:Request httpRequest) returns http:Response|error {
- if path[path.length() -1] == "authorize" {
+ if path.length() > 0 && path[path.length() - 1] == "authorize" {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if path[path.length() -1] == "authorize" { | |
| if path.length() > 0 && path[path.length() - 1] == "authorize" { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/proxy_service.bal` at line 35, The conditional if
path[path.length() -1] == "authorize" can panic when path is empty; update the
check to first ensure the array has at least one element (e.g., path.length() >
0) before indexing, so change the condition to combine a length check with the
existing last-element comparison (reference the use of the path variable and
this if expression) to avoid index-out-of-bounds.
| } | ||
|
|
||
| isolated resource function post [string... path](http:Request httpRequest) returns http:Response|error { | ||
| if path[path.length() - 1] == "token" { |
There was a problem hiding this comment.
Same bounds check needed for POST token endpoint.
Same issue as GET handler - accessing path[path.length() - 1] without checking if array is empty.
🐛 Proposed fix
isolated resource function post [string... path](http:Request httpRequest) returns http:Response|error {
- if path[path.length() - 1] == "token" {
+ if path.length() > 0 && path[path.length() - 1] == "token" {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if path[path.length() - 1] == "token" { | |
| if path.length() > 0 && path[path.length() - 1] == "token" { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/proxy_service.bal` at line 52, The POST token endpoint currently
reads the last path segment using path[path.length() - 1] without ensuring the
path array is non-empty; update the POST handler to first check that
path.length() > 0 (or path.length() != 0) before accessing the last element and
only then compare it to "token" (i.e., replace if path[path.length() - 1] ==
"token" with a guarded check), using the same bounds-check approach as the GET
handler to avoid panics when path is empty.
| // Rebuild the form-encoded payload without client_assertion | ||
| string[] payloadParts = []; | ||
| foreach [string, string] [key, value] in formParams.entries() { | ||
| payloadParts.push(key + "=" + value); | ||
| } | ||
| payload = string:'join("&", ...payloadParts); |
There was a problem hiding this comment.
Form parameter values not URL-encoded when rebuilding payload.
When reconstructing the form-encoded payload, values are concatenated directly without URL encoding. Special characters in values (e.g., &, =, spaces) will corrupt the payload.
🐛 Proposed fix using URL encoding
Consider using a URL encoding utility or the url:encode function:
// Rebuild the form-encoded payload without client_assertion
string[] payloadParts = [];
foreach [string, string] [key, value] in formParams.entries() {
- payloadParts.push(key + "=" + value);
+ // URL encode both key and value to handle special characters
+ payloadParts.push(url:encode(key, "UTF-8") + "=" + url:encode(value, "UTF-8"));
}
payload = string:'join("&", ...payloadParts);You'll need to import ballerina/url module.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/proxy_service.bal` around lines 86 - 91, Rebuild the form payload
using URL-encoded keys and values: import the ballerina/url module and, inside
the payload reconstruction (the loop that builds payloadParts), apply url:encode
to both key and value before concatenation so each entry becomes url:encode(key)
+ "=" + url:encode(value); then join with "&" (keep using payloadParts and
string:join) so special characters like &, = and spaces are properly encoded and
the payload remains valid.
| if contentType.toLowerAscii().includes("application/x-www-form-urlencoded") { | ||
| payload = check httpRequest.getTextPayload(); | ||
| } else { | ||
| payload = check httpRequest.getJsonPayload(); | ||
| } |
There was a problem hiding this comment.
Content-Type handling may fail for non-JSON/form payloads.
The payload extraction only handles application/x-www-form-urlencoded and JSON. Other content types (e.g., multipart/form-data, application/xml, application/fhir+json) will attempt JSON parsing and may fail. FHIR commonly uses application/fhir+json and application/fhir+xml.
🛡️ Suggested improvement
- if contentType.toLowerAscii().includes("application/x-www-form-urlencoded") {
+ if contentType.toLowerAscii().includes("application/x-www-form-urlencoded") {
payload = check httpRequest.getTextPayload();
+ } else if contentType.toLowerAscii().includes("xml") {
+ payload = check httpRequest.getTextPayload();
} else {
+ // Assume JSON for application/json, application/fhir+json, etc.
payload = check httpRequest.getJsonPayload();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if contentType.toLowerAscii().includes("application/x-www-form-urlencoded") { | |
| payload = check httpRequest.getTextPayload(); | |
| } else { | |
| payload = check httpRequest.getJsonPayload(); | |
| } | |
| if contentType.toLowerAscii().includes("application/x-www-form-urlencoded") { | |
| payload = check httpRequest.getTextPayload(); | |
| } else if contentType.toLowerAscii().includes("xml") { | |
| payload = check httpRequest.getTextPayload(); | |
| } else { | |
| // Assume JSON for application/json, application/fhir+json, etc. | |
| payload = check httpRequest.getJsonPayload(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@utilities/proxy_service.bal` around lines 205 - 209, The current contentType
handling in the block referencing contentType, payload,
httpRequest.getTextPayload(), and httpRequest.getJsonPayload() only
differentiates urlencoded vs JSON and will try JSON for all other types; update
it to branch on more content-type families: treat types that contain "json" or
"+json" (e.g., application/fhir+json) with httpRequest.getJsonPayload(), treat
types that contain "xml" or "+xml" (e.g., application/fhir+xml or
application/xml) with httpRequest.getTextPayload(), handle multipart/form-data
(and other binary/multipart types) using the appropriate binary/form retrieval
method (e.g., getBytePayload()/form-data accessor) rather than JSON, and
fallback to getTextPayload() for unknown types so non-JSON/XML payloads don't
get JSON-parsed and fail.
Purpose
Summary by CodeRabbit
New Features
Chores