From 14ef76a1c63a885f0b6d9da0234c2f5c5720d436 Mon Sep 17 00:00:00 2001 From: Viktar Donich Date: Thu, 22 Dec 2016 12:51:17 -0800 Subject: [PATCH] Handle email confirmation links in PolyGerrit Handles #/VE/token links from confirmation emails sent by Gerrit by redirecting to /settings/VE/token and validating the token using REST API. Errors and success messages are shown as a pop-up toast using gr-alert via show-alert DOM event handled by gr-error-manager. Feature: Issue 4196 Change-Id: Ia6d40894512cd77b4d19f88668af15c68f03b10d --- .../core/gr-error-manager/gr-error-manager.js | 5 ++ .../gr-error-manager_test.html | 7 +++ .../app/elements/core/gr-router/gr-router.js | 19 ++++++- polygerrit-ui/app/elements/gr-app.html | 1 + .../gr-settings-view/gr-settings-view.js | 23 +++++++- .../gr-settings-view_test.html | 57 ++++++++++++++++++- .../gr-rest-api-interface.js | 10 ++++ .../gr-rest-api-interface_test.html | 17 ++++-- 8 files changed, 130 insertions(+), 9 deletions(-) diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js index 479f389a0b..4c8c64fb61 100644 --- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js +++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js @@ -31,6 +31,7 @@ attached: function() { this.listen(document, 'server-error', '_handleServerError'); this.listen(document, 'network-error', '_handleNetworkError'); + this.listen(document, 'show-alert', '_handleShowAlert'); }, detached: function() { @@ -61,6 +62,10 @@ } }, + _handleShowAlert: function(e) { + this._showAlert(e.detail.message); + }, + _handleNetworkError: function(e) { this._showAlert('Server unavailable'); console.error(e.detail.error.message); diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html index 44cbde0603..e2cacf9398 100644 --- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html +++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html @@ -134,5 +134,12 @@ limitations under the License. }); }); }); + + test('show alert', function() { + sandbox.stub(element, '_showAlert'); + element.fire('show-alert', {message: 'foo'}); + assert.isTrue(element._showAlert.calledOnce); + assert.isTrue(element._showAlert.lastCall.calledWithExactly('foo')); + }); }); diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js index 74344a7511..3819bfe60d 100644 --- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js +++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js @@ -64,7 +64,11 @@ if (data.hash[0] !== '/') { data.hash = '/' + data.hash; } - page.redirect(data.hash); + var newUrl = data.hash; + if (newUrl.indexOf('/VE/') === 0) { + newUrl = '/settings' + data.hash; + } + page.redirect(newUrl); return; } restAPI.getLoggedIn().then(function(loggedIn) { @@ -171,6 +175,19 @@ app.params = params; }); + page(/^\/settings\/VE\/(\S+)/, function(data) { + restAPI.getLoggedIn().then(function(loggedIn) { + if (loggedIn) { + app.params = { + view: 'gr-settings-view', + emailToken: data.params[0], + }; + } else { + page.show('/login/' + encodeURIComponent(data.canonicalPath)); + } + }); + }); + page(/^\/settings\/?/, function(data) { restAPI.getLoggedIn().then(function(loggedIn) { if (loggedIn) { diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html index 83ceb57e70..269240a3a3 100644 --- a/polygerrit-ui/app/elements/gr-app.html +++ b/polygerrit-ui/app/elements/gr-app.html @@ -117,6 +117,7 @@ limitations under the License. diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js index 5e20c9ec01..e12d4347e6 100644 --- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js +++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js @@ -32,11 +32,21 @@ * @event title-change */ + /** + * Fired with email confirmation text. + * + * @event show-alert + */ + properties: { prefs: { type: Object, value: function() { return {}; }, }, + params: { + type: Object, + value: function() { return {}; }, + }, _accountInfoMutable: Boolean, _accountInfoChanged: Boolean, _diffPrefs: Object, @@ -116,7 +126,6 @@ var promises = [ this.$.accountInfo.loadData(), this.$.watchedProjectsEditor.loadData(), - this.$.emailEditor.loadData(), this.$.groupList.loadData(), this.$.httpPass.loadData(), ]; @@ -139,6 +148,18 @@ } }.bind(this))); + if (this.params.emailToken) { + promises.push(this.$.restAPI.confirmEmail(this.params.emailToken).then( + function(message) { + if (message) { + this.fire('show-alert', {message: message}); + } + this.$.emailEditor.loadData(); + }.bind(this))); + } else { + promises.push(this.$.emailEditor.loadData()); + } + this._loadingPromise = Promise.all(promises).then(function() { this._loading = false; }.bind(this)); diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html index 0b42da5829..6a2af187bb 100644 --- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html +++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html @@ -43,6 +43,7 @@ limitations under the License. var preferences; var diffPreferences; var config; + var sandbox; function valueOf(title, fieldsetid) { var sections = element.$[fieldsetid].querySelectorAll('section'); @@ -65,11 +66,12 @@ limitations under the License. } function stubAddAccountEmail(statusCode) { - return sinon.stub(element.$.restAPI, 'addAccountEmail', + return sandbox.stub(element.$.restAPI, 'addAccountEmail', function() { return Promise.resolve({status: statusCode}); }); } setup(function(done) { + sandbox = sinon.sandbox.create(); account = { _account_id: 123, name: 'user name', @@ -129,8 +131,12 @@ limitations under the License. element._loadingPromise.then(done); }); + teardown(function() { + sandbox.restore(); + }); + test('calls the title-change event', function() { - var titleChangedStub = sinon.stub(); + var titleChangedStub = sandbox.stub(); // Create a new view. var newElement = document.createElement('gr-settings-view'); @@ -341,5 +347,52 @@ limitations under the License. done(); }); }); + + test('emails are loaded without emailToken', function() { + sandbox.stub(element.$.emailEditor, 'loadData'); + element.params = {}; + element.attached(); + assert.isTrue(element.$.emailEditor.loadData.calledOnce); + }); + + suite('when email verification token is provided', function() { + var resolveConfirm; + + setup(function() { + sandbox.stub(element.$.emailEditor, 'loadData'); + sandbox.stub(element.$.restAPI, 'confirmEmail', function() { + return new Promise(function(resolve) { resolveConfirm = resolve; }); + }); + element.params = {emailToken: 'foo'}; + element.attached(); + }); + + test('it is used to confirm email via rest API', function() { + assert.isTrue(element.$.restAPI.confirmEmail.calledOnce); + assert.isTrue(element.$.restAPI.confirmEmail.calledWith('foo')); + }); + + test('emails are not loaded initially', function() { + assert.isFalse(element.$.emailEditor.loadData.called); + }); + + test('user emails are loaded after email confirmed', function(done) { + element._loadingPromise.then(function() { + assert.isTrue(element.$.emailEditor.loadData.calledOnce); + done(); + }); + resolveConfirm(); + }); + + test('show-alert is fired when email is confirmed', function(done) { + sandbox.spy(element, 'fire'); + element._loadingPromise.then(function() { + assert.isTrue(element.fire.calledWith('show-alert', {message: 'bar'})); + done(); + }); + resolveConfirm('bar'); + }); + + }); }); diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js index 3769a0b1b9..4b52503f86 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js @@ -942,5 +942,15 @@ this.getChangeActionURL(changeNum, patchNum, '/description'), {description: desc}); }, + + confirmEmail: function(token) { + return this.send('PUT', '/config/server/email.confirm', {token: token}) + .then(function(response) { + if (response.status === 204) { + return 'Email confirmed successfully.'; + } + return null; + }); + }, }); })(); diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html index a9cea241b2..f43d62c6f0 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html @@ -333,10 +333,10 @@ limitations under the License. test('saveDiffPreferences invalidates cache line', function() { var cacheKey = '/accounts/self/preferences.diff'; - var sendStub = sandbox.stub(element, 'send'); + sandbox.stub(element, 'send'); element._cache[cacheKey] = {tab_size: 4}; element.saveDiffPreferences({tab_size: 8}); - assert.isTrue(sendStub.called); + assert.isTrue(element.send.called); assert.notOk(element._cache[cacheKey]); }); @@ -414,10 +414,17 @@ limitations under the License. }); test('savPreferences normalizes download scheme', function() { - var sendStub = sandbox.stub(element, 'send'); + sandbox.stub(element, 'send'); element.savePreferences({download_scheme: 'HTTP'}); - assert.isTrue(sendStub.called); - assert.equal(sendStub.lastCall.args[2].download_scheme, 'http'); + assert.isTrue(element.send.called); + assert.equal(element.send.lastCall.args[2].download_scheme, 'http'); + }); + + test('confirmEmail', function() { + sandbox.spy(element, 'send'); + element.confirmEmail('foo'); + assert.isTrue(element.send.calledWith( + 'PUT', '/config/server/email.confirm', {token: 'foo'})); }); });