Skip to content

Commit

Permalink
Close USB devices when the user is locking the session. Close #100.
Browse files Browse the repository at this point in the history
Previously, smartcard users could not unlock the lock screen since
the user space smartcard connector app would still own the smartcard reader.
This makes the app show no available devices when the user is locking the
screen. PCSC automatically releases devices that it used to own but no longer
show as present.
  • Loading branch information
Fabian-Sommer authored Jan 16, 2020
1 parent d48f773 commit 5b3b811
Show file tree
Hide file tree
Showing 8 changed files with 545 additions and 10 deletions.
6 changes: 6 additions & 0 deletions smart_card_connector_app/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ goog.provide('GoogleSmartCard.ConnectorApp.BackgroundMain');

goog.require('GoogleSmartCard.ConnectorApp.Background.MainWindowManaging');
goog.require('GoogleSmartCard.Libusb.ChromeUsbBackend');
goog.require('GoogleSmartCard.Libusb.ChromeLoginStateHook');
goog.require('GoogleSmartCard.Logging');
goog.require('GoogleSmartCard.MessageChannelPair');
goog.require('GoogleSmartCard.MessageChannelPool');
Expand Down Expand Up @@ -54,6 +55,11 @@ naclModule.addOnDisposeCallback(naclModuleDisposedListener);

