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