PolyGerrit: don't show current HTTP password

With the move to NoteDB, the per-account data (including the HTTP
password) will be stored in a user-branch in the All-Users repo, where
it is subject to Gerrit ACLs.  Since these are notoriously hard to
set up correctly, we want to avoid storing the password in plaintext.

The simplest solution is to store the password in hashed form, which
precludes showing the current passwords in the settings UI.

This change removes the password GET call from PolyGerrit.

Bug: Issue 5373
Change-Id: I4f93873bdee0b82aaaf85090d0aa271a94ea8e17
This commit is contained in:
Han-Wen Nienhuys
2017-01-23 16:15:37 +01:00
committed by Wyatt Allen
parent 6f64316aca
commit 2d4b50c3d1
3 changed files with 60 additions and 153 deletions

View File

@@ -15,7 +15,9 @@ limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/gr-settings-styles.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-http-password">
@@ -24,9 +26,24 @@ limitations under the License.
.password {
font-family: var(--monospace-font-family);
}
.noPassword {
color: #777;
#generatedPasswordOverlay {
padding: 2em;
width: 50em;
}
#generatedPasswordDisplay {
margin: 1em 0;
}
#generatedPasswordDisplay .value {
font-family: var(--monospace-font-family);
}
#passwordWarning {
font-style: italic;
text-align: center;
}
.closeButton {
bottom: 2em;
position: absolute;
right: 2em;
}
</style>
<style include="gr-settings-styles"></style>
@@ -35,28 +52,28 @@ limitations under the License.
<span class="title">Username</span>
<span class="value">[[_username]]</span>
</section>
<section>
<span class="title">Password</span>
<span hidden$="[[!_hasPassword]]">
<span class="value" hidden$="[[_passwordVisible]]">
<gr-button
link
on-tap="_handleViewPasswordTap">Click to view</gr-button>
</span>
<span
class="value password"
hidden$="[[!_passwordVisible]]">[[_password]]</span>
</span>
<span class="value noPassword" hidden$="[[_hasPassword]]">(None)</span>
</section>
<gr-button
id="generateButton"
on-tap="_handleGenerateTap">Generate New Password</gr-button>
<gr-button
id="clearButton"
on-tap="_handleClearTap"
disabled="[[!_hasPassword]]">Clear Password</gr-button>
</div>
<gr-overlay
id="generatedPasswordOverlay"
on-iron-overlay-closed="_generatedPasswordOverlayClosed"
with-backdrop>
<div class="gr-settings-styles">
<section id="generatedPasswordDisplay">
<span class="title">New Password:</span>
<span class="value">[[_generatedPassword]]</span>
</section>
<section id="passwordWarning">
This password will not be displayed again.<br>
If you lose it, you will need to generate a new one.
</section>
<gr-button
class="closeButton"
on-tap="_closeOverlay">Close</gr-button>
</div>
</gr-overlay>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-http-password.js"></script>

View File

@@ -17,65 +17,31 @@
Polymer({
is: 'gr-http-password',
/**
* Fired when getting the password fails with non-404.
*
* @event network-error
*/
properties: {
_serverConfig: Object,
_username: String,
_password: String,
_passwordVisible: {
type: Boolean,
value: false,
},
_hasPassword: Boolean,
_generatedPassword: String,
},
loadData: function() {
var promises = [];
promises.push(this.$.restAPI.getAccount().then(function(account) {
return this.$.restAPI.getAccount().then(function(account) {
this._username = account.username;
}.bind(this)));
promises.push(this.$.restAPI
.getAccountHttpPassword(this._handleGetPasswordError.bind(this))
.then(function(pass) {
this._password = pass;
this._hasPassword = !!pass;
}.bind(this)));
return Promise.all(promises);
},
_handleGetPasswordError: function(response) {
if (response.status === 404) {
this._hasPassword = false;
} else {
this.fire('network-error', {response: response});
}
},
_handleViewPasswordTap: function() {
this._passwordVisible = true;
}.bind(this));
},
_handleGenerateTap: function() {
this._generatedPassword = 'Generating...';
this.$.generatedPasswordOverlay.open();
this.$.restAPI.generateAccountHttpPassword().then(function(newPassword) {
this._hasPassword = true;
this._passwordVisible = true;
this._password = newPassword;
this._generatedPassword = newPassword;
}.bind(this));
},
_handleClearTap: function() {
this.$.restAPI.deleteAccountHttpPassword().then(function() {
this._password = '';
this._hasPassword = false;
}.bind(this));
_closeOverlay: function() {
this.$.generatedPasswordOverlay.close();
},
_generatedPasswordOverlayClosed: function() {
this._generatedPassword = null;
},
});
})();

