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:
committed by
Wyatt Allen
parent
6f64316aca
commit
2d4b50c3d1
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user