Commit 06c1f04a authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

Settings UI: Split up security key-related browser proxies and message handlers

The Security Key settings subpage currently has a single monolithic
browser proxy and associated message handler for three sets of logically
independent functionality (PIN, credential management and reset). This
change, splits them into three individual parts.

This is a structural change only. No functional/behavior changes
intended.

Change-Id: Ie167ed58b4bd293764d6e784d80f922a43296551
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1688206
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#680566}
parent 381033f4
......@@ -28,7 +28,7 @@ let Credential;
cr.define('settings', function() {
/** @interface */
class SecurityKeysBrowserProxy {
class SecurityKeysPINBrowserProxy {
/**
* Starts a PIN set/change operation by flashing all security keys. Resolves
* with a pair of numbers. The first is one if the process has immediately
......@@ -50,6 +50,12 @@ cr.define('settings', function() {
*/
setPIN(oldPIN, newPIN) {}
/** Cancels all outstanding operations. */
close() {}
}
/** @interface */
class SecurityKeysCredentialBrowserProxy {
/**
* Starts a credential management operation.
*
......@@ -70,24 +76,30 @@ cr.define('settings', function() {
* @return {!Promise<?number>} a promise that resolves with null if the PIN
* was correct or the number of retries remaining otherwise.
*/
credentialManagementProvidePIN(pin) {}
providePIN(pin) {}
/**
* Enumerates credentials on the authenticator. A correct PIN must have
* previously been supplied via credentialManagementProvidePIN() before this
* previously been supplied via providePIN() before this
* method may be called.
* @return {!Promise<!Array<!Credential>>}
*/
credentialManagementEnumerate() {}
enumerateCredentials() {}
/**
* Deletes the credentials with the given IDs from the security key.
* @param {!Array<!string>} ids
* @return {!Promise<!string>} a localized response message to display to
* @param {!Array<string>} ids
* @return {!Promise<string>} a localized response message to display to
* the user (on either success or error)
*/
credentialManagementDeleteCredentials(ids) {}
deleteCredentials(ids) {}
/** Cancels all outstanding operations. */
close() {}
}
/** @interface */
class SecurityKeysResetBrowserProxy {
/**
* Starts a reset operation by flashing all security keys and sending a
* reset command to the one that the user activates. Resolves with a CTAP
......@@ -102,16 +114,12 @@ cr.define('settings', function() {
*/
completeReset() {}
/**
* Cancel all outstanding operations.
*/
/** Cancels all outstanding operations. */
close() {}
}
/**
* @implements {settings.SecurityKeysBrowserProxy}
*/
class SecurityKeysBrowserProxyImpl {
/** @implements {settings.SecurityKeysPINBrowserProxy} */
class SecurityKeysPINBrowserProxyImpl {
/** @override */
startSetPIN() {
return cr.sendWithPromise('securityKeyStartSetPIN');
......@@ -122,26 +130,42 @@ cr.define('settings', function() {
return cr.sendWithPromise('securityKeySetPIN', oldPIN, newPIN);
}
/** @override */
close() {
return chrome.send('securityKeyPINClose');
}
}
/** @implements {settings.SecurityKeysCredentialBrowserProxy} */
class SecurityKeysCredentialBrowserProxyImpl {
/** @override */
startCredentialManagement() {
return cr.sendWithPromise('securityKeyCredentialManagement');
return cr.sendWithPromise('securityKeyCredentialManagementStart');
}
/** @override */
credentialManagementProvidePIN(pin) {
providePIN(pin) {
return cr.sendWithPromise('securityKeyCredentialManagementPIN', pin);
}
/** @override */
credentialManagementEnumerate() {
enumerateCredentials() {
return cr.sendWithPromise('securityKeyCredentialManagementEnumerate');
}
/** @override */
credentialManagementDeleteCredentials(ids) {
deleteCredentials(ids) {
return cr.sendWithPromise('securityKeyCredentialManagementDelete', ids);
}
/** @override */
close() {
return chrome.send('securityKeyCredentialManagementClose');
}
}
/** @implements {settings.SecurityKeysResetBrowserProxy} */
class SecurityKeysResetBrowserProxyImpl {
/** @override */
reset() {
return cr.sendWithPromise('securityKeyReset');
......@@ -154,16 +178,23 @@ cr.define('settings', function() {
/** @override */
close() {
return chrome.send('securityKeyClose');
return chrome.send('securityKeyResetClose');
}
}
// The singleton instance_ is replaced with a test version of this wrapper
// during testing.
cr.addSingletonGetter(SecurityKeysBrowserProxyImpl);
cr.addSingletonGetter(SecurityKeysPINBrowserProxyImpl);
cr.addSingletonGetter(SecurityKeysCredentialBrowserProxyImpl);
cr.addSingletonGetter(SecurityKeysResetBrowserProxyImpl);
return {
SecurityKeysBrowserProxy: SecurityKeysBrowserProxy,
SecurityKeysBrowserProxyImpl: SecurityKeysBrowserProxyImpl,
SecurityKeysPINBrowserProxy: SecurityKeysPINBrowserProxy,
SecurityKeysPINBrowserProxyImpl: SecurityKeysPINBrowserProxyImpl,
SecurityKeysCredentialBrowserProxy: SecurityKeysCredentialBrowserProxy,
SecurityKeysCredentialBrowserProxyImpl:
SecurityKeysCredentialBrowserProxyImpl,
SecurityKeysResetBrowserProxy: SecurityKeysResetBrowserProxy,
SecurityKeysResetBrowserProxyImpl: SecurityKeysResetBrowserProxyImpl,
};
});
......@@ -59,7 +59,7 @@ Polymer({
deleteInProgress_: Boolean,
},
/** @private {?settings.SecurityKeysBrowserProxy} */
/** @private {?settings.SecurityKeysCredentialBrowserProxy} */
browserProxy_: null,
/** @private {?Set<string>} */
......@@ -72,7 +72,8 @@ Polymer({
'security-keys-credential-management-finished',
this.onError_.bind(this));
this.checkedCredentialIds_ = new Set();
this.browserProxy_ = settings.SecurityKeysBrowserProxyImpl.getInstance();
this.browserProxy_ =
settings.SecurityKeysCredentialBrowserProxyImpl.getInstance();
this.browserProxy_.startCredentialManagement().then(
this.collectPin_.bind(this));
},
......@@ -97,16 +98,15 @@ Polymer({
if (!this.$.pin.validate()) {
return;
}
this.browserProxy_.credentialManagementProvidePIN(this.$.pin.value)
.then((retries) => {
if (retries != null) {
this.$.pin.showIncorrectPINError(retries);
this.collectPin_();
return;
}
this.browserProxy_.credentialManagementEnumerate().then(
this.onCredentials_.bind(this));
});
this.browserProxy_.providePIN(this.$.pin.value).then((retries) => {
if (retries != null) {
this.$.pin.showIncorrectPINError(retries);
this.collectPin_();
return;
}
this.browserProxy_.enumerateCredentials().then(
this.onCredentials_.bind(this));
});
},
/**
......@@ -257,9 +257,7 @@ Polymer({
assert(this.checkedCredentialIds_.size > 0);
this.deleteInProgress_ = true;
this.browserProxy_
.credentialManagementDeleteCredentials(
Array.from(this.checkedCredentialIds_))
this.browserProxy_.deleteCredentials(Array.from(this.checkedCredentialIds_))
.then((err) => {
this.deleteInProgress_ = false;
this.onError_(err);
......
......@@ -42,13 +42,14 @@ Polymer({
title_: String,
},
/** @private {?settings.SecurityKeysBrowserProxy} */
/** @private {?settings.SecurityKeysResetBrowserProxy} */
browserProxy_: null,
/** @override */
attached: function() {
this.title_ = this.i18n('securityKeysResetTitle');
this.browserProxy_ = settings.SecurityKeysBrowserProxyImpl.getInstance();
this.browserProxy_ =
settings.SecurityKeysResetBrowserProxyImpl.getInstance();
this.$.dialog.showModal();
this.browserProxy_.reset().then(code => {
......
......@@ -140,13 +140,13 @@ Polymer({
title_: String,
},
/** @private {?settings.SecurityKeysBrowserProxy} */
/** @private {?settings.SecurityKeysPINBrowserProxy} */
browserProxy_: null,
/** @override */
attached: function() {
this.title_ = this.i18n('securityKeysSetPINInitialTitle');
this.browserProxy_ = settings.SecurityKeysBrowserProxyImpl.getInstance();
this.browserProxy_ = settings.SecurityKeysPINBrowserProxyImpl.getInstance();
this.$.dialog.showModal();
this.browserProxy_.startSetPIN().then(([success, errorCode]) => {
......
......@@ -28,59 +28,118 @@ class ResetRequestHandler;
namespace settings {
// SecurityKeysHandler processes messages from the "Security Keys" section of a
// settings page. An instance of this class is created for each settings tab and
// is destroyed when the tab is closed. See the comments in
// security_keys_browser_proxy.js about the interface.
class SecurityKeysHandler : public SettingsPageUIHandler {
public:
SecurityKeysHandler();
~SecurityKeysHandler() override;
// Base class for message handlers on the "Security Keys" settings subpage.
class SecurityKeysHandlerBase : public SettingsPageUIHandler {
protected:
SecurityKeysHandlerBase() = default;
void RegisterMessages() override;
// Subclasses must implement close to invalidate all pending callbacks.
virtual void Close() = 0;
private:
void OnJavascriptAllowed() override;
void OnJavascriptDisallowed() override;
SecurityKeysHandlerBase(const SecurityKeysHandlerBase&) = delete;
SecurityKeysHandlerBase& operator=(const SecurityKeysHandlerBase&) = delete;
};
// SecurityKeysPINHandler processes messages from the "Create a PIN" dialog of
// the "Security Keys" settings subpage. An instance of this class is created
// for each settings tab and is destroyed when the tab is closed. See
// SecurityKeysPINBrowserProxy about the interface.
class SecurityKeysPINHandler : public SecurityKeysHandlerBase {
public:
SecurityKeysPINHandler();
~SecurityKeysPINHandler() override;
private:
enum class State {
kNone,
kStartSetPIN,
kGatherNewPIN,
kGatherChangePIN,
kSettingPIN,
kStartReset,
kWaitingForResetNoCallbackYet,
kWaitingForResetHaveCallback,
kWaitingForCompleteReset,
kCredentialManagementStart,
kCredentialManagementPIN,
kCredentialManagementReady,
kCredentialManagementGettingCredentials,
kCredentialManagementDeleting,
};
void Close();
void RegisterMessages() override;
void Close() override;
// PIN
void HandleStartSetPIN(const base::ListValue* args);
void OnGatherPIN(base::Optional<int64_t> num_retries);
void OnSetPINComplete(device::CtapDeviceResponseCode code);
void HandleSetPIN(const base::ListValue* args);
// Reset
State state_ = State::kNone;
std::unique_ptr<device::SetPINRequestHandler> set_pin_;
std::string callback_id_;
base::WeakPtrFactory<SecurityKeysPINHandler> weak_factory_{this};
};
// SecurityKeysResetHandler processes messages from the "Reset your Security
// Key" dialog of the "Security Keys" settings subpage. An instance of this
// class is created for each settings tab and is destroyed when the tab is
// closed. See SecurityKeysResetBrowserProxy about the interface.
class SecurityKeysResetHandler : public SecurityKeysHandlerBase {
public:
SecurityKeysResetHandler();
~SecurityKeysResetHandler() override;
private:
enum class State {
kNone,
kStartReset,
kWaitingForResetNoCallbackYet,
kWaitingForResetHaveCallback,
kWaitingForCompleteReset,
};
void RegisterMessages() override;
void Close() override;
void HandleReset(const base::ListValue* args);
void OnResetSent();
void HandleCompleteReset(const base::ListValue* args);
void OnResetFinished(device::CtapDeviceResponseCode result);
// Credential Management
void HandleCredentialManagement(const base::ListValue* args);
void HandleCredentialManagementPIN(const base::ListValue* args);
void HandleCredentialManagementEnumerate(const base::ListValue* args);
void HandleCredentialManagementDelete(const base::ListValue* args);
State state_ = State::kNone;
std::unique_ptr<device::ResetRequestHandler> reset_;
base::Optional<device::CtapDeviceResponseCode> reset_result_;
std::string callback_id_;
base::WeakPtrFactory<SecurityKeysResetHandler> weak_factory_{this};
};
// SecurityKeysCredentialHandler processes messages from the "Manage
// sign-in data" dialog of the "Security Keys" settings subpage. An instance of
// this class is created for each settings tab and is destroyed when the tab is
// closed. See SecurityKeysCredentialBrowserProxy about the interface.
class SecurityKeysCredentialHandler : public SecurityKeysHandlerBase {
public:
SecurityKeysCredentialHandler();
~SecurityKeysCredentialHandler() override;
private:
enum class State {
kNone,
kStart,
kPIN,
kReady,
kGettingCredentials,
kDeletingCredentials,
};
void RegisterMessages() override;
void Close() override;
void HandleStart(const base::ListValue* args);
void HandlePIN(const base::ListValue* args);
void HandleEnumerate(const base::ListValue* args);
void HandleDelete(const base::ListValue* args);
void OnCredentialManagementReady();
void OnHaveCredentials(
device::CtapDeviceResponseCode status,
......@@ -88,24 +147,18 @@ class SecurityKeysHandler : public SettingsPageUIHandler {
std::vector<device::AggregatedEnumerateCredentialsResponse>>
responses,
base::Optional<size_t> remaining_credentials);
void OnCredentialManagementGatherPIN(int64_t num_retries,
base::OnceCallback<void(std::string)>);
void OnGatherPIN(int64_t num_retries, base::OnceCallback<void(std::string)>);
void OnCredentialsDeleted(device::CtapDeviceResponseCode status);
void OnCredentialManagementFinished(device::FidoReturnCode status);
void OnFinished(device::FidoReturnCode status);
void HandleClose(const base::ListValue* args);
State state_;
State state_ = State::kNone;
base::OnceCallback<void(std::string)> credential_management_provide_pin_cb_;
std::unique_ptr<device::FidoDiscoveryFactory> discovery_factory_;
std::unique_ptr<device::SetPINRequestHandler> set_pin_;
std::unique_ptr<device::CredentialManagementHandler> credential_management_;
std::unique_ptr<device::ResetRequestHandler> reset_;
base::Optional<device::CtapDeviceResponseCode> reset_result_;
std::string callback_id_;
std::unique_ptr<base::WeakPtrFactory<SecurityKeysHandler>> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(SecurityKeysHandler);
std::string callback_id_;
base::WeakPtrFactory<SecurityKeysCredentialHandler> weak_factory_{this};
};
} // namespace settings
......
......@@ -230,7 +230,9 @@ SettingsUI::SettingsUI(content::WebUI* web_ui)
AddSettingsPageUIHandler(std::make_unique<SearchEnginesHandler>(profile));
AddSettingsPageUIHandler(std::make_unique<SiteSettingsHandler>(profile));
AddSettingsPageUIHandler(std::make_unique<StartupPagesHandler>(web_ui));
AddSettingsPageUIHandler(std::make_unique<SecurityKeysHandler>());
AddSettingsPageUIHandler(std::make_unique<SecurityKeysPINHandler>());
AddSettingsPageUIHandler(std::make_unique<SecurityKeysResetHandler>());
AddSettingsPageUIHandler(std::make_unique<SecurityKeysCredentialHandler>());
#if defined(OS_WIN) || defined(OS_MACOSX)
AddSettingsPageUIHandler(std::make_unique<CaptionsHandler>());
......
......@@ -2,18 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/** @implements {settings.SecurityKeysBrowserProxy} */
class TestSecurityKeysBrowserProxy extends TestBrowserProxy {
/** @implements {settings.SecurityKeysPINBrowserProxy} */
class TestSecurityKeysPINBrowserProxy extends TestBrowserProxy {
constructor() {
super([
'startSetPIN',
'setPIN',
'reset',
'completeReset',
'startCredentialManagement',
'credentialManagementProvidePIN',
'credentialManagementEnumerate',
'credentialManagementDeleteCredentials',
'close',
]);
......@@ -62,6 +56,56 @@ class TestSecurityKeysBrowserProxy extends TestBrowserProxy {
return this.handleMethod_('setPIN', {oldPIN, newPIN});
}
/** @override */
close() {
this.methodCalled('close');
}
}
/** @implements {settings.SecurityKeysResetBrowserProxy} */
class TestSecurityKeysResetBrowserProxy extends TestBrowserProxy {
constructor() {
super([
'reset',
'completeReset',
'close',
]);
/**
* A map from method names to a promise to return when that method is
* called. (If no promise is installed, a never-resolved promise is
* returned.)
* @private {!Map<string, !Promise>}
*/
this.promiseMap_ = new Map();
}
/**
* @param {string} methodName
* @param {!Promise} promise
*/
setResponseFor(methodName, promise) {
this.promiseMap_.set(methodName, promise);
}
/**
* @param {string} methodName
* @param {*} opt_arg
* @return {!Promise}
* @private
*/
handleMethod_(methodName, opt_arg) {
this.methodCalled(methodName, opt_arg);
const promise = this.promiseMap_.get(methodName);
if (promise != undefined) {
this.promiseMap_.delete(methodName);
return promise;
}
// Return a Promise that never resolves.
return new Promise(() => {});
}
/** @override */
reset() {
return this.handleMethod_('reset');
......@@ -72,24 +116,76 @@ class TestSecurityKeysBrowserProxy extends TestBrowserProxy {
return this.handleMethod_('completeReset');
}
/** @override */
close() {
this.methodCalled('close');
}
}
/** @implements {settings.SecurityKeysCredentialBrowserProxy} */
class TestSecurityKeysCredentialBrowserProxy extends TestBrowserProxy {
constructor() {
super([
'startCredentialManagement',
'providePIN',
'enumerateCredentials',
'deleteCredentials',
'close',
]);
/**
* A map from method names to a promise to return when that method is
* called. (If no promise is installed, a never-resolved promise is
* returned.)
* @private {!Map<string, !Promise>}
*/
this.promiseMap_ = new Map();
}
/**
* @param {string} methodName
* @param {!Promise} promise
*/
setResponseFor(methodName, promise) {
this.promiseMap_.set(methodName, promise);
}
/**
* @param {string} methodName
* @param {*} opt_arg
* @return {!Promise}
* @private
*/
handleMethod_(methodName, opt_arg) {
this.methodCalled(methodName, opt_arg);
const promise = this.promiseMap_.get(methodName);
if (promise != undefined) {
this.promiseMap_.delete(methodName);
return promise;
}
// Return a Promise that never resolves.
return new Promise(() => {});
}
/** @override */
startCredentialManagement() {
return this.handleMethod_('startCredentialManagement');
}
/** @override */
credentialManagementProvidePIN(pin) {
return this.handleMethod_('credentialManagementProvidePIN', pin);
providePIN(pin) {
return this.handleMethod_('providePIN', pin);
}
/** @override */
credentialManagementEnumerate() {
return this.handleMethod_('credentialManagementEnumerate');
enumerateCredentials() {
return this.handleMethod_('enumerateCredentials');
}
/** @override */
credentialManagementDeleteCredentials(ids) {
return this.handleMethod_('credentialManagementDeleteCredentials', ids);
deleteCredentials(ids) {
return this.handleMethod_('deleteCredentials', ids);
}
/** @override */
......@@ -102,8 +198,8 @@ suite('SecurityKeysResetDialog', function() {
let dialog = null;
setup(function() {
browserProxy = new TestSecurityKeysBrowserProxy();
settings.SecurityKeysBrowserProxyImpl.instance_ = browserProxy;
browserProxy = new TestSecurityKeysResetBrowserProxy();
settings.SecurityKeysResetBrowserProxyImpl.instance_ = browserProxy;
PolymerTest.clearBody();
dialog = document.createElement('settings-security-keys-reset-dialog');
});
......@@ -221,8 +317,8 @@ suite('SecurityKeysSetPINDialog', function() {
let dialog = null;
setup(function() {
browserProxy = new TestSecurityKeysBrowserProxy();
settings.SecurityKeysBrowserProxyImpl.instance_ = browserProxy;
browserProxy = new TestSecurityKeysPINBrowserProxy();
settings.SecurityKeysPINBrowserProxyImpl.instance_ = browserProxy;
PolymerTest.clearBody();
dialog = document.createElement('settings-security-keys-set-pin-dialog');
});
......@@ -476,8 +572,8 @@ suite('SecurityKeysCredentialManagement', function() {
let dialog = null;
setup(function() {
browserProxy = new TestSecurityKeysBrowserProxy();
settings.SecurityKeysBrowserProxyImpl.instance_ = browserProxy;
browserProxy = new TestSecurityKeysCredentialBrowserProxy();
settings.SecurityKeysCredentialBrowserProxyImpl.instance_ = browserProxy;
PolymerTest.clearBody();
dialog = document.createElement(
'settings-security-keys-credential-management-dialog');
......@@ -530,14 +626,12 @@ suite('SecurityKeysCredentialManagement', function() {
browserProxy.setResponseFor(
'startCredentialManagement', startCredentialManagementResolver.promise);
const pinResolver = new PromiseResolver();
browserProxy.setResponseFor(
'credentialManagementProvidePIN', pinResolver.promise);
browserProxy.setResponseFor('providePIN', pinResolver.promise);
const enumerateResolver = new PromiseResolver();
browserProxy.setResponseFor(
'credentialManagementEnumerate', enumerateResolver.promise);
'enumerateCredentials', enumerateResolver.promise);
const deleteResolver = new PromiseResolver();
browserProxy.setResponseFor(
'credentialManagementDeleteCredentials', deleteResolver.promise);
browserProxy.setResponseFor('deleteCredentials', deleteResolver.promise);
document.body.appendChild(dialog);
await browserProxy.whenCalled('startCredentialManagement');
......@@ -551,12 +645,12 @@ suite('SecurityKeysCredentialManagement', function() {
assertShown('pinPrompt');
dialog.$.pin.value = '0000';
dialog.$.confirmButton.click();
const pin = await browserProxy.whenCalled('credentialManagementProvidePIN');
const pin = await browserProxy.whenCalled('providePIN');
assertEquals(pin, '0000');
// Show a list of three credentials.
pinResolver.resolve();
await browserProxy.whenCalled('credentialManagementEnumerate');
await browserProxy.whenCalled('enumerateCredentials');
uiReady = test_util.eventToPromise(
'credential-management-dialog-ready-for-testing', dialog);
const credentials = [
......@@ -596,8 +690,7 @@ suite('SecurityKeysCredentialManagement', function() {
assertFalse(dialog.$.confirmButton.disabled);
dialog.$.confirmButton.click();
const credentialIds =
await browserProxy.whenCalled('credentialManagementDeleteCredentials');
const credentialIds = await browserProxy.whenCalled('deleteCredentials');
assertDeepEquals(credentialIds, ['bbbbbb', 'cccccc']);
uiReady = test_util.eventToPromise(
'credential-management-dialog-ready-for-testing', dialog);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment