# Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """ Script that exercises the Smart Lock setup flow, testing that a nearby phone can be found and used to unlock a Chromebook. Note: This script does not currently automate Android phones, so make sure that a phone is properly configured and online before starting the test. Usage: python setup_test.py --remote_address REMOTE_ADDRESS --username USERNAME --password PASSWORD [--app_path APP_PATH] [--ssh_port SSH_PORT] [--cryptauth_staging_url STAGING_URL] If |--app_path| is provided, then a copy of the Smart Lock app on the local machine will be used instead of the app on the ChromeOS device. """ import argparse import cros import cryptauth import logging import os import subprocess import sys import tempfile logger = logging.getLogger('proximity_auth.%s' % __name__) class SmartLockSetupError(Exception): pass def pingable_address(address): try: subprocess.check_output(['ping', '-c', '1', '-W', '1', address]) except subprocess.CalledProcessError: raise argparse.ArgumentError('%s cannot be reached.' % address) return address def email(arg): tokens = arg.lower().split('@') if len(tokens) != 2 or '.' not in tokens[1]: raise argparse.ArgumentError('%s is not a valid email address' % arg) name, domain = tokens if domain == 'gmail.com': name = name.replace('.', '') return '@'.join([name, domain]) def directory(path): if not os.path.isdir(path): raise argparse.ArgumentError('%s is not a directory' % path) return path def ParseArgs(): parser = argparse.ArgumentParser(prog='python setup_test.py') parser.add_argument('--remote_address', required=True, type=pingable_address) parser.add_argument('--username', required=True, type=email) parser.add_argument('--password', required=True) parser.add_argument('--ssh_port', type=int) parser.add_argument('--app_path', type=directory) parser.add_argument('--cryptauth_staging_url', type=str) args = parser.parse_args() return args def CheckCryptAuthState(access_token): cryptauth_client = cryptauth.CryptAuthClient(access_token) # Check if we can make CryptAuth requests. if cryptauth_client.GetMyDevices() is None: logger.error('Cannot reach CryptAuth on test machine.') return False if cryptauth_client.GetUnlockKey() is not None: logger.info('Smart Lock currently enabled, turning off on Cryptauth...') if not cryptauth_client.ToggleEasyUnlock(False): logger.error('ToggleEasyUnlock request failed.') return False result = cryptauth_client.FindEligibleUnlockDevices() if result is None: logger.error('FindEligibleUnlockDevices request failed') return False eligibleDevices, _ = result if len(eligibleDevices) == 0: logger.warn('No eligible phones found, trying to ping phones...') result = cryptauth_client.PingPhones() if result is None or not len(result[0]): logger.error('Pinging phones failed :(') return False else: logger.info('Pinging phones succeeded!') else: logger.info('Found eligible device: %s' % ( eligibleDevices[0]['friendlyDeviceName'])) return True def _NavigateSetupDialog(chromeos, app): logger.info('Scanning for nearby phones...') btmon = chromeos.RunBtmon() find_phone_success = app.FindPhone() btmon.terminate() if not find_phone_success: fd, filepath = tempfile.mkstemp(prefix='btmon-') os.write(fd, btmon.stdout.read()) os.close(fd) logger.info('Logs for btmon can be found at %s' % filepath) raise SmartLockSetupError("Failed to find nearby phone.") logger.info('Phone found! Starting pairing...') if not app.PairPhone(): raise SmartLockSetupError("Failed to pair with phone.") logger.info('Pairing success! Starting trial run...') app.StartTrialRun() logger.info('Unlocking for trial run...') lock_screen = chromeos.GetAccountPickerScreen() lock_screen.WaitForSmartLockState( lock_screen.SmartLockState.AUTHENTICATED) lock_screen.UnlockWithClick() logger.info('Trial run success! Dismissing app...') app.DismissApp() def RunSetupTest(args): logger.info('Starting test for %s at %s' % ( args.username, args.remote_address)) if args.app_path is not None: logger.info('Replacing Smart Lock app with %s' % args.app_path) chromeos = cros.ChromeOS( args.remote_address, args.username, args.password, ssh_port=args.ssh_port) with chromeos.Start(local_app_path=args.app_path): logger.info('Chrome initialized') # TODO(tengs): The access token is currently fetched from the Smart Lock # app's background page. To be more robust, we should instead mint the # access token ourselves. if not CheckCryptAuthState(chromeos.cryptauth_access_token): raise SmartLockSetupError('Failed to check CryptAuth state') logger.info('Opening Smart Lock settings...') settings = chromeos.GetSmartLockSettings() assert(not settings.is_smart_lock_enabled) if args.cryptauth_staging_url is not None: chromeos.SetCryptAuthStaging(args.cryptauth_staging_url) logger.info('Starting Smart Lock setup flow...') app = settings.StartSetupAndReturnApp() if app is None: raise SmartLockSetupError('Failed to obtain set up app window') _NavigateSetupDialog(chromeos, app) def main(): logging.basicConfig() logging.getLogger('proximity_auth').setLevel(logging.INFO) args = ParseArgs() RunSetupTest(args) if __name__ == '__main__': main()