Skip to content

Commit 2ecdc25

Browse files
authored
feat(ip/hostame-questions): Enhance IP Adress retrieval
1 parent 3b879d3 commit 2ecdc25

File tree

4 files changed

+222
-4
lines changed

4 files changed

+222
-4
lines changed

src/Helpers/NetworkHelper.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/**
4+
* -------------------------------------------------------------------------
5+
* advancedforms plugin for GLPI
6+
* -------------------------------------------------------------------------
7+
*
8+
* MIT License
9+
*
10+
* Permission is hereby granted, free of charge, to any person obtaining a copy
11+
* of this software and associated documentation files (the "Software"), to deal
12+
* in the Software without restriction, including without limitation the rights
13+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
* copies of the Software, and to permit persons to whom the Software is
15+
* furnished to do so, subject to the following conditions:
16+
*
17+
* The above copyright notice and this permission notice shall be included in all
18+
* copies or substantial portions of the Software.
19+
*
20+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26+
* SOFTWARE.
27+
* -------------------------------------------------------------------------
28+
* @copyright Copyright (C) 2025 by the advancedforms plugin team.
29+
* @license MIT https://opensource.org/licenses/mit-license.php
30+
* @link https://github.com/pluginsGLPI/advancedforms
31+
* -------------------------------------------------------------------------
32+
*/
33+
34+
namespace GlpiPlugin\Advancedforms\Helpers;
35+
36+
final class NetworkHelper
37+
{
38+
public static function getRemoteIpAddress(): string
39+
{
40+
// Check X-Forwarded-For header first (proxy/load balancer)
41+
$forwarded_for = getenv("HTTP_X_FORWARDED_FOR");
42+
if (is_string($forwarded_for) && ($forwarded_for !== '')) {
43+
// May contain multiple IPs separated by ", " - get the first one
44+
$ips = explode(',', $forwarded_for);
45+
return trim($ips[0]);
46+
}
47+
48+
// Fallback to REMOTE_ADDR
49+
$remote_addr = getenv("REMOTE_ADDR");
50+
if (is_string($remote_addr) && ($remote_addr !== '')) {
51+
return $remote_addr;
52+
}
53+
54+
// Last fallback to $_SERVER
55+
if (isset($_SERVER['REMOTE_ADDR']) && is_string($_SERVER['REMOTE_ADDR'])) {
56+
return $_SERVER['REMOTE_ADDR'];
57+
}
58+
59+
return '';
60+
}
61+
}

src/Model/QuestionType/HostnameQuestion.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
use Glpi\Form\Question;
3939
use Glpi\Form\QuestionType\AbstractQuestionType;
4040
use Glpi\Form\QuestionType\QuestionTypeCategoryInterface;
41+
use GlpiPlugin\Advancedforms\Helpers\NetworkHelper;
4142
use GlpiPlugin\Advancedforms\Model\Config\ConfigurableItemInterface;
4243
use GlpiPlugin\Advancedforms\Model\Mapper\FormcreatorHostnameTypeMapper;
4344
use Override;
44-
use Toolbox;
4545