var libusbChromeUsbBackend = new GSC.Libusb.ChromeUsbBackend(
naclModule.messageChannel);
var chromeLoginStateHook = new GSC.Libusb.ChromeLoginStateHook();
libusbChromeUsbBackend.addRequestSuccessHook(
chromeLoginStateHook.getRequestSuccessHook());
chromeLoginStateHook.getHookReadyPromise().thenAlways(
function() { libusbChromeUsbBackend.startProcessingEvents(); });
var pcscLiteReadinessTracker =
new GSC.PcscLiteServerClientsManagement.ReadinessTracker(
naclModule.messageChannel);
Expand Down
1 change: 1 addition & 0 deletions smart_card_connector_app/src/manifest.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"permissions": [
"alwaysOnTopWindows",
"loginState",
"usb",
{
"usbDevices": [
Expand Down
46 changes: 46 additions & 0 deletions third_party/closure-compiler/src_additional/chrome_extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,49 @@ chrome.certificateProvider.SignDigestRequestEvent.prototype.removeListener = fun
* @type {!chrome.certificateProvider.SignDigestRequestEvent}
*/
chrome.certificateProvider.onSignDigestRequested;

/**
* @const
*/
chrome.loginState = {};

/**
* @enum {string}
*/
chrome.loginState.ProfileType = {
SIGNIN_PROFILE: 'SIGNIN_PROFILE',
USER_PROFILE: 'USER_PROFILE',
};

/**
* @enum {string}
*/
chrome.loginState.SessionState = {
UNKNOWN: 'UNKNOWN',
IN_OOBE_SCREEN: 'IN_OOBE_SCREEN',
IN_LOGIN_SCREEN: 'IN_LOGIN_SCREEN',
IN_SESSION: 'IN_SESSION',
IN_LOCK_SCREEN: 'IN_LOCK_SCREEN',
};

/**
* @param {function(!chrome.loginState.ProfileType)} callback
*/
chrome.loginState.getProfileType = function(callback) {};

/**
* @param {function(!chrome.loginState.SessionState)} callback
*/
chrome.loginState.getSessionState = function(callback) {};

/**
* Event that triggers when the session state changes.
* @interface
* @extends {ChromeBaseEvent<function(!chrome.loginState.SessionState)>}
*/
chrome.loginState.SessionStateEvent = function() {};

/**
* @type {!chrome.loginState.SessionStateEvent}
*/
chrome.loginState.onSessionStateChanged;
2 changes: 2 additions & 0 deletions third_party/libusb/naclport/build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ $(eval $(call NACL_LIBRARY_HEADERS_INSTALLATION_RULE,$(INSTALLING_HEADERS)))

test::
+$(MAKE) --directory tests run
+$(MAKE) --directory js_unittests run

tests_clean::
+$(MAKE) --directory tests clean
+$(MAKE) --directory js_unittests clean

clean: tests_clean
40 changes: 40 additions & 0 deletions third_party/libusb/naclport/build/js_unittests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2020 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


#
# This makefile builds and runs JavaScript unit tests for the libusb naclport.
#


TARGET := js_libusb_unittests

include ../../../../../common/make/common.mk

include $(COMMON_DIR_PATH)/make/js_building_common.mk

include $(COMMON_DIR_PATH)/js/include.mk
include ../../include.mk


# Compile all JavaScript files rooting in the tested component's directory
# (including test files), and also compile the common library files (excluding
# that library's test files).
#
# The path constants used below come from the include.mk files.
JS_COMPILER_INPUT_PATHS := \
$(LIBUSB_JS_COMPILER_INPUT_DIR_PATHS) \
$(JS_COMMON_JS_COMPILER_INPUT_DIR_PATHS) \

$(eval $(call BUILD_JS_UNITTESTS,$(JS_COMPILER_INPUT_PATHS)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/** @license
* Copyright 2020 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

goog.require('GoogleSmartCard.Libusb.ChromeLoginStateHook');
goog.require('GoogleSmartCard.Libusb.ChromeUsbBackend');
goog.require('GoogleSmartCard.RemoteCallMessage');
goog.require('GoogleSmartCard.Requester');
goog.require('GoogleSmartCard.RequestReceiver');
goog.require('GoogleSmartCard.SingleMessageBasedChannel');
goog.require('goog.Promise');
goog.require('goog.messaging.AbstractChannel');
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.mockmatchers');

goog.setTestOnly();

goog.scope(function() {

/** @const */
const GSC = GoogleSmartCard;

/** @const */
const USER_PROFILE_TYPE = 'USER_PROFILE';
/** @const */
const SIGNIN_PROFILE_TYPE = 'SIGNIN_PROFILE';
/** @const */
const IN_SESSION_STATE = 'IN_SESSION';
/** @const */
const IN_LOCK_SCREEN_STATE = 'IN_LOCK_SCREEN';

let sessionStateListeners = [];

/**
* Sets up mocks for the chrome.loginState Extensions API.
* @param {!goog.testing.PropertyReplacer} propertyReplacer
* @param {string} fakeProfileType Fake data to be returned from
* getProfileType().
* @param {string} fakeSessionState Fake data to be returned from
* getSessionState().
*/
function setUpChromeLoginStateMock(
propertyReplacer, fakeProfileType, fakeSessionState) {
propertyReplacer.set(
chrome, 'loginState',
{
getProfileType: function(callback) { callback(fakeProfileType); },
getSessionState: function(callback) { callback(fakeSessionState); },
onSessionStateChanged: {
addListener: function(callback) {
sessionStateListeners.push(callback);
}
},
ProfileType: {
USER_PROFILE: USER_PROFILE_TYPE,
SIGNIN_PROFILE: SIGNIN_PROFILE_TYPE
},
SessionState: {
IN_SESSION: IN_SESSION_STATE,
IN_LOCK_SCREEN: IN_LOCK_SCREEN_STATE
}
});
}

/**
* Wraps the given test function, providing the necessary setup and teardown.
* @param {string} fakeProfileType Fake data to be
* returned from chrome.loginState.getProfileType().
* @param {string} fakeSessionState Fake data to be
* returned from chrome.loginState.getSessionState().
* @param {function(!goog.testing.PropertyReplacer)} testCallback
* The test function to be run after the needed setup.
* @return {function()} The wrapped test function.
*/
function makeTest(fakeProfileType, fakeSessionState, testCallback) {
return function() {
const propertyReplacer = new goog.testing.PropertyReplacer;

function cleanup() {
propertyReplacer.reset();
sessionStateListeners = [];
}

setUpChromeLoginStateMock(
propertyReplacer, fakeProfileType, fakeSessionState);

/** @preserveTry */
try {
testCallback(propertyReplacer);
} finally {
cleanup();
}
};
}

/**
* Simulates Chrome changing the session state.
* @param {string} fakeNewSessionState
* @param {!goog.testing.PropertyReplacer} propertyReplacer
*/
function changeSessionState(fakeNewSessionState, propertyReplacer) {
propertyReplacer.replace(
chrome.loginState,
'getSessionState',
function(callback) { callback(fakeNewSessionState); });
goog.array.forEach(sessionStateListeners, function(listener) {
listener(fakeNewSessionState);
});
}

goog.exportSymbol(
'test_ChromeLoginStateHook_DoesNotFilterInSession', makeTest(
USER_PROFILE_TYPE, IN_SESSION_STATE,
function() {
const loginStateHook = new GSC.Libusb.ChromeLoginStateHook();
const hook = loginStateHook.getRequestSuccessHook();
let apiCallResult = [['fakeResult']];
const hookResult = hook('getDevices', apiCallResult);
assertObjectEquals([['fakeResult']], hookResult);
}
));

goog.exportSymbol(
'test_ChromeLoginStateHook_FiltersInLockScreen', makeTest(
USER_PROFILE_TYPE, IN_LOCK_SCREEN_STATE,
function() {
const loginStateHook = new GSC.Libusb.ChromeLoginStateHook();
const hook = loginStateHook.getRequestSuccessHook();

let apiCallResult = [['fakeResult']];
let hookResult = hook('getDevices', apiCallResult);
assertObjectEquals([[]], hookResult);

apiCallResult = [['fakeResult']];
hookResult = hook('getConfigurations', apiCallResult);
assertObjectEquals([[]], hookResult);

//only getDevices and getConfigurations should get filtered
apiCallResult = ['otherCallResult'];
hookResult = hook('listInterfaces', apiCallResult);
assertObjectEquals(['otherCallResult'], hookResult);
}
));

goog.exportSymbol(
'test_ChromeLoginStateHook_DoesNotFilterForSignInProfile', makeTest(
SIGNIN_PROFILE_TYPE, IN_SESSION_STATE,
function() {
const loginStateHook = new GSC.Libusb.ChromeLoginStateHook();
const hook = loginStateHook.getRequestSuccessHook();
const apiCallResult = [['fakeResult']];
const hookResult = hook('getDevices', apiCallResult);
assertObjectEquals([['fakeResult']], hookResult);
}
));

goog.exportSymbol(
'test_ChromeLoginStateHook_ChangeToLockScreen', makeTest(
USER_PROFILE_TYPE, IN_SESSION_STATE,
function(propertyReplacer) {
const loginStateHook = new GSC.Libusb.ChromeLoginStateHook();
changeSessionState(IN_LOCK_SCREEN_STATE, propertyReplacer);
const hook = loginStateHook.getRequestSuccessHook();
const apiCallResult = [['fakeResult']];
const hookResult = hook('getDevices', apiCallResult);
assertObjectEquals([[]], hookResult);
}
));

goog.exportSymbol(
'test_ChromeLoginStateHook_ChangeToSession', makeTest(
USER_PROFILE_TYPE, IN_LOCK_SCREEN_STATE,
function(propertyReplacer) {
const loginStateHook = new GSC.Libusb.ChromeLoginStateHook();
changeSessionState(IN_SESSION_STATE, propertyReplacer);
const hook = loginStateHook.getRequestSuccessHook();
const apiCallResult = [['fakeResult']];
const hookResult = hook('getDevices', apiCallResult);
assertObjectEquals([['fakeResult']], hookResult);
}
));

}); // goog.scope
Loading

0 comments on commit 5b3b811

Please sign in to comment.