bemade-addons/unifi_integration/models/unifi_site_controller.py
2025-03-26 08:46:03 -04:00

1700 lines
63 KiB
Python

# -*- coding: utf-8 -*-
# These imports will work in an Odoo environment, even if your IDE marks them as not found
# pylint: disable=import-error
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
# pylint: enable=import-error
import json
import logging
import requests
import urllib3
from datetime import datetime, timedelta
from typing import Dict, Tuple, List, Any
from requests.exceptions import RequestException, ConnectionError
_logger = logging.getLogger(__name__)
class UnifiSiteController(models.Model):
"""Extension du modèle UnifiSite pour l'API Controller (Local)
Cette classe ajoute les champs et méthodes spécifiques à l'API Controller local.
Elle est utilisée lorsque api_type = 'controller'.
"""
_name = 'unifi.site.controller'
_description = 'UniFi Site Controller API'
# Champs pour établir la relation avec unifi.site
site_id = fields.Many2one(
comodel_name='unifi.site',
string='Site',
required=True,
ondelete='cascade',
help='Site associé à cette configuration API Controller'
)
# Champs nécessaires pour le fonctionnement du modèle
name = fields.Char(related='site_id.name', string='Nom', readonly=True)
api_type = fields.Selection(related='site_id.api_type', string='Type API', readonly=True)
verify_ssl = fields.Boolean(string='Verify SSL', default=True, help='Vérifier les certificats SSL. Désactivez cette option pour les certificats auto-signés.')
ssl_cert_file = fields.Binary(string='Certificat SSL personnalisé', attachment=True, help='Fichier de certificat SSL personnalisé (.pem ou .crt)')
ssl_cert_filename = fields.Char(string='Nom du fichier de certificat')
ssl_cert_path = fields.Char(string='Chemin du certificat', compute='_compute_ssl_cert_path', store=True, help='Chemin vers le fichier de certificat SSL')
last_sync = fields.Datetime(string='Dernière synchronisation')
@api.depends('ssl_cert_file', 'ssl_cert_filename')
def _compute_ssl_cert_path(self):
"""Calcule le chemin vers le fichier de certificat SSL
Cette méthode est appelée lorsque le fichier de certificat SSL est modifié.
Elle sauvegarde le certificat dans un fichier temporaire et stocke le chemin.
"""
import tempfile
import os
import base64
for record in self:
if record.ssl_cert_file and record.ssl_cert_filename:
# Créer un fichier temporaire pour stocker le certificat
fd, path = tempfile.mkstemp(suffix='.pem')
try:
# Décoder le contenu du certificat et l'écrire dans le fichier
cert_content = base64.b64decode(record.ssl_cert_file)
os.write(fd, cert_content)
# Stocker le chemin du fichier
record.ssl_cert_path = path
finally:
os.close(fd)
else:
record.ssl_cert_path = False
@api.model
def _check_required_fields(self, site):
"""Vérifie que les champs requis pour l'API Controller sont renseignés
Cette méthode est appelée par le modèle principal lors de la validation des contraintes.
Args:
site: L'enregistrement du site à vérifier
Raises:
ValidationError: Si des champs requis ne sont pas renseignés
"""
if not site.host:
raise ValidationError(_("Le champ 'Host' est requis pour l'API Controller."))
if not site.port:
raise ValidationError(_("Le champ 'Port' est requis pour l'API Controller."))
if not site.username:
raise ValidationError(_("Le champ 'Username' est requis pour l'API Controller."))
if not site.password:
raise ValidationError(_("Le champ 'Password' est requis pour l'API Controller."))
@api.model
def _create_or_update_api_log(self, api_log=None, **kwargs):
"""Crée ou met à jour un enregistrement de log API
Cette méthode d'aide simplifie la gestion des logs API en créant un nouvel enregistrement
si api_log est None, ou en mettant à jour l'enregistrement existant sinon.
Args:
api_log: L'enregistrement de log API existant, ou None pour en créer un nouveau
**kwargs: Les valeurs à écrire dans l'enregistrement
Returns:
L'enregistrement de log API créé ou mis à jour
"""
if not api_log:
# Valeurs par défaut pour la création
default_values = {
'site_id': self.id,
'api_type': 'controller',
'method': 'GET',
'endpoint': 'error_handler',
'start_time': fields.Datetime.now()
}
# Fusionner les valeurs par défaut avec les valeurs fournies
values = {**default_values, **kwargs}
return self.env['unifi.api.log'].create(values)
else:
# Mettre à jour l'enregistrement existant
api_log.write(kwargs)
return api_log
def _clear_irrelevant_fields(self, site):
"""Nettoie les champs qui ne sont pas pertinents pour l'API Controller
Cette méthode est appelée par le modèle principal lors du changement de type d'API.
Elle efface les champs spécifiques à l'API Site Manager.
Args:
site: L'enregistrement du site à nettoyer
"""
# Effacer les champs spécifiques à l'API Site Manager
site.api_key = False
site.mfa_enabled = False
site.mfa_token = False
# Controller configuration - Only used when api_type = 'controller'
controller_type = fields.Selection(
selection=[
('udm', 'UDM/UDR'),
('software', 'Software Controller')
],
string='Controller Type',
required=False,
default='udm',
help='Type of UniFi controller managing this site'
)
# Controller API fields - Only used when api_type = 'controller'
host = fields.Char(
string='Host',
help='IP address or hostname of the controller'
)
port = fields.Integer(
string='Port',
default=443,
help='Port number (default: 443)'
)
username = fields.Char(
string='Username',
help='Username for controller login'
)
password = fields.Char(
string='Password',
help='Password for controller login'
)
# Champs pour la gestion de session
session_cookies = fields.Text(
string='Session Cookies',
help='Cookies de session pour l\'API Controller',
readonly=True,
copy=False
)
csrf_token = fields.Text(
string='CSRF Token',
help='Token CSRF pour l\'API Controller',
readonly=True,
copy=False
)
last_login = fields.Datetime(
string='Dernière connexion',
help='Date et heure de la dernière connexion réussie',
readonly=True,
copy=False
)
# Endpoints API communs à tous les types de contrôleurs
COMMON_ENDPOINTS = {
'login': '/api/auth/login',
'logout': '/api/auth/logout',
'status': '/api/s/{site_id}/stat/status',
'sites': '/api/self/sites',
'devices': '/api/s/{site_id}/stat/device',
'device': '/api/s/{site_id}/stat/device/{mac}',
'clients': '/api/s/{site_id}/stat/sta',
'client': '/api/s/{site_id}/stat/sta/{mac}',
'networks': '/api/s/{site_id}/rest/networkconf',
'wlans': '/api/s/{site_id}/rest/wlanconf',
'users': '/api/s/{site_id}/list/user',
'firewall_rules': '/api/s/{site_id}/rest/firewallrule',
'port_forwards': '/api/s/{site_id}/rest/portforward',
'health': '/api/s/{site_id}/stat/health',
'dashboard': '/api/s/{site_id}/stat/dashboard',
'alarms': '/api/s/{site_id}/list/alarm',
'events': '/api/s/{site_id}/stat/event',
'dpi': '/api/s/{site_id}/stat/dpi',
'settings': '/api/s/{site_id}/get/setting',
'routing': '/api/s/{site_id}/rest/routing',
'system_info': '/api/s/{site_id}/stat/sysinfo',
'vouchers': '/api/s/{site_id}/stat/voucher',
'hotspot': '/api/s/{site_id}/rest/hotspot',
}
# Endpoints spécifiques au type de contrôleur UDM
UDM_ENDPOINTS = {
'login': '/api/auth/login',
'logout': '/api/auth/logout',
}
# Endpoints spécifiques au type de contrôleur Cloud Key
CLOUD_KEY_ENDPOINTS = {
'login': '/api/login',
'logout': '/api/logout',
}
def _get_endpoint(self, endpoint_name: str, site_id: str = 'default', **kwargs) -> str:
"""Récupère l'URL complète d'un endpoint API en fonction du type de contrôleur
Args:
endpoint_name: Nom de l'endpoint à récupérer
site_id: ID du site UniFi (par défaut: 'default')
**kwargs: Paramètres supplémentaires pour formater l'URL
Returns:
str: URL complète de l'endpoint
"""
# Sélectionner les endpoints en fonction du type de contrôleur
if self.controller_type == 'udm':
endpoints = {**self.COMMON_ENDPOINTS, **self.UDM_ENDPOINTS}
elif self.controller_type == 'software':
endpoints = {**self.COMMON_ENDPOINTS, **self.CLOUD_KEY_ENDPOINTS}
else:
endpoints = self.COMMON_ENDPOINTS
# Vérifier si l'endpoint existe
if endpoint_name not in endpoints:
_logger.error(f"Endpoint '{endpoint_name}' non trouvé pour le contrôleur de type '{self.controller_type}'")
return ''
# Récupérer l'endpoint et le formater avec les paramètres
endpoint = endpoints[endpoint_name]
params = {'site_id': site_id, **kwargs}
try:
return endpoint.format(**params)
except KeyError as e:
_logger.error(f"Paramètre manquant pour l'endpoint '{endpoint_name}': {str(e)}")
return ''
def _get_base_url(self) -> str:
"""Construit l'URL de base pour les requêtes API
Returns:
str: URL de base pour les requêtes API
"""
return f"https://{self.host}:{self.port}"
def _get_headers(self, csrf_token: str = '') -> Dict[str, str]:
"""Prépare les en-têtes pour les requêtes API
Args:
csrf_token: Token CSRF à inclure dans les en-têtes (optionnel)
Returns:
Dict[str, str]: En-têtes pour les requêtes API
"""
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'Odoo UniFi Integration'
}
# Ajouter le token CSRF si présent
if csrf_token:
headers['X-CSRF-Token'] = csrf_token
return headers
def _authenticate(self) -> bool:
"""Authentifie la session auprès du contrôleur UniFi
Returns:
bool: True si l'authentification a réussi, False sinon
"""
self.ensure_one()
if self.api_type != 'controller':
return False
# Désactiver les avertissements SSL si verify_ssl est False
if not self.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Créer une nouvelle session pour cette authentification
session = requests.Session()
# Construire l'URL de connexion
base_url = self._get_base_url()
login_endpoint = self._get_endpoint('login')
login_url = f"{base_url}{login_endpoint}"
# Préparer les données de connexion
login_data = {
'username': self.username,
'password': self.password,
'remember': True
}
# Préparer les en-têtes
headers = self._get_headers()
# Initialiser le log API
auth_api_log = self.env['unifi.api.log'].create({
'site_id': self.site_id.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Déterminer comment gérer la vérification SSL
verify = self.verify_ssl
# Si un certificat personnalisé est fourni, l'utiliser
if self.ssl_cert_path and self.verify_ssl:
verify = self.ssl_cert_path
_logger.info(f"Utilisation du certificat SSL personnalisé: {verify}")
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=verify,
timeout=10
)
# Mettre à jour le log API avec les données de réponse
auth_api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code == 200:
# Stocker les cookies de session et le token CSRF
self.session_cookies = json.dumps(dict(session.cookies.items()))
# Extraire le token CSRF si présent
csrf_token = ''
if 'X-CSRF-Token' in response.headers:
csrf_token = response.headers['X-CSRF-Token']
self.csrf_token = csrf_token
# Mettre à jour la date de dernière connexion
self.last_login = fields.Datetime.now()
return True
# Journaliser l'échec de connexion
_logger.error(f"Échec de connexion à l'API Controller: {response.status_code} - {response.text}")
return False
except Exception as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de l'authentification: {str(e)}"
auth_api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False
finally:
# Fermer la session
session.close()
def _logout(self) -> bool:
"""Déconnecte la session du contrôleur UniFi
Returns:
bool: True si la déconnexion a réussi, False sinon
"""
self.ensure_one()
if self.api_type != 'controller':
return False
# Vérifier si nous avons des cookies de session
if not self.session_cookies:
return True # Déjà déconnecté
# Créer une nouvelle session pour cette déconnexion
session = requests.Session()
# Restaurer les cookies de session
if self.session_cookies:
try:
cookies = json.loads(self.session_cookies)
for key, value in cookies.items():
session.cookies.set(key, value)
except json.JSONDecodeError:
_logger.error("Impossible de décoder les cookies de session")
return False
# Construire l'URL de déconnexion
base_url = self._get_base_url()
logout_endpoint = self._get_endpoint('logout')
logout_url = f"{base_url}{logout_endpoint}"
# Préparer les en-têtes
headers = self._get_headers(self.csrf_token if self.csrf_token else '')
# Initialiser le log API
logout_api_log = self.env['unifi.api.log'].create({
'site_id': self.site_id.id,
'api_type': 'controller',
'endpoint': logout_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de déconnexion
response = session.post(
logout_url,
headers=headers,
verify=self.verify_ssl,
timeout=10
)
# Mettre à jour le log API avec les données de réponse
logout_api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Effacer les cookies de session et le token CSRF
self.session_cookies = False
self.csrf_token = False
return response.status_code in [200, 204]
except Exception as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de la déconnexion: {str(e)}"
logout_api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False
finally:
# Fermer la session
session.close()
def _make_request(self, method: str, endpoint_name: str, site_id: str = '',
params: Dict[str, str] = {}, data: Dict[str, str] = {},
retry_auth: bool = True) -> Tuple[bool, Dict, int]:
"""Effectue une requête API au contrôleur UniFi
Args:
method: Méthode HTTP à utiliser (GET, POST, PUT, DELETE)
endpoint_name: Nom de l'endpoint à appeler
site_id: ID du site UniFi (par défaut: '')
params: Paramètres de requête (pour GET et DELETE)
data: Données de requête (pour POST et PUT)
retry_auth: Si True, réessaie avec une nouvelle authentification en cas d'échec
Returns:
Tuple[bool, Dict, int]: (Succès, Données de réponse, Code de statut HTTP)
"""
self.ensure_one()
if self.api_type != 'controller':
return False, {"error": "Type d'API non pris en charge"}, 400
# Désactiver les avertissements SSL si verify_ssl est False
if not self.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Créer une nouvelle session pour cette requête
session = requests.Session()
# Restaurer les cookies de session
if self.session_cookies:
try:
cookies = json.loads(self.session_cookies)
for key, value in cookies.items():
session.cookies.set(key, value)
except json.JSONDecodeError:
_logger.error("Impossible de décoder les cookies de session")
if retry_auth:
# Tenter une nouvelle authentification
if self._authenticate():
return self._make_request(method, endpoint_name, site_id, params, data, False)
return False, {"error": "Erreur de session"}, 401
# Construire l'URL complète
base_url = self._get_base_url()
endpoint = self._get_endpoint(endpoint_name, site_id)
url = f"{base_url}{endpoint}"
# Préparer les en-têtes
headers = self._get_headers(self.csrf_token if self.csrf_token else '')
# Journaliser l'appel API
request_api_log = self.env['unifi.api.log'].create({
'site_id': self.site_id.id,
'api_type': 'controller',
'endpoint': url,
'method': method,
'request_headers': json.dumps(headers),
'request_params': json.dumps(params) if params else '',
'request_body': json.dumps(data) if data else '',
'start_time': fields.Datetime.now()
})
try:
# Déterminer comment gérer la vérification SSL
verify = self.verify_ssl
# Si un certificat personnalisé est fourni, l'utiliser
if self.ssl_cert_path and self.verify_ssl:
verify = self.ssl_cert_path
_logger.debug(f"Utilisation du certificat SSL personnalisé: {verify}")
# Effectuer la requête
if method == 'GET':
response = session.get(url, params=params, headers=headers, verify=verify, timeout=10)
elif method == 'POST':
response = session.post(url, json=data, headers=headers, verify=verify, timeout=10)
elif method == 'PUT':
response = session.put(url, json=data, headers=headers, verify=verify, timeout=10)
elif method == 'DELETE':
response = session.delete(url, params=params, headers=headers, verify=verify, timeout=10)
else:
return False, {"error": "Méthode HTTP non prise en charge"}, 400
# Mettre à jour le log API avec les données de réponse
request_api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la session a expiré (401 Unauthorized)
if response.status_code == 401 and retry_auth:
# Tenter une nouvelle authentification
if self._authenticate():
return self._make_request(method, endpoint_name, site_id, params, data, False)
return False, {"error": "Session expirée et échec de réauthentification"}, 401
# Traiter la réponse
if response.status_code in [200, 201, 204]:
try:
# Certains endpoints peuvent retourner une réponse vide
if not response.text:
return True, {}, response.status_code
# Décoder la réponse JSON
json_response = response.json()
return True, json_response, response.status_code
except json.JSONDecodeError:
return False, {"error": "Erreur de décodage JSON"}, response.status_code
# Gérer les erreurs
return False, {"error": f"Erreur HTTP {response.status_code}"}, response.status_code
except Exception as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de la requête: {str(e)}"
request_api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False, {"error": str(e)}, 500
finally:
# Fermer la session
session.close()
def _get(self, endpoint_name: str, site_id: str = '',
params: Dict[str, str] = {}) -> Tuple[bool, Dict, int]:
"""Effectue une requête GET
Args:
endpoint_name: Nom de l'endpoint à appeler
site_id: ID du site UniFi (par défaut: '')
params: Paramètres de requête
Returns:
Tuple[bool, Dict, int]: (Succès, Données de réponse, Code de statut HTTP)
"""
return self._make_request('GET', endpoint_name, site_id, params=params)
def _post(self, endpoint_name: str, site_id: str = '',
data: Dict[str, str] = {}) -> Tuple[bool, Dict, int]:
"""Effectue une requête POST
Args:
endpoint_name: Nom de l'endpoint à appeler
site_id: ID du site UniFi (par défaut: '')
data: Données de requête
Returns:
Tuple[bool, Dict, int]: (Succès, Données de réponse, Code de statut HTTP)
"""
return self._make_request('POST', endpoint_name, site_id, data=data)
def _put(self, endpoint_name: str, site_id: str = '',
data: Dict[str, str] = {}) -> Tuple[bool, Dict, int]:
"""Effectue une requête PUT
Args:
endpoint_name: Nom de l'endpoint à appeler
site_id: ID du site UniFi (par défaut: '')
data: Données de requête
Returns:
Tuple[bool, Dict, int]: (Succès, Données de réponse, Code de statut HTTP)
"""
return self._make_request('PUT', endpoint_name, site_id, data=data)
def _delete(self, endpoint_name: str, site_id: str = '',
params: Dict[str, str] = {}) -> Tuple[bool, Dict, int]:
"""Effectue une requête DELETE
Args:
endpoint_name: Nom de l'endpoint à appeler
site_id: ID du site UniFi (par défaut: '')
params: Paramètres de requête
Returns:
Tuple[bool, Dict, int]: (Succès, Données de réponse, Code de statut HTTP)
"""
return self._make_request('DELETE', endpoint_name, site_id, params=params)
def _test_controller_connection(self):
"""Test connection to the Controller API"""
self.ensure_one()
if self.api_type != 'controller':
return False
try:
# Tester l'authentification
if not self._authenticate():
return False
# Tester la récupération du statut du système
success, response, status_code = self._get('status')
if not success:
self._logout()
return False
# Déconnexion propre
self._logout()
return True
except Exception as e:
_logger.error(f"Erreur lors du test de connexion: {str(e)}")
return False
@api.model
def get_device_data(self, site, mac_address=None):
"""Récupère les données d'un appareil spécifique ou de tous les appareils du site
Cette méthode utilise l'API Controller pour obtenir les informations sur les appareils.
Args:
site: L'enregistrement du site UniFi
mac_address: Adresse MAC de l'appareil spécifique à récupérer (optionnel)
Returns:
dict ou list: Données de l'appareil ou liste des données de tous les appareils
"""
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données des appareils
if mac_address:
# URL pour un appareil spécifique
device_url = f"{base_url}/api/s/{site.site_id}/stat/device/{mac_address}"
else:
# URL pour tous les appareils
device_url = f"{base_url}/api/s/{site.site_id}/stat/device"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': device_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des appareils
response = session.get(
device_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données des appareils: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data:
_logger.error("Aucune donnée d'appareil trouvée dans la réponse")
return False
# Retourner les données des appareils
if mac_address and data['data']:
# Retourner les données de l'appareil spécifique
return data['data'][0] if data['data'] else False
else:
# Retourner la liste des données de tous les appareils
return data['data']
except (RequestException, json.JSONDecodeError) as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de la récupération des données des appareils: {str(e)}"
if 'api_log' in locals() and api_log:
api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False
finally:
# Fermer la session
session.close()
@api.model
def get_system_info_data(self, site):
"""Récupère les données d'information système du site
Cette méthode utilise l'API Controller pour obtenir les informations système.
Args:
site: L'enregistrement du site UniFi
Returns:
dict: Données d'information système
"""
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données système
system_url = f"{base_url}/api/s/{site.site_id}/stat/sysinfo"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': system_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données système
response = session.get(
system_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données système: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data or not data['data']:
_logger.error("Aucune donnée système trouvée dans la réponse")
return False
# Retourner les données système (premier élément de la liste)
return data['data'][0]
except (RequestException, json.JSONDecodeError) as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de la récupération des données système: {str(e)}"
if 'api_log' in locals() and api_log:
api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False
finally:
# Fermer la session
session.close()
@api.model
def get_vlan_data(self, site):
"""Récupère les données des VLANs du site
Cette méthode utilise l'API Controller pour obtenir les informations sur les VLANs.
Args:
site: L'enregistrement du site UniFi
Returns:
list: Liste des données de tous les VLANs
"""
# Initialiser api_log à None pour éviter les erreurs de variable potentiellement indépendante
api_log = None
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données des VLANs
vlan_url = f"{base_url}/api/s/{site.site_id}/rest/vlan"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': vlan_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des VLANs
response = session.get(
vlan_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données des VLANs: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data:
_logger.error("Aucune donnée de VLAN trouvée dans la réponse")
return False
# Retourner la liste des données de tous les VLANs
return data['data']
except (RequestException, json.JSONDecodeError) as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de la récupération des données des VLANs: {str(e)}"
if 'api_log' in locals() and api_log:
api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False
finally:
# Fermer la session
session.close()
@api.model
def get_network_data(self, site):
"""Récupère les données des réseaux du site
Cette méthode utilise l'API Controller pour obtenir les informations sur les réseaux.
Args:
site: L'enregistrement du site UniFi
Returns:
list: Liste des données de tous les réseaux
"""
# Initialiser api_log à None pour éviter les erreurs de variable potentiellement indépendante
api_log = None
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données des réseaux
network_url = f"{base_url}/api/s/{site.site_id}/rest/networkconf"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': network_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des réseaux
response = session.get(
network_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données des réseaux: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data:
_logger.error("Aucune donnée de réseau trouvée dans la réponse")
return False
# Retourner la liste des données de tous les réseaux
return data['data']
except (RequestException, json.JSONDecodeError) as e:
# Journaliser l'erreur
error_msg = f"Erreur lors de la récupération des données des réseaux: {str(e)}"
if 'api_log' in locals() and api_log:
api_log.write({
'end_time': fields.Datetime.now(),
'error_message': error_msg
})
_logger.error(error_msg)
return False
finally:
# Fermer la session
session.close()
def _sync_controller(self):
"""Synchronize data with the Controller API"""
self.ensure_one()
if self.api_type != 'controller':
return False
# Create a sync job to track progress
sync_job = self.env['unifi.sync.job'].create({
'site_id': self.id,
'api_type': 'controller',
'status': 'running',
'start_time': fields.Datetime.now()
})
try:
# Implement synchronization logic here
# This would typically involve:
# 1. Authenticating with the controller
# 2. Fetching device data
# 3. Fetching network data
# 4. Fetching client data
# 5. Updating local records
# For now, just a placeholder
_logger.info("Starting synchronization with Controller API for site %s", self.name)
# Update sync job with success status
sync_job.write({
'status': 'completed',
'end_time': fields.Datetime.now(),
'message': 'Synchronization completed successfully'
})
# Update site's last sync timestamp
self.write({
'last_sync': fields.Datetime.now()
})
return True
except Exception as e:
# Log the error and update sync job
_logger.error("Error during synchronization with Controller API: %s", str(e))
sync_job.write({
'status': 'failed',
'end_time': fields.Datetime.now(),
'message': f'Synchronization failed: {str(e)}'
})
return False
@api.model
def get_user_data(self, site):
"""Récupère les données des utilisateurs du site
Cette méthode utilise l'API Controller pour obtenir les informations sur les utilisateurs.
Args:
site: L'enregistrement du site UniFi
Returns:
list: Liste des données de tous les utilisateurs
"""
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données des utilisateurs
# Pour les utilisateurs réguliers
user_url = f"{base_url}/api/s/{site.site_id}/rest/user"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': user_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des utilisateurs
response = session.get(
user_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données des utilisateurs: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data:
_logger.warning("Aucune donnée d'utilisateur trouvée dans la réponse de l'API Controller")
return []
# Récupérer également les utilisateurs invités
guest_url = f"{base_url}/api/s/{site.site_id}/rest/guest"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': guest_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des utilisateurs invités
guest_response = session.get(
guest_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': guest_response.status_code,
'response_headers': json.dumps(dict(guest_response.headers)),
'response_body': guest_response.text
})
# Si la requête pour les invités a réussi, ajouter les données à la liste
if guest_response.status_code == 200:
guest_data = guest_response.json()
if 'data' in guest_data:
# Marquer ces utilisateurs comme invités
for guest in guest_data['data']:
guest['is_guest'] = True
# Ajouter les invités à la liste des utilisateurs
data['data'].extend(guest_data['data'])
return data['data']
except RequestException as e:
# Gérer les erreurs de requête
_logger.error("Erreur lors de la communication avec l'API Controller: %s", str(e))
return False
finally:
# Fermer la session
session.close()
@api.model
def get_firewall_data(self, site):
"""Récupère les données des règles de pare-feu du site
Cette méthode utilise l'API Controller pour obtenir les informations sur les règles de pare-feu.
Args:
site: L'enregistrement du site UniFi
Returns:
list: Liste des données de toutes les règles de pare-feu
"""
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données des règles de pare-feu
firewall_url = f"{base_url}/api/s/{site.site_id}/rest/firewallrule"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': firewall_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des règles de pare-feu
response = session.get(
firewall_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données des règles de pare-feu: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data:
_logger.warning("Aucune donnée de règle de pare-feu trouvée dans la réponse de l'API Controller")
return []
return data['data']
except RequestException as e:
# Gérer les erreurs de requête
_logger.error("Erreur lors de la communication avec l'API Controller: %s", str(e))
return False
finally:
# Fermer la session
session.close()
@api.model
def get_port_forward_data(self, site):
"""Récupère les données des redirections de port du site
Cette méthode utilise l'API Controller pour obtenir les informations sur les redirections de port.
Args:
site: L'enregistrement du site UniFi
Returns:
list: Liste des données de toutes les redirections de port
"""
# Créer une session pour les requêtes
session = requests.Session()
# Construire l'URL de base
base_url = f"https://{site.host}:{site.port}"
# URL pour la connexion
login_url = f"{base_url}/api/login"
# Données de connexion
login_data = {
'username': site.username,
'password': site.password,
'remember': True
}
# En-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Désactiver les avertissements SSL si verify_ssl est False
if not site.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': login_url,
'method': 'POST',
'request_headers': json.dumps(headers),
'request_body': json.dumps(login_data),
'start_time': fields.Datetime.now()
})
try:
# Effectuer la requête de connexion
response = session.post(
login_url,
json=login_data,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la connexion a réussi
if response.status_code != 200:
_logger.error("Erreur de connexion à l'API Controller: %s", response.text)
return False
# Construire l'URL pour récupérer les données des redirections de port
port_forward_url = f"{base_url}/api/s/{site.site_id}/rest/portforward"
# Journaliser l'appel API
api_log = self.env['unifi.api.log'].create({
'site_id': site.id,
'api_type': 'controller',
'endpoint': port_forward_url,
'method': 'GET',
'request_headers': json.dumps(headers),
'start_time': fields.Datetime.now()
})
# Effectuer la requête pour récupérer les données des redirections de port
response = session.get(
port_forward_url,
headers=headers,
verify=site.verify_ssl,
timeout=10
)
# Mettre à jour le journal API avec les données de réponse
api_log.write({
'end_time': fields.Datetime.now(),
'status_code': response.status_code,
'response_headers': json.dumps(dict(response.headers)),
'response_body': response.text
})
# Vérifier si la requête a réussi
if response.status_code != 200:
_logger.error("Erreur lors de la récupération des données des redirections de port: %s", response.text)
return False
# Analyser la réponse JSON
data = response.json()
# Vérifier si la réponse contient des données
if 'data' not in data:
_logger.warning("Aucune donnée de redirection de port trouvée dans la réponse de l'API Controller")
return []
return data['data']
except RequestException as e:
# Gérer les erreurs de requête
_logger.error("Erreur lors de la communication avec l'API Controller: %s", str(e))
return False
finally:
# Fermer la session
session.close()