LocalAuthorizationServer.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. # Copyright (c) 2020 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import sys
  4. import threading
  5. from typing import Any, Callable, Optional, TYPE_CHECKING
  6. from UM.Logger import Logger
  7. got_server_type = False
  8. try:
  9. from cura.OAuth2.AuthorizationRequestServer import AuthorizationRequestServer
  10. from cura.OAuth2.AuthorizationRequestHandler import AuthorizationRequestHandler
  11. got_server_type = True
  12. except PermissionError: # Bug in http.server: Can't access MIME types. This will prevent the user from logging in. See Sentry bug Cura-3Q.
  13. Logger.error("Can't start a server due to a PermissionError when starting the http.server.")
  14. if TYPE_CHECKING:
  15. from cura.OAuth2.Models import AuthenticationResponse
  16. from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
  17. class LocalAuthorizationServer:
  18. def __init__(self, auth_helpers: "AuthorizationHelpers",
  19. auth_state_changed_callback: Callable[["AuthenticationResponse"], Any],
  20. daemon: bool) -> None:
  21. """The local LocalAuthorizationServer takes care of the oauth2 callbacks.
  22. Once the flow is completed, this server should be closed down again by calling
  23. :py:meth:`cura.OAuth2.LocalAuthorizationServer.LocalAuthorizationServer.stop()`
  24. :param auth_helpers: An instance of the authorization helpers class.
  25. :param auth_state_changed_callback: A callback function to be called when the authorization state changes.
  26. :param daemon: Whether the server thread should be run in daemon mode.
  27. .. note::
  28. Daemon threads are abruptly stopped at shutdown. Their resources (e.g. open files) may never be released.
  29. """
  30. self._web_server = None # type: Optional[AuthorizationRequestServer]
  31. self._web_server_thread = None # type: Optional[threading.Thread]
  32. self._web_server_port = auth_helpers.settings.CALLBACK_PORT
  33. self._auth_helpers = auth_helpers
  34. self._auth_state_changed_callback = auth_state_changed_callback
  35. self._daemon = daemon
  36. def start(self, verification_code: str, state: str) -> None:
  37. """Starts the local web server to handle the authorization callback.
  38. :param verification_code: The verification code part of the OAuth2 client identification.
  39. :param state: The unique state code (to ensure that the request we get back is really from the server.
  40. """
  41. if self._web_server:
  42. # If the server is already running (because of a previously aborted auth flow), we don't have to start it.
  43. # We still inject the new verification code though.
  44. Logger.log("d", "Auth web server was already running. Updating the verification code")
  45. self._web_server.setVerificationCode(verification_code)
  46. return
  47. if self._web_server_port is None:
  48. raise Exception("Unable to start server without specifying the port.")
  49. Logger.log("d", "Starting local web server to handle authorization callback on port %s", self._web_server_port)
  50. # Create the server and inject the callback and code.
  51. if got_server_type:
  52. self._web_server = AuthorizationRequestServer(("0.0.0.0", self._web_server_port), AuthorizationRequestHandler)
  53. self._web_server.setAuthorizationHelpers(self._auth_helpers)
  54. self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
  55. self._web_server.setVerificationCode(verification_code)
  56. self._web_server.setState(state)
  57. # Start the server on a new thread.
  58. self._web_server_thread = threading.Thread(None, self._serve_forever, daemon = self._daemon)
  59. self._web_server_thread.start()
  60. def stop(self) -> None:
  61. """Stops the web server if it was running. It also does some cleanup."""
  62. Logger.log("d", "Stopping local oauth2 web server...")
  63. if self._web_server:
  64. try:
  65. self._web_server.shutdown()
  66. except OSError:
  67. # OS error can happen if the socket was already closed. We really don't care about that case.
  68. pass
  69. Logger.log("d", "Local oauth2 web server was shut down")
  70. self._web_server = None
  71. self._web_server_thread = None
  72. def _serve_forever(self) -> None:
  73. """
  74. If the platform is windows, this function calls the serve_forever function of the _web_server, catching any
  75. OSErrors that may occur in the thread, thus making the reported message more log-friendly.
  76. If it is any other platform, it just calls the serve_forever function immediately.
  77. :return: None
  78. """
  79. Logger.log("d", "Local web server for authorization has started")
  80. if self._web_server:
  81. if sys.platform == "win32":
  82. try:
  83. self._web_server.serve_forever()
  84. except OSError:
  85. Logger.logException("w", "An exception happened while serving the auth server")
  86. else:
  87. # Leave the default behavior in non-windows platforms
  88. self._web_server.serve_forever()