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.

164 lines
5.4KB

  1. """
  2. Fbx Client
  3. """
  4. import os
  5. import logging
  6. import re
  7. import hmac
  8. import hashlib
  9. import time
  10. import requests
  11. from . import api
  12. from . import utils
  13. from . import mdns
  14. __all__ = ["Transport", "Fbx", "FbxClass"]
  15. class FbxClass():
  16. """
  17. Base class for Fbx subsystems
  18. """
  19. def __init__(self, transport):
  20. self._trn = transport
  21. class Transport():
  22. """
  23. Transport abstraction and context handling for all methods
  24. """
  25. def __init__(self, url=None, nomdns=False, session=None):
  26. self.log = logging.getLogger("pyfbx.trans")
  27. self._session = session or requests.session()
  28. self._session.verify = os.path.join(os.path.dirname(__file__), 'fb.pem')
  29. self.set_url(url, nomdns)
  30. def set_url(self, url, nomdns):
  31. if url is None:
  32. if nomdns:
  33. self._url = self.local_base()
  34. else:
  35. self._url = mdns.FbxMDNS().search() or self.local_base()
  36. else:
  37. if re.search("https?://", url) is None:
  38. url = "http://" + url
  39. if "/api/" not in url:
  40. self._url = self.local_base(url)
  41. else:
  42. self._url = url
  43. def set_header(self, session_token):
  44. self._session.headers.update({'X-Fbx-App-Auth': session_token})
  45. def api_exec(self, http_method, endpoint, post_data=None, **kwargs):
  46. try:
  47. self.log.debug(">> Sent %s %s/%s Post: %s", http_method, self._url,
  48. endpoint.format(**kwargs), post_data)
  49. req_response = self._session.request(
  50. http_method,
  51. self._url + "/" + endpoint.format(**kwargs), json=post_data)
  52. req_response.raise_for_status()
  53. except requests.exceptions.RequestException as exc:
  54. raise RequestError(exc)
  55. response = req_response.json()
  56. if response['success']:
  57. if 'result' in response:
  58. self.log.debug("<< Got {}".format(response['result']))
  59. return response['result']
  60. else:
  61. raise ResponseError(response['error_code'], response['msg'])
  62. def local_base(self, url=api._DISC_HTTP_URL):
  63. response = self._session.get("{}/api_version".format(url)).json()
  64. self.log.debug("<< Detected api {}".format(response['api_version']))
  65. return "%s%sv%s" % (url, response['api_base_url'],
  66. response['api_version'][0])
  67. class Fbx():
  68. """
  69. Freebox object
  70. """
  71. def __init__(self, url=None, nomdns=False, session=None):
  72. self.log = logging.getLogger("pyfbx.fbx")
  73. self._trn = Transport(url, nomdns=nomdns, session=session)
  74. self._app_id = None
  75. self._token = None
  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", "login/authorize/{}".format(trackid))["status"]
  94. if s == "pending":
  95. time.sleep(1)
  96. self.log.debug("Registration returned: {}".format(s))
  97. return s == "granted" and self._token
  98. def mksession(self, app_id=None, token=None):
  99. self.log.debug("Making session with token={}[{}], app_id={}[{}]".format(
  100. token, self._token, app_id, self._app_id))
  101. if token: # Don't overwrite previous token (used for refresh)
  102. self._token = token
  103. if app_id:
  104. self._app_id = app_id
  105. elif not self._app_id:
  106. raise Exception("Missing app_id")
  107. login = self._trn.api_exec("GET", "login/")
  108. if not login['logged_in']:
  109. data = {
  110. "app_id": self._app_id,
  111. "password": hmac.new(bytes(self._token, "ascii"),
  112. bytes(login['challenge'], "ascii"),
  113. hashlib.sha1).hexdigest()
  114. }
  115. resp = self._trn.api_exec("POST", "login/session/", data)
  116. session_token = resp["session_token"]
  117. self._trn.set_header(session_token)
  118. self.log.info("Authenticated. Storing token={}, app_id={}".format(self._token, self._app_id))
  119. return resp["permissions"]
  120. class RequestError(Exception):
  121. def __init__(self, msg):
  122. self.msg = msg
  123. def __str__(self):
  124. return '{}'.format(self.msg)
  125. class ResponseError(Exception):
  126. def __init__(self, error_code, msg):
  127. self.error_code = error_code
  128. self.msg = msg
  129. def __str__(self):
  130. return '{} [{}]'.format(self.msg, self.error_code)
  131. log = logging.getLogger("pyfbx")
  132. # All FB subsystems are classes deriving from FbxClass
  133. for _classname in api.SYSTEMS:
  134. log.debug("Adding class {} to locals".format(_classname))
  135. locals()[_classname] = type(_classname, (FbxClass, ), {})
  136. __all__.append(_classname)