No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

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