4646
/**
4747
* Legacy question type from the formcreator plugin
@@ -108,7 +108,7 @@ public function renderEndUserTemplate(Question|null $question): string
108108
$twig = TemplateRenderer::getInstance();
109109
return $twig->renderFromStringTemplate($template, [
110110
'question' => $question,
111-
'hostname' => gethostbyaddr(Toolbox::getRemoteIpAddress()),
111+
'hostname' => gethostbyaddr(NetworkHelper::getRemoteIpAddress()),
112112
]);
113113
}
114114

src/Model/QuestionType/IpAddressQuestion.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
use Glpi\Form\Question;
3939
use Glpi\Form\QuestionType\AbstractQuestionType;
4040
use Glpi\Form\QuestionType\QuestionTypeCategoryInterface;
41+
use GlpiPlugin\Advancedforms\Helpers\NetworkHelper;
4142
use GlpiPlugin\Advancedforms\Model\Config\ConfigurableItemInterface;
4243
use GlpiPlugin\Advancedforms\Model\Mapper\FormcreatorIpTypeMapper;
4344
use Override;
44-
use Toolbox;
4545

4646
/**
4747
* Legacy question type from the formcreator plugin
@@ -108,7 +108,7 @@ public function renderEndUserTemplate(Question|null $question): string
108108
$twig = TemplateRenderer::getInstance();
109109
return $twig->renderFromStringTemplate($template, [
110110
'question' => $question,
111-
'ip' => Toolbox::getRemoteIpAddress(),
111+
'ip' => NetworkHelper::getRemoteIpAddress(),
112112
]);
113113
}
114114

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php
2+
3+
/**
4+
* -------------------------------------------------------------------------
5+
* advancedforms plugin for GLPI
6+
* -------------------------------------------------------------------------
7+
*
8+
* MIT License
9+
*
10+
* Permission is hereby granted, free of charge, to any person obtaining a copy
11+
* of this software and associated documentation files (the "Software"), to deal
12+
* in the Software without restriction, including without limitation the rights
13+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
* copies of the Software, and to permit persons to whom the Software is
15+
* furnished to do so, subject to the following conditions:
16+
*
17+
* The above copyright notice and this permission notice shall be included in all
18+
* copies or substantial portions of the Software.
19+
*
20+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26+
* SOFTWARE.
27+
* -------------------------------------------------------------------------
28+
* @copyright Copyright (C) 2025 by the advancedforms plugin team.
29+
* @license MIT https://opensource.org/licenses/mit-license.php
30+
* @link https://github.com/pluginsGLPI/advancedforms
31+
* -------------------------------------------------------------------------
32+
*/
33+
34+
namespace GlpiPlugin\Advancedforms\Tests\Helpers;
35+
36+
use GlpiPlugin\Advancedforms\Helpers\NetworkHelper;
37+
use GlpiPlugin\Advancedforms\Tests\AdvancedFormsTestCase;
38+
use PHPUnit\Framework\Attributes\DataProvider;
39+
40+
final class NetworkHelperTest extends AdvancedFormsTestCase
41+
{
42+
public static function getRemoteIpAddressProvider(): \Generator
43+
{
44+
// Test X-Forwarded-For header with single IP (highest priority)
45+
yield 'X-Forwarded-For with single IP' => [
46+
'http_x_forwarded_for' => '192.168.1.100',
47+
'remote_addr_env' => '10.0.0.1',
48+
'remote_addr_server' => '10.0.0.1',
49+
'expected' => '192.168.1.100',
50+
];
51+
52+
// Test X-Forwarded-For header with multiple IPs (should use first one)
53+
yield 'X-Forwarded-For with multiple IPs' => [
54+
'http_x_forwarded_for' => '192.168.1.100, 10.0.0.5, 172.16.0.1',
55+
'remote_addr_env' => '10.0.0.1',
56+
'remote_addr_server' => '10.0.0.1',
57+
'expected' => '192.168.1.100',
58+
];
59+
60+
// Test X-Forwarded-For header with spaces
61+
yield 'X-Forwarded-For with spaces' => [
62+
'http_x_forwarded_for' => ' 192.168.1.200 ',
63+
'remote_addr_env' => '10.0.0.1',
64+
'remote_addr_server' => '10.0.0.1',
65+
'expected' => '192.168.1.200',
66+
];
67+
68+
// Test fallback to REMOTE_ADDR from getenv
69+
yield 'Fallback to REMOTE_ADDR from getenv' => [
70+
'http_x_forwarded_for' => '',
71+
'remote_addr_env' => '123.123.123.123',
72+
'remote_addr_server' => '10.0.0.1',
73+
'expected' => '123.123.123.123',
74+
];
75+
76+
// Test fallback to $_SERVER['REMOTE_ADDR']
77+
yield 'Fallback to $_SERVER REMOTE_ADDR' => [
78+
'http_x_forwarded_for' => '',
79+
'remote_addr_env' => '',
80+
'remote_addr_server' => '99.99.99.99',
81+
'expected' => '99.99.99.99',
82+
];
83+
84+
// Test no IP available at all
85+
yield 'No IP available' => [
86+
'http_x_forwarded_for' => '',
87+
'remote_addr_env' => '',
88+
'remote_addr_server' => null,
89+
'expected' => '',
90+
];
91+
92+
// Test IPv6 address in X-Forwarded-For
93+
yield 'IPv6 in X-Forwarded-For' => [
94+
'http_x_forwarded_for' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
95+
'remote_addr_env' => '10.0.0.1',
96+
'remote_addr_server' => '10.0.0.1',
97+
'expected' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
98+
];
99+
100+
// Test IPv6 address in REMOTE_ADDR
101+
yield 'IPv6 in REMOTE_ADDR' => [
102+
'http_x_forwarded_for' => '',
103+
'remote_addr_env' => '2001:0db8:85a3::8a2e:0370:7334',
104+
'remote_addr_server' => '10.0.0.1',
105+
'expected' => '2001:0db8:85a3::8a2e:0370:7334',
106+
];
107+
108+
// Test X-Forwarded-For with comma separated IPs without spaces
109+
yield 'X-Forwarded-For comma separated without spaces' => [
110+
'http_x_forwarded_for' => '192.168.1.50,10.0.0.5',
111+
'remote_addr_env' => '10.0.0.1',
112+
'remote_addr_server' => '10.0.0.1',
113+
'expected' => '192.168.1.50',
114+
];
115+
}
116+
117+
#[DataProvider('getRemoteIpAddressProvider')]
118+
public function testGetIPAddress(
119+
string $http_x_forwarded_for,
120+
string $remote_addr_env,
121+
?string $remote_addr_server,
122+
string $expected,
123+
): void {
124+
// Save values
125+
$saveServer = $_SERVER;
126+
127+
// Setup environment
128+
if ($http_x_forwarded_for !== '') {
129+
putenv("HTTP_X_FORWARDED_FOR={$http_x_forwarded_for}");
130+
} else {
131+
putenv("HTTP_X_FORWARDED_FOR=");
132+
}
133+
134+
if ($remote_addr_env !== '') {
135+
putenv("REMOTE_ADDR={$remote_addr_env}");
136+
} else {
137+
putenv("REMOTE_ADDR=");
138+
}
139+
140+
if ($remote_addr_server !== null) {
141+
$_SERVER['REMOTE_ADDR'] = $remote_addr_server;
142+
} else {
143+
unset($_SERVER['REMOTE_ADDR']);
144+
}
145+
146+
// Test
147+
$ip = NetworkHelper::getRemoteIpAddress();
148+
$this->assertEquals($expected, $ip);
149+
150+
// Clean up environment variables
151+
putenv("HTTP_X_FORWARDED_FOR");
152+
putenv("REMOTE_ADDR");
153+
154+
// Restore values
155+
$_SERVER = $saveServer;
156+
}
157+
}

0 commit comments

Comments
 (0)