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