From ffd9a071641f800fe8a2255c16b9f5b94fc49a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Wed, 2 Dec 2015 16:13:29 +0000 Subject: [PATCH] Restores sync credential functionality of 4.0. Improves UX of sync authentication. The account is now linked and unlinked and an API key related to the client is generated transparently in the background. The API key is deleted on unlinking. No sync options are allowed before linking an account. --- .../zotero/preferences/preferences_sync.js | 221 +++++++++- .../zotero/preferences/preferences_sync.xul | 400 ++++++++++-------- chrome/content/zotero/xpcom/http.js | 22 +- .../zotero/xpcom/sync/syncAPIClient.js | 54 ++- chrome/content/zotero/xpcom/sync/syncLocal.js | 33 +- .../content/zotero/xpcom/sync/syncRunner.js | 162 ++----- chrome/locale/en-US/zotero/preferences.dtd | 4 +- chrome/locale/en-US/zotero/zotero.properties | 5 +- chrome/skin/default/zotero/preferences.css | 24 ++ test/content/runtests.html | 1 + test/tests/preferences_syncTest.js | 160 +++++++ test/tests/syncRunnerTest.js | 122 +++--- 12 files changed, 826 insertions(+), 382 deletions(-) create mode 100644 test/tests/preferences_syncTest.js diff --git a/chrome/content/zotero/preferences/preferences_sync.js b/chrome/content/zotero/preferences/preferences_sync.js index 40eeb3bb53..e495dd94f0 100644 --- a/chrome/content/zotero/preferences/preferences_sync.js +++ b/chrome/content/zotero/preferences/preferences_sync.js @@ -24,19 +24,232 @@ */ "use strict"; +Components.utils.import("resource://gre/modules/Services.jsm"); Zotero_Preferences.Sync = { - init: function () { + init: Zotero.Promise.coroutine(function* () { this.updateStorageSettings(null, null, true); - - document.getElementById('sync-api-key').value = Zotero.Sync.Data.Local.getAPIKey(); - + + var username = Zotero.Users.getCurrentUsername() || ""; + var apiKey = Zotero.Sync.Data.Local.getAPIKey(); + this.displayFields(apiKey ? username : ""); + if (apiKey) { + try { + var keyInfo = yield Zotero.Sync.Runner.checkAccess( + Zotero.Sync.Runner.getAPIClient({apiKey}), + {timeout: 5000} + ); + this.displayFields(keyInfo.username); + } + catch (e) { + // API key wrong/invalid + if (!(e instanceof Zotero.HTTP.UnexpectedStatusException) && + !(e instanceof Zotero.HTTP.TimeoutException)) { + Zotero.alert( + window, + Zotero.getString('general.error'), + Zotero.getString('sync.error.apiKeyInvalid', Zotero.clientName) + ); + this.unlinkAccount(false); + } + else { + throw e; + } + } + } + + // TEMP: Disabled //var pass = Zotero.Sync.Storage.WebDAV.password; //if (pass) { // document.getElementById('storage-password').value = pass; //} + }), + + displayFields: function (username) { + document.getElementById('sync-unauthorized').hidden = !!username; + document.getElementById('sync-authorized').hidden = !username; + document.getElementById('sync-reset-tab').disabled = !username; + document.getElementById('sync-username').value = username; + document.getElementById('sync-password').value = ''; + document.getElementById('sync-username-textbox').value = Zotero.Prefs.get('sync.server.username'); + + var img = document.getElementById('sync-status-indicator'); + img.removeAttribute('verified'); + img.removeAttribute('animated'); }, + + + credentialsKeyPress: function (event) { + var username = document.getElementById('sync-username-textbox'); + username.value = username.value.trim(); + var password = document.getElementById('sync-password'); + + var syncAuthButton = document.getElementById('sync-auth-button'); + + syncAuthButton.setAttribute('disabled', 'true'); + + // When using backspace, the value is not updated until after the keypress event + setTimeout(function() { + if (username.value.length && password.value.length) { + syncAuthButton.setAttribute('disabled', 'false'); + } + }); + + if (event.keyCode == 13) { + Zotero_Preferences.Sync.linkAccount(event); + } + }, + + + linkAccount: Zotero.Promise.coroutine(function* (event) { + var username = document.getElementById('sync-username-textbox').value; + var password = document.getElementById('sync-password').value; + + if (!username.length || !password.length) { + this.updateSyncIndicator(); + return; + } + + // Try to acquire API key with current credentials + this.updateSyncIndicator('animated'); + var json = yield Zotero.Sync.Runner.createAPIKeyFromCredentials(username, password); + this.updateSyncIndicator(); + + // Invalid credentials + if (!json) { + Zotero.alert(window, + Zotero.getString('general.error'), + Zotero.getString('sync.error.invalidLogin') + ); + return; + } + + if (!(yield this.checkUser(json.userID, json.username))) { + // createAPIKeyFromCredentials will have created an API key, + // but user decided not to use it, so we remove it here. + Zotero.Sync.Runner.deleteAPIKey(); + return; + } + + this.displayFields(json.username); + }), + + /** + * Updates the auth indicator icon, depending on status + * @param {string} status + */ + updateSyncIndicator: function (status) { + var img = document.getElementById('sync-status-indicator'); + + img.removeAttribute('animated'); + if (status == 'animated') { + img.setAttribute('animated', true); + } + }, + + unlinkAccount: Zotero.Promise.coroutine(function* (showAlert=true) { + if (showAlert) { + if (!Services.prompt.confirm( + null, + Zotero.getString('general.warning'), + Zotero.getString('sync.unlinkWarning', Zotero.clientName) + )) { + return; + } + } + + this.displayFields(); + yield Zotero.Sync.Runner.deleteAPIKey(); + }), + + + /** + * Make sure we're syncing with the same account we used last time, and prompt if not. + * If user accepts, change the current user, delete existing groups, and update relation + * URIs to point to the new user's library. + * + * @param {Integer} userID New userID + * @param {Integer} libraryID New libraryID + * @return {Boolean} - True to continue, false to cancel + */ + checkUser: Zotero.Promise.coroutine(function* (userID, username) { + var lastUserID = Zotero.Users.getCurrentUserID(); + var lastUsername = Zotero.Users.getCurrentUsername(); + + if (lastUserID && lastUserID != userID) { + var groups = Zotero.Groups.getAll(); + + var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Components.interfaces.nsIPromptService); + var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) + + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL) + + (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING) + + ps.BUTTON_POS_1_DEFAULT + + ps.BUTTON_DELAY_ENABLE; + + var msg = Zotero.getString('sync.lastSyncWithDifferentAccount', [lastUsername, username]); + var syncButtonText = Zotero.getString('sync.sync'); + + msg += " " + Zotero.getString('sync.localDataWillBeCombined', username); + // If there are local groups belonging to the previous user, + // we need to remove them + if (groups.length) { + msg += " " + Zotero.getString('sync.localGroupsWillBeRemoved1'); + var syncButtonText = Zotero.getString('sync.removeGroupsAndSync'); + } + msg += "\n\n" + Zotero.getString('sync.avoidCombiningData', lastUsername); + + var index = ps.confirmEx( + null, + Zotero.getString('general.warning'), + msg, + buttonFlags, + syncButtonText, + null, + Zotero.getString('sync.openSyncPreferences'), + null, {} + ); + + if (index > 0) { + if (index == 2) { + var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + var lastWin = wm.getMostRecentWindow("navigator:browser"); + lastWin.ZoteroPane.openPreferences('zotero-prefpane-sync'); + } + return false; + } + } + + yield Zotero.DB.executeTransaction(function* () { + if (lastUserID != userID) { + if (lastUserID) { + // Delete all local groups if changing users + for (let group of groups) { + yield group.erase(); + } + + // Update relations pointing to the old library to point to this one + yield Zotero.Relations.updateUser(userID); + } + // Replace local user key with libraryID, in case duplicates were + // merged before the first sync + else { + yield Zotero.Relations.updateUser(userID); + } + + yield Zotero.Users.setCurrentUserID(userID); + } + + if (lastUsername != username) { + yield Zotero.Users.setCurrentUsername(username); + } + }) + + return true; + }), + updateStorageSettings: function (enabled, protocol, skipWarnings) { if (enabled === null) { diff --git a/chrome/content/zotero/preferences/preferences_sync.xul b/chrome/content/zotero/preferences/preferences_sync.xul index 8b79a81a45..0e459276c4 100644 --- a/chrome/content/zotero/preferences/preferences_sync.xul +++ b/chrome/content/zotero/preferences/preferences_sync.xul @@ -47,216 +47,240 @@ - + - - - - - + + + + + + + + + + + + + + + + + + +