Browse Source

extend API

Dmitriev Roman 3 months ago
parent
commit
ac4f54965e
4 changed files with 251 additions and 93 deletions
  1. 27 18
      README.md
  2. 1 0
      html/admin/devices/switchstatus.php
  3. 208 28
      html/api.php
  4. 15 47
      html/inc/auth.utils.php

+ 27 - 18
README.md

@@ -80,13 +80,17 @@ apt-get install apache2 php8.2 php8.2-mysqlnd php8.2-pgsql \
 php8.2-intl php8.2-mbstring php8.2-fpm-fcgi apache2-mod_fcgid
 
 # Perl-модули
-apt-get install perl perl-Net-Patricia perl-NetAddr-IP \
-perl-Config-Tiny perl-Net-DNS perl-DateTime perl-Net-Ping \
-perl-Net-Netmask perl-Text-Iconv perl-Net-SNMP perl-Net-Telnet \
-perl-DBI perl-DBD-mysql perl-DBD-Pg perl-Parallel-ForkManager \
-perl-Proc-Daemon perl-DateTime-Format-DateParse perl-Net-OpenSSH \
-perl-File-Tail perl-Crypt-Rijndael perl-Crypt-CBC perl-CryptX \
-perl-Crypt-DES perl-File-Path-Tiny perl-Expect perl-Proc-ProcessTable
+apt-get install perl-Net-Patricia perl-NetAddr-IP perl-Config-Tiny \
+perl-Net-DNS perl-DateTime perl-Net-Ping \
+perl-Net-Netmask perl-Text-Iconv perl-Net-SNMP \
+perl-Net-Telnet perl-DBI \
+perl-Parallel-ForkManager perl-Proc-Daemon \
+perl-DateTime-Format-DateParse perl-DateTime-Format-Strptime \
+perl-Net-OpenSSH perl-File-Tail perl-Tie-File \
+perl-Crypt-Rijndael perl-Crypt-CBC perl-CryptX perl-Crypt-DES \
+perl-File-Path-Tiny perl-Expect perl-Proc-ProcessTable \
+perl-Text-CSV \
+perl-DBD-Pg perl-DBD-mysql
 
 # Дополнительные сервисы
 apt-get install dnsmasq syslog-ng syslog-ng-journal pwgen
@@ -112,14 +116,17 @@ php-intl php-mbstring php-date php-mail php-snmp php-zip \
 php-db php-fpm libapache2-mod-fcgid
 
 # Perl-модули
-apt-get install perl libnet-patricia-perl libnetaddr-ip-perl \
-libconfig-tiny-perl libnet-dns-perl libdatetime-perl \
-libnet-netmask-perl libtext-iconv-perl libnet-snmp-perl \
-libnet-telnet-perl libdbi-perl libdbd-mysql-perl libdbd-pg-perl \
-libparallel-forkmanager-perl libproc-daemon-perl \
-libdatetime-format-dateparse-perl libnet-openssh-perl \
-libfile-tail-perl libcrypt-rijndael-perl libcrypt-cbc-perl \
-libcryptx-perl libfile-path-tiny-perl libexpect-perl libcrypt-des-perl
+apt-get install -y perl \
+libnet-patricia-perl libnetaddr-ip-perl libconfig-tiny-perl \
+libnet-dns-perl libdatetime-perl libnet-netmask-perl \
+libtext-iconv-perl libnet-snmp-perl libnet-telnet-perl \
+libdbi-perl libparallel-forkmanager-perl libproc-daemon-perl \
+libdatetime-format-dateparse-perl libnetwork-ipv4addr-perl \
+libnet-openssh-perl libfile-tail-perl libdatetime-format-strptime-perl \
+libcrypt-rijndael-perl libcrypt-cbc-perl libcryptx-perl \
+libcrypt-des-perl libfile-path-tiny-perl libexpect-perl \
+libtext-csv-perl \
+libdbd-pg-perl libdbd-mysql-perl
 
 # Дополнительные сервисы
 apt-get install dnsmasq syslog-ng
@@ -161,6 +168,11 @@ mkdir -p /opt/Eye/html/cfg
 mkdir -p /opt/Eye/html/js
 mkdir -p /opt/Eye/docs
 
+# Установка прав
+chmod 750 /opt/Eye
+chmod 770 /opt/Eye/scripts/log
+chmod 750 /opt/Eye/scripts
+
 # Копирование файлов
 cp -R scripts/ /opt/Eye/
 cp -R html/ /opt/Eye/
@@ -168,9 +180,6 @@ cp -R docs/ /opt/Eye/
 
 # Установка прав
 chown -R eye:eye /opt/Eye
