Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

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