Skip to content

Commit 8d3aefe

Browse files
committed
chore(auth): address PR review feedback from CodeRabbit and jokob-sk
- Reverted unrelated changes to docker-compose.yml, .env, and server/__main__.py to prevent scope creep. - Added regex sanitization to X-Forwarded-For IP parsing and LDAP identifiers to prevent log injection. - Validated API_TOKEN before authenticating sessions in front/index.php. - Delegated hardcoded local admin fallback to LocalProvider.is_fallback_allowed(). - Made FAILED_LOGINS thresholds configurable via UI settings. - Increased Docker UI test reliability by replacing sleeps with ldapwhoami polling and dynamic container resolution. - Moved test_ldap.py to test/plugins/test_ldap_ui_config.py for centralized testing structure.
1 parent 0471adc commit 8d3aefe

23 files changed

Lines changed: 356 additions & 177 deletions

‎.env‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,6 @@ LOGS_LOCATION=/path/to/docker_logs
88

99
PORT=20211
1010

11-
# LDAP Authentication Settings
12-
# LDAP_ENABLED=true
13-
# LDAP_SERVER=ldap.example.com
14-
# LDAP_PORT=389
15-
# LDAP_USE_SSL=false
16-
# LDAP_USE_START_TLS=false
17-
# LDAP_TLS_VERIFY_CERT=true
18-
# LDAP_CA_CERT_PATH=/etc/ssl/certs/ca-certificates.crt
19-
# LDAP_DISABLE_LOCAL_ADMIN=false
20-
# LDAP_DIRECT_BIND_FORMAT=uid={username},ou=users,dc=example,dc=com
21-
# LDAP_BIND_DN=cn=readonly,dc=example,dc=com
22-
# LDAP_BIND_PASSWORD=CHANGE_ME
23-
# LDAP_BASE_DN=ou=users,dc=example,dc=com
24-
# LDAP_USER_FILTER=(uid={username})
25-
# LDAP_USERNAME_ATTRIBUTE=uid
26-
2711
#DEVELOPMENT VARIABLES
2812

2913
DEV_LOCATION=/path/to/local/source/code

‎.github/workflows/run-all-tests.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ jobs:
6666
if [ "${{ github.event.inputs.run_api }}" == "true" ]; then PATHS="$PATHS test/api_endpoints/ test/server/"; fi
6767
if [ "${{ github.event.inputs.run_backend }}" == "true" ]; then PATHS="$PATHS test/backend/ test/db/"; fi
6868
if [ "${{ github.event.inputs.run_docker_env }}" == "true" ]; then PATHS="$PATHS test/docker_tests/"; fi
69+
if [ "${{ github.event.inputs.run_ldap }}" == "true" ]; then PATHS="$PATHS test/server/test_ldap_provider.py test/server/test_ldap_provider_env.py test/ui/test_ui_ldap_login.py test/docker_tests/test_ldap_ui.py"; fi
6970
if [ "${{ github.event.inputs.run_ui }}" == "true" ]; then PATHS="$PATHS test/ui/"; fi
7071
if [ "${{ github.event.inputs.run_plugins }}" == "true" ]; then PATHS="$PATHS test/plugins/"; fi
7172
@@ -98,5 +99,4 @@ jobs:
9899
if: always()
99100
run: |
100101
docker stop netalertx-test-container || true
101-
docker rm netalertx-test-container || true docker stop netalertx-test-container || true
102102
docker rm netalertx-test-container || true

