Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

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