選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

434 行
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
  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():
  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. items = []
  164. with open(path, newline="") as f:
  165. reader = csv.DictReader(f, delimiter=";")
  166. for row in reader:
  167. try:
  168. items.append(
  169. {
  170. "mac": row.get("mac"),
  171. "z": int(row["z"]) if row.get("z") not in (None, "") else None,
  172. "x": int(row["x"]) if row.get("x") not in (None, "") else None,
  173. "y": int(row["y"]) if row.get("y") not in (None, "") else None,
  174. }
  175. )
  176. except (KeyError, ValueError):
  177. continue
  178. return {"items": items, "count": len(items)}
  179. @app.post("/majortel/call/", tags=["Majortel"])
  180. async def route_call(active_user=Depends(manager),callerNumber=None, calledNumber=None):
  181. try:
  182. if DEBUG: print("Entro nel TRY")
  183. # Check if the callerNumber sent is an ip address and retrieve the associated number
  184. ipaddress.ip_address(callerNumber)
  185. callerNumberRetrieved = os.popen('asterisk -rx "sip show peers" | grep ' + callerNumber).read()
  186. callerNumberRetrieved = callerNumberRetrieved.split("/")
  187. callerNumber = callerNumberRetrieved[0]
  188. except:
  189. if DEBUG: print("EXCEPT")
  190. mode = "callFromTo"
  191. from_ = callerNumber
  192. to_ = calledNumber
  193. if DEBUG: print("DATI ARRIVATI: ")
  194. if DEBUG: print(callerNumber)
  195. if DEBUG: print(calledNumber)
  196. subprocess.Popen(['perl', '/usr/local/bin/ast/voice.pl', mode, from_, to_], stdout=subprocess.PIPE)
  197. return
  198. @app.post("/majortel/ring/", tags=["Majortel"])
  199. async def route_ring(active_user=Depends(manager),calledNumber=None, calledId=None, ringTime=None):
  200. try:
  201. if DEBUG: print("Entro nel TRY")
  202. # Check if the callerNumber sent is an ip address and retrieve the associated number
  203. ipaddress.ip_address(calledNumber)
  204. calledNumberRetrieved = os.popen('asterisk -rx "sip show peers" | grep ' + calledNumber).read()
  205. calledNumberRetrieved = calledNumberRetrieved.split("/")
  206. calledNumber = calledNumberRetrieved[0]
  207. except:
  208. if DEBUG: print("EXCEPT")
  209. mode = "ringAlert"
  210. to_ = calledNumber
  211. calledId_ = calledId
  212. ringTime_ = ringTime
  213. if DEBUG: print("DATI ARRIVATI: ")
  214. if DEBUG: print(calledNumber)
  215. if DEBUG: print(calledId)
  216. if DEBUG: print(ringTime)
  217. subprocess.Popen(['perl', '/usr/local/bin/ast/voice.pl', mode, to_, calledId_, ringTime_], stdout=subprocess.PIPE)
  218. return
  219. @app.get("/majortel/hardware/", response_model=cellularHardwares, tags=["Majortel"])
  220. #async def majortel_hardware_get(current_user: User = Depends(get_current_active_user)):
  221. async def majortel_hardware_get():
  222. gsm_temp_list = "GSM span1: Provisioned, Up, Active"
  223. response = {"CellularHardwares": []}
  224. hardware_dict = {}
  225. myCmd = os.popen('asterisk -rx "gsm show spans"').read()
  226. myCmd = myCmd.split("\n")
  227. # cancello l'ultimo item della lista poichè, risultando vuoto dopo lo split,
  228. # genera errore "index out of range" nei successivi accessi alla lista
  229. if DEBUG: print("spans: ")
  230. if DEBUG: print(myCmd)
  231. myCmd = os.popen('asterisk -rx "dongle show devices"').read()
  232. myCmd = myCmd.split("\n")
  233. # cancello il primo item della lista poichè contiene la legenda
  234. del myCmd[0]
  235. del myCmd[-1]
  236. # costruisco la response
  237. for device in myCmd:
  238. device = device.split()
  239. hardware_id = device[0]
  240. current_device = os.popen('asterisk -rx "dongle show device state ' + hardware_id + '"').read()
  241. current_device = current_device.split("\n")
  242. # cancello il primo e gli ultimi item della lista poichè, risultando vuoti dopo lo split,
  243. # generano errore "index out of range" nei successivi accessi alla lista
  244. del current_device[0]
  245. del current_device[-1]
  246. del current_device[-1]
  247. # costruisco un dizionario a partire dall'output della system call
  248. for row in current_device:
  249. row = row.split(":")
  250. row[0] = row[0].strip()
  251. row[1] = row[1].strip()
  252. hardware_dict[row[0]] = row[1]
  253. hardware_id = hardware_dict["Device"]
  254. status = hardware_dict["State"]
  255. signal = hardware_dict["RSSI"]
  256. signal = int(signal[0:2])
  257. operator = hardware_dict["Provider Name"]
  258. device_obj = {"id": hardware_id, "description": "description", "status": status, "signal_level": signal,
  259. "registered_number": "To Do", "operator": operator}
  260. response["CellularHardwares"].append(device_obj)
  261. return response
  262. @app.get("/majortel/hardware/{item_id}", response_model=cellularHardware, tags=["Majortel"], responses={400: {"model": httpResponse400}})
  263. #async def majortel_hardware_id_get(item_id: str, current_user: User = Depends(get_current_active_user)):
  264. async def majortel_hardware_id_get(item_id: str,active_user=Depends(manager)):
  265. hardware_response = {}
  266. hardware_dict = {}
  267. current_device = os.popen('asterisk -rx "dongle show device state ' + item_id + '"').read()
  268. current_device = current_device.split("\n")
  269. # cancello il primo e gli ultimi item della lista poichè, risultando vuoti dopo lo split,
  270. # generano errore "index out of range" nei successivi accessi alla lista
  271. del current_device[0]
  272. if (current_device[0]):
  273. del current_device[-1]
  274. del current_device[-1]
  275. # costruisco un dizionario a partire dall'output della system call
  276. for row in current_device:
  277. row = row.split(":")
  278. row[0] = row[0].strip()
  279. row[1] = row[1].strip()
  280. hardware_dict[row[0]] = row[1]
  281. hardware_id = hardware_dict["Device"]
  282. status = hardware_dict["State"]
  283. signal = hardware_dict["RSSI"]
  284. signal = int(signal[0:2])
  285. operator = hardware_dict["Provider Name"]
  286. hardware_response = {"id": hardware_id, "description": "description", "status": status, "signal_level": signal,
  287. "registered_number": "To Do", "operator": operator}
  288. return hardware_response
  289. else:
  290. return JSONResponse(status_code=404, content={"message": "Device not found"})
  291. @app.get("/majortel/calls/", response_model=calls, tags=["Majortel"])
  292. #async def majortel_calls_get(current_user: User = Depends(get_current_active_user)):
  293. async def majortel_calls_get(active_user=Depends(manager)):
  294. response = {"Calls": []}
  295. p = subprocess.Popen(['perl', '/opt/api_project_python/addCallQueue.pl', "get_calls"], stdout=subprocess.PIPE)
  296. calls = str(p.stdout.read())
  297. calls = calls.split("'")
  298. response1 = calls
  299. calls = calls[1].split("||")
  300. # cancello l'ultimo item della lista poichè, risultando vuoto dopo lo split,
  301. # genera errore "index out of range" nei successivi accessi alla lista
  302. del calls[-1]
  303. for call in calls:
  304. call = call.split("|")
  305. call_id = call[0]
  306. status = call[1]
  307. history = call[2]
  308. ntel = call[3]
  309. ackid = call[4]
  310. call_obj = {"id": call_id, "status": status, "history": history, "called_number": ntel, "ack_id": ackid}
  311. response["Calls"].append(call_obj)
  312. return response
  313. @app.get("/majortel/calls/{call_id}/", response_model=call, tags=["Majortel"])
  314. async def majortel_calls_id_get(call_id,active_user=Depends(manager)):
  315. p = subprocess.Popen(['perl', '/var/opt/FastAPI/addCallQueue.pl', "call_id", call_id],
  316. stdout=subprocess.PIPE)
  317. call = str(p.stdout.read())
  318. call = call.split("|")
  319. call_id = call[0].split("'")[1]
  320. status = call[1]
  321. # history = call[2]
  322. ntel = call[3]
  323. ackid = call[4]
  324. ackid = int(ackid.split("'")[0])
  325. response = {"id": call_id, "status": status, "called_number": ntel, "ack_id": ackid}
  326. return response
  327. # response_model=post_call,
  328. @app.post("/majortel/calls", response_model=post_call, tags=["Majortel"])
  329. async def majortel_calls_post(hw_id, ack_id, called_number, text_message="codice di riscontro ", timeout=30,
  330. retry=1, file: UploadFile = File(None),active_user=Depends(manager)):
  331. # controllo se l'hw_id corrisponde ai devices connessi
  332. current_device = os.popen('asterisk -rx "dongle show device state ' + hw_id + '"').read()
  333. current_device = current_device.split("\n")
  334. del current_device[0]
  335. if (current_device[0]):
  336. curr_time = datetime.now()
  337. call_id = str(curr_time.strftime('%y')) + str(curr_time.strftime('%m')) + str(curr_time.strftime('%d')) + str(
  338. curr_time.strftime('%H')) + str(curr_time.strftime('%M')) + str(curr_time.strftime('%S')) + str(
  339. curr_time.strftime('%f'))
  340. file_path = ""
  341. if (file):
  342. nomefile = file.filename
  343. extension = nomefile.split(".")[1]
  344. if (extension == "wav"):
  345. file_path = "/data/service/voip.majornet/ivr/" + call_id
  346. renamed_file = file_path + ".wav"
  347. with open(renamed_file, "wb") as f:
  348. shutil.copyfileobj(file.file, f)
  349. f.close()
  350. # p = subprocess.Popen(['(cd /tmp/audioCalls;/usr/local/bin/sox_wav2gsm)'],stdout=subprocess.PIPE)
  351. # os.popen('/usr/local/bin/sox_wav2gsm 1>/dev/null 2>/dev/null')
  352. subprocess.Popen(
  353. ['perl', '/opt/api_project_python/addCallQueue.pl', "voicegateway", "digiovine@afasystems.it", call_id,
  354. called_number, ack_id, text_message, "retry message here", timeout, retry, "yes", "notification_to",
  355. "notification_bcc", "setup", file_path, "fromemailssss", hw_id], stdout=subprocess.PIPE)
  356. response = {"id": call_id}
  357. return response
  358. else:
  359. return JSONResponse(status_code=404, content={"message": "Device not found"})
  360. @app.delete("/majortel/calls/", tags=["Majortel"],
  361. responses={200: {"model": httpResponse200}, 400: {"model": httpResponse400},
  362. 500: {"model": httpResponse500}})
  363. #async def majortel_calls_id_delete(call_id, current_user: User = Depends(get_current_active_user)):
  364. async def majortel_calls_id_delete(call_id,active_user=Depends(manager)):
  365. p = subprocess.Popen(['perl', '/opt/api_project_python/addCallQueue.pl', "delete_call", call_id],
  366. stdout=subprocess.PIPE)
  367. response = str(p.stdout.read())
  368. response = response.split("'")[1]
  369. if (response == "Success"):
  370. return JSONResponse(status_code=200, content={"message": "Success"})
  371. elif (response == "Not found"):
  372. return JSONResponse(status_code=404, content={"message": "Call not found"})
  373. else:
  374. return JSONResponse(status_code=500, content={"message": "Server error"})