Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

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