-chmod -R 755 /opt/Eye/html
-chmod -R 770 /opt/Eye/scripts/log
-chmod 750 /opt/Eye/scripts
 ```
 
 ---

+ 1 - 0
html/admin/devices/switchstatus.php

@@ -126,6 +126,7 @@ print_editdevice_submenu($page_url, $id, $device['device_type'], $user_info['log
             $snmp_ok = check_snmp_access($device['ip'], $snmp);
             $modules_oids = NULL;
             if ($snmp_ok) {
+                update_record($db_link, 'devices', 'id=?', [ 'nagios_status'=> 'UP' ], [ $id ]);
                 $modules_oids = walk_snmp($device["ip"], $snmp, CISCO_MODULES);
                 $vlan_list = get_switch_vlans($device['vendor_id'], $device['ip'], $snmp);
                 //if port number 1 not exists - try detect by snmp interface index

+ 208 - 28
html/api.php

@@ -1,19 +1,33 @@
 <?php
-require_once ($_SERVER['DOCUMENT_ROOT']."/inc/auth.php");
 
-// Определяем page_url для сессии (можно использовать константу или путь)
-$page_url = 'api';
+require_once ($_SERVER['DOCUMENT_ROOT']."/inc/auth.utils.php");
+
+login($db_link);
 
 // Получаем параметры через безопасные функции
-$action_get  = getParam('get',    $page_url);
-$action_send = getParam('send',   $page_url);
-$ip          = getParam('ip',     $page_url, '', FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4]);
-$mac_raw     = getParam('mac',    $page_url, '');
-$rec_id      = getParam('id',     $page_url, null, FILTER_VALIDATE_INT);
-$f_subnet    = getParam('subnet', $page_url, '');
+$action_get  = getParam('get');
+$action_send = getParam('send');
+$ip          = getParam('ip', null, null, FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4]);
+$mac_raw     = getParam('mac');
+$rec_id      = getParam('id', null, null, FILTER_VALIDATE_INT);
+$f_subnet    = getParam('subnet');
+
+// Новые параметры для универсальных методов
+$table       = getParam('table');
+$filter      = getParam('filter'); // JSON-строка для кастомного фильтра
+$update_data = getParam('data'); // JSON-данные для обновления
+
+// Параметры пагинации
+$limit_param = getParam('limit', null, null, FILTER_VALIDATE_INT);
+$offset_param = getParam('offset', null, null, FILTER_VALIDATE_INT);
+$limit = ($limit_param !== null && $limit_param > 0) ? min((int)$limit_param, 1000) : 1000;
+$offset = ($offset_param !== null && $offset_param >= 0) ? (int)$offset_param : 0;
 
 // Обработка MAC-адреса
-$mac = !empty($mac_raw) ? mac_dotted(trim($mac_raw)) : '';
+$mac = '';
+if (!empty($mac_raw) && checkValidMac($mac_raw)) {
+    $mac = mac_dotted(trim($mac_raw));
+}
 
 // Определяем действие
 $action = '';
@@ -21,15 +35,178 @@ if (!empty($action_get))  { $action = 'get_' . $action_get; }
 if (!empty($action_send)) { $action = 'send_' . $action_send; }
 
 // Дополнительные параметры для send_dhcp
-$dhcp_hostname = getParam('hostname', $page_url, '');
-$faction_raw   = getParam('action', $page_url, 1, FILTER_VALIDATE_INT);
+$dhcp_hostname = getParam('hostname', '');
+$dhcp_action   = getParam('action', 1, FILTER_VALIDATE_INT);
+
+// === Список разрешённых таблиц ===
+$allowed_tables = [
+    'building',
+    'devices',
+    'device_models',
+    'ou',
+    'queue_list',
+    'group_list',
+    'subnets',
+    'config',
+    'user_auth',
+    'user_list',
+    'vendors'
+];
+
+function do_exit() {
+    exit;
+}
+
+// === Валидация таблицы ===
+function validate_table($table_name, $allowed) {
+    return in_array($table_name, $allowed) ? $table_name : null;
+}
+
+// === Безопасное получение данных из таблицы ===
+function safe_get_records($db, $table, $filter = null, $limit = 1000, $offset = 0) {
+    global $allowed_tables;
+    
+    if (!validate_table($table, $allowed_tables)) {
+        return ['error' => 'Invalid table name'];
+    }
+    
+    $sql = "SELECT * FROM " . $table;
+    $params = [];
+    
+    if ($filter) {
+        // Фильтр в формате: {"field":"value","field2":"value2"}
+        $filter_arr = json_decode($filter, true);
+        if (is_array($filter_arr) && !empty($filter_arr)) {
+            $conditions = [];
+            foreach ($filter_arr as $field => $value) {
+                // Защита от SQL-инъекции: проверяем имя поля
+                if (!preg_match('/^[a-z_][a-z0-9_]*$/i', $field)) {
+                    continue;
+                }
+                $conditions[] = "$field = ?";
+                $params[] = $value;
+            }
+            if (!empty($conditions)) {
+                $sql .= " WHERE " . implode(" AND ", $conditions);
+            }
+        }
+    }
+    
+    $sql .= " LIMIT " . (int)$limit;
+    if ($offset > 0) {
+        $sql .= " OFFSET " . (int)$offset;
+    }
+    
+    return get_records_sql($db, $sql, $params);
+}
+
+// === Безопасное получение одной записи ===
+function safe_get_record($db, $table, $id) {
+    global $allowed_tables;
+    
+    if (!validate_table($table, $allowed_tables)) {
+        return ['error' => 'Invalid table name'];
+    }
+    
+    if (!is_numeric($id) || $id <= 0) {
+        return ['error' => 'Invalid ID'];
+    }
+    
+    $pk_field = 'id'; // Все таблицы используют 'id' как первичный ключ
+    return get_record_sql($db, "SELECT * FROM $table WHERE $pk_field = ?", [(int)$id]);
+}
 
 if (!empty($action)) {
 
     // Преобразуем IP в BIGINT (если валиден)
     $ip_aton = null;
-    if ($ip) {
-        $ip_aton = sprintf('%u', ip2long($ip));
+    if (!empty($ip)) { 
+        $ip_aton = sprintf('%u', ip2long($ip)); 
+    }
+
+    // === УНИВЕРСАЛЬНЫЙ МЕТОД: get_table_record ===
+    if ($action === 'get_table_record' && !empty($table) && $rec_id > 0) {
+        $result = safe_get_record($db_link, $table, $rec_id);
+        
+        if (isset($result['error'])) {
+            http_response_code(400);
+            echo json_encode($result);
+            do_exit();
+        }
+        
+        if ($result) {
+            header('Content-Type: application/json; charset=utf-8');
+            echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
+        } else {
+            http_response_code(404);
+            echo json_encode(['error' => 'Record not found']);
+        }
+        do_exit();
+    }
+
+    // === УНИВЕРСАЛЬНЫЙ МЕТОД: get_table_list ===
+    if ($action === 'get_table_list' && !empty($table)) {
+        $result = safe_get_records($db_link, $table, $filter, $limit, $offset);
+        
+        if (isset($result['error'])) {
+            http_response_code(400);
+            echo json_encode($result);
+            do_exit();
+        }
+        
+        header('Content-Type: application/json; charset=utf-8');
+        echo json_encode($result ?: [], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
+        do_exit();
+    }
+
+    // === ОБНОВЛЕНИЕ USER_LIST ===
+    if ($action === 'send_update_user' && $rec_id > 0 && !empty($update_data)) {
+        $data = json_decode($update_data, true);
+        
+        if (!is_array($data)) {
+            http_response_code(400);
+            echo json_encode(['error' => 'Invalid data format']);
+            do_exit();
+        }
+        
+        // Разрешённые поля для обновления
+        $allowed_fields = [
+            'login', 'fio', 'enabled', 'blocked', 'ou_id', 
+            'filter_group_id', 'queue_id', 'day_quota', 'month_quota', 'permanent'
+        ];
+        
+        $update_fields = [];
+        foreach ($data as $key => $value) {
+            if (in_array($key, $allowed_fields)) {
+                $update_fields[$key] = $value;
+            }
+        }
+        
+        if (empty($update_fields)) {
+            http_response_code(400);
+            echo json_encode(['error' => 'No valid fields to update']);
+            do_exit();
+        }
+        
+        // Проверяем существование пользователя
+        $existing = get_record_sql($db_link, "SELECT id FROM user_list WHERE id = ?", [$rec_id]);
+        if (!$existing) {
+            http_response_code(404);
+            echo json_encode(['error' => 'User not found']);
+            do_exit();
+        }
+        
+        // Выполняем обновление
+        if (update_record($db_link, 'user_list', 'id = ?', $update_fields, [$rec_id])) {
+            LOG_VERBOSE($db_link, "API: User $rec_id updated successfully");
+            http_response_code(200);
+            echo json_encode(['status' => 'updated', 'id' => $rec_id]);
+        } else {
+            LOG_ERROR($db_link, "API: Failed to update user $rec_id");
+            http_response_code(500);
+            echo json_encode(['error' => 'Update failed']);
+        }
+        do_exit();
     }
 
     // === get_user_auth ===
@@ -70,6 +247,7 @@ if (!empty($action)) {
             http_response_code(400);
             echo json_encode(['error' => 'Missing parameters']);
         }
+        do_exit();
     }
 
     // === get_user ===
@@ -80,7 +258,7 @@ if (!empty($action)) {
             $user = get_record_sql($db_link, "SELECT * FROM user_list WHERE id = ?", [$rec_id]);
             if ($user) {
                 $auth_records = get_records_sql($db_link, 
-                    "SELECT * FROM user_auth WHERE deleted = 0 AND user_id = ?", 
+                    "SELECT * FROM user_auth WHERE deleted = 0 AND user_id = ? ORDER BY id LIMIT 100", 
                     [$rec_id]
                 );
                 $user['auth'] = $auth_records ?: [];
@@ -98,6 +276,7 @@ if (!empty($action)) {
             http_response_code(400);
             echo json_encode(['error' => 'Missing user ID']);
         }
+        do_exit();
     }
 
     // === get_dhcp_all ===
@@ -112,11 +291,13 @@ if (!empty($action)) {
             JOIN subnets s ON ua.ip_int BETWEEN s.ip_int_start AND s.ip_int_stop
             WHERE ua.dhcp = 1 AND ua.deleted = 0 AND s.dhcp = 1 
             ORDER BY ua.ip_int
-        ");
+            LIMIT ? OFFSET ?
+        ", [$limit, $offset]);
         
         LOG_VERBOSE($db_link, "API: " . count($result) . " records found.");
         header('Content-Type: application/json; charset=utf-8');
         echo json_encode($result ?: [], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
+        do_exit();
     }
 
     // === get_dhcp_subnet ===
@@ -125,7 +306,7 @@ if (!empty($action)) {
         if (!filter_var($f_subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
             http_response_code(400);
             echo json_encode(['error' => 'Invalid subnet format']);
-            exit;
+            do_exit();
         }
         
         LOG_VERBOSE($db_link, "API: Get dhcp records for subnet " . $f_subnet);
@@ -139,24 +320,26 @@ if (!empty($action)) {
             WHERE ua.dhcp = 1 AND ua.deleted = 0 AND s.dhcp = 1 
               AND SUBSTRING_INDEX(s.subnet, '/', 1) = ?
             ORDER BY ua.ip_int
-        ", [$f_subnet]);
+            LIMIT ? OFFSET ?
+        ", [$f_subnet, $limit, $offset]);
 
         LOG_VERBOSE($db_link, "API: " . count($result) . " records found.");
         header('Content-Type: application/json; charset=utf-8');
         echo json_encode($result ?: [], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
+        do_exit();
     }
 
     // === send_dhcp ===
     if ($action === 'send_dhcp') {
         if ($ip && $mac) {
-            $faction = $faction_raw !== null ? (int)$faction_raw : 1;
-            $dhcp_action = ($faction === 0) ? 'del' : 'add';
+            $faction = $dhcp_action !== null ? (int)$dhcp_action : 1;
+            $action_str = ($faction === 0) ? 'del' : 'add';
 
-            LOG_VERBOSE($db_link, "API: external dhcp request for $ip [$mac] $dhcp_action");
+            LOG_VERBOSE($db_link, "API: external dhcp request for $ip [$mac] $action_str");
             
             if (is_our_network($db_link, $ip)) {
                 insert_record($db_link, "dhcp_queue", [
-                    'action' => $dhcp_action,
+                    'action' => $action_str,
                     'mac' => $mac,
                     'ip' => $ip,
                     'dhcp_hostname' => $dhcp_hostname
@@ -172,18 +355,15 @@ if (!empty($action)) {
             http_response_code(400);
             echo json_encode(['error' => 'Missing IP or MAC']);
         }
+        do_exit();
     }
+
 } else {
     LOG_WARNING($db_link, "API: Unknown request");
     http_response_code(400);
     echo json_encode(['error' => 'Unknown action']);
 }
 
-ob_end_flush();
+do_exit();
 
-// Очистка сессии
-if (session_status() === PHP_SESSION_ACTIVE) {
-    $_SESSION = [];
-    session_destroy();
-}
 ?>

+ 15 - 47
html/inc/auth.utils.php

@@ -10,7 +10,7 @@ define('SESSION_TABLE', 'sessions');
 define('USER_SESSIONS_TABLE', 'user_sessions');
 
 //set default const values
-if (!defined('SESSION_LIFETIME') || SESSION_LIFETIME < 60) { define('SESSION_LIFETIME', 86400); }
+if (!defined("SESSION_LIFETIME") || SESSION_LIFETIME < 60) { define("SESSION_LIFETIME", 86400); }
 if (!defined("HTML_LANG")) { define("HTML_LANG","english"); }
 if (!defined("HTML_STYLE")) { define("HTML_STYLE","white"); }
 if (!defined("IPCAM_GROUP_ID")) { define("IPCAM_GROUP_ID","5"); }
@@ -187,6 +187,12 @@ function sess_gc($maxLifetime) {
 }
 
 function login($db) {
+
+    if (strpos($_SERVER['REQUEST_URI'], '/api.php') === 0) {
+        LOG_DEBUG($db, "API request detected, attempting silent auth");
+        return IsSilentAuthenticated($db);
+    }
+
     log_session_debug($db, "Login function started", [
         'session_status' => session_status(),
         'session_id' => session_id(),
@@ -221,10 +227,6 @@ function login($db) {
         log_session_debug($db, "No user_id found in session");
     }
 
-    if (strpos($_SERVER['REQUEST_URI'], '/api.php') === 0) {
-        log_session_debug($db, "API request detected, attempting silent auth");
-        return IsSilentAuthenticated($db);
-    }
 
     if (!empty($_POST['login']) && !empty($_POST['password'])) {
         log_session_debug($db, "POST login attempt", ['login' => $_POST['login']]);
@@ -387,63 +389,29 @@ function get_client_ip() {
             }
         }
     }
-    log_session_debug($GLOBALS['db_link'], "Client IP determined", ['ip' => $ip]);
     return $ip;
 }
 
 function IsSilentAuthenticated($db) {
-    log_session_debug($db, "Silent authentication attempt");
-
-    if (!empty($_SESSION['user_id'])) {
-        log_session_debug($db, "Silent auth - already has user_id in session");
-        return true;
-    }
-
     $auth_ip = get_client_ip();
-    $api_key = '';
-    $login = '';
-
-    if (!empty($_GET['api_key'])) {
-        $api_key = trim($_GET['api_key']);
-    } elseif (!empty($_POST['api_key'])) {
-        $api_key = trim($_POST['api_key']);
-    }
-
-    if (!empty($_GET['login'])) {
-        $login = trim($_GET['login']);
-    } elseif (!empty($_POST['login'])) {
-        $login = trim($_POST['login']);
-    }
-
-    log_session_debug($db, "Silent auth parameters", ['login' => $login, 'has_api_key' => !empty($api_key)]);
-
+    $api_key = getParam('api_key', null, null, FILTER_SANITIZE_STRING);
+    $login   = getParam('login', null, null, FILTER_SANITIZE_STRING);
+    LOG_DEBUG($db, "Silent auth parameters login => {$login} from {$auth_ip}");
     if (empty($login) || empty($api_key) || strlen($api_key) < 20) {
-        log_session_debug($db, "Silent auth failed - missing parameters");
+        LOG_WARNING($db, "Silent auth failed from {$auth_ip} - missing parameters");
         return false;
     }
 
-    $stmt = $db->prepare("SELECT id, rights FROM customers WHERE rights>0 AND login = ? AND api_key = ? LIMIT 1");
+    $stmt = $db->prepare("SELECT * FROM customers WHERE rights>0 AND login = ? AND api_key = ? LIMIT 1");
     $stmt->execute([$login, $api_key]);
-
     if ($stmt->rowCount() === 0) {
-        LOG_DEBUG($db, "API auth failed for: $login");
-        log_session_debug($db, "Silent auth failed - user not found, disabled  or invalid API key");
+        LOG_WARNING($db, "API auth failed for $login from $auth_ip: user not found, disabled or invalid API key");
         return false;
     }
 
     $user = $stmt->fetch(PDO::FETCH_ASSOC);
-
-    $_SESSION = [
-        'user_id'    => $user['id'],
-        'login'      => $login,
-        'acl'        => $user['rights'],
-        'ip'         => $auth_ip,
-        'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
-        'api_auth'   => true
-    ];
-
-    log_session_debug($db, "Silent auth successful", ['user_id' => $user['id'], 'login' => $login]);
-    LOG_INFO($db, "Logged in to api customer id: ".$_SESSION['user_id']." name: ".$_SESSION['login']." from ".$_SESSION['ip']." with acl: ".$_SESSION['acl']);
+    if (!empty($user)) { return false; }
+    LOG_DEBUG($db, "Silent auth successful user_id => {$user['id']} login => {$user['login']} from {$auth_ip}");
     return true;
 }