Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa

## [Unreleased]

- Fix IP validation in digital signature file download (CIDR support)

## [5.0.0] 2025-11-18

- [PR-192](https://github.com/OS2Forms/os2forms/pull/192)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\os2forms_digital_signature\Form\SettingsForm;
use Symfony\Component\HttpFoundation\IpUtils;

/**
* Implements hook_cron().
Expand Down Expand Up @@ -57,21 +58,54 @@ function os2forms_digital_signature_file_download($uri) {
$config = \Drupal::config(SettingsForm::$configName);
$allowedIps = $config->get('os2forms_digital_signature_submission_allowed_ips');

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

// IP list is empty, or request IP is allowed.
if (empty($allowedIpsArr) || in_array($remoteIp, $allowedIpsArr)) {
// IP list is empty, allow access.
if (empty($allowedIpsArr)) {
$basename = basename($uri);
return [
'Content-disposition' => 'attachment; filename="' . $basename . '"',
];
}

// Otherwise - Deny access.
// Check if remote IP matches any allowed IP or CIDR range.
foreach ($allowedIpsArr as $allowedIp) {
if ($remoteIp === $allowedIp || os2forms_digital_signature_ip_in_cidr($remoteIp, $allowedIp)) {
$basename = basename($uri);
return [
'Content-disposition' => 'attachment; filename="' . $basename . '"',
];
}
}

// Deny access and log warning.
\Drupal::logger('os2forms_digital_signature')->warning('File download denied for IP @ip on URI @uri. Allowed IPs: @allowed', [
'@ip' => $remoteIp,
'@uri' => $uri,
'@allowed' => $allowedIps,
]);

return -1;
}

// Not submission file, allow normal access.
return NULL;
}

/**
* Check if an IP address is within a CIDR range.
*
* @param string $ip
* The IP address to check.
* @param string $cidr
* The CIDR range (e.g. "172.16.0.0/16").
*
* @return bool
* TRUE if the IP is within the CIDR range, FALSE otherwise.
*/
function os2forms_digital_signature_ip_in_cidr(string $ip, string $cidr): bool {
return IpUtils::checkIp($ip, $cidr);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@ services:
- '@config.factory'
- '@entity_type.manager'
- '@logger.channel.os2forms_digital_signature'
services:
logger.channel.os2forms_digital_signature:
parent: logger.channel_base
arguments: [ 'os2forms_digital_signature' ]

os2forms_digital_signature.signing_service:
class: Drupal\os2forms_digital_signature\Service\SigningService
arguments:
- '@http_client'
- '@datetime.time'
- '@config.factory'
- '@entity_type.manager'
- '@logger.channel.os2forms_digital_signature'
5 changes: 4 additions & 1 deletion modules/os2forms_digital_signature/src/Form/SettingsForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,27 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$form['os2forms_digital_signature_remote_service_url'] = [
'#type' => 'textfield',
'#title' => $this->t('Signature server URL'),
'#required' => TRUE,
'#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remote_service_url'),
'#description' => $this->t('E.g. https://signering.bellcom.dk/sign.php?'),
];
$form['os2forms_digital_signature_sign_hash_salt'] = [
'#type' => 'textfield',
'#title' => $this->t('Hash Salt used for signature'),
'#required' => TRUE,
'#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_sign_hash_salt'),
'#description' => $this->t('Must match hash salt on the signature server'),
];
$form['os2forms_digital_signature_submission_allowed_ips'] = [
'#type' => 'textfield',
'#title' => $this->t('List IPs which can download unsigned PDF submissions'),
'#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_submission_allowed_ips'),
'#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1'),
'#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1 or 172.16.0.0/16. If left empty no restrictions will be applied.'),
];
$form['os2forms_digital_signature_submission_retention_period'] = [
'#type' => 'textfield',
'#title' => $this->t('Unsigned submission timespan (s)'),
'#required' => TRUE,
'#default_value' => ($this->config(self::$configName)->get('os2forms_digital_signature_submission_retention_period')) ?? 300,
'#description' => $this->t('How many seconds can unsigned submission exist before being automatically deleted'),
];
Expand Down
Loading