Skip to content

Commit 7631097

Browse files
committed
Fix IP validation in digital signature file download (CIDR support)
1 parent 6e81c86 commit 7631097

File tree

4 files changed

+73
-6
lines changed

4 files changed

+73
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa
1111

1212
## [Unreleased]
1313

14+
- Fix IP validation in digital signature file download (CIDR support)
15+
1416
## [5.0.0] 2025-11-18
1517

1618
- [PR-192](https://github.com/OS2Forms/os2forms/pull/192)

modules/os2forms_digital_signature/os2forms_digital_signature.module

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,70 @@ function os2forms_digital_signature_file_download($uri) {
5757
$config = \Drupal::config(SettingsForm::$configName);
5858
$allowedIps = $config->get('os2forms_digital_signature_submission_allowed_ips');
5959

60-
$allowedIpsArr = explode(',', $allowedIps);
61-
$remoteIp = Drupal::request()->getClientIp();
60+
$allowedIpsArr = array_map('trim', explode(',', $allowedIps));
61+
// Remove empty entries (e.g. from trailing comma or empty config).
62+
$allowedIpsArr = array_filter($allowedIpsArr);
63+
$remoteIp = \Drupal::request()->getClientIp();
6264

63-
// IP list is empty, or request IP is allowed.
64-
if (empty($allowedIpsArr) || in_array($remoteIp, $allowedIpsArr)) {
65+
// IP list is empty, allow access.
66+
if (empty($allowedIpsArr)) {
6567
$basename = basename($uri);
6668
return [
6769
'Content-disposition' => 'attachment; filename="' . $basename . '"',
6870
];
6971
}
7072

71-
// Otherwise - Deny access.
73+
// Check if remote IP matches any allowed IP or CIDR range.
74+
foreach ($allowedIpsArr as $allowedIp) {
75+
if ($remoteIp === $allowedIp || os2forms_digital_signature_ip_in_cidr($remoteIp, $allowedIp)) {
76+
$basename = basename($uri);
77+
return [
78+
'Content-disposition' => 'attachment; filename="' . $basename . '"',
79+
];
80+
}
81+
}
82+
83+
// Deny access and log warning.
84+
\Drupal::logger('os2forms_digital_signature')->warning('File download denied for IP @ip on URI @uri. Allowed IPs: @allowed', [
85+
'@ip' => $remoteIp,
86+
'@uri' => $uri,
87+
'@allowed' => $allowedIps,
88+
]);
89+
7290
return -1;
7391
}
7492

7593
// Not submission file, allow normal access.
7694
return NULL;
7795
}
96+
97+
/**
98+
* Check if an IP address is within a CIDR range.
99+
*
100+
* @param string $ip
101+
* The IP address to check.
102+
* @param string $cidr
103+
* The CIDR range (e.g. "172.16.0.0/16").
104+
*
105+
* @return bool
106+
* TRUE if the IP is within the CIDR range, FALSE otherwise.
107+
*/
108+
function os2forms_digital_signature_ip_in_cidr(string $ip, string $cidr): bool {
109+
if (!str_contains($cidr, '/')) {
110+
return FALSE;
111+
}
112+
113+
[$subnet, $bits] = explode('/', $cidr, 2);
114+
$bits = (int) $bits;
115+
116+
$ip = ip2long($ip);
117+
$subnet = ip2long($subnet);
118+
119+
if ($ip === FALSE || $subnet === FALSE || $bits < 0 || $bits > 32) {
120+
return FALSE;
121+
}
122+
123+
$mask = -1 << (32 - $bits);
124+
125+
return ($ip & $mask) === ($subnet & $mask);
126+
}

modules/os2forms_digital_signature/os2forms_digital_signature.services.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,16 @@ services:
1111
- '@config.factory'
1212
- '@entity_type.manager'
1313
- '@logger.channel.os2forms_digital_signature'
14+
services:
15+
logger.channel.os2forms_digital_signature:
16+
parent: logger.channel_base
17+
arguments: [ 'os2forms_digital_signature' ]
18+
19+
os2forms_digital_signature.signing_service:
20+
class: Drupal\os2forms_digital_signature\Service\SigningService
21+
arguments:
22+
- '@http_client'
23+
- '@datetime.time'
24+
- '@config.factory'
25+
- '@entity_type.manager'
26+
- '@logger.channel.os2forms_digital_signature'

modules/os2forms_digital_signature/src/Form/SettingsForm.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,27 @@ public function buildForm(array $form, FormStateInterface $form_state) {
4040
$form['os2forms_digital_signature_remote_service_url'] = [
4141
'#type' => 'textfield',
4242
'#title' => $this->t('Signature server URL'),
43+
'#required' => TRUE,
4344
'#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remote_service_url'),
4445
'#description' => $this->t('E.g. https://signering.bellcom.dk/sign.php?'),
4546
];
4647
$form['os2forms_digital_signature_sign_hash_salt'] = [
4748
'#type' => 'textfield',
4849
'#title' => $this->t('Hash Salt used for signature'),
50+
'#required' => TRUE,
4951
'#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_sign_hash_salt'),
5052
'#description' => $this->t('Must match hash salt on the signature server'),
5153
];
5254
$form['os2forms_digital_signature_submission_allowed_ips'] = [
5355
'#type' => 'textfield',
5456
'#title' => $this->t('List IPs which can download unsigned PDF submissions'),
5557
'#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_submission_allowed_ips'),
56-
'#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1'),
58+
'#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1. If left empty no restictions will be applied.'),
5759
];
5860
$form['os2forms_digital_signature_submission_retention_period'] = [
5961
'#type' => 'textfield',
6062
'#title' => $this->t('Unsigned submission timespan (s)'),
63+
'#required' => TRUE,
6164
'#default_value' => ($this->config(self::$configName)->get('os2forms_digital_signature_submission_retention_period')) ?? 300,
6265
'#description' => $this->t('How many seconds can unsigned submission exist before being automatically deleted'),
6366
];

0 commit comments

Comments
 (0)