View File

@@ -31,7 +31,7 @@ limitations under the License.
</test-fixture>
<script>
suite('gr-http-password tests (already has password)', function() {
suite('gr-http-password tests', function() {
var element;
var account;
var password;
@@ -51,106 +51,30 @@ limitations under the License.
element.loadData().then(function() { flush(done); });
});
test('loads data', function() {
assert.equal(element._username, 'user name');
assert.equal(element._password, 'the password');
assert.isFalse(element._passwordVisible);
assert.isTrue(element._hasPassword);
});
test('view password', function() {
var button = element.$$('.value gr-button');
assert.isFalse(element._passwordVisible);
MockInteractions.tap(button);
assert.isTrue(element._passwordVisible);
});
test('generate password', function() {
var button = element.$.generateButton;
var nextPassword = 'the new password';
var generateResolve;
var generateStub = sinon.stub(element.$.restAPI,
'generateAccountHttpPassword', function() {
return Promise.resolve(nextPassword);
return new Promise(function(resolve) {
generateResolve = resolve;
});
});
assert.isTrue(element._hasPassword);
assert.isFalse(element._passwordVisible);
assert.equal(element._password, 'the password');
assert.isNotOk(element._generatedPassword);
MockInteractions.tap(button);
assert.isTrue(generateStub.called);
assert.equal(element._generatedPassword, 'Generating...');
generateResolve(nextPassword);
generateStub.lastCall.returnValue.then(function() {
assert.isTrue(element._passwordVisible);
assert.isTrue(element._hasPassword);
assert.equal(element._password, 'the new password');
});
});
test('clear password', function() {
var button = element.$.clearButton;
var clearStub = sinon.stub(element.$.restAPI, 'deleteAccountHttpPassword',
function() { return Promise.resolve(); });
assert.isTrue(element._hasPassword);
assert.equal(element._password, 'the password');
MockInteractions.tap(button);
assert.isTrue(clearStub.called);
clearStub.lastCall.returnValue.then(function() {
assert.isFalse(element._hasPassword);
assert.equal(element._password, '');
assert.equal(element._generatedPassword, nextPassword);
});
});
});
suite('gr-http-password tests (has no password)', function() {
var element;
var account;
setup(function(done) {
account = {username: 'user name'};
stub('gr-rest-api-interface', {
getAccount: function() { return Promise.resolve(account); },
getAccountHttpPassword: function(errFn) {
errFn({status: 404});
return Promise.resolve('');
},
});
element = fixture('basic');
element.loadData().then(function() { flush(done); });
});
test('loads data', function() {
assert.equal(element._username, 'user name');
assert.isNotOk(element._password);
assert.isFalse(element._passwordVisible);
assert.isFalse(element._hasPassword);
});
test('generate password', function() {
var button = element.$.generateButton;
var nextPassword = 'the new password';
var generateStub = sinon.stub(element.$.restAPI,
'generateAccountHttpPassword', function() {
return Promise.resolve(nextPassword);
});
assert.isFalse(element._hasPassword);
assert.isFalse(element._passwordVisible);
assert.isNotOk(element._password);
MockInteractions.tap(button);
assert.isTrue(generateStub.called);
generateStub.lastCall.returnValue.then(function() {
assert.isTrue(element._passwordVisible);
assert.isOk(element._hasPassword);
assert.equal(element._password, 'the new password');
});
});
});
</script>