summaryrefslogtreecommitdiff
path: root/systrace/catapult/telemetry/telemetry/internal/backends/form_based_credentials_backend.py
blob: bebf3f13f45a5e3190d496d7ec331a8989130c69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# Copyright 2013 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.
import logging

from telemetry import decorators

import py_utils


class FormBasedCredentialsBackend(object):
  def __init__(self):
    self._logged_in = False

  def IsAlreadyLoggedIn(self, tab):
    return tab.EvaluateJavaScript(self.logged_in_javascript)

  @property
  def credentials_type(self):
    raise NotImplementedError()

  @property
  def url(self):
    raise NotImplementedError()

  @property
  def login_form_id(self):
    raise NotImplementedError()

  @property
  def login_button_javascript(self):
    """Some sites have custom JS to log in."""
    return None

  @property
  def login_input_id(self):
    raise NotImplementedError()

  @property
  def password_input_id(self):
    raise NotImplementedError()

  @property
  def logged_in_javascript(self):
    """Evaluates to true iff already logged in."""
    raise NotImplementedError()

  def IsLoggedIn(self):
    return self._logged_in

  def _ResetLoggedInState(self):
    """Makes the backend think we're not logged in even though we are.
    Should only be used in unit tests to simulate --dont-override-profile.
    """
    self._logged_in = False

  def _WaitForLoginState(self, action_runner):
    """Waits until it can detect either the login form, or already logged in."""
    action_runner.WaitForJavaScriptCondition(
        '(document.querySelector({{ form_id }}) !== null) || ({{ @code }})',
        form_id='#' + self.login_form_id, code=self.logged_in_javascript,
        timeout_in_seconds=60)

  def _SubmitLoginFormAndWait(self, action_runner, tab, username, password):
    """Submits the login form and waits for the navigation."""
    tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
    # TODO(catapult:#3028): Fix interpolation of JavaScript values.
    email_id = 'document.querySelector("#%s #%s").value = "%s"; ' % (
        self.login_form_id, self.login_input_id, username)
    password = 'document.querySelector("#%s #%s").value = "%s"; ' % (
        self.login_form_id, self.password_input_id, password)
    tab.ExecuteJavaScript(email_id)
    tab.ExecuteJavaScript(password)
    if self.login_button_javascript:
      tab.ExecuteJavaScript(self.login_button_javascript)
    else:
      # TODO(catapult:#3028): Fix interpolation of JavaScript values.
      tab.ExecuteJavaScript(
          'document.getElementById("%s").submit();' % self.login_form_id)
    # Wait for the form element to disappear as confirmation of the navigation.
    action_runner.WaitForNavigate()

  # pylint: disable=line-too-long
  @decorators.Deprecated(2017, 5, 5,
                         'FormBasedCredentialsBackend is deprecated. Use the '
                         'login helper modules in '
                         'https://code.google.com/p/chromium/codesearch#chromium/src/tools/perf/page_sets/login_helpers/'
                         ' instead.')
  # pylint: enable=line-too-long
  def LoginNeeded(self, tab, action_runner, config):
    """Logs in to a test account.

    Raises:
      RuntimeError: if could not get credential information.
    """
    if self._logged_in:
      return True

    if 'username' not in config or 'password' not in config:
      message = ('Credentials for "%s" must include username and password.' %
                 self.credentials_type)
      raise RuntimeError(message)

    logging.debug('Logging into %s account...' % self.credentials_type)

    if 'url' in config:
      url = config['url']
    else:
      url = self.url

    try:
      logging.info('Loading %s...', url)
      tab.Navigate(url)
      self._WaitForLoginState(action_runner)

      if self.IsAlreadyLoggedIn(tab):
        self._logged_in = True
        return True

      self._SubmitLoginFormAndWait(
          action_runner, tab, config['username'], config['password'])

      self._logged_in = True
      return True
    except py_utils.TimeoutException:
      logging.warning('Timed out while loading: %s', url)
      return False

  def LoginNoLongerNeeded(self, tab): # pylint: disable=unused-argument
    assert self._logged_in