ABDM Gateway settings: add admin UI toggle for SSL certificate verification

Adds a 'Verify SSL Certificate' switch on Admin -> ABDM Gateway page,
persisted as hospital_setting.ABDM_BRIDGE_SSL_VERIFY (default ON/secure).
Wired into M3HiuGatewayClient, EAtriaBridgeConnector, and the settings
page's own Test Connection check. Per-machine ABDM_BRIDGE_SSL_VERIFY env
var still overrides this DB setting for deployments needing a quick
local-only workaround. Verification stays on by default everywhere;
disabling it is an explicit, visible opt-in with an on-page warning.
This commit is contained in:
DevSoftTech Server 2026-07-28 03:47:06 +05:30
parent b26920c6f4
commit a5219bca2d
5 changed files with 101 additions and 16 deletions

View File

@ -20,6 +20,8 @@ class AbdmGatewaySettings extends BaseController
$hmsName = $this->readSettingValue('ABDM_HMS_NAME'); $hmsName = $this->readSettingValue('ABDM_HMS_NAME');
$gwUrl = $this->readSettingValue('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'; $gwUrl = $this->readSettingValue('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api';
$bridgeHospitalId = $this->readSettingValue('ABDM_BRIDGE_HOSPITAL_ID'); $bridgeHospitalId = $this->readSettingValue('ABDM_BRIDGE_HOSPITAL_ID');
$sslVerifyRaw = strtolower(trim($this->readSettingValue('ABDM_BRIDGE_SSL_VERIFY')));
$sslVerify = ! in_array($sslVerifyRaw, ['0', 'false', 'no', 'off'], true);
return view('Setting/Admin/abdm_gateway_settings', [ return view('Setting/Admin/abdm_gateway_settings', [
'gateway_url' => $gwUrl, 'gateway_url' => $gwUrl,
@ -30,6 +32,7 @@ class AbdmGatewaySettings extends BaseController
'token_exists' => $token !== '', 'token_exists' => $token !== '',
'connector' => $this->readSettingValue('ABDM_CONNECTOR') ?: 'eatria_bridge', 'connector' => $this->readSettingValue('ABDM_CONNECTOR') ?: 'eatria_bridge',
'abdm_sync_provider' => $this->readSettingValue('ABDM_SYNC_PROVIDER') ?: 'eatria', 'abdm_sync_provider' => $this->readSettingValue('ABDM_SYNC_PROVIDER') ?: 'eatria',
'ssl_verify' => $sslVerify,
]); ]);
} }
@ -51,6 +54,8 @@ class AbdmGatewaySettings extends BaseController
$token = $this->sanitizeBearerToken((string) $this->request->getPost('api_token')); $token = $this->sanitizeBearerToken((string) $this->request->getPost('api_token'));
$hfrId = trim((string) $this->request->getPost('hfr_id')); $hfrId = trim((string) $this->request->getPost('hfr_id'));
$hmsName = trim((string) $this->request->getPost('hms_name')); $hmsName = trim((string) $this->request->getPost('hms_name'));
$sslVerifyPosted = $this->request->getPost('ssl_verify');
$sslVerify = $sslVerifyPosted === null ? true : (bool) (int) $sslVerifyPosted;
// Validate gateway URL format // Validate gateway URL format
if ($gwUrl !== '' && ! filter_var($gwUrl, FILTER_VALIDATE_URL)) { if ($gwUrl !== '' && ! filter_var($gwUrl, FILTER_VALIDATE_URL)) {
@ -87,6 +92,7 @@ class AbdmGatewaySettings extends BaseController
// Always set connector to eatria_bridge when saving from this page // Always set connector to eatria_bridge when saving from this page
$this->upsertSettingValue('ABDM_CONNECTOR', 'eatria_bridge'); $this->upsertSettingValue('ABDM_CONNECTOR', 'eatria_bridge');
$this->upsertSettingValue('ABDM_SYNC_PROVIDER', 'eatria'); $this->upsertSettingValue('ABDM_SYNC_PROVIDER', 'eatria');
$this->upsertSettingValue('ABDM_BRIDGE_SSL_VERIFY', $sslVerify ? '1' : '0');
if ($saved === 0) { if ($saved === 0) {
return $this->response->setJSON([ return $this->response->setJSON([
@ -104,6 +110,7 @@ class AbdmGatewaySettings extends BaseController
'error_text' => 'ABDM Gateway settings saved.', 'error_text' => 'ABDM Gateway settings saved.',
'token_exists' => $storedToken !== '', 'token_exists' => $storedToken !== '',
'token_masked' => $this->maskKey($storedToken), 'token_masked' => $this->maskKey($storedToken),
'ssl_verify' => $sslVerify,
'csrfName' => csrf_token(), 'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(), 'csrfHash' => csrf_hash(),
]); ]);
@ -139,6 +146,16 @@ class AbdmGatewaySettings extends BaseController
$gwUrl = rtrim($gwUrl, '/'); $gwUrl = rtrim($gwUrl, '/');
// ssl_verify: prefer explicitly posted value (from the unsaved form state),
// else fall back to the stored setting. Defaults to secure (verify ON).
$sslVerifyPosted = $this->request->getPost('ssl_verify');
if ($sslVerifyPosted !== null) {
$sslVerify = (bool) (int) $sslVerifyPosted;
} else {
$sslVerifyRaw = strtolower(trim($this->readSettingValue('ABDM_BRIDGE_SSL_VERIFY')));
$sslVerify = ! in_array($sslVerifyRaw, ['0', 'false', 'no', 'off'], true);
}
// ── Step 1: Health check — include Bearer if available (gateway may require auth even on /health) ── // ── Step 1: Health check — include Bearer if available (gateway may require auth even on /health) ──
$healthUrl = $gwUrl . '/v3/health' . ($hfrId !== '' ? '?hfr_id=' . urlencode($hfrId) : ''); $healthUrl = $gwUrl . '/v3/health' . ($hfrId !== '' ? '?hfr_id=' . urlencode($hfrId) : '');
$healthHeaders = ['Accept: application/json']; $healthHeaders = ['Accept: application/json'];
@ -150,7 +167,8 @@ class AbdmGatewaySettings extends BaseController
CURLOPT_URL => $healthUrl, CURLOPT_URL => $healthUrl,
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10, CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYPEER => $sslVerify,
CURLOPT_SSL_VERIFYHOST => $sslVerify ? 2 : 0,
CURLOPT_HTTPHEADER => $healthHeaders, CURLOPT_HTTPHEADER => $healthHeaders,
]); ]);
$rawHealth = (string) curl_exec($ch); $rawHealth = (string) curl_exec($ch);
@ -222,7 +240,8 @@ class AbdmGatewaySettings extends BaseController
CURLOPT_URL => $statusUrl, CURLOPT_URL => $statusUrl,
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10, CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYPEER => $sslVerify,
CURLOPT_SSL_VERIFYHOST => $sslVerify ? 2 : 0,
CURLOPT_HTTPHEADER => [ CURLOPT_HTTPHEADER => [
'Accept: application/json', 'Accept: application/json',
'Authorization: Bearer ' . $token, 'Authorization: Bearer ' . $token,

View File

@ -23,6 +23,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
private string $hfrId; private string $hfrId;
private string $bridgeHospitalId; private string $bridgeHospitalId;
private int $timeoutSec; private int $timeoutSec;
private bool $sslVerify = true;
/** @var array<int, string> */ /** @var array<int, string> */
private array $tokenCandidates = []; private array $tokenCandidates = [];
/** @var array<string, string> */ /** @var array<string, string> */
@ -76,6 +77,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
'ABDM_HOSPITAL_HFR_ID', 'ABDM_HOSPITAL_HFR_ID',
'ABDM_BRIDGE_HOSPITAL_ID', 'ABDM_BRIDGE_HOSPITAL_ID',
'EATRIA_BRIDGE_HOSPITAL_ID', 'EATRIA_BRIDGE_HOSPITAL_ID',
'ABDM_BRIDGE_SSL_VERIFY',
]); ]);
// Ensure deterministic override when duplicate setting rows exist. // Ensure deterministic override when duplicate setting rows exist.
@ -147,6 +149,15 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
if ($dbBridgeHospitalId !== '') { if ($dbBridgeHospitalId !== '') {
$this->bridgeHospitalId = $dbBridgeHospitalId; $this->bridgeHospitalId = $dbBridgeHospitalId;
} }
// SSL verification stays ON by default (secure). Admin → ABDM Gateway
// page can opt out per-deployment for servers with a stale local CA
// bundle (cURL error 60); an env override on that specific machine
// takes precedence over the DB setting.
$dbSslVerifyRaw = strtolower(trim((string) ($dbSettings['ABDM_BRIDGE_SSL_VERIFY'] ?? '')));
if ($dbSslVerifyRaw !== '') {
$this->sslVerify = ! in_array($dbSslVerifyRaw, ['0', 'false', 'no', 'off'], true);
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
log_message('warning', '[EAtriaBridge] refreshRuntimeSettingsFromDb failed: ' . $e->getMessage()); log_message('warning', '[EAtriaBridge] refreshRuntimeSettingsFromDb failed: ' . $e->getMessage());
} }
@ -308,10 +319,13 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
// SSL verification stays ON by default. Some local WAMP/XAMPP // SSL verification stays ON by default. Some local WAMP/XAMPP
// installs ship a stale bundled cacert.pem, causing cURL error 60 // installs ship a stale bundled cacert.pem, causing cURL error 60
// on outbound HTTPS calls. Fix that by updating curl.cainfo in // on outbound HTTPS calls. Fix that by updating curl.cainfo in
// php.ini on the affected machine; only as a last resort can a // php.ini on the affected machine, or opt out for this specific
// specific deployment opt out via ABDM_BRIDGE_SSL_VERIFY=false. // deployment via Admin → ABDM Gateway (or ABDM_BRIDGE_SSL_VERIFY=false
$sslVerifyRaw = strtolower(trim((string) (getenv('ABDM_BRIDGE_SSL_VERIFY') ?: ''))); // in that machine's .env, which takes precedence).
$sslVerify = ! in_array($sslVerifyRaw, ['0', 'false', 'no', 'off'], true); $sslVerifyEnvRaw = strtolower(trim((string) (getenv('ABDM_BRIDGE_SSL_VERIFY') ?: '')));
$sslVerify = $sslVerifyEnvRaw !== ''
? ! in_array($sslVerifyEnvRaw, ['0', 'false', 'no', 'off'], true)
: $this->sslVerify;
$ch = curl_init(); $ch = curl_init();
$curlOptions = [ $curlOptions = [

View File

@ -19,16 +19,26 @@ class M3HiuGatewayClient
* WAMP/XAMPP/Laragon installs ship a stale bundled cacert.pem which makes * WAMP/XAMPP/Laragon installs ship a stale bundled cacert.pem which makes
* outbound HTTPS calls fail with cURL error 60 (SSL certificate problem). * outbound HTTPS calls fail with cURL error 60 (SSL certificate problem).
* Rather than disabling verification for everyone, allow an explicit, * Rather than disabling verification for everyone, allow an explicit,
* per-deployment opt-out via env for that specific broken environment: * per-deployment opt-out configured in Admin → ABDM Gateway
* ABDM_BRIDGE_SSL_VERIFY=false * (hospital_setting.ABDM_BRIDGE_SSL_VERIFY), or via env for that specific
* The correct long-term fix on the affected machine is to update PHP's * machine (ABDM_BRIDGE_SSL_VERIFY=false, takes precedence over the DB
* CA bundle (curl.cainfo in php.ini), not to disable verification. * setting). The correct long-term fix on the affected machine is to
* update PHP's CA bundle (curl.cainfo in php.ini), not to disable
* verification.
*/ */
private function sslVerifyEnabled(): bool private function sslVerifyEnabled(string $dbSetting = ''): bool
{ {
$raw = strtolower(trim((string) (getenv('ABDM_BRIDGE_SSL_VERIFY') ?: ''))); $envRaw = strtolower(trim((string) (getenv('ABDM_BRIDGE_SSL_VERIFY') ?: '')));
if ($envRaw !== '') {
return ! in_array($envRaw, ['0', 'false', 'no', 'off'], true);
}
return ! in_array($raw, ['0', 'false', 'no', 'off'], true); $dbRaw = strtolower(trim($dbSetting));
if ($dbRaw !== '') {
return ! in_array($dbRaw, ['0', 'false', 'no', 'off'], true);
}
return true;
} }
private function sanitizeBearerToken(string $token): string private function sanitizeBearerToken(string $token): string
@ -105,7 +115,7 @@ class M3HiuGatewayClient
$headers[] = 'X-Hospital-Id: ' . $hospitalId; $headers[] = 'X-Hospital-Id: ' . $hospitalId;
} }
$sslVerify = $this->sslVerifyEnabled(); $sslVerify = $this->sslVerifyEnabled((string) ($settings['ssl_verify'] ?? ''));
$ch = curl_init(); $ch = curl_init();
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_URL => $url, CURLOPT_URL => $url,
@ -285,7 +295,7 @@ class M3HiuGatewayClient
$url .= '?' . http_build_query($cleanQuery); $url .= '?' . http_build_query($cleanQuery);
} }
$sslVerify = $this->sslVerifyEnabled(); $sslVerify = $this->sslVerifyEnabled((string) ($settings['ssl_verify'] ?? ''));
$ch = curl_init(); $ch = curl_init();
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_URL => $url, CURLOPT_URL => $url,
@ -408,6 +418,7 @@ class M3HiuGatewayClient
'ABDM_BRIDGE_HOSPITAL_ID', 'ABDM_BRIDGE_HOSPITAL_ID',
'EATRIA_BRIDGE_HOSPITAL_ID', 'EATRIA_BRIDGE_HOSPITAL_ID',
'EATRIA_BRIDGE_URL', 'EATRIA_BRIDGE_URL',
'ABDM_BRIDGE_SSL_VERIFY',
]); ]);
if ($orderCol !== null) { if ($orderCol !== null) {
$rowsBuilder->orderBy($orderCol, 'DESC'); $rowsBuilder->orderBy($orderCol, 'DESC');
@ -452,6 +463,7 @@ class M3HiuGatewayClient
'hfr_id' => $hfrId, 'hfr_id' => $hfrId,
'hospital_id' => $hospitalId, 'hospital_id' => $hospitalId,
'base_url' => $baseUrl, 'base_url' => $baseUrl,
'ssl_verify' => (string) ($kv['ABDM_BRIDGE_SSL_VERIFY'] ?? ''),
]; ];
} }

