browser_credentials.py revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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 logging
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
93551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)from telemetry.core import util
10424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from telemetry.core.backends import facebook_credentials_backend
11424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from telemetry.core.backends import google_credentials_backend
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)from telemetry.unittest import options_for_unittests
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
1546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)class CredentialsError(Exception):
1646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  """Error that can be thrown when logging in."""
1746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
1846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class BrowserCredentials(object):
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, backends = None):
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials = {}
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials_path = None
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._extra_credentials = {}
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if backends is None:
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      backends = [
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        facebook_credentials_backend.FacebookCredentialsBackend(),
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        google_credentials_backend.GoogleCredentialsBackend()]
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._backends = {}
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for backend in backends:
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._backends[backend.credentials_type] = backend
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddBackend(self, backend):
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    assert backend.credentials_type not in self._backends
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._backends[backend.credentials_type] = backend
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def IsLoggedIn(self, credentials_type):
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if credentials_type not in self._backends:
4046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      raise CredentialsError(
4146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          'Unrecognized credentials type: %s', credentials_type)
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if credentials_type not in self._credentials:
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return False
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return self._backends[credentials_type].IsLoggedIn()
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CanLogin(self, credentials_type):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._backends:
4846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      raise CredentialsError(
4946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          'Unrecognized credentials type: %s', credentials_type)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return credentials_type in self._credentials
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def LoginNeeded(self, tab, credentials_type):
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._backends:
5446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      raise CredentialsError(
5546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          'Unrecognized credentials type: %s', credentials_type)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._credentials:
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._backends[credentials_type].LoginNeeded(
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      tab, self._credentials[credentials_type])
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def LoginNoLongerNeeded(self, tab, credentials_type):
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    assert credentials_type in self._backends
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._backends[credentials_type].LoginNoLongerNeeded(tab)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @property
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def credentials_path(self):
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._credentials_path
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @credentials_path.setter
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def credentials_path(self, credentials_path):
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials_path = credentials_path
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._RebuildCredentials()
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Add(self, credentials_type, data):
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if credentials_type not in self._extra_credentials:
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._extra_credentials[credentials_type] = {}
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in data.items():
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert k not in self._extra_credentials[credentials_type]
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._extra_credentials[credentials_type][k] = v
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._RebuildCredentials()
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def _ResetLoggedInState(self):
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Makes the backends think we're not logged in even though we are.
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Should only be used in unit tests to simulate --dont-override-profile.
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    for backend in self._backends.keys():
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self._backends[backend]._ResetLoggedInState() # pylint: disable=W0212
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _RebuildCredentials(self):
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    credentials = {}
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._credentials_path == None:
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif os.path.exists(self._credentials_path):
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      with open(self._credentials_path, 'r') as f:
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        credentials = json.loads(f.read())
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO(nduca): use system keychain, if possible.
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    homedir_credentials_path = os.path.expanduser('~/.telemetry-credentials')
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    homedir_credentials = {}
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (not options_for_unittests.GetCopy() and
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.path.exists(homedir_credentials_path)):
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info("Found ~/.telemetry-credentials. Its contents will be used "
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   "when no other credentials can be found.")
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      with open(homedir_credentials_path, 'r') as f:
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        homedir_credentials = json.loads(f.read())
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._credentials = {}
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    all_keys = set(credentials.keys()).union(
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      homedir_credentials.keys()).union(
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._extra_credentials.keys())
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k in all_keys:
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in credentials:
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._credentials[k] = credentials[k]
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in homedir_credentials:
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.info("Will use ~/.telemetry-credentials for %s logins." % k)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._credentials[k] = homedir_credentials[k]
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in self._extra_credentials:
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._credentials[k] = self._extra_credentials[k]
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def WarnIfMissingCredentials(self, page_set):
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    num_pages_missing_login = 0
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    missing_credentials = set()
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for page in page_set:
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (page.credentials
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          and not self.CanLogin(page.credentials)):
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        num_pages_missing_login += 1
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        missing_credentials.add(page.credentials)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if num_pages_missing_login > 0:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      files_to_tweak = []
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if page_set.credentials_path:
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        files_to_tweak.append(
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          os.path.relpath(os.path.join(os.path.dirname(page_set.file_path),
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       page_set.credentials_path)))
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      files_to_tweak.append('~/.telemetry-credentials')
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      example_credentials_file = os.path.join(
1403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          util.GetTelemetryDir(), 'examples', 'credentials_example.json')
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.warning("""
143c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        Credentials for %s were not found. %i pages will not be tested.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        To fix this, either add svn-internal to your .gclient using
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        http://goto/read-src-internal, or add your own credentials to:
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            %s
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        An example credentials file you can copy from is here:
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            %s\n""" % (', '.join(missing_credentials),
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         num_pages_missing_login,
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         ' or '.join(files_to_tweak),
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         example_credentials_file))
153