📋 Product Owner view

Feature inventory, spec alignment, behavioural gaps  ·  Product Owner, Product Manager

Generated 2 months ago
⚡ Agent trace  ·  13 steps  ·  $0.0482  ·  click to expand
search_claims(query='feature capability user facing', category='any')search_claims(query='API endpoint route handler', category='any')get_dependencies(file_path='.')search_claims(query='patient resource FHIR', category='any')search_claims(query='pagination search bundle', category='any')search_claims(query='client FHIRClient connect', category='any')read_source_file(file_path='fhirclient/__init__.py', start_line=1)read_source_file(file_path='fhirclient/client.py', start_line=1)read_source_file(file_path='fhirclient/server.py', start_line=1)read_source_file(file_path='fhirclient/client.py', start_line=62)read_source_file(file_path='fhirclient/server.py', start_line=62)read_source_file(file_path='fhirclient/auth.py', start_line=1)read_source_file(file_path='fhirclient/auth.py', start_line=62)read_source_file(file_path='fhirclient/client.py', start_line=123)read_source_file(file_path='fhirclient/client.py', start_line=184)read_source_file(file_path='fhirclient/auth.py', start_line=123)read_source_file(file_path='fhirclient/_utils.py', start_line=1)read_source_file(file_path='fhirclient/_utils.py', start_line=62)read_source_file(file_path='fhirclient/auth.py', start_line=184)read_source_file(file_path='demos/flask/flask_app.py', start_line=1)read_source_file(file_path='demos/flask/flask_app.py', start_line=62)read_source_file(file_path='fhirclient/auth.py', start_line=245)read_source_file(file_path='fhirclient/server.py', start_line=120)read_source_file(file_path='fhirclient/server.py', start_line=180)read_source_file(file_path='fhirclient/auth.py', start_line=302)read_source_file(file_path='fhirclient/auth.py', start_line=362)read_source_file(file_path='fhirclient/server.py', start_line=240)read_source_file(file_path='fhirclient/server.py', start_line=300)read_source_file(file_path='fhirclient/auth.py', start_line=422)read_source_file(file_path='tests/client_test.py', start_line=1)read_source_file(file_path='tests/server_test.py', start_line=1)

Product View

Feature Inventory

Feature Status Confidence Notes
SMART on FHIR OAuth2 Authorization Implemented High Full OAuth2 flow with authorization code grant, PKCE support, client credentials grant, and refresh tokens. Supports both public and confidential clients.
Patient Context Management Implemented High Patient ID tracking, automatic Patient resource fetching, reauthorization on 401, patient ID clearing on 404.
Server Capability Discovery Implemented High Fetches CapabilityStatement from metadata endpoint, extracts OAuth2 URIs from SMART extensions.
FHIR Resource CRUD Operations Implemented High GET, PUT, POST, DELETE via request_json, put_json, post_json, delete_json methods.
Pagination / Bundle Iteration Implemented High iter_pages() generator, _fetch_next_page(), _get_next_link() with URL sanitization.
State Persistence / Session Management Implemented High Full state serialization via state property and from_state(); pluggable save_func callback.
Flask Demo App (Patient Prescriptions) Implemented High Working web app showing patient name, prescriptions, OAuth2 login/logout, medication name resolution.
Reference Resolution (Contained & Relative) Implemented High Tests confirm contained resource detection, relative reference resolution, type mismatch handling, and endpoint mismatch handling.
FHIR Date/Time Parsing Implemented High Tests exist for FHIRDate, FHIRDateTime, FHIRInstant, FHIRTime classes.
Client Credentials Grant (Backend Auth) Implemented High authorize() method on FHIROAuth2Auth uses client_credentials grant type with optional JWT assertion.
Reauthorization via Refresh Tokens Implemented High reauthorize() method uses refresh_token grant type when refresh token is available.
Logout / Patient Reset Implemented High reset_patient() clears launch context, patient ID, and cached patient object.
Human Name Formatting Implemented High human_name() method formats HumanName instances into display strings.
OAuth Error Handling Implemented High extract_oauth_error() maps OAuth error codes to human-readable messages.
HTTP Error Handling (401/403/404) Implemented High Custom exceptions FHIRUnauthorizedException, FHIRPermissionDeniedException, FHIRNotFoundException.
Open / No-Auth Server Support Implemented High FHIRAuth base class with auth_type = "none" allows connecting to servers without authentication.

