This specification details the implementation plan, configuration footprint, security matrix, and runtime data flows for adding a custom Hyperlink Control Property to the Bahmni Forms 2.0 engine.
- Custom Form Component: While the baseline Bahmni Forms 2.0 engine natively supports controls like
TextBox,NumericBox,ObsControl,Section,Image, andVideo, it lacks a nativeHyperlinkcontrol. This architecture establishes a framework to support hyperlinks configured directly within the form builder infrastructure. - Dual-Path Routing: The control supports two distinct operational classifications: Internal paths (which use client-side hash routing parameters unique to the EMR context) and External targets (which navigate away from the application space). the path type is now auto-classified by URL shape (see Section 3).
- Dashboard Printing Exclusion: Per system-level rules regarding layout transformations, any physical print summary sheet or print action generated from the patient dashboard layout must explicitly omit or strip out hyperlinks entirely to protect document structure. Implemented via a CSS
@media printrule that hides alla[data-bahmni-hyperlink]elements. - Context Intercept Mechanisms: External links open directly in a new tab; isolation is enforced via HTML security attributes only (see Section 4).
To ensure clear processing and validation rules, the control enforces distinct structural differences between the two route types. Path type is determined automatically at runtime — no explicit toggle is required from the form designer.
- Structure: Relative paths only; they must not contain the protocol or hostname.
- Auto-Detection Rule: Any URL beginning with
/is classified as internal. - Examples:
/bahmni/app/{uuid}/patient/doc.pdf
- Token Support:
{patientUuid}is the only supported token; all other{...}patterns are rejected. - Routing Behavior: These paths leverage client-side relative routing within the local EMR deployment domain, maintaining the clinician's active authenticated session state.
- Structure: Complete absolute URLs including host and path information.
- Auto-Detection Rule: Any URL beginning with
https://is classified as external. - Examples:
https://abc.com/xyzhttps://abc.com/doc.pdf
- Token Support: All
{...}tokens are forbidden in external URLs to prevent variable injection leakage. - Routing Behavior: These targets open in an entirely separate browser tab using
target="_blank".
To preserve the modular boundary between form structure and local server orchestration, validation configurations are isolated from standalone form definitions.
- File Directory: The validation array resides inside the module configuration profile deployed on the web server at:
/var/www/bahmni_config/openmrs/apps/clinical/app.json - Schema Integration: The custom parameter array integrates directly inside the core application configuration block. This mirrors the management architecture used for other system attributes like
quickPrints,diagnosisStatus,hideFormName, andobsIgnoreList:
"config": {
"hyperlinkAllowedDomains": [
"*.who.int",
"*.health.gov"
]
}+-------------------------------------------------------------------------+
| [Top Action Bar: Save / Preview Mode Toggle] |
+------------------------------------+------------------------------------+
| | [Right Property Inspector Panel] |
| | |
| [Left Controls Pane] | Hyperlink Control Properties |
| -> Select ObsControl | - Input URL Field: [ ] |
| | - Label Field: [ ] |
| | (disabled until URL is entered) |
| [Center Form Details Canvas] +------------------------------------+
| -> Drag-and-Drop Drop Zone | [Split Layout Real-Time Feedback] |
| | Preview on Right (Editable View) |
| | -> Live Context Component Update |
+------------------------------------+------------------------------------+
Implementation Note: The system determines path type automatically from the URL value entered. The designer sees two fields only:
hyperlinkUrlandhyperlinkLabel.
- Property Configuration Inspector: Selecting the ObsControl with a hyperlink URL activates two properties in the right-hand panel:
hyperlinkUrl(text input, validated on blur) andhyperlinkLabel(text input, disabled until a valid URL is present). - Split-Layout Real-Time Feedback: As the designer enters a URL, a live non-navigable anchor preview updates in the properties inspector.
- Preview Mode Activation: Toggling the top-level Preview tool isolates the workspace canvas, hiding all administrative sidebars to simulate the end-user clinical workspace before changes are committed.
When a hyperlink path is evaluated at runtime by hyperlinkValidator.js, the URL is passed through an inspection pipeline:
| Validation Target | Mechanism Applied | Intended Action |
|---|---|---|
| Protocol Scheme | Strict String Matching | Exclusively passes https:// for external and / for internal; rejects and blocks javascript:, data:, file:, and vbscript:. |
| Path Type Classification | URL Shape Detection | Auto-classifies: / prefix → internal, https:// prefix → external, anything else → invalid. No explicit toggle. |
| Domain Legitimacy | Array Iteration Lookup | Parses the target domain and evaluates it against the hyperlinkAllowedDomains list from app.json. Supports wildcard patterns (*.who.int). Default-deny: empty list blocks all external URLs. |
| Context Isolation | HTML Property Injection | External links: target="_blank", rel="noopener noreferrer", referrerPolicy="no-referrer". Internal links: target="_blank", rel="noopener". |
| Token Containment | Pattern Matching | External URLs: all {...} tokens rejected. Internal URLs: only {patientUuid} permitted; all other tokens rejected. |
| Error Rendering | Inline UI Feedback | Validation failures render an inline <span class="hyperlink-error"> message; no broken anchor is rendered. |
The original spec called for a confirmation dialog before external navigation. This was deferred. External links open directly in a new tab. The security boundary is maintained entirely through HTML isolation attributes rather than a user-facing intercept.
- Automated Environment Monitoring: The broader ecosystem handles host-level compliance and software library status via server-side scanning layers including OpenVAS, Lynis, OpenSCAP, and Vuls.
- Vulnerability Bounds: Core platform updates manage foundational system logic vulnerabilities (such as Stored XSS mitigations and OpenMRS authentication state). However, the system contains no native mechanisms to dynamically track the reputation of third-party domains or audit external file contents during live browser sessions.
| Status | Threat | Mitigation |
|---|---|---|
| [COVERED] | Stored XSS via Script Injection | Eliminated by strict protocol checks blocking unsafe URI schemes. |
| [COVERED] | Reverse Tabnabbing Context Hijacking | Blocked by injecting rel="noopener noreferrer" on all external links. |
| [COVERED] | Clinical Information Leaks via Referrer | referrerPolicy="no-referrer" prevents metadata exposure to external destinations. |
| [COVERED] | Variable/Token Injection in External URLs | All {...} tokens rejected in external URLs at validation time. |
| [DEFERRED] | Clinician Navigating Away Without Warning | Confirmation popup not implemented; clinician navigates directly to new tab. |
| [REMAINING] | Domain Takeovers & Expiration Compromise | An allowed domain could expire or change ownership in the future. |
| [REMAINING] | Drive-By Third-Party Malicious Payloads | The application layer cannot intercept malicious binaries or PDFs hosted on destination targets. |
Form properties are captured at design-time via ObsControlDesigner.jsx in /src/components/designer. At runtime, the structural JSON metadata is passed into the root form Container component. The hyperlinkAllowedDomains config is read from app.json and propagated through Container → Section → ObsControl via props. Path type is resolved automatically inside hyperlinkValidator.js.
[ Form Builder App ]
│
▼ (Designer enters a relative URL starting with "/"; no type toggle needed)
[ Form Metadata JSON ]
│
▼ (Form loads inside core application Container component)
[ Runtime Rendering Engine ]
│
▼ (hyperlinkValidator.js detects internal path by "/" prefix)
[ Token Resolver ] ──► (Substitutes {patientUuid} if present via Util.resolveUrlTokens())
│
▼ (Generates relative anchor: target="_blank", rel="noopener")
[ Client-Side Router Navigation ]
│
▼ (Standard OpenMRS role permissions & session auth enforced natively)
[ Targeted Internal EMR Application View ]
[ Form Builder App ]
│
▼ (Designer enters an absolute URL starting with "https://"; no type toggle needed)
[ Form Metadata JSON ]
│
▼ (Form loads inside core application Container component)
[ Runtime Rendering Engine ]
│
▼ (hyperlinkValidator.js detects external by "https://" prefix)
▼ (Rejects any {token} patterns in URL)
[ Domain Allowlist Check ] ◄─────► Checks target domain against hyperlinkAllowedDomains array
│
├───[ Domain Not Listed ]──► Render inline error message; no anchor rendered
│
└───[ Domain Approved ]
│
▼ (Appends: target="_blank", rel="noopener noreferrer", referrerPolicy="no-referrer")
[ Anchor Rendered — No Confirmation Dialog ]
│
▼ (Clinician clicks link; opens directly in new tab)
[ External Destination Website / Document ]
If a form builder bypasses or misconfigures a link validation by mistake, the application handles the failure modes through the following structural behaviors.
- Validator behavior: Detects
https://prefix, runs domain allowlist check. If domain not inhyperlinkAllowedDomains, renders inline error and no anchor. - User Experience Result: Designer or clinician sees a
hyperlink-errormessage in place of the link.
- Validator behavior: Detects
/prefix, skips domain check, allows only{patientUuid}token substitution. - User Experience Result: Link renders and navigates within the EMR context. No authentication tokens are leaked externally.
- Validator behavior: Neither
/norhttps://prefix — classified asinvalid. Inline error rendered. - User Experience Result: No broken anchor; error message guides the designer to correct the input.
The diagram below illustrates the complete lifecycle — from form builder configuration through runtime rendering and final navigation — for both internal and external hyperlink paths.
[ IMPLEMENTER INTERFACE (Form Builder) ]
│
├─► Add hyperlinkUrl property to ObsControl
├─► Optionally set hyperlinkLabel for display text
└─► System auto-classifies URL type on save (no toggle)
│
▼
[ FORM METADATA (JSON Schema Registry) ]
│
▼ (Loads into Clinical Workspace during Consultation)
[ BAHMNI APP FORM RENDERING ENGINE ]
│ (hyperlinkAllowedDomains propagated from app.json → Container → ObsControl)
│
├─────────── hyperlinkValidator.js: Detect URL Shape ───────────┤
│ │
▼ [ INTERNAL: starts with "/" ] ▼ [ EXTERNAL: starts with "https://" ]
│ │
Substitute {patientUuid} token Reject all {token} patterns
(via Util.resolveUrlTokens) │
│ Read hyperlinkAllowedDomains
▼ │
[ Render Anchor ] Is Domain Approved?
target="_blank" / \
rel="noopener" [ MATCH ] [ NO MATCH ]
│ / \
│ Render Anchor: Render inline error
│ target="_blank" (no anchor rendered)
│ rel="noopener noreferrer"
│ referrerPolicy="no-referrer"
│ │
▼ Clinician Clicks ──► Opens new tab directly
Navigate Within EMR App Context (no confirmation dialog)
| Repository | File | Purpose |
|---|---|---|
form-controls |
src/helpers/hyperlinkValidator.js |
Core validation: protocol check, type detection, domain allowlist, token rules |
form-controls |
src/components/ObsControl.jsx |
Runtime rendering of hyperlink anchor with security attributes |
form-controls |
src/components/designer/ObsControlDesigner.jsx |
Designer preview; exposes hyperlinkUrl and hyperlinkLabel properties |
form-controls |
styles/bahmniAppsFormBuilder/_form.scss |
Styling + @media print exclusion rule |
form-controls |
test/components/ObsControlHyperlink.spec.js |
15 component-level tests |
form-controls |
test/helpers/hyperlinkValidator.spec.js |
23 validator unit tests |
implementer-interface |
src/form-builder/constants.js |
Property name constants |
implementer-interface |
src/form-builder/components/Property.jsx |
onBlur validation trigger for hyperlinkUrl |
implementer-interface |
src/form-builder/components/PropertyEditor.jsx |
Disables hyperlinkLabel when URL is empty |
openmrs-module-bahmniapps |
ui/app/common/concept-set/directives/formControls.js |
Reads hyperlinkAllowedDomains from appService config |
cure-bahmni-emr |
openmrs/apps/clinical/app.json |
Defines hyperlinkAllowedDomains array |