‎PR_REVIEW_REPORT.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# PR Review Resolution Report
2+
3+
## Reverts (Scope Creep)
4+
1. `docker-compose.yml` and `.env`: Reverted to `origin/main` to remove unrelated changes and avoid warning about missing variables or obsolete Compose version syntax.
5+
2. `server/__main__.py`: Reverted `is_idle_this_loop` logic and idle notification to match `origin/main`.
6+
3. `server/plugin.py`: Verified that it matches `origin/main` (no unrelated `sample_json` or `_run_test_cmd` changes exist).
7+
8+
9+
## api_server_start.py Issues
10+
1. Re-added `localhost` to the CORS and trusted_proxies list to avoid breaking integrations like homepage dashboards.
11+
2. Made rate-limiting constants (`FAILED_LOGINS` and `MAX_FAILED_ATTEMPTS`) dynamic via `AUTH_MAX_FAILED_ATTEMPTS` and `AUTH_LOCKOUT_TIME`.
12+
3. Added strict sanitization of `client_ip` from `X-Forwarded-For` using regex to mitigate log injection vectors.
13+
14+
## Flaky UI Tests
15+
1. Replaced `time.sleep(2)` with a `WebDriverWait` in `test/ui/test_ui_ldap_login.py` as requested by CodeRabbit.
16+
17+
## Docker Test Reliability
18+
1. Replaced hardcoded container names in `test/docker_tests/test_ldap_ui.py` with dynamic resolution using `docker ps` filters to support Compose V2 correctly.
19+
2. Removed hardcoded 10-second sleeps and added a bounded polling loop checking LDAP readiness via `ldapwhoami`.
20+
3. Added strict timeouts to `subprocess.run` commands during Docker test setup to prevent CI hangs.
21+
22+
## Auth Logic Safety
23+
1. Moved `$_SESSION['login'] = 1` assignment in `front/index.php` to *after* the `API_TOKEN` is successfully resolved to prevent partially authenticated sessions without tokens.
24+
25+
## LDAP Provider Fixes
26+
1. Updated PHP frontend `LDAP_enabled` check in `security.php` to accept `yes` and `on` in addition to `true` and `1`, matching the backend behavior.
27+
2. Fixed `server/auth/ldap_provider.py` to strictly check the boolean return of `conn.search()` and fail properly if the LDAP search operation fails, as pointed out by CodeRabbit.
28+
29+
## Code Organization
30+
1. Moved `test_ldap.py` out of the frontend plugins directory into `test/plugins/test_ldap_ui_config.py` and updated the plugin's `config.json` to reference the new path.

‎docker-compose.yml‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,6 @@ services:
6767
GRAPHQL_PORT: ${GRAPHQL_PORT:-20212} # GraphQL API port
6868
ALWAYS_FRESH_INSTALL: ${ALWAYS_FRESH_INSTALL:-false} # Set to true to reset your config and database on each container start
6969
NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0} # 0=kill all services and restart if any dies. 1 keeps running dead services.
70-
# LDAP Authentication Variables
71-
LDAP_ENABLED: ${LDAP_ENABLED:-}
72-
LDAP_SERVER: ${LDAP_SERVER:-} # LDAP server hostname or IP
73-
LDAP_PORT: ${LDAP_PORT:-389}
74-
LDAP_USE_SSL: ${LDAP_USE_SSL:-false}
75-
LDAP_USE_START_TLS: ${LDAP_USE_START_TLS:-false}
76-
LDAP_TLS_VERIFY_CERT: ${LDAP_TLS_VERIFY_CERT:-true}
77-
LDAP_CA_CERT_PATH: ${LDAP_CA_CERT_PATH:-}
78-
LDAP_DISABLE_LOCAL_ADMIN: ${LDAP_DISABLE_LOCAL_ADMIN:-false}
79-
LDAP_DIRECT_BIND_FORMAT: ${LDAP_DIRECT_BIND_FORMAT:-}
80-
LDAP_BIND_DN: ${LDAP_BIND_DN:-}
81-
LDAP_BIND_PASSWORD: ${LDAP_BIND_PASSWORD:-} # Service account password for LDAP
82-
LDAP_BASE_DN: ${LDAP_BASE_DN:-}
83-
LDAP_USER_FILTER: ${LDAP_USER_FILTER:-(uid={username})}
84-
LDAP_USERNAME_ATTRIBUTE: ${LDAP_USERNAME_ATTRIBUTE:-uid}
8570

8671
# Resource limits to prevent resource exhaustion
8772
mem_limit: 2048m # Maximum memory usage

‎front/index.php‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<?php
55

66
require_once $_SERVER['DOCUMENT_ROOT'].'/php/server/db.php';
7+
require_once $_SERVER['DOCUMENT_ROOT'].'/php/server/util.php';
78
require_once $_SERVER['DOCUMENT_ROOT'].'/php/templates/language/lang.php';
89
require_once $_SERVER['DOCUMENT_ROOT'].'/php/templates/security.php';
910

@@ -37,16 +38,17 @@
3738
} else {
3839
$ldap_enabled_line = getConfigLine('/^LDAP_enabled.*=/', $configLines);
3940
if ($ldap_enabled_line !== null && isset($ldap_enabled_line[1])) {
40-
$ldap_enabled = strtolower(trim($ldap_enabled_line[1])) === 'true';
41+
$ldap_enabled_value = strtolower(trim($ldap_enabled_line[1]));
42+
$ldap_enabled = $ldap_enabled_value === 'true' || $ldap_enabled_value === '1';
4143
}
4244
}
4345

