1# Copyright 2013 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. 4import logging 5 6from telemetry import decorators 7 8import py_utils 9 10 11class FormBasedCredentialsBackend(object): 12 def __init__(self): 13 self._logged_in = False 14 15 def IsAlreadyLoggedIn(self, tab): 16 return tab.EvaluateJavaScript(self.logged_in_javascript) 17 18 @property 19 def credentials_type(self): 20 raise NotImplementedError() 21 22 @property 23 def url(self): 24 raise NotImplementedError() 25 26 @property 27 def login_form_id(self): 28 raise NotImplementedError() 29 30 @property 31 def login_button_javascript(self): 32 """Some sites have custom JS to log in.""" 33 return None 34 35 @property 36 def login_input_id(self): 37 raise NotImplementedError() 38 39 @property 40 def password_input_id(self): 41 raise NotImplementedError() 42 43 @property 44 def logged_in_javascript(self): 45 """Evaluates to true iff already logged in.""" 46 raise NotImplementedError() 47 48 def IsLoggedIn(self): 49 return self._logged_in 50 51 def _ResetLoggedInState(self): 52 """Makes the backend think we're not logged in even though we are. 53 Should only be used in unit tests to simulate --dont-override-profile. 54 """ 55 self._logged_in = False 56 57 def _WaitForLoginState(self, action_runner): 58 """Waits until it can detect either the login form, or already logged in.""" 59 action_runner.WaitForJavaScriptCondition( 60 '(document.querySelector({{ form_id }}) !== null) || ({{ @code }})', 61 form_id='#' + self.login_form_id, code=self.logged_in_javascript, 62 timeout=60) 63 64 def _SubmitLoginFormAndWait(self, action_runner, tab, username, password): 65 """Submits the login form and waits for the navigation.""" 66 tab.WaitForDocumentReadyStateToBeInteractiveOrBetter() 67 tab.ExecuteJavaScript( 68 'document.querySelector({{ selector }}).value = {{ username }};', 69 selector='#%s #%s' % (self.login_form_id, self.login_input_id), 70 username=username) 71 tab.ExecuteJavaScript( 72 'document.querySelector({{ selector }}).value = {{ password }};', 73 selector='#%s #%s' % (self.login_form_id, self.password_input_id), 74 password=password) 75 if self.login_button_javascript: 76 tab.ExecuteJavaScript(self.login_button_javascript) 77 else: 78 tab.ExecuteJavaScript( 79 'document.getElementById({{ form_id }}).submit();', 80 form_id=self.login_form_id) 81 # Wait for the form element to disappear as confirmation of the navigation. 82 action_runner.WaitForNavigate() 83 84 # pylint: disable=line-too-long 85 @decorators.Deprecated(2017, 5, 5, 86 'FormBasedCredentialsBackend is deprecated. Use the ' 87 'login helper modules in ' 88 'https://code.google.com/p/chromium/codesearch#chromium/src/tools/perf/page_sets/login_helpers/' 89 ' instead.') 90 # pylint: enable=line-too-long 91 def LoginNeeded(self, tab, action_runner, config): 92 """Logs in to a test account. 93 94 Raises: 95 RuntimeError: if could not get credential information. 96 """ 97 if self._logged_in: 98 return True 99 100 if 'username' not in config or 'password' not in config: 101 message = ('Credentials for "%s" must include username and password.' % 102 self.credentials_type) 103 raise RuntimeError(message) 104 105 logging.debug('Logging into %s account...' % self.credentials_type) 106 107 if 'url' in config: 108 url = config['url'] 109 else: 110 url = self.url 111 112 try: 113 logging.info('Loading %s...', url) 114 tab.Navigate(url) 115 self._WaitForLoginState(action_runner) 116 117 if self.IsAlreadyLoggedIn(tab): 118 self._logged_in = True 119 return True 120 121 self._SubmitLoginFormAndWait( 122 action_runner, tab, config['username'], config['password']) 123 124 self._logged_in = True 125 return True 126 except py_utils.TimeoutException: 127 logging.warning('Timed out while loading: %s', url) 128 return False 129 130 def LoginNoLongerNeeded(self, tab): # pylint: disable=unused-argument 131 assert self._logged_in 132