146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Copyright 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
4424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
66e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import logging
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
93551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)from telemetry.core import util
101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom telemetry.core.backends import codepen_credentials_backend
11424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from telemetry.core.backends import facebook_credentials_backend
12424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from telemetry.core.backends import google_credentials_backend
131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom telemetry.page.actions import action_runner
14868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)from telemetry.unittest import options_for_unittests
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
1746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)class CredentialsError(Exception):
1846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  """Error that can be thrown when logging in."""
1946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
2046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class BrowserCredentials(object):
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, backends = None):
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials = {}
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials_path = None
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._extra_credentials = {}
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if backends is None:
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      backends = [
291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        codepen_credentials_backend.CodePenCredentialsBackend(),
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        facebook_credentials_backend.FacebookCredentialsBackend(),
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        google_credentials_backend.GoogleCredentialsBackend()]
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._backends = {}
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for backend in backends:
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._backends[backend.credentials_type] = backend
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddBackend(self, backend):
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    assert backend.credentials_type not in self._backends
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._backends[backend.credentials_type] = backend
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def IsLoggedIn(self, credentials_type):
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if credentials_type not in self._backends:
4346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      raise CredentialsError(
4446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          'Unrecognized credentials type: %s', credentials_type)
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if credentials_type not in self._credentials:
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return False
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return self._backends[credentials_type].IsLoggedIn()
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CanLogin(self, credentials_type):
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._backends:
5146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      raise CredentialsError(
5246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          'Unrecognized credentials type: %s', credentials_type)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return credentials_type in self._credentials
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def LoginNeeded(self, tab, credentials_type):
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._backends:
5746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      raise CredentialsError(
5846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          'Unrecognized credentials type: %s', credentials_type)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._credentials:
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    runner = action_runner.ActionRunner(tab)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._backends[credentials_type].LoginNeeded(
631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      tab, runner, self._credentials[credentials_type])
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def LoginNoLongerNeeded(self, tab, credentials_type):
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    assert credentials_type in self._backends
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._backends[credentials_type].LoginNoLongerNeeded(tab)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @property
701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def credentials_path(self):  # pylint: disable=E0202
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._credentials_path
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @credentials_path.setter
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def credentials_path(self, credentials_path):  # pylint: disable=E0202
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials_path = credentials_path
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._RebuildCredentials()
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Add(self, credentials_type, data):
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._extra_credentials:
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._extra_credentials[credentials_type] = {}
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in data.items():
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert k not in self._extra_credentials[credentials_type]
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._extra_credentials[credentials_type][k] = v
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._RebuildCredentials()
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def _ResetLoggedInState(self):
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Makes the backends think we're not logged in even though we are.
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Should only be used in unit tests to simulate --dont-override-profile.
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    for backend in self._backends.keys():
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self._backends[backend]._ResetLoggedInState() # pylint: disable=W0212
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _RebuildCredentials(self):
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    credentials = {}
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._credentials_path == None:
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif os.path.exists(self._credentials_path):
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      with open(self._credentials_path, 'r') as f:
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        credentials = json.loads(f.read())
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO(nduca): use system keychain, if possible.
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    homedir_credentials_path = os.path.expanduser('~/.telemetry-credentials')
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    homedir_credentials = {}
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (not options_for_unittests.GetCopy() and
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.path.exists(homedir_credentials_path)):
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Found ~/.telemetry-credentials. Its contents will be used "
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   "when no other credentials can be found.")
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      with open(homedir_credentials_path, 'r') as f:
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        homedir_credentials = json.loads(f.read())
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials = {}
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    all_keys = set(credentials.keys()).union(
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      homedir_credentials.keys()).union(
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._extra_credentials.keys())
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k in all_keys:
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in credentials:
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._credentials[k] = credentials[k]
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in homedir_credentials:
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.info("Will use ~/.telemetry-credentials for %s logins." % k)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._credentials[k] = homedir_credentials[k]
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in self._extra_credentials:
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._credentials[k] = self._extra_credentials[k]
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def WarnIfMissingCredentials(self, page_set):
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    num_pages_missing_login = 0
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    missing_credentials = set()
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for page in page_set:
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (page.credentials
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          and not self.CanLogin(page.credentials)):
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        num_pages_missing_login += 1
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        missing_credentials.add(page.credentials)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if num_pages_missing_login > 0:
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      files_to_tweak = []
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if page_set.credentials_path:
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        files_to_tweak.append(
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          os.path.relpath(os.path.join(os.path.dirname(page_set.file_path),
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       page_set.credentials_path)))
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      files_to_tweak.append('~/.telemetry-credentials')
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      example_credentials_file = os.path.join(
1443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          util.GetTelemetryDir(), 'examples', 'credentials_example.json')
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.warning("""
147c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        Credentials for %s were not found. %i pages will not be tested.
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        To fix this, either follow the instructions to authenticate to gsutil
1501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        here:
1511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        http://www.chromium.org/developers/telemetry/upload_to_cloud_storage,
1521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        or add your own credentials to:
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            %s
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        An example credentials file you can copy from is here:
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            %s\n""" % (', '.join(missing_credentials),
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         num_pages_missing_login,
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         ' or '.join(files_to_tweak),
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         example_credentials_file))
160