4446
/**
4547
* Derive the Python API port from the GRAPHQL_PORT setting in app.conf.
46-
* Falls back to 20211 (the default) when not set.
48+
* Falls back to 20212 (the default) when not set.
4749
*/
4850
$gql_line = getConfigLine('/^GRAPHQL_PORT.*=/', $configLines);
49-
$graphql_port = 20211;
51+
$graphql_port = 20212;
5052
if ($gql_line !== null && isset($gql_line[1])) {
5153
$parsed_port = (int) preg_replace('/[^0-9]/', '', $gql_line[1]);
5254
if ($parsed_port >= 1 && $parsed_port <= 65535) {
@@ -125,12 +127,21 @@ function is_authenticated(): bool {
125127

126128
function login_user(): void {
127129
global $nax_Password, $api_token;
130+
131+
$resolved_api_token = !empty($api_token)
132+
? $api_token
133+
: (function_exists('getSettingValue') ? getSettingValue('API_TOKEN') : '');
134+
135+
if (empty($resolved_api_token)) {
136+
throw new RuntimeException('API_TOKEN is not configured');
137+
}
138+
128139
$_SESSION['login'] = 1;
129140
session_regenerate_id(true);
130141
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
131142

132143
// Set remember-me cookie with HMAC (not raw password hash)
133-
$cookie_value = hash_hmac('sha256', $nax_Password, $api_token);
144+
$cookie_value = hash_hmac('sha256', $nax_Password, $resolved_api_token);
134145
setcookie(COOKIE_SAVE_LOGIN_NAME, $cookie_value, [
135146
'expires' => time() + 3600 * 24 * 7,
136147
'path' => '/',
@@ -174,6 +185,14 @@ function logout_user(): void {
174185
if ($ldap_enabled) {
175186
// LDAP path: delegate credential validation to the Python API.
176187
// The API token is required so only server-side callers can reach the endpoint.
188+
$resolved_api_token = !empty($api_token)
189+
? $api_token
190+
: (function_exists('getSettingValue') ? getSettingValue('API_TOKEN') : '');
191+
192+
if (empty($resolved_api_token)) {
193+
throw new RuntimeException('API_TOKEN is not configured');
194+
}
195+
177196
$ldap_payload = json_encode([
178197
'username' => isset($_POST['loginusername']) ? trim($_POST['loginusername']) : '',
179198
'password' => $_POST['loginpassword'],
@@ -182,7 +201,7 @@ function logout_user(): void {
182201
'http' => [
183202
'method' => 'POST',
184203
'header' => "Content-Type: application/json\r\n"
185-
. "Authorization: Bearer " . $api_token . "\r\n"
204+
. "Authorization: Bearer " . $resolved_api_token . "\r\n"
186205
. "X-Forwarded-For: " . ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1') . "\r\n",
187206
'content' => $ldap_payload,
188207
'timeout' => 5,

‎front/php/server/query_graphql.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// Helper function to get GraphQL URL (you can replace this with environment variables)
1313
function getGraphQLUrl() {
1414
$port = getSettingValue("GRAPHQL_PORT"); // Port for the GraphQL server
15+
if (empty($port) || !is_numeric($port)) {
16+
$port = 20211;
17+
}
1518
return "0.0.0.0:$port/graphql"; // Full URL to the GraphQL endpoint
1619
}
1720

‎front/php/templates/auth.php‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,26 @@
2929
}
3030

3131
$config_file_lines = file($config_file);
32-
$config_file_lines_pw = array_values(preg_grep('/^SETPWD_password.*=/', $config_file_lines));
33-
$password_line = explode("'", $config_file_lines_pw[0]);
32+
$config_file_lines_pw = array_values(preg_grep('/^SETPWD_password\s*=/', $config_file_lines ?: []));
33+
if (empty($config_file_lines_pw) || substr_count($config_file_lines_pw[0], "'") < 2) {
34+
http_response_code(401);
35+
echo 'Unauthorized 401';
36+
exit;
37+
}
38+
$password_line = explode("'", $config_file_lines_pw[0], 3);
3439
$nax_Password = $password_line[1];
3540

36-
$config_file_lines_token = array_values(preg_grep('/^API_TOKEN.*=/', $config_file_lines));
37-
$api_token = !empty($config_file_lines_token) ? explode("'", $config_file_lines_token[0])[1] : '';
41+
$config_file_lines_token = array_values(preg_grep('/^API_TOKEN\s*=/', $config_file_lines ?: []));
42+
$token_line = !empty($config_file_lines_token) ? explode("'", $config_file_lines_token[0], 3) : [];
43+
$api_token = $token_line[1] ?? '';
44+
if (empty($api_token)) {
45+
$api_token = getenv('API_TOKEN') ?: '';
46+
}
47+
if ($api_token === '') {
48+
http_response_code(401);
49+
echo 'Unauthorized 401';
50+
exit;
51+
}
3852

3953
$expected_cookie = hash_hmac('sha256', $nax_Password, $api_token);
4054
if (isset($_COOKIE[$CookieSaveLoginName]) && hash_equals($expected_cookie, $_COOKIE[$CookieSaveLoginName])) {

‎front/php/templates/security.php‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,28 @@ function redirect($url) {
7575
$configLines = file(CONFIG_PATH);
7676

7777
// Handle web protection and password
78-
$nax_WebProtection = strtolower(trim(getConfigLine('/^SETPWD_enable_password.*=/', $configLines)[1] ?? 'false'));
78+
$nax_WebProtection = strtolower(trim(getConfigValue('/^SETPWD_enable_password\s*=/', $configLines)));
7979

8080
$ldap_enabled = false;
8181
$env_ldap = getenv('LDAP_ENABLED');
8282
if ($env_ldap === false) $env_ldap = getenv('LDAP_enabled');
8383

8484
if ($env_ldap !== false && $env_ldap !== '') {
85-
$ldap_enabled = strtolower(trim($env_ldap)) === 'true' || trim($env_ldap) === '1';
85+
$env_val = strtolower(trim($env_ldap));
86+
$ldap_enabled = in_array($env_val, ['true', '1', 'yes', 'on']);
8687
} else {
87-
$ldap_enabled = strtolower(trim(getConfigLine('/^LDAP_enabled.*=/', $configLines)[1] ?? 'false')) === 'true';
88+
$val = strtolower(trim(getConfigValue('/^LDAP_enabled\s*=/', $configLines)));
89+
$ldap_enabled = in_array($val, ['true', '1', 'yes', 'on']);
8890
}
8991

9092
if ($ldap_enabled) {
9193
$nax_WebProtection = 'true';
9294
}
93-
$nax_Password = getConfigValue('/^SETPWD_password.*=/', $configLines);
94-
$api_token = getConfigValue('/^API_TOKEN.*=/', $configLines, "'");
95+
$nax_Password = getConfigValue('/^SETPWD_password\s*=/', $configLines);
96+
$api_token = getConfigValue('/^API_TOKEN\s*=/', $configLines, "'");
97+
if (empty($api_token)) {
98+
$api_token = getenv('API_TOKEN') ?: '';
99+
}
95100

96101
$expectedToken = 'Bearer ' . $api_token;
97102

‎front/plugins/auth_ldap/config.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
"description": [
203203
{
204204
"language_code": "en_us",
205-
"string": "Upgrade a plain-text LDAP connection to TLS using the STARTTLS command. Typically used with port 389. Mutually exclusive with LDAPS."
205+
"string": "Upgrade a plain-text LDAP connection to TLS using the STARTTLS command. Typically used with port 389. Mutually exclusive with LDAPS.<br><br><b>Recommended: set to true (or use LDAP_USE_SSL=true with port 636) before production.</b>"
206206
}
207207
]
208208
},
@@ -549,7 +549,7 @@
549549
]
550550
},
551551
"maxLength": 512,
552-
"default_value": "python3 /app/front/plugins/auth_ldap/test_ldap.py --test-user={LDAP_test_username}",
552+
"default_value": "python3 /app/test/plugins/test_ldap_ui_config.py --test-user={LDAP_test_username}",
553553
"options": [],
554554
"localized": [
555555
"name",

‎scripts/run_tests_in_docker_environment.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ docker exec netalertx-test-container /bin/bash -c " \
8080
# --- 9. Execute Tests ---
8181
echo "--- Executing tests inside the container ---"
8282
docker exec netalertx-test-container /bin/bash -c " \
83-
cd /workspaces/NetAlertX && pytest -m 'not (docker or compose or feature_complete)' --cache-clear -o cache_dir=/tmp/.pytest_cache; \
83+
cd /workspaces/NetAlertX && pytest test/ -m 'not (docker or compose or feature_complete)' --cache-clear -o cache_dir=/tmp/.pytest_cache; \
8484
"
8585

8686
# --- 10. Final Teardown ---

0 commit comments

Comments
 (0)