Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

469 строки
18 KiB

  1. import json
  2. import subprocess
  3. import logging
  4. import ipaddress
  5. #import api_utils.carddav_util as carddav_util
  6. #from .api_utils import carddav_util
  7. from enum import Enum
  8. from typing import Any, Dict, List, Optional
  9. # import wave
  10. import os
  11. import shutil
  12. # import enviroment variables
  13. import config_env
  14. #other
  15. from pathlib import Path
  16. from tempfile import NamedTemporaryFile
  17. from typing import Callable
  18. import base64
  19. from datetime import datetime, timedelta
  20. import csv
  21. from api_utils import api_utils, coerce_methods
  22. from collections import OrderedDict
  23. from pydantic import BaseModel, Field
  24. from fastapi import Depends, FastAPI, HTTPException, File, UploadFile, Query
  25. from fastapi.encoders import jsonable_encoder
  26. from fastapi.openapi.docs import get_swagger_ui_html
  27. from fastapi.openapi.utils import get_openapi
  28. from starlette.status import HTTP_403_FORBIDDEN
  29. from starlette.responses import RedirectResponse, Response, JSONResponse
  30. from starlette.requests import Request
  31. from starlette.middleware.cors import CORSMiddleware
  32. from starlette.responses import FileResponse
  33. from starlette.types import ASGIApp, Message, Receive, Scope, Send
  34. from models.cellular_hardware import cellularHardware
  35. from models.cellular_hardwares import cellularHardwares
  36. from models.call import call, post_call
  37. from models.calls import calls
  38. from models.httpresponse import httpResponse400, httpResponse200, httpResponse500
  39. from fastapi_login import LoginManager
  40. from core.security import manager, NotAuthenticatedException
  41. from security import get_current_user
  42. from routes import auth as _auth
  43. auth_router = _auth.router
  44. from routes import user as _user
  45. user_router = _user.router
  46. #from routes.posts import router as posts_router
  47. from routes import majornet as _majornet
  48. majornet_router = _majornet.router
  49. from routes import presence as _presence
  50. presence_router = _presence.router
  51. from routes import contacts as _contacts
  52. contacts_router = _contacts.router
  53. from routes import reslevis as _reslevis
  54. reslevis_router = _reslevis.router
  55. #security
  56. from fastapi import FastAPI, Security
  57. from fastapi.security import OAuth2AuthorizationCodeBearer
  58. #Proxy al CORE ResLevis
  59. import httpx
  60. from mqtt_gateway_monitor import MqttGatewayMonitor
  61. AUTH_URL = config_env.KEYCLOAK_AUTH_URL
  62. TOKEN_URL = config_env.KEYCLOAK_TOKEN_URL
  63. oauth2 = OAuth2AuthorizationCodeBearer(
  64. authorizationUrl=AUTH_URL,
  65. tokenUrl=TOKEN_URL,
  66. scopes={"items:read": "Read items", "items:write": "Write items"},
  67. )
  68. log = logging.getLogger(__name__) # pylint: disable=invalid-name
  69. DEBUG = True
  70. app = FastAPI(title="MajorNet API", redoc_url=None, docs_url=None, openapi_url=None)
  71. ####DEV
  72. ##app = FastAPI(title=PROJECT_NAME)
  73. app.debug = True
  74. #logging
  75. from audit import AuditMiddleware
  76. app.add_middleware(AuditMiddleware)
  77. logging.basicConfig(filename='/data/var/log/FastAPI/AuthenticatedAPI.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
  78. logging.basicConfig(
  79. level=logging.INFO,
  80. format="%(levelname)s:%(name)s:%(message)s"
  81. )
  82. #ResLevis CORE Proxying
  83. CORE_BASE_URL = config_env.CORE_API_URL.rstrip("/")
  84. HOP_BY_HOP = {
  85. "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
  86. "te", "trailers", "transfer-encoding", "upgrade",
  87. }
  88. def _filter_headers(headers: dict) -> dict:
  89. return {k: v for k, v in headers.items() if k.lower() not in HOP_BY_HOP}
  90. async def _forward_to_core(request: Request, body: bytes) -> Response:
  91. url = f"{CORE_BASE_URL}{request.url.path}"
  92. async with httpx.AsyncClient(timeout=30.0) as client:
  93. resp = await client.request(
  94. request.method,
  95. url,
  96. params=request.query_params,
  97. content=body,
  98. headers=_filter_headers(dict(request.headers)),
  99. )
  100. return Response(
  101. content=resp.content,
  102. status_code=resp.status_code,
  103. headers=_filter_headers(dict(resp.headers)),
  104. media_type=resp.headers.get("content-type"),
  105. )
  106. ALLOWED_HOSTS = ["*"]
  107. app.add_middleware(
  108. CORSMiddleware,
  109. allow_origins=ALLOWED_HOSTS,
  110. allow_credentials=True,
  111. allow_methods=["*"],
  112. allow_headers=["*"],
  113. )
  114. # MQTT gateway monitor
  115. mqtt_monitor = MqttGatewayMonitor()
  116. @app.on_event("startup")
  117. async def start_mqtt_monitor():
  118. await mqtt_monitor.start()
  119. @app.on_event("shutdown")
  120. async def stop_mqtt_monitor():
  121. await mqtt_monitor.stop()
  122. #ResLevis CORE middleware
  123. @app.middleware("http")
  124. async def local_then_core(request: Request, call_next):
  125. # only proxy CRUD for Reslevis (change prefix or methods if needed)
  126. if request.url.path.startswith("/reslevis/") and request.method in {"POST", "PUT", "DELETE", "PATCH"}:
  127. body = await request.body() # raw body preserved
  128. local_resp = await call_next(request) # local storage runs here
  129. if local_resp.status_code >= 400:
  130. return local_resp # stop if local failed
  131. try:
  132. core_resp = await _forward_to_core(request, body)
  133. except httpx.RequestError:
  134. return local_resp
  135. if core_resp.status_code >= 400:
  136. return local_resp
  137. return core_resp
  138. return await call_next(request)
  139. @app.exception_handler(NotAuthenticatedException)
  140. def auth_exception_handler(request: Request, exc: NotAuthenticatedException):
  141. """
  142. Redirect the user to the login page if not logged in
  143. """
  144. return RedirectResponse(url='/login')
  145. # Nasconde i dettagli degli errori 5xx
  146. @app.exception_handler(HTTPException)
  147. async def http_exception_handler(request: Request, exc: HTTPException):
  148. if exc.status_code >= 500:
  149. log.exception("HTTP %s: %s", exc.status_code, exc.detail)
  150. return JSONResponse(status_code=exc.status_code, content={"detail": "Internal Server Error"})
  151. return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
  152. @app.exception_handler(Exception)
  153. async def unhandled_exception_handler(request: Request, exc: Exception):
  154. log.exception("Unhandled exception")
  155. return JSONResponse(status_code=500, content={"detail": "Internal Server Error"})
  156. app.include_router(auth_router)
  157. app.include_router(user_router)
  158. #app.include_router(posts_router)
  159. app.include_router(presence_router)
  160. app.include_router(majornet_router)
  161. app.include_router(contacts_router)
  162. app.include_router(reslevis_router, prefix="/reslevis", tags=["Reslevis"])
  163. @app.get("/")
  164. async def root():
  165. #return {"url": "/docs"}
  166. return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")
  167. @app.get("/ble-ai/infer", tags=["BLE-AI"], dependencies=[Depends(get_current_user)])
  168. async def get_ble_ai_infer(mac: Optional[List[str]] = Query(default=None)):
  169. path = config_env.BLE_AI_INFER_CSV
  170. if not os.path.isfile(path):
  171. raise HTTPException(status_code=404, detail="CSV not found")
  172. mac_filter = None
  173. if mac:
  174. mac_filter = set()
  175. for item in mac:
  176. if not item:
  177. continue
  178. for part in str(item).split(","):
  179. part = part.strip()
  180. if part:
  181. mac_filter.add(part.lower())
  182. items = []
  183. with open(path, newline="") as f:
  184. reader = csv.DictReader(f, delimiter=";")
  185. for row in reader:
  186. row_mac = row.get("mac")
  187. if mac_filter is not None:
  188. if not row_mac or row_mac.lower() not in mac_filter:
  189. continue
  190. try:
  191. items.append(
  192. {
  193. "mac": row_mac,
  194. "z": int(row["z"]) if row.get("z") not in (None, "") else None,
  195. "x": int(row["x"]) if row.get("x") not in (None, "") else None,
  196. "y": int(row["y"]) if row.get("y") not in (None, "") else None,
  197. }
  198. )
  199. except (KeyError, ValueError):
  200. continue
  201. return {"items": items, "count": len(items)}
  202. @app.get("/openapi.json/", tags=["Documentation"])
  203. async def get_open_api_endpoint():
  204. #async def get_open_api_endpoint(current_user: User = Depends(get_current_active_user)):
  205. return JSONResponse(get_openapi(title="MajorNet APIs", version="1.0", routes=app.routes))
  206. @app.get("/docs/", tags=["Documentation"])
  207. #async def get_documentation(current_user: User = Depends(get_current_active_user)):
  208. async def get_documentation():
  209. if DEBUG: print("SONO IN /DOCS")
  210. return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")
  211. @app.post("/majortel/call/", tags=["Majortel"])
  212. async def route_call(active_user=Depends(manager),callerNumber=None, calledNumber=None):
  213. try:
  214. if DEBUG: print("Entro nel TRY")
  215. # Check if the callerNumber sent is an ip address and retrieve the associated number
  216. ipaddress.ip_address(callerNumber)
  217. callerNumberRetrieved = os.popen('asterisk -rx "sip show peers" | grep ' + callerNumber).read()
  218. callerNumberRetrieved = callerNumberRetrieved.split("/")
  219. callerNumber = callerNumberRetrieved[0]
  220. except:
  221. if DEBUG: print("EXCEPT")
  222. mode = "callFromTo"
  223. from_ = callerNumber
  224. to_ = calledNumber
  225. if DEBUG: print("DATI ARRIVATI: ")
  226. if DEBUG: print(callerNumber)
  227. if DEBUG: print(calledNumber)
  228. subprocess.Popen(['perl', '/usr/local/bin/ast/voice.pl', mode, from_, to_], stdout=subprocess.PIPE)
  229. return
  230. @app.post("/majortel/ring/", tags=["Majortel"])
  231. async def route_ring(active_user=Depends(manager),calledNumber=None, calledId=None, ringTime=None):
  232. try:
  233. if DEBUG: print("Entro nel TRY")
  234. # Check if the callerNumber sent is an ip address and retrieve the associated number
  235. ipaddress.ip_address(calledNumber)
  236. calledNumberRetrieved = os.popen('asterisk -rx "sip show peers" | grep ' + calledNumber).read()
  237. calledNumberRetrieved = calledNumberRetrieved.split("/")
  238. calledNumber = calledNumberRetrieved[0]
  239. except:
  240. if DEBUG: print("EXCEPT")
  241. mode = "ringAlert"
  242. to_ = calledNumber
  243. calledId_ = calledId
  244. ringTime_ = ringTime
  245. if DEBUG: print("DATI ARRIVATI: ")
  246. if DEBUG: print(calledNumber)
  247. if DEBUG: print(calledId)
  248. if DEBUG: print(ringTime)
  249. subprocess.Popen(['perl', '/usr/local/bin/ast/voice.pl', mode, to_, calledId_, ringTime_], stdout=subprocess.PIPE)
  250. return
  251. @app.get("/majortel/hardware/", response_model=cellularHardwares, tags=["Majortel"])
  252. #async def majortel_hardware_get(current_user: User = Depends(get_current_active_user)):
  253. async def majortel_hardware_get():
  254. gsm_temp_list = "GSM span1: Provisioned, Up, Active"
  255. response = {"CellularHardwares": []}
  256. hardware_dict = {}
  257. myCmd = os.popen('asterisk -rx "gsm show spans"').read()
  258. myCmd = myCmd.split("\n")
  259. # cancello l'ultimo item della lista poichè, risultando vuoto dopo lo split,
  260. # genera errore "index out of range" nei successivi accessi alla lista
  261. if DEBUG: print("spans: ")
  262. if DEBUG: print(myCmd)
  263. myCmd = os.popen('asterisk -rx "dongle show devices"').read()
  264. myCmd = myCmd.split("\n")
  265. # cancello il primo item della lista poichè contiene la legenda
  266. del myCmd[0]
  267. del myCmd[-1]
  268. # costruisco la response
  269. for device in myCmd:
  270. device = device.split()
  271. hardware_id = device[0]
  272. current_device = os.popen('asterisk -rx "dongle show device state ' + hardware_id + '"').read()
  273. current_device = current_device.split("\n")
  274. # cancello il primo e gli ultimi item della lista poichè, risultando vuoti dopo lo split,
  275. # generano errore "index out of range" nei successivi accessi alla lista
  276. del current_device[0]
  277. del current_device[-1]
  278. del current_device[-1]
  279. # costruisco un dizionario a partire dall'output della system call
  280. for row in current_device:
  281. row = row.split(":")
  282. row[0] = row[0].strip()
  283. row[1] = row[1].strip()
  284. hardware_dict[row[0]] = row[1]
  285. hardware_id = hardware_dict["Device"]
  286. status = hardware_dict["State"]
  287. signal = hardware_dict["RSSI"]
  288. signal = int(signal[0:2])
  289. operator = hardware_dict["Provider Name"]
  290. device_obj = {"id": hardware_id, "description": "description", "status": status, "signal_level": signal,
  291. "registered_number": "To Do", "operator": operator}
  292. response["CellularHardwares"].append(device_obj)
  293. return response
  294. @app.get("/majortel/hardware/{item_id}", response_model=cellularHardware, tags=["Majortel"], responses={400: {"model": httpResponse400}})
  295. #async def majortel_hardware_id_get(item_id: str, current_user: User = Depends(get_current_active_user)):
  296. async def majortel_hardware_id_get(item_id: str,active_user=Depends(manager)):
  297. hardware_response = {}
  298. hardware_dict = {}
  299. current_device = os.popen('asterisk -rx "dongle show device state ' + item_id + '"').read()
  300. current_device = current_device.split("\n")
  301. # cancello il primo e gli ultimi item della lista poichè, risultando vuoti dopo lo split,
  302. # generano errore "index out of range" nei successivi accessi alla lista
  303. del current_device[0]
  304. if (current_device[0]):
  305. del current_device[-1]
  306. del current_device[-1]
  307. # costruisco un dizionario a partire dall'output della system call
  308. for row in current_device:
  309. row = row.split(":")
  310. row[0] = row[0].strip()
  311. row[1] = row[1].strip()
  312. hardware_dict[row[0]] = row[1]
  313. hardware_id = hardware_dict["Device"]
  314. status = hardware_dict["State"]
  315. signal = hardware_dict["RSSI"]
  316. signal = int(signal[0:2])
  317. operator = hardware_dict["Provider Name"]
  318. hardware_response = {"id": hardware_id, "description": "description", "status": status, "signal_level": signal,
  319. "registered_number": "To Do", "operator": operator}
  320. return hardware_response
  321. else:
  322. return JSONResponse(status_code=404, content={"message": "Device not found"})
  323. @app.get("/majortel/calls/", response_model=calls, tags=["Majortel"])
  324. #async def majortel_calls_get(current_user: User = Depends(get_current_active_user)):
  325. async def majortel_calls_get(active_user=Depends(manager)):
  326. response = {"Calls": []}
  327. p = subprocess.Popen(['perl', '/opt/api_project_python/addCallQueue.pl', "get_calls"], stdout=subprocess.PIPE)
  328. calls = str(p.stdout.read())
  329. calls = calls.split("'")
  330. response1 = calls
  331. calls = calls[1].split("||")
  332. # cancello l'ultimo item della lista poichè, risultando vuoto dopo lo split,
  333. # genera errore "index out of range" nei successivi accessi alla lista
  334. del calls[-1]
  335. for call in calls:
  336. call = call.split("|")
  337. call_id = call[0]
  338. status = call[1]
  339. history = call[2]
  340. ntel = call[3]
  341. ackid = call[4]
  342. call_obj = {"id": call_id, "status": status, "history": history, "called_number": ntel, "ack_id": ackid}
  343. response["Calls"].append(call_obj)
  344. return response
  345. @app.get("/majortel/calls/{call_id}/", response_model=call, tags=["Majortel"])
  346. async def majortel_calls_id_get(call_id,active_user=Depends(manager)):
  347. p = subprocess.Popen(['perl', '/var/opt/FastAPI/addCallQueue.pl', "call_id", call_id],
  348. stdout=subprocess.PIPE)
  349. call = str(p.stdout.read())
  350. call = call.split("|")
  351. call_id = call[0].split("'")[1]
  352. status = call[1]
  353. # history = call[2]
  354. ntel = call[3]
  355. ackid = call[4]
  356. ackid = int(ackid.split("'")[0])
  357. response = {"id": call_id, "status": status, "called_number": ntel, "ack_id": ackid}
  358. return response
  359. # response_model=post_call,
  360. @app.post("/majortel/calls", response_model=post_call, tags=["Majortel"])
  361. async def majortel_calls_post(hw_id, ack_id, called_number, text_message="codice di riscontro ", timeout=30,
  362. retry=1, file: UploadFile = File(None),active_user=Depends(manager)):
  363. # controllo se l'hw_id corrisponde ai devices connessi
  364. current_device = os.popen('asterisk -rx "dongle show device state ' + hw_id + '"').read()
  365. current_device = current_device.split("\n")
  366. del current_device[0]
  367. if (current_device[0]):
  368. curr_time = datetime.now()
  369. call_id = str(curr_time.strftime('%y')) + str(curr_time.strftime('%m')) + str(curr_time.strftime('%d')) + str(
  370. curr_time.strftime('%H')) + str(curr_time.strftime('%M')) + str(curr_time.strftime('%S')) + str(
  371. curr_time.strftime('%f'))
  372. file_path = ""
  373. if (file):
  374. nomefile = file.filename
  375. extension = nomefile.split(".")[1]
  376. if (extension == "wav"):
  377. file_path = "/data/service/voip.majornet/ivr/" + call_id
  378. renamed_file = file_path + ".wav"
  379. with open(renamed_file, "wb") as f:
  380. shutil.copyfileobj(file.file, f)
  381. f.close()
  382. # p = subprocess.Popen(['(cd /tmp/audioCalls;/usr/local/bin/sox_wav2gsm)'],stdout=subprocess.PIPE)
  383. # os.popen('/usr/local/bin/sox_wav2gsm 1>/dev/null 2>/dev/null')
  384. subprocess.Popen(
  385. ['perl', '/opt/api_project_python/addCallQueue.pl', "voicegateway", "digiovine@afasystems.it", call_id,
  386. called_number, ack_id, text_message, "retry message here", timeout, retry, "yes", "notification_to",
  387. "notification_bcc", "setup", file_path, "fromemailssss", hw_id], stdout=subprocess.PIPE)
  388. response = {"id": call_id}
  389. return response
  390. else:
  391. return JSONResponse(status_code=404, content={"message": "Device not found"})
  392. @app.delete("/majortel/calls/", tags=["Majortel"],
  393. responses={200: {"model": httpResponse200}, 400: {"model": httpResponse400},
  394. 500: {"model": httpResponse500}})
  395. #async def majortel_calls_id_delete(call_id, current_user: User = Depends(get_current_active_user)):
  396. async def majortel_calls_id_delete(call_id,active_user=Depends(manager)):
  397. p = subprocess.Popen(['perl', '/opt/api_project_python/addCallQueue.pl', "delete_call", call_id],
  398. stdout=subprocess.PIPE)
  399. response = str(p.stdout.read())
  400. response = response.split("'")[1]
  401. if (response == "Success"):
  402. return JSONResponse(status_code=200, content={"message": "Success"})
  403. elif (response == "Not found"):
  404. return JSONResponse(status_code=404, content={"message": "Call not found"})
  405. else:
  406. return JSONResponse(status_code=500, content={"message": "Server error"})