View File

@ -51,6 +51,27 @@
</div> </div>
</div> </div>
<!-- SSL Verification -->
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="abdm_ssl_verify"
<?= (! isset($ssl_verify) || $ssl_verify) ? 'checked' : '' ?>>
<label class="form-check-label fw-semibold" for="abdm_ssl_verify">Verify SSL Certificate</label>
</div>
<div class="form-text">
Keep this <strong>ON</strong> (default). Only turn it <strong>OFF</strong> if this server's local PHP
has an outdated CA certificate bundle causing "SSL certificate problem" (cURL error 60) errors when
calling the gateway — this is a local server issue, not a gateway issue. Disabling verification
removes protection against man-in-the-middle attacks, so use it only as a temporary workaround on a
trusted network while the server's CA bundle is updated.
</div>
<div class="alert alert-warning py-1 px-2 mt-1 d-none" id="abdm_ssl_verify_warning" style="font-size:12px">
<i class="bi bi-exclamation-triangle me-1"></i>SSL verification is OFF. Health data will still be
encrypted in transit, but the gateway's certificate identity won't be checked. Turn this back on once
the server's CA bundle is fixed.
</div>
</div>
<!-- API Key --> <!-- API Key -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-semibold">API Key (Bearer Token)</label> <label class="form-label fw-semibold">API Key (Bearer Token)</label>
@ -90,6 +111,13 @@
<?= esc($gateway_url ?? 'https://abdm-bridge.e-atria.in/api') ?> <?= esc($gateway_url ?? 'https://abdm-bridge.e-atria.in/api') ?>
</a> </a>
</span> </span>
<span><strong>SSL Verify:</strong>
<?php if (! isset($ssl_verify) || $ssl_verify) : ?>
<span class="badge bg-success">ON</span>
<?php else : ?>
<span class="badge bg-warning text-dark">OFF</span>
<?php endif; ?>
</span>
</div> </div>
</div> </div>
@ -180,10 +208,17 @@
gateway_url: ($('#abdm_gateway_url').val() || '').trim(), gateway_url: ($('#abdm_gateway_url').val() || '').trim(),
api_token: ($('#abdm_api_token').val() || '').trim(), api_token: ($('#abdm_api_token').val() || '').trim(),
hfr_id: ($('#abdm_hfr_id').val() || '').trim(), hfr_id: ($('#abdm_hfr_id').val() || '').trim(),
hms_name: ($('#abdm_hms_name').val() || '').trim() hms_name: ($('#abdm_hms_name').val() || '').trim(),
ssl_verify: $('#abdm_ssl_verify').is(':checked') ? 1 : 0
}; };
} }
function updateSslWarning() {
$('#abdm_ssl_verify_warning').toggleClass('d-none', $('#abdm_ssl_verify').is(':checked'));
}
updateSslWarning();
$('#abdm_ssl_verify').on('change', updateSslWarning);
// Toggle show/hide API key // Toggle show/hide API key
$('#btn_toggle_abdm_token').on('click', function () { $('#btn_toggle_abdm_token').on('click', function () {
var inp = document.getElementById('abdm_api_token'); var inp = document.getElementById('abdm_api_token');

5
check_ssl_setting.php Normal file
View File

@ -0,0 +1,5 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $db->query("SELECT s_name, s_value FROM hospital_setting WHERE s_name = 'ABDM_BRIDGE_SSL_VERIFY'");
while ($row = $res->fetch_assoc()) { print_r($row); }
$db->close();