What the Code Does That the Spec Doesn't Mention

  1. PKCE (Proof Key for Code Exchange) is always included — The OAuth2 authorization flow always generates a code_verifier and code_challenge (S256) even when the server doesn't require it. This is an undocumented security hardening decision. (fhirclient/auth.py:241-248)

  2. JWT Bearer Assertion for backend auth — The client supports client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer for client credentials grants, allowing backend systems to authenticate with a JWT token instead of a client secret. This is not mentioned in the package docstring or settings documentation. (fhirclient/auth.py:362-366)

  3. Launch token is automatically appended to scope — When a launch_token is present, the client automatically prepends launch to the requested scope. When no patient is selected and wants_patient is True, it prepends launch/patient. This is a behavioral decision that could surprise developers who set their own scope. (fhirclient/client.py:92-101)

  4. The patient property silently reauthorizes on 401 — If fetching the patient resource returns a 401, the client automatically attempts reauthorization (using refresh token) and retries the read. If reauthorization fails, it returns None silently. (fhirclient/client.py:174-179)

  5. The patient property clears patient_id on 404 — If the patient resource is not found (404), the client silently clears the stored patient_id and returns None. This is a destructive operation that could be surprising. (fhirclient/client.py:180-182)

  6. State serialization includes sensitive tokens — The state dictionary includes access_token, refresh_token, code_verifier, and app_secret in plaintext. The demo app stores this in Flask's session cookie, which is only signed, not encrypted by default. (fhirclient/auth.py:400-419, demos/flask/flask_app.py:19-20)

  7. The demo app has a hardcoded app_id of 'my_web_app' — The Flask demo defaults to app_id: 'my_web_app' and requires the user to manually edit the file to set api_base. There is no configuration UI or environment variable support. (demos/flask/flask_app.py:11-15)

  8. Pagination URL sanitization validates scheme and hostname — The _sanitize_next_link function validates that the next page URL uses http or https and has a non-empty hostname, but it does not validate that the hostname matches the original server. This is a basic safety check, not a security boundary. (fhirclient/_utils.py:48-73)

  9. The human_name method adds a comma before suffixes — When formatting a human name with suffixes, a comma is inserted before the suffix (e.g., "John Doe, Jr."). This is a specific formatting choice not documented in any spec. (fhirclient/client.py:198-200)

  10. The demo app resolves medication references by splitting the reference string_get_medication_by_ref does ref.split("/")[1] to extract the medication ID, rather than using the FHIR reference resolution infrastructure. This is fragile and assumes a specific reference format. (demos/flask/flask_app.py:44-46)

Spec Gaps

  1. No documented support for app_secret in the settings — The __init__ docstring lists app_id, app_secret, api_base, redirect_uri, patient_id, scope, and launch_token as supported settings, but the actual code also supports jwt_token. This is a documentation gap. (fhirclient/client.py:20-28 vs fhirclient/client.py:82)

  2. No documented behavior for what happens when save_func is None — The code raises an exception if save_func is None, but the docstring doesn't mention this requirement. (fhirclient/client.py:59-62)

  3. The reauthorize method's return value is inconsistent — The docstring says it returns a bool, but the underlying server.reauthorize() returns the launch context dict or None. The client method converts this to a bool check (self.launch_context is not None), which works but is confusing. (fhirclient/client.py:145-152)

  4. No documented rate limiting or retry behavior — The code has no retry logic for transient failures. If a request fails (other than 401 which triggers reauthorization), the exception propagates directly to the caller.

  5. The wants_patient flag is undocumented — This flag controls whether the client requests launch/patient scope when no patient is selected. It's a significant behavioral toggle with no documentation. (fhirclient/client.py:47-49)

  6. No documented support for launch_context — The launch_context dictionary is stored and serialized but its structure and possible keys (e.g., patient, encounter, id_token) are not documented anywhere.

Open Questions for Engineering

  1. Token storage security — The state serialization stores access tokens, refresh tokens, and client secrets in plaintext. The Flask demo stores this in a session cookie. Is this acceptable for production use, or should we add encryption/warning documentation?

  2. Patient ID clearing on 404 — When patient property encounters a 404, it silently clears patient_id. This means a transient data issue could permanently lose the patient context. Should this be a configurable behavior or at least logged more prominently?

  3. Scope augmentation behavior — The desired_scope property automatically adds launch or launch/patient to the scope. Is this always desired? Could it conflict with a developer who explicitly sets a scope that already includes these values?

  4. Pagination URL validation scope — The _sanitize_next_link function validates scheme and hostname but doesn't verify the hostname matches the original server. Is this a deliberate design choice (to allow cross-server pagination) or a security gap?

  5. Demo app configuration — The Flask demo requires editing source code to set api_base. Should we add environment variable support or a configuration page before considering this demo-ready for customer evaluation?

  6. Refresh token rotation — The code replaces the refresh token when a new one is issued, but there's no documented behavior for what happens if a refresh token is revoked or expires. Should we add explicit error handling for this case?

  7. Client credentials grant vs. authorization code grant — The authorize() method on FHIROAuth2Auth uses client credentials, while the web flow uses authorization code via handle_callback(). Is the client credentials path intended for backend/service scenarios only? The naming is ambiguous.

  8. The _get_medication_by_ref string splitting — The demo app uses ref.split("/")[1] to extract medication IDs. This will break if references use full URLs (absolute references). Should we fix this to use the proper FHIR reference resolution?