Skip to content

API endpoint changes#342

Open
yingbull wants to merge 2 commits intodevelopfrom
claude/fix-api-security-access-jKll6
Open

API endpoint changes#342
yingbull wants to merge 2 commits intodevelopfrom
claude/fix-api-security-access-jKll6

Conversation

@yingbull
Copy link
Collaborator

@yingbull yingbull commented Feb 11, 2026

Description

  • Added comprehensive authorization checks across multiple services to enhance security.
  • Fixed vulnerabilities allowing unauthorized access to sensitive data and operations.
  • Implemented privilege checks for demographic, measurement, prescription, appointment, and job management services.
  • Redacted sensitive information in API responses to prevent exposure.

Changes walkthrough 📝

Relevant files
Bug fix
DemographicWs.java
Enhance Security in Demographic Web Service                           

src/main/java/io/github/carlos_emr/carlos/webserv/DemographicWs.java

  • Added security checks for demographic data access.
  • Implemented privilege checks for read operations.
  • Enhanced security by throwing exceptions for unauthorized access.
  • +13/-0   
    MeasurementWs.java
    Secure Measurement Web Service Endpoints                                 

    src/main/java/io/github/carlos_emr/carlos/webserv/MeasurementWs.java

  • Introduced security checks for measurement data access.
  • Added privilege checks for creating and retrieving measurements.
  • +7/-0     
    PrescriptionWs.java
    Secure Prescription Web Service Endpoints                               

    src/main/java/io/github/carlos_emr/carlos/webserv/PrescriptionWs.java

  • Implemented security checks for prescription data access.
  • Added privilege checks for retrieving prescriptions.
  • +7/-0     
    ScheduleWs.java
    Secure Appointment Management Endpoints                                   

    src/main/java/io/github/carlos_emr/carlos/webserv/ScheduleWs.java

  • Added security checks for appointment management.
  • Implemented privilege checks for adding and updating appointments.
  • +10/-0   
    SystemInfoWs.java
    Harden System Info Web Service                                                     

    src/main/java/io/github/carlos_emr/carlos/webserv/SystemInfoWs.java

  • Removed sensitive system information exposure.
  • Enhanced security for unauthenticated SOAP endpoint.
  • +13/-29 
    DemographicService.java
    Secure Demographic REST Service Endpoints                               

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java

  • Added security checks for demographic data access.
  • Implemented privilege checks for various demographic operations.
  • +28/-0   
    OscarJobService.java
    Secure Oscar Job Management Endpoints                                       

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/OscarJobService.java

  • Introduced security checks for job management.
  • Added admin privilege checks for job operations.
  • +37/-2   
    ProviderService.java
    Secure Provider Service Endpoints                                               

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java

  • Implemented security checks for provider settings.
  • Added privilege checks to prevent unauthorized modifications.
  • +31/-6   

    💡 Penify usage:
    Comment /help on the PR to get a list of all available Penify tools and their descriptions

    Summary by CodeRabbit

    • New Features

      • Enhanced security with privilege-based access control across demographics, measurements, prescriptions, and appointments.
      • Admin-level authorization enforcement for job management endpoints.
      • Added protection against unauthorized access to provider settings modifications.
    • Bug Fixes

      • Redacted sensitive credentials in provider responses to prevent data exposure.
    • Chores

      • Removed unnecessary public API endpoints that exposed server internals.

    …and allowing unauthorized modifications
    
    Security vulnerabilities fixed:
    
    1. OscarJobService (/jobs/*) - All 12 endpoints lacked authorization checks.
       Any authenticated user could view, create, modify, enable/disable, and
       cancel system jobs, including saving job types with arbitrary class names.
       Added _admin read/write privilege checks to all endpoints.
    
    2. ProviderService (/providerService/settings/{providerNo}/save) - IDOR
       vulnerability allowed any authenticated user to modify any other
       provider's settings by providing their providerNo in the URL path.
       Fixed by enforcing that users can only modify their own settings
       unless they have _admin write privileges.
    
    3. ProviderService (/providerService/settings/get) - eRxPassword
       (e-prescribing credential) was returned in plaintext in the API
       response. Now redacted to "********" in responses, with logic to
       preserve existing passwords when the redacted placeholder is sent back.
    
    4. DemographicService - 7 data endpoints lacked _demographic privilege
       checks (getAllDemographics, getDemographicData, getBasicDemographicData,
       getDemographicSummary, createDemographicData, updateDemographicData,
       deleteDemographicData). The search endpoints already had checks but
       direct ID-based access did not. Added appropriate read/write privilege
       checks with demographic-specific scoping.
    
    5. ProviderService (/providerService/suggestProviderNo) - Admin-only
       functionality (suggesting next available provider number) was accessible
       to any authenticated user. Added _admin read privilege check.
    
    Generated with Claude Code
    …er endpoints
    
    SOAP web service security hardening:
    
    1. ScheduleWs - Added _appointment write privilege checks to
       addAppointment() and updateAppointment(). Previously any
       authenticated SOAP user could create or modify appointments
       for any patient without authorization.
    
    2. MeasurementWs - Added _measurement write privilege check to
       addMeasurement(). Previously any authenticated SOAP user could
       create clinical measurements (vital signs) for any patient.
    
    3. DemographicWs - Added _demographic read privilege checks to
       getDemographic(), getDemographic2(), and searchDemographicByName().
       Previously any authenticated SOAP user could access any patient's
       full demographic record including HIN, address, and contacts.
    
    4. PrescriptionWs - Added _rx read privilege check to
       getPrescription(). Previously any authenticated SOAP user could
       read any patient's prescription/medication data.
    
    5. SystemInfoWs - Removed helloWorld(), getMaxListReturnSize(),
       getServerTime(), and getServerTimeGmtOffset() methods from this
       unauthenticated endpoint. These exposed system internals (server
       timestamps, timezone, DAO configuration) that aid reconnaissance.
       Retained only isAlive() as a basic health check.
    
    REST API hardening:
    
    6. ProviderService /provider/me - Changed from serializing raw
       Provider entity (which includes all JPA fields) to using
       ProviderTransfer DTO, which provides a controlled field set.
    
    7. ProviderService /providerjson/{id} - Same fix as above, using
       ProviderTransfer instead of raw Provider entity serialization.
    
    Generated with Claude Code
    Copilot AI review requested due to automatic review settings February 11, 2026 01:07
    @sourcery-ai
    Copy link
    Contributor

    sourcery-ai bot commented Feb 11, 2026

    Reviewer's Guide

    The PR tightens security across SOAP and REST APIs by stripping sensitive data from public endpoints, enforcing privilege checks via SecurityInfoManager on admin, provider, demographic, appointment, measurement, and prescription operations, and improving data exposure patterns (e.g., transfer objects, redacted credentials, IDOR protection).

    Sequence diagram for secure provider settings update via REST API

    sequenceDiagram
      actor Provider
      participant ProviderService
      participant SecurityInfoManager
      participant ProviderManager
    
      Provider->>ProviderService: PUT /providers/{providerNo}/settings ProviderSettings json
      ProviderService->>ProviderService: getLoggedInInfo()
      ProviderService->>ProviderService: extract currentProviderNo
      alt providerNo != currentProviderNo
        ProviderService->>SecurityInfoManager: hasPrivilege(info,_admin,w,null)
        alt missing_admin_privilege
          ProviderService-->>Provider: SecurityException
        else has_admin_privilege
          opt eRxPassword_placeholder
            ProviderService->>ProviderManager: getProviderSettings(info,providerNo)
            ProviderManager-->>ProviderService: existingSettings
            ProviderService->>ProviderService: copy existing eRxPassword to json
          end
          ProviderService->>ProviderManager: updateProviderSettings(info,providerNo,json)
          ProviderManager-->>ProviderService: updated
          ProviderService-->>Provider: 200 OK GenericRESTResponse
        end
      else providerNo == currentProviderNo
        opt eRxPassword_placeholder
          ProviderService->>ProviderManager: getProviderSettings(info,providerNo)
          ProviderManager-->>ProviderService: existingSettings
          ProviderService->>ProviderService: copy existing eRxPassword to json
        end
        ProviderService->>ProviderManager: updateProviderSettings(info,providerNo,json)
        ProviderManager-->>ProviderService: updated
        ProviderService-->>Provider: 200 OK GenericRESTResponse
      end
    
    Loading

    Class diagram for updated API service security integration

    classDiagram
    
    class AbstractWs
    class AbstractServiceImpl
    
    class SecurityInfoManager {
      +boolean hasPrivilege(LoggedInInfo info, String objectName, String permission, String resourceId)
    }
    
    class SystemInfoWs {
      +String isAlive()
    }
    
    class OscarJobService {
      -SecurityInfoManager securityInfoManager
      +OscarJobTypeResponse getCurrentlyAvailableJobTypes()
      +OscarJobTypeResponse getAllJobTypes()
      +OscarJobResponse getAllJobs()
      +OscarJobResponse getJob(Integer jobId)
      +OscarJobResponse saveJob(MultivaluedMap params)
      +OscarJobResponse cancelJob(Integer jobId)
      +OscarJobResponse saveCrontabExpression(MultivaluedMap params)
      +OscarJobTypeResponse getJobType(Integer jobTypeId)
      +OscarJobTypeResponse saveJobType(MultivaluedMap params)
      +OscarJobResponse enableJob(Integer jobId)
      +OscarJobResponse disableJob(Integer jobId)
      -void requireAdminRead()
      -void requireAdminWrite()
    }
    
    class ProviderService {
      -SecurityInfoManager securityInfoManager
      +Response getLoggedInProvider()
      +String getProviderAsJSON(String id)
      +AbstractSearchResponse getProviderSettings()
      +GenericRESTResponse saveProviderSettings(ProviderSettings json, String providerNo)
      +GenericRESTResponse suggestProviderNo()
    }
    
    class DemographicService {
      -SecurityInfoManager securityInfoManager
      +OscarSearchResponse getAllDemographics(Integer offset, Integer limit)
      +DemographicTo1 getDemographicData(Integer id, List includes)
      +DemographicTo1 getBasicDemographicData(Integer id, List includes)
      +DemographicTo1 getDemographicSummary(Integer demographicNo)
      +DemographicTo1 createDemographicData(DemographicTo1 data)
      +DemographicTo1 updateDemographicData(DemographicTo1 data)
      +DemographicTo1 deleteDemographicData(Integer id)
    }
    
    class DemographicWs {
      -SecurityInfoManager securityInfoManager
      +DemographicTransfer getDemographic(Integer demographicId)
      +DemographicTransfer2 getDemographic2(Integer demographicId)
      +DemographicTransfer[] searchDemographicByName(String searchString, int startIndex, int itemsToReturn)
    }
    
    class ScheduleWs {
      -SecurityInfoManager securityInfoManager
      +ScheduleTemplateCodeTransfer[] getScheduleTemplateCodes()
      +AppointmentTypeTransfer[] getAppointmentTypes()
      +Integer addAppointment(AppointmentTransfer appointmentTransfer)
      +void updateAppointment(AppointmentTransfer appointmentTransfer)
    }
    
    class MeasurementWs {
      -SecurityInfoManager securityInfoManager
      +MeasurementTransfer getMeasurement(Integer measurementId)
      +MeasurementTypeTransfer[] getMeasurementTypes()
      +MeasurementMapTransfer[] getMeasurementMaps()
      +Integer addMeasurement(MeasurementTransfer measurementTransfer)
    }
    
    class PrescriptionWs {
      -SecurityInfoManager securityInfoManager
      +PrescriptionTransfer getPrescription(Integer prescriptionId)
    }
    
    class ProviderTransfer {
      +static ProviderTransfer toTransfer(Provider provider)
    }
    
    class ProviderSettings {
      +String geteRxPassword()
      +void seteRxPassword(String eRxPassword)
    }
    
    class ProviderManager {
      +ProviderSettings getProviderSettings(LoggedInInfo info, String providerNo)
      +void updateProviderSettings(LoggedInInfo info, String providerNo, ProviderSettings settings)
    }
    
    SystemInfoWs --|> AbstractWs
    OscarJobService --|> AbstractServiceImpl
    ProviderService --|> AbstractServiceImpl
    DemographicService --|> AbstractServiceImpl
    DemographicWs --|> AbstractWs
    ScheduleWs --|> AbstractWs
    MeasurementWs --|> AbstractWs
    PrescriptionWs --|> AbstractWs
    
    OscarJobService --> SecurityInfoManager
    ProviderService --> SecurityInfoManager
    DemographicService --> SecurityInfoManager
    DemographicWs --> SecurityInfoManager
    ScheduleWs --> SecurityInfoManager
    MeasurementWs --> SecurityInfoManager
    PrescriptionWs --> SecurityInfoManager
    
    ProviderService --> ProviderManager
    ProviderService --> ProviderTransfer
    ProviderService --> ProviderSettings
    DemographicService --> DemographicManager
    DemographicWs --> DemographicManager
    ScheduleWs --> ScheduleManager
    MeasurementWs --> MeasurementManager
    PrescriptionWs --> PrescriptionManager
    
    Loading

    File-Level Changes

    Change Details Files
    Harden unauthenticated SystemInfo SOAP endpoint by removing exposure of system internals.
    • Removed helloWorld method that returned server time in response string.
    • Removed methods exposing max list size, server time, and GMT offset.
    • Updated class-level Javadoc to clarify purpose as a minimal unauthenticated health check endpoint and kept only isAlive().
    src/main/java/io/github/carlos_emr/carlos/webserv/SystemInfoWs.java
    Enforce admin privileges on Oscar job management REST endpoints.
    • Injected SecurityInfoManager into OscarJobService.
    • Introduced helper methods requireAdminRead and requireAdminWrite that check _admin privileges and throw SecurityException on failure.
    • Applied requireAdminRead to job type and job read endpoints (current/all types, all jobs, single job, job type by id).
    • Applied requireAdminWrite to mutating endpoints (saveJob, cancelJob, saveCrontabExpression, saveJobType, enableJob, disableJob).
    src/main/java/io/github/carlos_emr/carlos/webserv/rest/OscarJobService.java
    Tighten provider REST API to use transfer objects, redact credentials, and enforce authorization on settings operations.
    • Injected SecurityInfoManager into ProviderService.
    • Switched JSON serialization from Provider entity to ProviderTransfer for logged-in provider and provider-by-id endpoints to avoid leaking raw entity fields.
    • Redacted eRx password in ProviderSettings responses by replacing non-empty values with a placeholder string.
    • On saveProviderSettings, enforced that only the current provider or an admin with _admin write privilege can update settings (prevents IDOR).
    • When saving ProviderSettings, preserved existing eRx password if the request contains the redacted placeholder, preventing unwanted password overwrites.
    • Required _admin read privilege for suggestProviderNo endpoint.
    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java
    Add fine-grained demographic authorization checks to REST and SOAP demographics APIs.
    • In DemographicService, added _demographic read privilege checks for listing all demographics, fetching demographic data (full/basic) and summary, including passing demographic id as context where appropriate.
    • Added _demographic write privilege checks for creating, updating, and deleting demographic data, again passing demographic id when available.
    • In DemographicWs SOAP service, injected SecurityInfoManager and enforced _demographic read checks for getDemographic, getDemographic2, and searchDemographicByName methods.
    src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java
    src/main/java/io/github/carlos_emr/carlos/webserv/DemographicWs.java
    Enforce authorization for appointment, measurement, and prescription operations in SOAP services.
    • Injected SecurityInfoManager into ScheduleWs and added _appointment write privilege checks to addAppointment and updateAppointment.
    • Injected SecurityInfoManager into MeasurementWs and added _measurement write privilege checks to addMeasurement.
    • Injected SecurityInfoManager into PrescriptionWs and added _rx read privilege check to getPrescription.
    src/main/java/io/github/carlos_emr/carlos/webserv/ScheduleWs.java
    src/main/java/io/github/carlos_emr/carlos/webserv/MeasurementWs.java
    src/main/java/io/github/carlos_emr/carlos/webserv/PrescriptionWs.java

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @github-actions
    Copy link
    Contributor

    Dependency Review

    ✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

    Snapshot Warnings

    ⚠️: No snapshots were found for the head SHA 83a7f9f.
    Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

    Scanned Files

    None

    @gemini-code-assist
    Copy link
    Contributor

    Summary of Changes

    Hello @yingbull, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

    This pull request significantly enhances the security posture of the application's API endpoints. It introduces granular privilege checks for both SOAP and REST services, ensuring that users can only access or modify data for which they have explicit permissions. Additionally, it hardens a public-facing system information endpoint by removing potentially sensitive data exposure and improves data handling in provider-related services to prevent information leakage and unauthorized modifications.

    Highlights

    • Comprehensive Privilege Checks: Implemented granular privilege checks across various SOAP and REST API endpoints, including Demographic, Measurement, Prescription, Schedule, OscarJob, and Provider services, to enforce strict access control based on user roles and permissions.
    • System Information Endpoint Hardening: Hardened the unauthenticated SystemInfoWs endpoint by removing methods that exposed system internals (like server time, timezone offset, and max list size), thereby reducing the attack surface for reconnaissance.
    • Sensitive Data Exposure Prevention: Enhanced data security in ProviderService by utilizing transfer objects for serialization, redacting sensitive credentials (e.g., eRxPassword) in API responses, and preventing Insecure Direct Object Reference (IDOR) vulnerabilities.
    • Admin Privilege Helper Methods: Introduced dedicated helper methods (requireAdminRead, requireAdminWrite) in OscarJobService to standardize and simplify the application of admin-level privilege checks across its various job management endpoints.

    🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

    Changelog
    • src/main/java/io/github/carlos_emr/carlos/webserv/DemographicWs.java
      • Imported and autowired SecurityInfoManager.
      • Added privilege checks to getDemographic, getDemographic2, and searchDemographicByName methods.
    • src/main/java/io/github/carlos_emr/carlos/webserv/MeasurementWs.java
      • Imported and autowired SecurityInfoManager.
      • Added privilege checks to addMeasurement method.
    • src/main/java/io/github/carlos_emr/carlos/webserv/PrescriptionWs.java
      • Imported and autowired SecurityInfoManager.
      • Added privilege checks to getPrescription method.
    • src/main/java/io/github/carlos_emr/carlos/webserv/ScheduleWs.java
      • Imported and autowired SecurityInfoManager.
      • Added privilege checks to addAppointment and updateAppointment methods.
    • src/main/java/io/github/carlos_emr/carlos/webserv/SystemInfoWs.java
      • Removed helloWorld, getMaxListReturnSize, getServerTime, and getServerTimeGmtOffset methods.
      • Updated Javadoc to reflect the minimal, unauthenticated nature of the service for security reasons.
    • src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java
      • Added privilege checks to getAllDemographics, getDemographicData, getBasicDemographicData, getDemographicSummary, createDemographicData, updateDemographicData, and deleteDemographicData methods.
    • src/main/java/io/github/carlos_emr/carlos/webserv/rest/OscarJobService.java
      • Imported and autowired SecurityInfoManager.
      • Introduced requireAdminRead and requireAdminWrite helper methods for privilege checks.
      • Applied admin privilege checks to getCurrentlyAvailableJobTypes, getAllJobTypes, getAllJobs, getJob, saveJob, cancelJob, saveCrontabExpression, getJobType, saveJobType, enableJob, and disableJob methods.
    • src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java
      • Imported and autowired SecurityInfoManager.
      • Modified getLoggedInProvider and getProviderAsJSON to serialize ProviderTransfer objects instead of raw Provider entities to prevent exposing raw entity fields.
      • Redacted eRxPassword field in getProviderSettings responses.
      • Implemented IDOR prevention and privilege checks in saveProviderSettings, including logic to preserve existing passwords if a redacted placeholder is received.
      • Added privilege check to suggestProviderNo method.
    Using Gemini Code Assist

    The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

    Invoking Gemini

    You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

    Feature Command Description
    Code Review /gemini review Performs a code review for the current pull request in its current state.
    Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
    Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
    Help /gemini help Displays a list of available commands.

    Customization

    To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

    Limitations & Feedback

    Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

    You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

    Footnotes

    1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

    @penify-dev penify-dev bot added the Bug fix label Feb 11, 2026
    @coderabbitai
    Copy link
    Contributor

    coderabbitai bot commented Feb 11, 2026

    📝 Walkthrough

    Walkthrough

    Security authorization checks are integrated across multiple web service endpoints via SecurityInfoManager dependency injection. Privilege-based access control enforces read/write restrictions on demographic, measurement, prescription, appointment, and other resources. ProviderService adds IDOR protection and password redaction. SystemInfoWs removes four public health-check endpoints, retaining only isAlive().

    Changes

    Cohort / File(s) Summary
    Web Service Privilege Enforcement
    src/main/java/io/github/carlos_emr/carlos/webserv/DemographicWs.java, MeasurementWs.java, PrescriptionWs.java, ScheduleWs.java
    Added SecurityInfoManager dependency and runtime privilege checks in data access methods. DemographicWs enforces read privileges in three endpoints; MeasurementWs and PrescriptionWs each add a single write/read check; ScheduleWs enforces write privileges for appointment creation and updates.
    REST Service Authorization
    src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java
    Added privilege-based access control across multiple endpoints, verifying read/write permissions on _demographic resource before data retrieval or modification.
    Admin-Level Access Control
    src/main/java/io/github/carlos_emr/carlos/webserv/rest/OscarJobService.java
    Introduced SecurityInfoManager dependency with private helper methods requireAdminRead() and requireAdminWrite(). Admin-level authorization now gates all job-related endpoints including retrieval, creation, cancellation, and status modification.
    Provider Service Authorization & Data Protection
    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java
    Added admin privilege enforcement on /suggestProviderNo; replaced raw Provider JSON serialization with ProviderTransfer to prevent internal field exposure; redacted eRxPassword as "\\\\\\\\" in responses; implemented IDOR protection requiring users to modify only their own settings unless holding admin write privilege; preserved existing passwords when redacted values are submitted.
    System Endpoints Cleanup
    src/main/java/io/github/carlos_emr/carlos/webserv/SystemInfoWs.java
    Removed four public API endpoints (helloWorld, getMaxListReturnSize, getServerTime, getServerTimeGmtOffset) and associated imports; retained isAlive() as the sole public health-check endpoint with enhanced class documentation.

    Estimated code review effort

    🎯 3 (Moderate) | ⏱️ ~22 minutes

    Poem

    🐰 A rabbit's ode to security's embrace:

    With privileges checked at every gate,
    The warren's data is kept safe and sound,
    No passwords bare, no admin's fate,
    Each burrow protected, with checks all 'round! 🔐

    🚥 Pre-merge checks | ✅ 1 | ❌ 2
    ❌ Failed checks (2 warnings)
    Check name Status Explanation Resolution
    Title check ⚠️ Warning The title 'API endpoint changes' is too vague and generic. It fails to convey the primary change: implementing security access controls across multiple API endpoints. Use a more specific title that captures the main objective, such as 'Add security authorization checks to API endpoints' or 'Implement privilege validation across web services'.
    Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
    ✅ Passed checks (1 passed)
    Check name Status Explanation
    Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

    ✨ Finishing touches
    • 📝 Generate docstrings
    🧪 Generate unit tests (beta)
    • Create PR with unit tests
    • Post copyable unit tests in a comment
    • Commit unit tests in branch claude/fix-api-security-access-jKll6

    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.

    @penify-dev
    Copy link
    Contributor

    penify-dev bot commented Feb 11, 2026

    PR Review 🔍

    ⏱️ Estimated effort to review [1-5]

    4, because the PR introduces significant changes across multiple files with new security checks and privilege validations. The complexity of ensuring that all paths are correctly secured requires careful review.

    🧪 Relevant tests

    No

    ⚡ Possible issues

    Possible Bug: Ensure that the security checks do not inadvertently block legitimate access. Testing should confirm that authorized users can still access necessary data.

    Possible Bug: The handling of exceptions thrown for unauthorized access should be consistent across all services to avoid confusion.

    🔒 Security concerns

    No

    @penify-dev
    Copy link
    Contributor

    penify-dev bot commented Feb 11, 2026

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Logging
    Log security check failures to improve monitoring and debugging

    Consider logging the security check failures to aid in debugging and monitoring access
    issues.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java [425]

    +logger.warn("Access denied for user {}: missing required security object (_admin)", getLoggedInInfo().getUserId());
     throw new SecurityException("missing required security object (_admin)");
     
    Suggestion importance[1-10]: 9

    Why: Logging security check failures is a valuable practice for monitoring and debugging, making this suggestion highly relevant and beneficial for maintainability.

    9
    Security
    Ensure consistent security checks for admin privileges across all relevant methods

    Ensure that the requireAdminRead and requireAdminWrite methods are called consistently
    across all endpoints that modify or read sensitive data, to maintain security.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/OscarJobService.java [103]

    -+        requireAdminRead();
    ++        requireAdminRead(); // Ensure admin read privilege is checked
     
    Suggestion importance[1-10]: 8

    Why: The suggestion addresses the importance of consistent security checks across endpoints, which is crucial for maintaining application security.

    8
    Add a null check for the logged-in user information to prevent potential null pointer exceptions

    Ensure that the getLoggedInInfo() method is not returning null before using it in
    privilege checks to avoid potential NullPointerExceptions.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java [208-210]

    -+        if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", null)) {
    ++        LoggedInInfo loggedInInfo = getLoggedInInfo();
    ++        if (loggedInInfo == null) throw new SecurityException("User not logged in");
    ++        if (!securityInfoManager.hasPrivilege(loggedInInfo, "_demographic", "r", null)) {
     
    Suggestion importance[1-10]: 8

    Why: This suggestion addresses a potential NullPointerException, which is a significant issue that could lead to application crashes, making it a crucial improvement.

    8
    Maintainability
    Add a null check for the provider before converting to ProviderTransfer to avoid potential NullPointerExceptions

    Ensure that the ProviderTransfer class handles null values appropriately to avoid
    NullPointerExceptions when converting the provider.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java [223]

    +if (provider == null) {
    +    throw new IllegalArgumentException("Provider cannot be null");
    +}
     ProviderTransfer transfer = ProviderTransfer.toTransfer(provider);
     
    Suggestion importance[1-10]: 8

    Why: This suggestion addresses a potential issue by ensuring that null values are handled before conversion, which is crucial for preventing runtime exceptions.

    8
    Improve exception handling to provide more context in security exceptions

    Consider handling the SecurityException in a way that provides more context about the
    request, such as the user ID or the endpoint being accessed.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/OscarJobService.java [83]

    -+            throw new SecurityException("missing required security object (_admin)");
    ++            throw new SecurityException("User " + getLoggedInInfo().getUserId() + " missing required security object (_admin) for endpoint " + request.getRequestURI());
     
    Suggestion importance[1-10]: 7

    Why: Improving exception handling to include user context enhances debuggability and security, making it a valuable suggestion.

    7
    Best practice
    Use a more specific exception type for better error handling

    Consider using a more specific exception type instead of SecurityException to provide
    clearer error handling.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java [425]

    -throw new SecurityException("missing required security object (_admin)");
    +throw new AccessDeniedException("missing required security object (_admin)");
     
    Suggestion importance[1-10]: 8

    Why: This suggestion improves error handling by recommending a more specific exception type, which enhances clarity and maintainability.

    8
    Possible bug
    Add validation for required parameters before processing them

    Validate the params in the saveJob and saveJobType methods to ensure that the required
    fields are present before attempting to parse them.

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java [187-188]

    -+          job.setId(Integer.parseInt(params.getFirst("job.id")));
    ++          String jobIdStr = params.getFirst("job.id");
    ++          if (jobIdStr == null) throw new IllegalArgumentException("Job ID is required");
    ++          job.setId(Integer.parseInt(jobIdStr));
     
    Suggestion importance[1-10]: 6

    Why: Adding validation for required parameters helps prevent runtime errors, which is important for robustness, though it addresses a less critical issue.

    6

    Copy link
    Contributor

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey - I've found 2 issues, and left some high level feedback:

    • In several SOAP services the new privilege checks are only applied to write operations (e.g., MeasurementWs.addMeasurement) while corresponding read operations (e.g., MeasurementWs.getMeasurement) remain unrestricted; consider consistently enforcing appropriate read privileges for these entry points as well.
    • The new security checks in REST resources (e.g., OscarJobService, ProviderService, DemographicService) throw plain SecurityException; if your JAX-RS stack does not already map this to a consistent HTTP error, it may be clearer and safer to throw a specific WebApplicationException/mapper-backed exception that returns an appropriate status code and message.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - In several SOAP services the new privilege checks are only applied to write operations (e.g., MeasurementWs.addMeasurement) while corresponding read operations (e.g., MeasurementWs.getMeasurement) remain unrestricted; consider consistently enforcing appropriate read privileges for these entry points as well.
    - The new security checks in REST resources (e.g., OscarJobService, ProviderService, DemographicService) throw plain SecurityException; if your JAX-RS stack does not already map this to a consistent HTTP error, it may be clearer and safer to throw a specific WebApplicationException/mapper-backed exception that returns an appropriate status code and message.
    
    ## Individual Comments
    
    ### Comment 1
    <location> `src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java:403-405` </location>
    <code_context>
    
             ProviderSettings settings = providerManager.getProviderSettings(getLoggedInInfo(), getLoggedInInfo().getLoggedInProviderNo());
    +
    +        // Redact sensitive credentials - never expose passwords via API
    +        if (settings.geteRxPassword() != null && !settings.geteRxPassword().isEmpty()) {
    +            settings.seteRxPassword("********");
    +        }
    +
    </code_context>
    
    <issue_to_address>
    **suggestion (bug_risk):** Redacting by mutating the ProviderSettings entity may have side effects on shared instances
    
    Mutating the `ProviderSettings` returned from `providerManager` to mask the password can leak the masked value into persistence or caches if this object is shared. Instead, derive a separate DTO or copy for the API response and apply redaction there so the domain object remains unchanged.
    
    Suggested implementation:
    
    ```java
            ProviderSettings settings = providerManager.getProviderSettings(getLoggedInInfo(), getLoggedInInfo().getLoggedInProviderNo());
    
            // Create a detached copy for API exposure so we don't mutate the managed entity
            ProviderSettings redactedSettings = new ProviderSettings();
            org.springframework.beans.BeanUtils.copyProperties(settings, redactedSettings);
    
            // Redact sensitive credentials - never expose passwords via API
            if (redactedSettings.geteRxPassword() != null && !redactedSettings.geteRxPassword().isEmpty()) {
                redactedSettings.seteRxPassword("********");
            }
    
    ```
    
    1. Anywhere in this method (or downstream) where the original `settings` instance is used for building the HTTP response / JSON payload, switch to using `redactedSettings` instead. For example, if you currently do something like `response.setResult(settings)` or serialize `settings` with `objectMapper`, change those usages to `redactedSettings`.
    2. If `ProviderSettings` does not have a no-arg constructor or is not suitable as a DTO, you may want to introduce a dedicated DTO class (e.g., `ProviderSettingsDto`) and copy properties into that instead of using `ProviderSettings` directly.
    3. If you prefer to avoid the fully-qualified `org.springframework.beans.BeanUtils` reference, add `import org.springframework.beans.BeanUtils;` at the top of this file and then call `BeanUtils.copyProperties(settings, redactedSettings);`.
    </issue_to_address>
    
    ### Comment 2
    <location> `src/main/java/io/github/carlos_emr/carlos/webserv/MeasurementWs.java:53-57` </location>
    <code_context>
         @Autowired
         private PatientConsentManager patientConsentManager;
    
    +    @Autowired
    +    private SecurityInfoManager securityInfoManager;
    +
         public DemographicTransfer getDemographic(Integer demographicId) {
    </code_context>
    
    <issue_to_address>
    **🚨 question (security):** SecurityInfoManager is injected but only used for writes; reads remain unguarded
    
    You’ve added a `_measurement` write-privilege check for `addMeasurement`, but `getMeasurement` and `getMeasurementMaps` are still callable without any authorization and don’t use the injected `SecurityInfoManager`. If reads are meant to be restricted, add corresponding read checks there; if not, the new injection is unused on read paths and may indicate a missing auth requirement or a design inconsistency.
    </issue_to_address>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Comment on lines +403 to +405
    // Redact sensitive credentials - never expose passwords via API
    if (settings.geteRxPassword() != null && !settings.geteRxPassword().isEmpty()) {
    settings.seteRxPassword("********");
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    suggestion (bug_risk): Redacting by mutating the ProviderSettings entity may have side effects on shared instances

    Mutating the ProviderSettings returned from providerManager to mask the password can leak the masked value into persistence or caches if this object is shared. Instead, derive a separate DTO or copy for the API response and apply redaction there so the domain object remains unchanged.

    Suggested implementation:

            ProviderSettings settings = providerManager.getProviderSettings(getLoggedInInfo(), getLoggedInInfo().getLoggedInProviderNo());
    
            // Create a detached copy for API exposure so we don't mutate the managed entity
            ProviderSettings redactedSettings = new ProviderSettings();
            org.springframework.beans.BeanUtils.copyProperties(settings, redactedSettings);
    
            // Redact sensitive credentials - never expose passwords via API
            if (redactedSettings.geteRxPassword() != null && !redactedSettings.geteRxPassword().isEmpty()) {
                redactedSettings.seteRxPassword("********");
            }
    1. Anywhere in this method (or downstream) where the original settings instance is used for building the HTTP response / JSON payload, switch to using redactedSettings instead. For example, if you currently do something like response.setResult(settings) or serialize settings with objectMapper, change those usages to redactedSettings.
    2. If ProviderSettings does not have a no-arg constructor or is not suitable as a DTO, you may want to introduce a dedicated DTO class (e.g., ProviderSettingsDto) and copy properties into that instead of using ProviderSettings directly.
    3. If you prefer to avoid the fully-qualified org.springframework.beans.BeanUtils reference, add import org.springframework.beans.BeanUtils; at the top of this file and then call BeanUtils.copyProperties(settings, redactedSettings);.

    Comment on lines 53 to +57
    @Autowired
    private MeasurementManager measurementManager;

    @Autowired
    private SecurityInfoManager securityInfoManager;
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🚨 question (security): SecurityInfoManager is injected but only used for writes; reads remain unguarded

    You’ve added a _measurement write-privilege check for addMeasurement, but getMeasurement and getMeasurementMaps are still callable without any authorization and don’t use the injected SecurityInfoManager. If reads are meant to be restricted, add corresponding read checks there; if not, the new injection is unused on read paths and may indicate a missing auth requirement or a design inconsistency.

    Copy link
    Contributor

    @gemini-code-assist gemini-code-assist bot left a comment

    Choose a reason for hiding this comment

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

    Code Review

    This pull request introduces significant security enhancements across both SOAP and REST APIs. It correctly adds privilege checks to various endpoints, protecting sensitive patient and system data. The changes also include redacting sensitive information like passwords from API responses and preventing insecure direct object references. The cleanup of the unauthenticated SystemInfoWs endpoint is a great hardening step. I've identified a couple of areas for improvement: one related to using the correct privilege type for a delete operation, and another for improving consistency in the DTOs used in the REST services. Overall, this is a solid contribution to improving the application's security posture.

    Comment on lines +628 to +630
    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "w", id.toString())) {
    throw new SecurityException("missing required security object (_demographic)");
    }
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    high

    The privilege check for deleting a demographic record uses the write privilege ("w"). According to the SecurityInfoManager implementation, the delete privilege ("d") is distinct from the write privilege. To ensure proper access control, this check should specifically verify the delete privilege. Using the SecurityInfoManager.DELETE constant is recommended for clarity and maintainability.

    Suggested change
    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "w", id.toString())) {
    throw new SecurityException("missing required security object (_demographic)");
    }
    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "d", id.toString())) {
    throw new SecurityException("missing required security object (_demographic)");
    }

    Comment on lines +222 to +224
    // Use ProviderTransfer to avoid exposing raw entity fields
    ProviderTransfer transfer = ProviderTransfer.toTransfer(provider);
    String body = objectMapper.valueToTree(transfer).toString();
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    medium

    This REST endpoint uses ProviderTransfer, which appears to be intended for SOAP services. For consistency with other REST endpoints and to leverage the conversion logic (e.g., for status fields), it would be better to use ProviderTo1 and the autowired providerConverter.

    Suggested change
    // Use ProviderTransfer to avoid exposing raw entity fields
    ProviderTransfer transfer = ProviderTransfer.toTransfer(provider);
    String body = objectMapper.valueToTree(transfer).toString();
    // Use ProviderTo1 to avoid exposing raw entity fields and for consistency
    ProviderTo1 transfer = providerConverter.getAsTransferObject(getLoggedInInfo(), provider);
    String body = objectMapper.valueToTree(transfer).toString();

    Comment on lines +257 to +260
    // Use ProviderTransfer to avoid exposing raw entity fields
    ProviderTransfer transfer = ProviderTransfer.toTransfer(provider);
    logger.info("Successfully retrieved provider {} as JSON", id);
    return objectMapper.valueToTree(provider).toString();
    return objectMapper.valueToTree(transfer).toString();
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    medium

    This REST endpoint uses ProviderTransfer, which appears to be intended for SOAP services. For consistency with other REST endpoints and to leverage the conversion logic, it would be better to use ProviderTo1 and the autowired providerConverter.

    Suggested change
    // Use ProviderTransfer to avoid exposing raw entity fields
    ProviderTransfer transfer = ProviderTransfer.toTransfer(provider);
    logger.info("Successfully retrieved provider {} as JSON", id);
    return objectMapper.valueToTree(provider).toString();
    return objectMapper.valueToTree(transfer).toString();
    // Use ProviderTo1 to avoid exposing raw entity fields and for consistency
    ProviderTo1 transfer = providerConverter.getAsTransferObject(getLoggedInInfo(), provider);
    logger.info("Successfully retrieved provider {} as JSON", id);
    return objectMapper.valueToTree(transfer).toString();

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

    Caution

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

    ⚠️ Outside diff range comments (4)
    src/main/java/io/github/carlos_emr/carlos/webserv/MeasurementWs.java (1)

    59-95: ⚠️ Potential issue | 🟠 Major

    Read endpoints lack authorization checks — PHI exposure risk.

    addMeasurement is now gated, but the read methods (getMeasurement, getMeasurementsCreatedAfterDate, getMeasurementsByProgramProviderDemographicDate, getMeasurementsByDemographicIdAfter) have no privilege checks. Measurements are PHI and should require at least "_measurement" read ("r") privilege. This is especially important given HIPAA/PIPEDA compliance requirements.

    Example fix for getMeasurement
         public MeasurementTransfer getMeasurement(Integer measurementId) {
    +        if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_measurement", "r", null)) {
    +            throw new SecurityException("missing required security object (_measurement)");
    +        }
             Measurement measurement = measurementManager.getMeasurement(getLoggedInInfo(), measurementId);
             return (MeasurementTransfer.toTransfer(measurement));
         }

    Based on learnings: "PHI (Patient Health Information) protection is legally mandated under HIPAA/PIPEDA compliance. Security is non-negotiable in all code changes and is the top priority for this healthcare system."

    src/main/java/io/github/carlos_emr/carlos/webserv/PrescriptionWs.java (1)

    74-90: ⚠️ Potential issue | 🟠 Major

    Remaining prescription read endpoints lack authorization checks.

    getPrescription is now gated with _rx read privilege, but getPrescriptionUpdatedAfterDate, getPrescriptionsByProgramProviderDemographicDate, and getPrescriptionsByDemographicIdAfter are unprotected. Prescription data is highly sensitive PHI — all read endpoints should enforce the same _rx read check.

    Example fix for getPrescriptionUpdatedAfterDate
         public PrescriptionTransfer[] getPrescriptionUpdatedAfterDate(Date updatedAfterThisDateExclusive, int itemsToReturn) {
    +        if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_rx", "r", null)) {
    +            throw new SecurityException("missing required security object (_rx)");
    +        }
             LoggedInInfo loggedInInfo = getLoggedInInfo();

    Based on learnings: "PHI (Patient Health Information) protection is legally mandated under HIPAA/PIPEDA compliance."

    src/main/java/io/github/carlos_emr/carlos/webserv/ScheduleWs.java (1)

    80-116: ⚠️ Potential issue | 🟠 Major

    Read endpoints for appointments lack authorization checks.

    Multiple read endpoints (getAppointment, getAppointment2, getAppointmentsForProvider, getAppointmentsForProvider2, getAppointmentsForPatient, getAppointmentsForPatient2, etc.) do not enforce any privilege check. Appointment data may include patient-identifying information. Consider adding _appointment read checks for consistency with the write-side enforcement.

    src/main/java/io/github/carlos_emr/carlos/webserv/DemographicWs.java (1)

    102-105: ⚠️ Potential issue | 🟠 Major

    searchDemographicsByAttributes lacks an authorization check.

    This method accepts HIN (Health Insurance Number) as a search parameter and returns demographic data — both are PHI. It should have the same _demographic read privilege check as searchDemographicByName.

    Proposed fix
         public DemographicTransfer[] searchDemographicsByAttributes(String hin, String firstName, String lastName, Gender gender, Calendar dateOfBirth, String city, String province, String phone, String email, String alias, int startIndex, int itemsToReturn) {
    +        if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", null)) {
    +            throw new SecurityException("missing required security object (_demographic)");
    +        }
             List<Demographic> demographics = demographicManager.searchDemographicsByAttributes(getLoggedInInfo(), hin, firstName, lastName, gender, dateOfBirth, city, province, phone, email, alias, startIndex, itemsToReturn);

    Based on learnings: "PHI (Patient Health Information) protection is legally mandated under HIPAA/PIPEDA compliance."

    🤖 Fix all issues with AI agents
    In
    `@src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java`:
    - Around line 242-244: Guard against id being null before calling id.toString()
    in DemographicService: change the hasPrivilege call(s) to pass a null-safe
    string (e.g., (id == null ? null : id.toString()) or
    Optional.ofNullable(id).map(Object::toString).orElse(null)) instead of
    id.toString(); apply this fix in the three affected methods (the method around
    the shown diff, getBasicDemographicData, and getDemographicSummary) so the
    security check doesn’t throw a NullPointerException when dataId is null.
    
    🧹 Nitpick comments (2)
    src/main/java/io/github/carlos_emr/carlos/webserv/MeasurementWs.java (1)

    56-57: @Autowired used instead of SpringUtils.getBean() pattern.

    The coding guidelines specify using SpringUtils.getBean(SecurityInfoManager.class) for dependency injection. However, the existing field measurementManager on line 53–54 already uses @Autowired, so this is at least consistent within the file. Consider aligning with the guideline in a follow-up.

    As per coding guidelines: "Use Spring dependency injection via SpringUtils.getBean(ClassName.class) pattern for retrieving managed beans from the Spring container. Do not use @Autowired or @Inject annotations in action classes."

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java (1)

    422-434: Good IDOR protection and password preservation logic.

    The check at line 424 correctly prevents non-admin users from modifying other providers' settings. The password placeholder handling at lines 429–434 properly preserves the existing password when the client sends back the redacted value.

    One minor note: the magic string "********" is duplicated (lines 405 and 429). Consider extracting it to a constant.

    Extract redaction constant
    +    private static final String REDACTED_PASSWORD = "********";
    +
         // In getProviderSettings:
    -    settings.seteRxPassword("********");
    +    settings.seteRxPassword(REDACTED_PASSWORD);
    
         // In saveProviderSettings:
    -    if ("********".equals(json.geteRxPassword())) {
    +    if (REDACTED_PASSWORD.equals(json.geteRxPassword())) {

    Comment on lines +242 to +244
    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", id.toString())) {
    throw new SecurityException("missing required security object (_demographic)");
    }
    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 | 🟡 Minor

    NullPointerException if id is nullid.toString() called without null guard.

    If a client sends a request with a null dataId, id.toString() on line 242 will throw an NPE before the security check can reject the request. The same issue exists in getBasicDemographicData (line 440) and getDemographicSummary (line 547). DemographicWs.getDemographic (line 73) handles this correctly with a ternary null check.

    Proposed fix
    -        if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", id.toString())) {
    +        if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", (id != null) ? id.toString() : null)) {
    📝 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 (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", id.toString())) {
    throw new SecurityException("missing required security object (_demographic)");
    }
    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", (id != null) ? id.toString() : null)) {
    throw new SecurityException("missing required security object (_demographic)");
    }
    🤖 Prompt for AI Agents
    In
    `@src/main/java/io/github/carlos_emr/carlos/webserv/rest/DemographicService.java`
    around lines 242 - 244, Guard against id being null before calling id.toString()
    in DemographicService: change the hasPrivilege call(s) to pass a null-safe
    string (e.g., (id == null ? null : id.toString()) or
    Optional.ofNullable(id).map(Object::toString).orElse(null)) instead of
    id.toString(); apply this fix in the three affected methods (the method around
    the shown diff, getBasicDemographicData, and getDemographicSummary) so the
    security check doesn’t throw a NullPointerException when dataId is null.
    

    Copy link
    Contributor

    Copilot AI left a comment

    Choose a reason for hiding this comment

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

    Pull request overview

    This pull request implements comprehensive security hardening across REST and SOAP API endpoints in the CARLOS EMR system. The changes add authorization checks, prevent information disclosure, implement IDOR protection, and establish secure credential handling patterns to protect Protected Health Information (PHI) and system internals.

    Changes:

    • Added SecurityInfoManager privilege checks to 15+ API endpoints across multiple services
    • Implemented password redaction and preservation pattern for provider settings
    • Added IDOR prevention for cross-user resource access requiring admin privileges
    • Removed information disclosure from unauthenticated SystemInfoWs endpoint
    • Introduced ProviderTransfer objects to control exposed fields in provider APIs

    Reviewed changes

    Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

    Show a summary per file
    File Description
    ProviderService.java Added security checks to suggestProviderNo, implemented password redaction in getProviderSettings, added IDOR protection in saveProviderSettings, and introduced ProviderTransfer usage to avoid exposing raw entity fields
    OscarJobService.java Added comprehensive admin privilege checks (read/write) to all job management endpoints with well-documented helper methods
    DemographicService.java Added _demographic privilege checks to all CRUD operations (getAllDemographics, getDemographicData, getBasicDemographicData, getDemographicSummary, createDemographicData, updateDemographicData, deleteDemographicData)
    SystemInfoWs.java Removed information disclosure methods (helloWorld, getServerTime, getServerTimeGmtOffset, getMaxListReturnSize) from unauthenticated endpoint, leaving only isAlive health check
    ScheduleWs.java Added _appointment write privilege checks to addAppointment and updateAppointment methods
    PrescriptionWs.java Added _rx read privilege check to getPrescription method
    MeasurementWs.java Added _measurement write privilege check to addMeasurement method
    DemographicWs.java Added _demographic read privilege checks to getDemographic, getDemographic2, and searchDemographicByName methods
    Comments suppressed due to low confidence (2)

    src/main/java/io/github/carlos_emr/carlos/webserv/rest/ProviderService.java:413

    • Inconsistent IDOR protection between GET and POST endpoints. The getProviderSettings() method only allows users to retrieve their own settings (hardcoded to getLoggedInProviderNo()), but saveProviderSettings() accepts a providerNo path parameter and allows admins to modify other providers' settings. For consistency and flexibility, consider either: (1) allowing getProviderSettings() to also accept an optional providerNo parameter with similar admin privilege checks, or (2) documenting that the current design intentionally restricts GET to self-access only while allowing POST for admin delegation. The asymmetry could confuse API consumers.
        @GET
        @Path("/settings/get")
        @Produces("application/json")
        public AbstractSearchResponse<ProviderSettings> getProviderSettings() {
            AbstractSearchResponse<ProviderSettings> response = new AbstractSearchResponse<ProviderSettings>();
    
            ProviderSettings settings = providerManager.getProviderSettings(getLoggedInInfo(), getLoggedInInfo().getLoggedInProviderNo());
    
            // Redact sensitive credentials - never expose passwords via API
            if (settings.geteRxPassword() != null && !settings.geteRxPassword().isEmpty()) {
                settings.seteRxPassword("********");
            }
    
            List<ProviderSettings> content = new ArrayList<ProviderSettings>();
            content.add(settings);
            response.setContent(content);
            response.setTotal(1);
            return response;
        }
    

    src/main/java/io/github/carlos_emr/carlos/webserv/ScheduleWs.java:106

    • Missing security checks on read operations. While write operations (addAppointment, updateAppointment) now correctly check _appointment privileges, the read operations (getAppointment, getAppointmentsForProvider, getAppointmentsForPatient, getScheduleTemplateCodes, etc.) lack any privilege checks. Appointment data contains PHI and should require "_appointment" read privileges. Consider adding securityInfoManager.hasPrivilege(getLoggedInInfo(), "_appointment", "r", null) checks to all read methods for consistency with the security pattern established in DemographicWs and other services.
        public ScheduleTemplateCodeTransfer[] getScheduleTemplateCodes() {
            List<ScheduleTemplateCode> scheduleTemplateCodes = scheduleManager.getScheduleTemplateCodes();
            return (ScheduleTemplateCodeTransfer.toTransfer(scheduleTemplateCodes));
        }
    
        /**
         * @deprecated you should use the method with the useGMTTime option
         */
        @Deprecated
        public AppointmentTransfer getAppointment(Integer appointmentId) {
            Appointment appointment = scheduleManager.getAppointment(getLoggedInInfo(), appointmentId);
            return (AppointmentTransfer.toTransfer(appointment, false));
        }
    
        /**
         * @deprecated you should use the method with the useGMTTime option
         */
        @Deprecated
        public AppointmentTransfer[] getAppointmentsForProvider(String providerNo, Calendar date) {
            List<Appointment> appointments = scheduleManager.getDayAppointments(getLoggedInInfo(), providerNo, date);
            return (AppointmentTransfer.toTransfers(appointments, false));
        }
    
        /**
         * @deprecated you should use the method with the useGMTTime option
         */
        @Deprecated
        public AppointmentTransfer[] getAppointmentsForPatient(Integer demographicId, int startIndex, int itemsToReturn) {
            List<Appointment> appointments = scheduleManager.getAppointmentsForPatient(getLoggedInInfo(), demographicId, startIndex, itemsToReturn);
            return (AppointmentTransfer.toTransfers(appointments, false));
        }
    
        public AppointmentTransfer getAppointment2(Integer appointmentId, boolean useGMTTime) {
            Appointment appointment = scheduleManager.getAppointment(getLoggedInInfo(), appointmentId);
            return (AppointmentTransfer.toTransfer(appointment, useGMTTime));
        }
    

    @claude
    Copy link

    claude bot commented Feb 11, 2026

    Code review

    No issues found. Checked for bugs and CLAUDE.md compliance.


    Review Summary:

    This PR successfully adds comprehensive authorization checks across 8 files in the web service layer. The implementation follows established security patterns and CLAUDE.md requirements:

    Security Checks: All endpoints now include proper SecurityInfoManager.hasPrivilege() checks with appropriate security objects (_demographic, _rx, _appointment, _measurement, _admin)

    IDOR Prevention: Object-level authorization properly implemented in ProviderService.saveProviderSettings()

    PHI Protection: eRx passwords redacted in API responses, use of ProviderTransfer to limit exposed fields

    Attack Surface Reduction: Removed information disclosure methods from unauthenticated SystemInfoWs endpoint

    Documentation: JavaDoc is comprehensive and meets all CLAUDE.md standards

    The authorization checks use correct access modes ("r" for read, "w" for write) and pass record IDs where applicable for per-record authorization.

    🤖 Generated with Claude Code

    @yingbull
    Copy link
    Collaborator Author

    @claude review

    @github-actions
    Copy link
    Contributor

    github-actions bot commented Feb 12, 2026

    Claude finished @yingbull's task in 2m 6s —— View job


    Code Review Complete

    Tasks:

    • Read changed files and understand security implementations
    • Verify compliance with CLAUDE.md security requirements
    • Check for authorization coverage gaps
    • Analyze security patterns and potential issues
    • Provide comprehensive review feedback

    Review Summary

    This PR successfully adds comprehensive authorization checks across 8 files in the web service layer. The implementation follows established security patterns and CLAUDE.md requirements with one critical issue and several important gaps to address.

    Strengths

    Security Implementation:

    • All new authorization checks use proper SecurityInfoManager.hasPrivilege() calls with appropriate security objects (_demographic, _rx, _appointment, _measurement, _admin)
    • IDOR prevention properly implemented in ProviderService.saveProviderSettings() (lines 422-426)
    • PHI protection: eRx passwords redacted in API responses (line 405), use of ProviderTransfer to limit exposed fields
    • Attack surface reduction: Removed information disclosure methods from unauthenticated SystemInfoWs endpoint
    • Documentation: JavaDoc is comprehensive and meets CLAUDE.md standards

    Code Quality:

    • Helper methods in OscarJobService (requireAdminRead(), requireAdminWrite()) promote consistency and reduce code duplication
    • Authorization checks use correct access modes ("r" for read, "w" for write)
    • Record IDs passed where applicable for per-record authorization

    🚨 Critical Issue

    1. Delete operation uses wrong privilege type (DemographicService.java:628)

    // INCORRECT - uses "w" (write) for delete operation
    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "w", id.toString())) {

    Issue: According to CARLOS security architecture, delete operations require the "d" privilege, not "w". Using "w" allows users with write-only permissions to delete records, which violates the principle of least privilege.

    Fix: Change to use "d" privilege:

    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "d", id.toString())) {

    Fix this →


    ⚠️ Major Issues - Authorization Coverage Gaps

    2. Read operations missing authorization in SOAP services

    Several SOAP services only protect write operations, leaving read endpoints completely unguarded for PHI access:

    MeasurementWs.java - Only addMeasurement() (line 78) is protected:

    • getMeasurement() (line 59) - unprotected PHI access
    • getMeasurementsCreatedAfterDate() (line 64) - unprotected
    • getMeasurementsByProgramProviderDemographicDate() (line 87) - unprotected
    • getMeasurementsByDemographicIdAfter() (line 92) - unprotected

    PrescriptionWs.java - Only getPrescription() (line 60) is protected:

    • getPrescriptionUpdatedAfterDate() (line 74) - unprotected PHI access
    • getPrescriptionsByProgramProviderDemographicDate() (line 80) - unprotected
    • getPrescriptionsByDemographicIdAfter() (line 86) - unprotected

    ScheduleWs.java - Only write operations protected (lines 133, 143):

    • getAppointment() / getAppointment2() - unprotected (lines 80, 103)
    • getAppointmentsForProvider() / getAppointmentsForProvider2() - unprotected (lines 89, 108)
    • getAppointmentsForPatient() / getAppointmentsForPatient2() - unprotected (lines 98, 113)
    • Multiple other read endpoints - all unprotected

    DemographicWs.java - Partial protection:

    • searchDemographicsByAttributes() (line 102) - unprotected (accepts HIN as search parameter - critical PHI)
    • getDemographics() (line 117) - unprotected batch access
    • getActiveDemographicsAfter() / getActiveDemographicsAfter2() - unprotected (lines 127, 148)

    HIPAA/PIPEDA Compliance Risk: These unprotected endpoints expose Protected Health Information without authorization, violating CLAUDE.md's requirement: "PHI (Patient Health Information) protection is legally mandated under HIPAA/PIPEDA compliance. Security is non-negotiable in all code changes."

    Fix MeasurementWs reads →

    Fix PrescriptionWs reads →

    Fix ScheduleWs reads →

    Fix DemographicWs gaps →


    💡 Minor Issues

    3. NullPointerException risk in DemographicService (DemographicService.java:242)

    If id parameter is null, id.toString() will throw NPE before the security check can reject it. Same issue at lines 440 and 547.

    Fix: Use null-safe pattern:

    if (!securityInfoManager.hasPrivilege(getLoggedInInfo(), "_demographic", "r", 
        (id != null) ? id.toString() : null)) {

    This pattern is already correctly used in DemographicWs.java:73 and DemographicWs.java:81.

    Fix NPE risk →

    4. Password redaction implementation (ProviderService.java:403-405)

    Current implementation mutates the ProviderSettings object returned from the manager, which could leak the redacted value into caches or persistence if the object is shared. Consider creating a detached copy for API exposure.

    5. Magic string duplication (ProviderService.java:405, 429)

    The redacted password string "********" is duplicated. Consider extracting to a constant:

    private static final String REDACTED_PASSWORD = "********";

    📋 Recommendations

    1. Immediate action required: Fix the critical delete privilege issue and add missing read authorization checks to all SOAP endpoints
    2. Security testing: Verify that all endpoints now properly reject unauthorized access attempts
    3. Consider: Using a dedicated DTO/transfer object for ProviderSettings responses to avoid mutating managed entities
    4. Follow-up: Audit other SOAP services in the codebase for similar missing authorization patterns

    🎯 Verdict

    Cannot approve in current state due to:

    • Critical issue: Incorrect delete privilege check
    • Major PHI exposure risk: 15+ unprotected read endpoints accessing sensitive patient data

    The security hardening approach is sound, but implementation is incomplete. Once the read authorization gaps are addressed and the delete privilege is corrected, this will be a solid security improvement.


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

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    3 participants