cmx.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #
  2. # Copyright (c) 2017-2018 Lionel Hercot <lhercot@cisco.com>
  3. # All rights reserved.
  4. #
  5. import requests
  6. import sys
  7. import json
  8. from requests.packages.urllib3.exceptions import InsecureRequestWarning # type: ignore
  9. from flask import Flask
  10. from flask import abort
  11. from flask_restful import Api
  12. from flask_restful import Resource
  13. from flask_restful import reqparse
  14. from flask import send_file
  15. from pathlib import Path
  16. from io import BytesIO
  17. from ipaddress import IPv6Address
  18. import os
  19. import traceback
  20. import CLEUCreds # type: ignore
  21. from cleu.config import Config as C # type: ignore
  22. from PIL import Image, ImageDraw
  23. app = Flask(__name__)
  24. api = Api(app)
  25. # Disable SSL warnings
  26. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  27. @app.after_request
  28. def after_request(response):
  29. response.headers.add("Access-Control-Allow-Origin", "*")
  30. response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
  31. response.headers.add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE")
  32. return response
  33. cmxBaseUrl = C.CMX
  34. cmxAuth = (CLEUCreds.CMX_USERNAME, CLEUCreds.CMX_PASSWORD)
  35. cmxMapAuth = (CLEUCreds.CMX_MAP_USERNAME, CLEUCreds.CMX_PASSWORD)
  36. PORT = 8002
  37. imgDir = "maps/"
  38. markerDir = "markers/"
  39. markerFiles = os.listdir(markerDir)
  40. markers = {}
  41. for filename in markerFiles:
  42. name = filename.split(".")[0]
  43. markers[name] = Image.open(markerDir + filename).convert("RGBA")
  44. markers[name].thumbnail((100, 100), Image.ANTIALIAS)
  45. # markerW, markerH = marker.size
  46. apiUri = {
  47. "mac": "/api/location/v3/clients?macAddress=",
  48. "ip": "/api/location/v3/clients?ipAddress=",
  49. "map": "/api/config/v1/maps/imagesource/",
  50. "ssid": "/api/location/v3/clients/?include=metadata&pageSize=1000&page=",
  51. "count": "/api/location/v3/clients/count",
  52. "floors": "/api/config/v1/maps",
  53. "tag": "/api/location/v1/tags/",
  54. }
  55. def serve_pil_image(pil_img):
  56. img_io = BytesIO()
  57. pil_img.save(img_io, "JPEG", quality=100)
  58. img_io.seek(0)
  59. return send_file(img_io, mimetype="image/jpeg")
  60. def getMap(imageName):
  61. imgFile = Path(imgDir + imageName)
  62. if not imgFile.is_file():
  63. respImg = requests.get(cmxBaseUrl + apiUri["map"] + imageName, auth=cmxMapAuth, verify=False, stream=True)
  64. if respImg.status_code == 200:
  65. with open(str(imgFile), "wb") as f:
  66. for chunk in respImg:
  67. f.write(chunk)
  68. def getAllFloorMaps():
  69. response = requests.get(cmxBaseUrl + apiUri["floors"], auth=cmxMapAuth, verify=False)
  70. floorList = {}
  71. if response and response != "":
  72. try:
  73. dataDict = json.loads(response.text)
  74. if "campuses" in dataDict:
  75. for campus in dataDict["campuses"]:
  76. if "buildingList" in campus:
  77. for building in campus["buildingList"]:
  78. for floor in building["floorList"]:
  79. floorId = floor["aesUid"]
  80. imageName = floor["image"]["imageName"]
  81. floorList[floorId] = imageName
  82. getMap(imageName)
  83. except:
  84. print("Unexpected error" + str(sys.exc_info()[0]), file=sys.stderr)
  85. raise
  86. return floorList
  87. class CMX(Resource):
  88. def get(self):
  89. parser = reqparse.RequestParser()
  90. parser.add_argument("ip", location="args", help="IP address of the endpoint")
  91. parser.add_argument("ipv6", location="args", help="IPv6 address of the endpoint")
  92. parser.add_argument("mac", location="args", help="MAC address of the endpoint")
  93. parser.add_argument("marker", location="args", help="Marker used to display the location of the endpoint", default="marker")
  94. parser.add_argument("size", location="args", help="Size of the image returned")
  95. parser.add_argument("tag", location="args", help="Asset tag MAC address")
  96. args = parser.parse_args()
  97. response = ""
  98. if args.get("ip"):
  99. clientIp = args.get("ip")
  100. response = requests.get(cmxBaseUrl + apiUri["ip"] + clientIp, auth=cmxAuth, verify=False)
  101. elif args.get("tag"):
  102. clientMac = args.get("tag")
  103. response = requests.get(cmxBaseUrl + apiUri["tag"] + clientMac, auth=cmxAuth, verify=False)
  104. elif args.get("mac"):
  105. clientMac = args.get("mac")
  106. response = requests.get(cmxBaseUrl + apiUri["mac"] + clientMac, auth=cmxAuth, verify=False)
  107. elif args.get("ipv6"):
  108. clientIp = IPv6Address(args.get("ipv6")).exploded
  109. response = requests.get(cmxBaseUrl + apiUri["ip"] + clientIp, auth=cmxAuth, verify=False)
  110. markerName = args.get("marker")
  111. marker = markers["marker"]
  112. if markerName in markers:
  113. marker = markers[markerName]
  114. markerW, markerH = marker.size
  115. if response and response != "":
  116. try:
  117. dataDict = json.loads(response.text)
  118. result = None
  119. if args.get("tag") and dataDict and "mapInfo" in dataDict:
  120. result = dataDict
  121. elif len(dataDict) > 0 and "mapInfo" in dataDict[0]:
  122. result = dataDict[0]
  123. if result is not None:
  124. imageName = result["mapInfo"]["image"]["imageName"]
  125. mapLength = result["mapInfo"]["floorDimension"]["length"]
  126. mapWidth = result["mapInfo"]["floorDimension"]["width"]
  127. imageLength = result["mapInfo"]["image"]["height"]
  128. imageWidth = result["mapInfo"]["image"]["width"]
  129. coordX = result["mapCoordinate"]["x"]
  130. coordY = result["mapCoordinate"]["y"]
  131. positionX = (imageWidth / mapWidth) * coordX
  132. positionY = (imageLength / mapLength) * coordY
  133. getMap(imageName)
  134. im = Image.open(str(imgDir + imageName))
  135. positionX = positionX - markerW / 2
  136. positionY = positionY - markerH
  137. offset = (int(positionX), int(positionY))
  138. im.paste(marker, offset, marker)
  139. if args.get("size"):
  140. # print('SIZE', file=sys.stderr)
  141. size = args.get("size")
  142. im.thumbnail((int(size), int(size)), Image.ANTIALIAS)
  143. return serve_pil_image(im)
  144. else:
  145. abort(404, "Requested element not found")
  146. except Exception as inst:
  147. traceback.print_exc()
  148. print("Unexpected error with request= {} | error : {}".format(response.text, inst), file=sys.stderr)
  149. return {"response": str(response.text), "error": str(inst)}, 500
  150. return abort(404, "Missing parameter ip, ipv6 or mac. Other possible parameters are: marker (" + ", ".join(markers.keys()) + ")")
  151. class CMX_SSID(Resource):
  152. def get(self):
  153. parser = reqparse.RequestParser()
  154. parser.add_argument("ssid", help="SSID used by the clients")
  155. parser.add_argument("floor", help="Floor used by the clients")
  156. parser.add_argument("marker", help="Marker used to display the location of the endpoint", default="marker")
  157. args = parser.parse_args()
  158. response = ""
  159. if args.get("ssid"):
  160. ssid = args.get("ssid")
  161. countResp = requests.get(cmxBaseUrl + apiUri["count"], auth=cmxAuth, verify=False)
  162. try:
  163. dataDict = json.loads(countResp.text)
  164. if "count" in dataDict:
  165. count = dataDict["count"]
  166. maxPageId = (count // 1000) + 1
  167. print("Count: {} MaxPage: {}".format(count, maxPageId), file=sys.stderr)
  168. userList = {}
  169. floorList = getAllFloorMaps()
  170. for pageId in range(1, maxPageId):
  171. print("Page: {} MaxPage: {}".format(pageId, maxPageId), file=sys.stderr)
  172. response = requests.get(cmxBaseUrl + apiUri["ssid"] + str(pageId), auth=cmxAuth, verify=False)
  173. if response and response.text != "":
  174. try:
  175. userDict = json.loads(response.text)
  176. for user in userDict:
  177. if user["ssId"] == ssid:
  178. floorName = user["mapInfo"]["floorRefId"]
  179. if floorName in userList:
  180. userList[floorName].append(user)
  181. else:
  182. userList[floorName] = [user]
  183. except:
  184. print(
  185. "Unexpected error with page request= " + response.text + " | error : " + str(sys.exc_info()[0]),
  186. file=sys.stderr,
  187. )
  188. return {"response": str(response.text), "error": str(inst)}, 500
  189. if args.get("floor"):
  190. floor = args.get("floor")
  191. if floor in userList:
  192. markerName = args.get("marker")
  193. marker = ""
  194. if markerName in markers:
  195. marker = markers[markerName]
  196. else:
  197. marker = markers["marker"]
  198. markerW, markerH = marker.size
  199. imageName = floorList[floorName]
  200. getMap()
  201. im = Image.open(str(imgDir + imageName))
  202. for data in userList[floor]:
  203. mapInfo = data["mapInfo"]
  204. mapLength = mapInfo["floorDimension"]["length"]
  205. mapWidth = mapInfo["floorDimension"]["width"]
  206. imageLength = mapInfo["image"]["height"]
  207. imageWidth = mapInfo["image"]["width"]
  208. coordX = data["mapCoordinate"]["x"]
  209. coordY = data["mapCoordinate"]["y"]
  210. positionX = (imageWidth / mapWidth) * coordX
  211. positionY = (imageLength / mapLength) * coordY
  212. positionX = positionX - markerW / 2
  213. positionY = positionY - markerH
  214. offset = (int(positionX), int(positionY))
  215. im.paste(marker, offset, marker)
  216. return serve_pil_image(im)
  217. elif floor in floorList:
  218. imageName = floorList[floor]
  219. getMap(imageName)
  220. im = Image.open(str(imgDir + imageName))
  221. return serve_pil_image(im)
  222. else:
  223. abort(404)
  224. else:
  225. return list(floorList.keys())
  226. except Exception as inst:
  227. print("Unexpected error with request= {} | error : {}".format(countResp, inst), file=sys.stderr)
  228. return {"response": str(countResp), "error": str(inst)}, 500
  229. class sync(Resource):
  230. def get(self):
  231. return getAllFloorMaps()
  232. class home(Resource):
  233. def get(self):
  234. return {}
  235. api.add_resource(home, "/")
  236. api.add_resource(CMX, "/api/v0.1/cmx")
  237. api.add_resource(CMX_SSID, "/api/v0.1/ssid")
  238. api.add_resource(sync, "/api/v0.1/sync")
  239. if __name__ == "__main__":
  240. app.run(host=C.WSGI_SERVER, debug=True, port=PORT, threaded=True)