OLD | NEW |
(Empty) | |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """ Script that exercises the Smart Lock setup flow, testing that a nearby phone |
| 6 can be found and used to unlock a Chromebook. |
| 7 |
| 8 Note: This script does not currently automate Android phones, so make sure that |
| 9 a phone is properly configured and online before starting the test. |
| 10 |
| 11 Usage: |
| 12 python setup_test.py --remote_address REMOTE_ADDRESS |
| 13 --username USERNAME |
| 14 --password PASSWORD |
| 15 [--app_path APP_PATH] |
| 16 [--ssh_port SSH_PORT] |
| 17 If |--app_path| is provided, then a copy of the Smart Lock app on the local |
| 18 machine will be used instead of the app on the ChromeOS device. |
| 19 """ |
| 20 |
| 21 import argparse |
| 22 import cros |
| 23 import cryptauth |
| 24 import logging |
| 25 import os |
| 26 import subprocess |
| 27 import sys |
| 28 import tempfile |
| 29 |
| 30 logger = logging.getLogger('proximity_auth.%s' % __name__) |
| 31 |
| 32 class SmartLockSetupError(Exception): |
| 33 pass |
| 34 |
| 35 def pingable_address(address): |
| 36 try: |
| 37 subprocess.check_output(['ping', '-c', '1', '-W', '1', address]) |
| 38 except subprocess.CalledProcessError: |
| 39 raise argparse.ArgumentError('%s cannot be reached.' % address) |
| 40 return address |
| 41 |
| 42 def email(arg): |
| 43 tokens = arg.lower().split('@') |
| 44 if len(tokens) != 2 or '.' not in tokens[1]: |
| 45 raise argparse.ArgumentError('%s is not a valid email address' % arg) |
| 46 name, domain = tokens |
| 47 if domain == 'gmail.com': |
| 48 name = name.replace('.', '') |
| 49 return '@'.join([name, domain]) |
| 50 |
| 51 def directory(path): |
| 52 if not os.path.isdir(path): |
| 53 raise argparse.ArgumentError('%s is not a directory' % path) |
| 54 return path |
| 55 |
| 56 def ParseArgs(): |
| 57 parser = argparse.ArgumentParser(prog='python setup_test.py') |
| 58 parser.add_argument('--remote_address', required=True, type=pingable_address) |
| 59 parser.add_argument('--username', required=True, type=email) |
| 60 parser.add_argument('--password', required=True) |
| 61 parser.add_argument('--ssh_port', type=int) |
| 62 parser.add_argument('--app_path', type=directory) |
| 63 args = parser.parse_args() |
| 64 return args |
| 65 |
| 66 def CheckCryptAuthState(access_token): |
| 67 cryptauth_client = cryptauth.CryptAuthClient(access_token) |
| 68 |
| 69 # Check if we can make CryptAuth requests. |
| 70 if cryptauth_client.GetMyDevices() is None: |
| 71 logger.error('Cannot reach CryptAuth on test machine.') |
| 72 return False |
| 73 |
| 74 if cryptauth_client.GetUnlockKey() is not None: |
| 75 logger.info('Smart Lock currently enabled, turning off on Cryptauth...') |
| 76 if not cryptauth_client.ToggleEasyUnlock(False): |
| 77 logger.error('ToggleEasyUnlock request failed.') |
| 78 return False |
| 79 |
| 80 result = cryptauth_client.FindEligibleUnlockDevices() |
| 81 if result is None: |
| 82 logger.error('FindEligibleUnlockDevices request failed') |
| 83 return False |
| 84 eligibleDevices, _ = result |
| 85 if len(eligibleDevices) == 0: |
| 86 logger.warn('No eligible phones found, trying to ping phones...') |
| 87 result = cryptauth_client.PingPhones() |
| 88 if result is None or not len(result[0]): |
| 89 logger.error('Pinging phones failed :(') |
| 90 return False |
| 91 else: |
| 92 logger.info('Pinging phones succeeded!') |
| 93 else: |
| 94 logger.info('Found eligible device: %s' % ( |
| 95 eligibleDevices[0]['friendlyDeviceName'])) |
| 96 return True |
| 97 |
| 98 def _NavigateSetupDialog(chromeos, app): |
| 99 logger.info('Scanning for nearby phones...') |
| 100 btmon = chromeos.RunBtmon() |
| 101 find_phone_success = app.FindPhone() |
| 102 btmon.terminate() |
| 103 |
| 104 if not find_phone_success: |
| 105 fd, filepath = tempfile.mkstemp(prefix='btmon-') |
| 106 os.write(fd, btmon.stdout.read()) |
| 107 os.close(fd) |
| 108 logger.info('Logs for btmon can be found at %s' % filepath) |
| 109 raise SmartLockSetupError("Failed to find nearby phone.") |
| 110 |
| 111 logger.info('Phone found! Starting pairing...') |
| 112 if not app.PairPhone(): |
| 113 raise SmartLockSetupError("Failed to pair with phone.") |
| 114 logger.info('Pairing success! Starting trial run...') |
| 115 app.StartTrialRun() |
| 116 |
| 117 logger.info('Unlocking for trial run...') |
| 118 lock_screen = chromeos.GetAccountPickerScreen() |
| 119 lock_screen.WaitForSmartLockState( |
| 120 lock_screen.SmartLockState.AUTHENTICATED) |
| 121 lock_screen.UnlockWithClick() |
| 122 |
| 123 logger.info('Trial run success! Dismissing app...') |
| 124 app.DismissApp() |
| 125 |
| 126 def RunSetupTest(args): |
| 127 logger.info('Starting test for %s at %s' % ( |
| 128 args.username, args.remote_address)) |
| 129 if args.app_path is not None: |
| 130 logger.info('Replacing Smart Lock app with %s' % args.app_path) |
| 131 |
| 132 chromeos = cros.ChromeOS( |
| 133 args.remote_address, args.username, args.password, ssh_port=args.ssh_port) |
| 134 with chromeos.Start(local_app_path=args.app_path): |
| 135 logger.info('Chrome initialized') |
| 136 |
| 137 # TODO(tengs): The access token is currently fetched from the Smart Lock |
| 138 # app's background page. To be more robust, we should instead mint the |
| 139 # access token ourselves. |
| 140 if not CheckCryptAuthState(chromeos.cryptauth_access_token): |
| 141 raise SmartLockSetupError('Failed to check CryptAuth state') |
| 142 |
| 143 logger.info('Opening Smart Lock settings...') |
| 144 settings = chromeos.GetSmartLockSettings() |
| 145 assert(not settings.is_smart_lock_enabled) |
| 146 logger.info('Starting Smart Lock setup flow...') |
| 147 app = settings.StartSetupAndReturnApp() |
| 148 |
| 149 _NavigateSetupDialog(chromeos, app) |
| 150 |
| 151 def main(): |
| 152 logging.basicConfig() |
| 153 logging.getLogger('proximity_auth').setLevel(logging.INFO) |
| 154 args = ParseArgs() |
| 155 RunSetupTest(args) |
| 156 |
| 157 if __name__ == '__main__': |
| 158 main() |
OLD | NEW |