Account.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Optional, Dict, TYPE_CHECKING
  4. from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
  5. from UM.Message import Message
  6. from UM.i18n import i18nCatalog
  7. from cura.OAuth2.AuthorizationService import AuthorizationService
  8. from cura.OAuth2.Models import OAuth2Settings
  9. from cura.UltimakerCloud import UltimakerCloudAuthentication
  10. if TYPE_CHECKING:
  11. from cura.CuraApplication import CuraApplication
  12. i18n_catalog = i18nCatalog("cura")
  13. ## The account API provides a version-proof bridge to use Ultimaker Accounts
  14. #
  15. # Usage:
  16. # ``from cura.API import CuraAPI
  17. # api = CuraAPI()
  18. # api.account.login()
  19. # api.account.logout()
  20. # api.account.userProfile # Who is logged in``
  21. #
  22. class Account(QObject):
  23. # Signal emitted when user logged in or out.
  24. loginStateChanged = pyqtSignal(bool)
  25. accessTokenChanged = pyqtSignal()
  26. cloudPrintersDetectedChanged = pyqtSignal(bool)
  27. def __init__(self, application: "CuraApplication", parent = None) -> None:
  28. super().__init__(parent)
  29. self._application = application
  30. self._new_cloud_printers_detected = False
  31. self._error_message = None # type: Optional[Message]
  32. self._logged_in = False
  33. self._callback_port = 32118
  34. self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
  35. self._oauth_settings = OAuth2Settings(
  36. OAUTH_SERVER_URL= self._oauth_root,
  37. CALLBACK_PORT=self._callback_port,
  38. CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
  39. CLIENT_ID="um----------------------------ultimaker_cura",
  40. CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download "
  41. "packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write "
  42. "cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write",
  43. AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
  44. AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
  45. AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)
  46. )
  47. self._authorization_service = AuthorizationService(self._oauth_settings)
  48. def initialize(self) -> None:
  49. self._authorization_service.initialize(self._application.getPreferences())
  50. self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
  51. self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
  52. self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged)
  53. self._authorization_service.loadAuthDataFromPreferences()
  54. def _onAccessTokenChanged(self):
  55. self.accessTokenChanged.emit()
  56. ## Returns a boolean indicating whether the given authentication is applied against staging or not.
  57. @property
  58. def is_staging(self) -> bool:
  59. return "staging" in self._oauth_root
  60. @pyqtProperty(bool, notify=loginStateChanged)
  61. def isLoggedIn(self) -> bool:
  62. return self._logged_in
  63. @pyqtProperty(bool, notify=cloudPrintersDetectedChanged)
  64. def newCloudPrintersDetected(self) -> bool:
  65. return self._new_cloud_printers_detected
  66. def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[str] = None) -> None:
  67. if error_message:
  68. if self._error_message:
  69. self._error_message.hide()
  70. self._error_message = Message(error_message, title = i18n_catalog.i18nc("@info:title", "Login failed"))
  71. self._error_message.show()
  72. self._logged_in = False
  73. self.loginStateChanged.emit(False)
  74. return
  75. if self._logged_in != logged_in:
  76. self._logged_in = logged_in
  77. self.loginStateChanged.emit(logged_in)
  78. @pyqtSlot()
  79. def login(self) -> None:
  80. if self._logged_in:
  81. # Nothing to do, user already logged in.
  82. return
  83. self._authorization_service.startAuthorizationFlow()
  84. @pyqtProperty(str, notify=loginStateChanged)
  85. def userName(self):
  86. user_profile = self._authorization_service.getUserProfile()
  87. if not user_profile:
  88. return None
  89. return user_profile.username
  90. @pyqtProperty(str, notify = loginStateChanged)
  91. def profileImageUrl(self):
  92. user_profile = self._authorization_service.getUserProfile()
  93. if not user_profile:
  94. return None
  95. return user_profile.profile_image_url
  96. @pyqtProperty(str, notify=accessTokenChanged)
  97. def accessToken(self) -> Optional[str]:
  98. return self._authorization_service.getAccessToken()
  99. # Get the profile of the logged in user
  100. # @returns None if no user is logged in, a dict containing user_id, username and profile_image_url
  101. @pyqtProperty("QVariantMap", notify = loginStateChanged)
  102. def userProfile(self) -> Optional[Dict[str, Optional[str]]]:
  103. user_profile = self._authorization_service.getUserProfile()
  104. if not user_profile:
  105. return None
  106. return user_profile.__dict__
  107. @pyqtSlot()
  108. def logout(self) -> None:
  109. if not self._logged_in:
  110. return # Nothing to do, user isn't logged in.
  111. self._authorization_service.deleteAuthData()