Skip to content

Add fhir proxy service#217

Open
RivinduM wants to merge 1 commit intowso2:mainfrom
RivinduM:proxy
Open

Add fhir proxy service#217
RivinduM wants to merge 1 commit intowso2:mainfrom
RivinduM:proxy

Conversation

@RivinduM
Copy link
Copy Markdown
Contributor

@RivinduM RivinduM commented Mar 3, 2026

Purpose

Add fhir proxy service

Summary by CodeRabbit

  • New Features

    • Added proxy service for healthcare (FHIR) server integration with secure OAuth-based authentication
    • Implemented token assertion validation for authenticated client requests
    • Added configurable parameters for healthcare endpoints and security settings
  • Chores

    • Added project manifest and build configuration
    • Added default ignore rules for compiler-generated artifacts and configuration files

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 3, 2026

Walkthrough

Introduces 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

Cohort / File(s) Summary
Project Configuration
utilities/.gitignore, utilities/Ballerina.toml, utilities/constants.bal
Sets up Ballerina project structure with ignore rules, manifest metadata (org: "wso2", version: "1.0.0"), and configurable runtime parameters for FHIR server, ASG, authorization, and SMART settings.
JWT Validation & OAuth
utilities/jwtValidator.bal
Implements JWT assertion validation for OAuth client authentication, including token decoding, JWKS-based signature verification, ASG application/inbound protocol lookups, and structured error handling with defined response types.
HTTP Proxy Service
utilities/proxy_service.bal
Implements a proxy service with global HTTP clients (FHIR, ASG admin, ASG), specialized handlers for OAuth authorize/token endpoints with JWT client assertion support, and a generic request forwarder with header/payload management and token response enrichment.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops of joy! New utilities bloom,
JWT tokens validated room by room,
ASG dances with FHIR so bright,
Proxy forwards auth with all its might,
Constants configured, errors caught right! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete. It only fills in the 'Purpose' section with minimal detail, while all other required sections (Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning) are completely missing. Complete the PR description by providing details for all required template sections, especially Goals, Approach, Automation tests, Security checks, Test environment, and Documentation to adequately document this significant addition of new proxy service and utility modules.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add fhir proxy service' clearly and concisely describes the main change: introducing a new FHIR proxy service. It directly summarizes the primary objective evident in the file changes (new proxy_service.bal file and supporting utilities).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy Markdown
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: 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 function createUnauthorizedResponse.

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 use snake_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

📥 Commits

Reviewing files that changed from the base of the PR and between 84a9bc1 and 719a787.

📒 Files selected for processing (5)
  • utilities/.gitignore
  • utilities/Ballerina.toml
  • utilities/constants.bal
  • utilities/jwtValidator.bal
  • utilities/proxy_service.bal

Comment on lines +19 to +21
configurable string fhirServerUrl = "";
configurable string asgServerUrl = "";
configurable string orgResolverServiceUrl = "";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +65 to +66
AsgAppResponse|http:ClientError appData =
asgAdminClient->get(string `api/server/v1/applications?filter=clientId+co+${jwtSubject}=&attributes=advancedConfigurations`);
Copy link
Copy Markdown
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

🏁 Script executed:

#!/bin/bash
# Search for similar API filter patterns in the codebase
rg -n "api/server/v1/applications" --type-add 'bal:*.bal' --type bal

Repository: 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 bal

Repository: 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 bal

Repository: 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.bal

Repository: 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>), plus not (...) (datatracker.ietf.org)

Supported operators include: eq, ne, co, sw, ew, gt, lt, ge, le, and pr (present). (datatracker.ietf.org)

Examples:

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.:

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:


🌐 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].

[1] (wso2.com)
[2] (wso2.com)

Citations:


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.

Comment on lines +71 to +73
string appId = appData.applications[0].id;
string jwksUri = "";
json[] additionalSpProperties = appData.applications[0].advancedConfigurations.additionalSpProperties;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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].

Comment on lines +72 to +79
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;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +21 to +30
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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" {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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" {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines +86 to +91
// 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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +205 to +209
if contentType.toLowerAscii().includes("application/x-www-form-urlencoded") {
payload = check httpRequest.getTextPayload();
} else {
payload = check httpRequest.getJsonPayload();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

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