diff --git a/pype/ftrack/Login/__init__.py b/pype/ftrack/Login/__init__.py new file mode 100644 index 0000000000..80bdb7f70f --- /dev/null +++ b/pype/ftrack/Login/__init__.py @@ -0,0 +1,6 @@ +import os +import sys +import credentials +import login_dialog + +# login_dialogue_usage.main() diff --git a/pype/ftrack/Login/credentials.py b/pype/ftrack/Login/credentials.py new file mode 100644 index 0000000000..9f409c5349 --- /dev/null +++ b/pype/ftrack/Login/credentials.py @@ -0,0 +1,49 @@ +import os +import toml +# import ftrack_api + +# TODO JUST TEST PATH - path should be in Environment Variables... +config_path = r"C:\test" +config_name = 'credentials.toml' +fpath = os.path.join(config_path, config_name) + +def _get_credentials(): + try: + file = open(fpath, 'r') + except: + file = open(fpath, 'w') + + credentials = toml.load(file) + file.close() + + return credentials + +def _save_credentials(username, apiKey): + file = open(fpath, 'w') + + data = { + 'username':username, + 'apiKey':apiKey + } + + credentials = toml.dumps(data) + file.write(credentials) + file.close() + +def _clear_credentials(): + file = open(fpath, 'w').close() + +def _set_env(username, apiKey): + os.environ['FTRACK_API_USER'] = username + os.environ['FTRACK_API_KEY'] = apiKey + +def _check_credentials(username, apiKey): + + _set_env(username, apiKey) + + try: + session = ftrack_api.Session() + return True + except Exception as e: + print(e) + return False diff --git a/pype/ftrack/Login/login_dialog.py b/pype/ftrack/Login/login_dialog.py new file mode 100644 index 0000000000..a7186ab415 --- /dev/null +++ b/pype/ftrack/Login/login_dialog.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '..\CODE\github\pypeclub\pype-setup\temp\pype_project_settins_ui\login_dialogue.ui' +# +# Created by: PyQt5 UI code generator 5.7.1 +# +# WARNING! All changes made in this file will be lost! + +import sys +from PyQt5 import QtCore, QtGui, QtWidgets +from app import style +import credentials +import login_tools + +class Login_Dialog_ui(QtWidgets.QWidget): + + SIZE_W = 300 + SIZE_H = 160 + + def __init__(self): + super().__init__() + + _translate = QtCore.QCoreApplication.translate + + self.resize(self.SIZE_W, self.SIZE_H) + self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) + self.setStyleSheet(style.load_stylesheet()) + + self.main = QtWidgets.QVBoxLayout() + self.main.setObjectName("main") + + self.form = QtWidgets.QFormLayout() + self.form.setContentsMargins(10, 15, 10, 5) + self.form.setObjectName("form") + + font = QtGui.QFont() + font.setFamily("DejaVu Sans Condensed") + font.setPointSize(9) + font.setBold(True) + font.setWeight(50) + font.setKerning(True) + + self.ftsite_label = QtWidgets.QLabel("FTrack URL:") + self.ftsite_label.setFont(font) + self.ftsite_label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + self.ftsite_label.setTextFormat(QtCore.Qt.RichText) + self.ftsite_label.setObjectName("user_label") + + self.ftsite_input = QtWidgets.QLineEdit() + self.ftsite_input.setEnabled(True) + self.ftsite_input.setFrame(True) + self.ftsite_input.setEnabled(False) + self.ftsite_input.setReadOnly(True) + self.ftsite_input.setObjectName("ftsite_input") + + self.user_label = QtWidgets.QLabel("Username:") + self.user_label.setFont(font) + self.user_label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + self.user_label.setTextFormat(QtCore.Qt.RichText) + self.user_label.setObjectName("user_label") + + self.user_input = QtWidgets.QLineEdit() + self.user_input.setEnabled(True) + self.user_input.setFrame(True) + self.user_input.setObjectName("user_input") + self.user_input.setPlaceholderText(_translate("main","user.name")) + + self.api_label = QtWidgets.QLabel("API Key:") + self.api_label.setFont(font) + self.api_label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + self.api_label.setTextFormat(QtCore.Qt.RichText) + self.api_label.setObjectName("api_label") + + self.api_input = QtWidgets.QLineEdit() + self.api_input.setEnabled(True) + self.api_input.setFrame(True) + self.api_input.setObjectName("api_input") + self.api_input.setPlaceholderText(_translate("main","e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")) + + self.form.addRow(self.ftsite_label, self.ftsite_input) + self.form.addRow(self.user_label, self.user_input) + self.form.addRow(self.api_label,self.api_input) + + self.btnGroup = QtWidgets.QHBoxLayout() + self.btnGroup.addStretch(1) + self.btnGroup.setObjectName("btnGroup") + + self.btnEnter = QtWidgets.QPushButton("Login") + self.btnEnter.setToolTip('Set Username and API Key with entered values') + self.btnEnter.clicked.connect(self._enter_credentials) + + self.btnClose = QtWidgets.QPushButton("Close") + self.btnClose.setToolTip('Close this window') + self.btnClose.clicked.connect(self._close_widget) + + self.btnFtrack = QtWidgets.QPushButton("Ftrack") + self.btnFtrack.setToolTip('Open browser for Login to Ftrack') + self.btnFtrack.clicked.connect(self._open_ftrack) + + self.btnGroup.addWidget(self.btnFtrack) + self.btnGroup.addWidget(self.btnEnter) + self.btnGroup.addWidget(self.btnClose) + + self.main.addLayout(self.form) + self.main.addLayout(self.btnGroup) + + self.setLayout(self.main) + self.setWindowTitle('FTrack Login') + self._set_site() + self.show() + + def _set_site(self): + try: + txt = os.getenv('FTRACK_SERVER') + except: + txt = "FTrack site si is not set!" + + self.ftsite_input.setText(txt) + + def _enter_credentials(self): + print("EnteredCredentials!") + user = self.user_input.text() + api = self.api_input.text() + verification = credentials._check_credentials(user, api) + + if verification: + print("SUCCESS") + credentials._save_credentials(user, api) + credentials._set_env(user, api) + self._close_widget() + + def _open_ftrack(self): + print("OpenWindow!") + try: + url = "pype.ftrackapp.com" + self.loginSignal = QtCore.pyqtSignal(object, object, object) + self._login_server_thread = login_tools.LoginServerThread() + self._login_server_thread.loginSignal.connect(self.loginSignal) + self._login_server_thread.start(url) + except Exception as e: + print(e) + + def _close_widget(self): + sys.exit(app.exec_()) + + +class Login_Dialog(Login_Dialog_ui): + def __init__(self): + super(Login_Dialog, self).__init__() + + def execute(self): + self._check_credentials() + + +def getApp(): + return QtWidgets.QApplication(sys.argv) + +def main(): + app = getApp() + ui = Login_Dialog() + ui.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() + +main() diff --git a/pype/ftrack/Login/login_setup.py b/pype/ftrack/Login/login_setup.py new file mode 100644 index 0000000000..6ebae672fa --- /dev/null +++ b/pype/ftrack/Login/login_setup.py @@ -0,0 +1,442 @@ +import os +import sys +import login_tools +from PyQt5 import QtCore +import requests + + +class FtrackLogin(object): + + # loginSignal = QtCore.pyqtSignal(object, object, object) + # + # def __init__(self): + # self.username = None + # self.apiKey = None + # self.url = "https://pype.ftrackapp.com" + # + # self._login_server_thread = None + # self.loginSignal.connect(self.loginWithCredentials) + # self.login() + # + # def login(self): + # '''Login using stored credentials or ask user for them.''' + # + # credentials = self._get_credentials() + # if credentials: + # # Try to login. + # self.loginWithCredentials( + # credentials['server_url'], + # credentials['api_user'], + # credentials['api_key'] + # ) + # + # def setup_session(self): + # try: + # session = ftrack_api.session() + # except Exception as e: + # return False + # return session + # + # def report_session_setup_error(self, error): + # msg = ( + # u'\nAn error occured while starting ftrack: {0}.'.format(error) + # ) + # print(msg) + # # self.loginError.emit(msg) + # + # def _get_credentials(self): + # data = {'server_url':self.url, + # 'api_user':self.username, + # 'api_key':self.apiKey + # } + # return data + # + # def _save_credentials(self, url, username, apiKey): + # self.url = url + # self.username = username + # self.apiKey = apiKey + # + # def loginWithCredentials(self, url, username, apiKey): + # url = url.strip('/ ') + # + # if not url: + # self.loginError.emit( + # 'You need to specify a valid server URL, ' + # 'for example https://server-name.ftrackapp.com' + # ) + # return + # + # if not 'http' in url: + # if url.endswith('ftrackapp.com'): + # url = 'https://' + url + # else: + # url = 'https://{0}.ftrackapp.com'.format(url) + # + # try: + # result = requests.get( + # url, + # allow_redirects=False # Old python API will not work with redirect. + # ) + # except requests.exceptions.RequestException: + # self.logger.exception('Error reaching server url.') + # self.loginError.emit( + # 'The server URL you provided could not be reached.' + # ) + # return + # + # if ( + # result.status_code != 200 or 'FTRACK_VERSION' not in result.headers + # ): + # self.loginError.emit( + # 'The server URL you provided is not a valid ftrack server.' + # ) + # return + # + # # If there is an existing server thread running we need to stop it. + # if self._login_server_thread: + # self._login_server_thread.quit() + # self._login_server_thread = None + # + # # If credentials are not properly set, try to get them using a http + # # server. + # if not username or not apiKey: + # self._login_server_thread = _login_tools.LoginServerThread() + # self._login_server_thread.loginSignal.connect(self.loginSignal) + # self._login_server_thread.start(url) + # return + # + # # Set environment variables supported by the old API. + # os.environ['FTRACK_SERVER'] = url + # os.environ['LOGNAME'] = username + # os.environ['FTRACK_APIKEY'] = apiKey + # + # # Set environment variables supported by the new API. + # os.environ['FTRACK_API_USER'] = username + # os.environ['FTRACK_API_KEY'] = apiKey + # + # # Login using the new ftrack API. + # try: + # self._session = self._setup_session() + # except Exception as error: + # self.logger.exception(u'Error during login.:') + # self._report_session_setup_error(error) + # return + # + # # Store credentials since login was successful. + # self._save_credentials(url, username, apiKey) + # + # # Verify storage scenario before starting. + # if 'storage_scenario' in self._session.server_information: + # storage_scenario = self._session.server_information.get( + # 'storage_scenario' + # ) + # if storage_scenario is None: + # # Hide login overlay at this time since it will be deleted + # self.logger.debug('Storage scenario is not configured.') + # scenario_widget = _scenario_widget.ConfigureScenario( + # self._session + # ) + # scenario_widget.configuration_completed.connect( + # self.location_configuration_finished + # ) + # self.setCentralWidget(scenario_widget) + # self.focus() + # return + + + + + + + + + + + + + + + + + + + + + + + + + + + loginError = QtCore.pyqtSignal(object) + + #: Signal when event received via ftrack's event hub. + eventHubSignal = QtCore.pyqtSignal(object) + + # Login signal. + loginSignal = QtCore.pyqtSignal(object, object, object) + + def __init__(self, *args, **kwargs): + + # self.logger = logging.getLogger( + # __name__ + '.' + self.__class__.__name__ + # ) + + self._login_server_thread = None + + self._login_overlay = None + self.loginSignal.connect(self.loginWithCredentials) + self.login() + + + def _onConnectTopicEvent(self, event): + '''Generic callback for all ftrack.connect events. + + .. note:: + Events not triggered by the current logged in user will be dropped. + + ''' + if event['topic'] != 'ftrack.connect': + return + + self._routeEvent(event) + + def logout(self): + '''Clear stored credentials and quit Connect.''' + self._clear_qsettings() + config = ftrack_connect.ui.config.read_json_config() + + config['accounts'] = [] + ftrack_connect.ui.config.write_json_config(config) + + QtWidgets.qApp.quit() + + def _clear_qsettings(self): + '''Remove credentials from QSettings.''' + settings = QtCore.QSettings() + settings.remove('login') + + def _get_credentials(self): + '''Return a dict with API credentials from storage.''' + credentials = None + + # Read from json config file. + json_config = ftrack_connect.ui.config.read_json_config() + if json_config: + try: + data = json_config['accounts'][0] + credentials = { + 'server_url': data['server_url'], + 'api_user': data['api_user'], + 'api_key': data['api_key'] + } + except Exception: + self.logger.debug( + u'No credentials were found in config: {0}.'.format( + json_config + ) + ) + + # Fallback on old QSettings. + if not json_config and not credentials: + settings = QtCore.QSettings() + server_url = settings.value('login/server', None) + api_user = settings.value('login/username', None) + api_key = settings.value('login/apikey', None) + + if not None in (server_url, api_user, api_key): + credentials = { + 'server_url': server_url, + 'api_user': api_user, + 'api_key': api_key + } + + return credentials + + def _save_credentials(self, server_url, api_user, api_key): + '''Save API credentials to storage.''' + # Clear QSettings since they should not be used any more. + self._clear_qsettings() + + # Save the credentials. + json_config = ftrack_connect.ui.config.read_json_config() + + if not json_config: + json_config = {} + + # Add a unique id to the config that can be used to identify this + # machine. + if not 'id' in json_config: + json_config['id'] = str(uuid.uuid4()) + + json_config['accounts'] = [{ + 'server_url': server_url, + 'api_user': api_user, + 'api_key': api_key + }] + + ftrack_connect.ui.config.write_json_config(json_config) + + def login(self): + '''Login using stored credentials or ask user for them.''' + credentials = self._get_credentials() + self.showLoginWidget() + + if credentials: + # Try to login. + self.loginWithCredentials( + credentials['server_url'], + credentials['api_user'], + credentials['api_key'] + ) + + def showLoginWidget(self): + '''Show the login widget.''' + self._login_overlay = ftrack_connect.ui.widget.overlay.CancelOverlay( + self.loginWidget, + message='Signing in' + ) + + self._login_overlay.hide() + self.setCentralWidget(self.loginWidget) + self.loginWidget.login.connect(self._login_overlay.show) + self.loginWidget.login.connect(self.loginWithCredentials) + self.loginError.connect(self.loginWidget.loginError.emit) + self.loginError.connect(self._login_overlay.hide) + self.focus() + + # Set focus on the login widget to remove any focus from its child + # widgets. + self.loginWidget.setFocus() + self._login_overlay.hide() + + def _setup_session(self): + '''Setup a new python API session.''' + if hasattr(self, '_hub_thread'): + self._hub_thread.quit() + + plugin_paths = os.environ.get( + 'FTRACK_EVENT_PLUGIN_PATH', '' + ).split(os.pathsep) + + plugin_paths.extend(self.pluginHookPaths) + + try: + session = ftrack_connect.session.get_shared_session( + plugin_paths=plugin_paths + ) + except Exception as error: + raise ftrack_connect.error.ParseError(error) + + # Listen to events using the new API event hub. This is required to + # allow reconfiguring the storage scenario. + self._hub_thread = _event_hub_thread.NewApiEventHubThread() + self._hub_thread.start(session) + + ftrack_api._centralized_storage_scenario.register_configuration( + session + ) + + return session + + def _report_session_setup_error(self, error): + '''Format error message and emit loginError.''' + msg = ( + u'\nAn error occured while starting ftrack-connect: {0}.' + u'\nPlease check log file for more informations.' + u'\nIf the error persists please send the log file to:' + u' support@ftrack.com'.format(error) + + ) + self.loginError.emit(msg) + + def loginWithCredentials(self, url, username, apiKey): + '''Connect to *url* with *username* and *apiKey*. + + loginError will be emitted if this fails. + + ''' + # Strip all leading and preceeding occurances of slash and space. + url = url.strip('/ ') + + if not url: + self.loginError.emit( + 'You need to specify a valid server URL, ' + 'for example https://server-name.ftrackapp.com' + ) + return + + if not 'http' in url: + if url.endswith('ftrackapp.com'): + url = 'https://' + url + else: + url = 'https://{0}.ftrackapp.com'.format(url) + + try: + result = requests.get( + url, + allow_redirects=False # Old python API will not work with redirect. + ) + except requests.exceptions.RequestException: + self.logger.exception('Error reaching server url.') + self.loginError.emit( + 'The server URL you provided could not be reached.' + ) + return + + if ( + result.status_code != 200 or 'FTRACK_VERSION' not in result.headers + ): + self.loginError.emit( + 'The server URL you provided is not a valid ftrack server.' + ) + return + + # If there is an existing server thread running we need to stop it. + if self._login_server_thread: + self._login_server_thread.quit() + self._login_server_thread = None + + # If credentials are not properly set, try to get them using a http + # server. + if not username or not apiKey: + self._login_server_thread = _login_tools.LoginServerThread() + self._login_server_thread.loginSignal.connect(self.loginSignal) + self._login_server_thread.start(url) + return + + # Set environment variables supported by the old API. + os.environ['FTRACK_SERVER'] = url + os.environ['LOGNAME'] = username + os.environ['FTRACK_APIKEY'] = apiKey + + # Set environment variables supported by the new API. + os.environ['FTRACK_API_USER'] = username + os.environ['FTRACK_API_KEY'] = apiKey + + # Login using the new ftrack API. + try: + self._session = self._setup_session() + except Exception as error: + self.logger.exception(u'Error during login.:') + self._report_session_setup_error(error) + return + + # Store credentials since login was successful. + self._save_credentials(url, username, apiKey) + + # Verify storage scenario before starting. + if 'storage_scenario' in self._session.server_information: + storage_scenario = self._session.server_information.get( + 'storage_scenario' + ) + if storage_scenario is None: + # Hide login overlay at this time since it will be deleted + self.logger.debug('Storage scenario is not configured.') + scenario_widget = _scenario_widget.ConfigureScenario( + self._session + ) + + self.setCentralWidget(scenario_widget) + self.focus() + return diff --git a/pype/ftrack/Login/login_tools.py b/pype/ftrack/Login/login_tools.py new file mode 100644 index 0000000000..e7a35ff97f --- /dev/null +++ b/pype/ftrack/Login/login_tools.py @@ -0,0 +1,107 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2016 ftrack + +from http.server import BaseHTTPRequestHandler, HTTPServer +# import BaseHTTPServer +from urllib import parse +# import urlparse +import webbrowser +import functools +# from QtExt import QtCore +from PyQt5 import QtCore + +# class LoginServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): +class LoginServerHandler(BaseHTTPRequestHandler): + '''Login server handler.''' + + def __init__(self, login_callback, *args, **kw): + '''Initialise handler.''' + self.login_callback = login_callback + BaseHTTPRequestHandler.__init__(self, *args, **kw) + + def do_GET(self): + '''Override to handle requests ourselves.''' + parsed_path = parse.urlparse(self.path) + query = parsed_path.query + + api_user = None + api_key = None + if 'api_user' and 'api_key' in query: + login_credentials = parse.parse_qs(query) + api_user = login_credentials['api_user'][0] + api_key = login_credentials['api_key'][0] + message = """ + + +
++ You signed in with username {0} and can now + close this window. +
+ + + """.format(api_user) + else: + message = '