Freebox thin client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
4.9KB

  1. #!/usr/bin/python3
  2. """
  3. Fbx Client
  4. """
  5. import os
  6. import logging
  7. import re
  8. import hmac
  9. import hashlib
  10. import logging
  11. import time
  12. import requests
  13. from . import api
  14. from . import utils
  15. from . import mdns
  16. __all__ = [
  17. "FbxTransport",
  18. "Fbx",
  19. "FbxClass", ]
  20. class FbxClass():
  21. """
  22. Base class for Fbx subsystems
  23. """
  24. def __init__(self, transport):
  25. self._trn = transport
  26. class FbxTransport():
  27. """
  28. Transport abstraction and context handling for all methods
  29. """
  30. def __init__(self, url=None, nomdns=False, session=None):
  31. self.log = logging.getLogger("pyfbx.trans")
  32. self._session = session or requests.session()
  33. self._session.verify = os.path.join(os.path.dirname(__file__), 'fb.pem')
  34. self.set_url(url, nomdns)
  35. def set_url(self, url, nomdns):
  36. if url is None:
  37. if nomdns:
  38. self._url = self.get_local_base()
  39. else:
  40. self._url = mdns.FbxMDNS().search() or self.get_local_base()
  41. else:
  42. if re.search("https?://", url) is None:
  43. url = "http://" + url
  44. if "/api/" not in url:
  45. self._url = self.get_local_base(url)
  46. else:
  47. self._url = url
  48. def set_session_header(self, session_token):
  49. self._session.headers.update({'X-Fbx-App-Auth': session_token})
  50. def api_exec(self, http_method, endpoint, post_data=None, **kwargs):
  51. req_response = self._session.request(
  52. http_method,
  53. self._url + "/" + endpoint.format(**kwargs), json=post_data)
  54. self.log.debug(">> Sent %s %s/%s Post: %s", http_method, self._url,
  55. endpoint.format(**kwargs), post_data)
  56. req_response.raise_for_status()
  57. response = req_response.json()
  58. if response['success']:
  59. if 'result' in response:
  60. self.log.debug(f"<< Got {response['result']}")
  61. return response['result']
  62. else:
  63. raise FbxErrorResponse(response['error_code'], response['msg'])
  64. def get_local_base(self, url=api._DISC_HTTP_URL):
  65. response = self._session.get(f"{url}/api_version").json()
  66. self.log.debug(f"<< Detected api {response['api_version']}")
  67. return "%s%sv%s" % (url, response['api_base_url'],
  68. response['api_version'][0])
  69. class Fbx():
  70. """
  71. Freebox object
  72. """
  73. def __init__(self, url=None, nomdns=False, session=None):
  74. self.log = logging.getLogger("pyfbx.fbx")
  75. self._trn = FbxTransport(url, nomdns=nomdns, session=session)
  76. # Create on the fly attributes to classes
  77. _globals = globals()
  78. for m_class in api.SYSTEMS:
  79. setattr(self, m_class, _globals[m_class](self._trn))
  80. for name, meth in api.SYSTEMS[m_class].items():
  81. utils.add_class_func(getattr(self, m_class).__class__, name, meth)
  82. def register(self, app_id, app_name, device):
  83. """
  84. Register app
  85. """
  86. self.app_id = app_id
  87. data = {"app_id": self.app_id, "app_name": app_name, "device_name": device}
  88. res = self._trn.api_exec("POST", "login/authorize/", data)
  89. trackid, self.token = res["track_id"], res["app_token"]
  90. s = "pending"
  91. self.log.info("Press Ok on the freebox to register application")
  92. while s == "pending":
  93. s = self._trn.api_exec("GET", f"login/authorize/{trackid}")["status"]
  94. if s == "pending":
  95. time.sleep(1)
  96. self.log.debug(f"Registration returned: {s}")
  97. return s == "granted" and self.token
  98. def mksession(self, app_id=None, token=None):
  99. if token: # Don't overwrite previous token (used for refresh)
  100. self.token = token
  101. if app_id:
  102. self.app_id = app_id
  103. login = self._trn.api_exec("GET", "login/")
  104. if not login['logged_in']:
  105. data = {
  106. "app_id": self.app_id,
  107. "password": hmac.new(bytes(self.token, "ascii"),
  108. bytes(login['challenge'], "ascii"),
  109. hashlib.sha1).hexdigest()
  110. }
  111. resp = self._trn.api_exec("POST", "login/session/", data)
  112. session_token = resp["session_token"]
  113. self.app_id = app_id
  114. self._trn.set_session_header(session_token)
  115. self.log.info(f"Authenticated")
  116. return resp["permissions"]
  117. class FbxErrorResponse(Exception):
  118. def __init__(self, error_code, msg):
  119. self.error_code = error_code
  120. self.msg = msg
  121. def __str__(self):
  122. return f'{self.msg} [{self.error_code}]'
  123. log = logging.getLogger("pyfbx")
  124. # All FB subsystems are classes deriving from FbxClass
  125. for _classname in api.SYSTEMS:
  126. log.debug(f"Adding class {_classname} to locals")
  127. locals()[_classname] = type(_classname, (FbxClass, ), {})
  128. __all__.append(_classname)