Example #1
0
def _TestAuthGoogleUser(action,
                        tester,
                        user_dict,
                        device_dict=None,
                        user_cookie=None):
    """Called by the ServiceTester in order to test login/google, link/google, and
  register/google calls.
  """
    ident_dict = {
        'key': 'Email:%s' % Identity.CanonicalizeEmail(user_dict['email']),
        'authority': 'Google',
        'refresh_token': 'refresh_token',
        'access_token': 'access_token',
        'expires': util._TEST_TIME + 3600
    }
    if device_dict:
        device_dict.pop('device_uuid', None)
        device_dict.pop('test_udid', None)

    # Mock responses from Google.
    with mock.patch('tornado.httpclient.AsyncHTTPClient',
                    MockAsyncHTTPClient()) as mock_client:
        # Response to request for access token.
        auth_test._AddMockJSONResponse(
            mock_client, r'https://accounts.google.com/o/oauth2/token', {
                'access_token': ident_dict['access_token'],
                'token_type': 'Bearer',
                'expires_in': ident_dict['expires'] - util._TEST_TIME,
                'id_token': 'id_token',
                'refresh_token': ident_dict['refresh_token']
            })

        # Response to request for user info.
        auth_test._AddMockJSONResponse(
            mock_client, r'https://www.googleapis.com/oauth2/v1/userinfo\?',
            user_dict)

        # Response to request for people (i.e. contacts).
        auth_test._AddMockJSONResponse(
            mock_client,
            r'https://www.google.com/m8/feeds/contacts/default/full', {
                'feed': {
                    'entry': [],
                    'openSearch$startIndex': {
                        '$t': '1'
                    },
                    'openSearch$totalResults': {
                        '$t': '0'
                    }
                }
            })

        response = auth_test._AuthFacebookOrGoogleUser(tester, action,
                                                       user_dict, ident_dict,
                                                       device_dict,
                                                       user_cookie)
        return auth_test._ValidateAuthUser(tester, action, user_dict,
                                           ident_dict, device_dict,
                                           user_cookie, response)
Example #2
0
    def _GetUserInfo(self, device_dict, refresh_token, response):
        """Parses the google access token from the JSON response body. Gets user data via OAUTH2
    with access token.
    """
        tokens = www_util.ParseJSONResponse(response)
        assert tokens, 'unable to fetch access token'
        access_token = tokens['access_token']
        expires = tokens['expires_in']
        if tokens.has_key('refresh_token') and not refresh_token:
            refresh_token = tokens['refresh_token']

        # Using the access token that was previously retrieved, request information about the
        # user that is logging in.
        assert access_token, 'no access token was provided'
        url = AuthGoogleHandler._OAUTH2_USERINFO_URL + '?' + urllib.urlencode(
            {'access_token': access_token})
        http_client = httpclient.AsyncHTTPClient()
        response = yield gen.Task(http_client.fetch, url)

        # Parse the user information from the JSON response body and invoke _OnAuthenticate to
        # register the user as a viewfinder account. Create user dict from Google's JSON response.
        user_dict = www_util.ParseJSONResponse(response)
        assert user_dict, 'unable to fetch user data'
        assert 'phone' not in user_dict, user_dict
        assert 'email' in user_dict, user_dict
        user_dict['email'] = Identity.CanonicalizeEmail(user_dict['email'])

        # Ensure that user email is verified, else we can't trust that the user really owns it.
        if not user_dict.get('verified_email', False):
            raise web.HTTPError(
                403, _CANNOT_USE_UNVERIFIED_EMAIL % user_dict['email'])

        # Create identity dict from Google's email field.
        ident_dict = {
            'key': 'Email:%s' % user_dict['email'],
            'authority': 'Google',
            'refresh_token': refresh_token,
            'access_token': access_token,
            'expires': util.GetCurrentTimestamp() + expires
        }

        self._AuthUser(user_dict, ident_dict, device_dict)
Example #3
0
    def _FetchGoogleContacts(self):
        """Do GMail specific data gathering and checking.
    Queries Google data API for contacts in JSON format.
    """
        # Track fetched contacts regardless of rank in order to dedup contacts retrieved from Google.
        assert self._identity.refresh_token is not None, self._identity

        if self._identity.expires and self._identity.expires < time.time():
            yield gen.Task(self._identity.RefreshGoogleAccessToken,
                           self._client)

        logging.info('fetching Google contacts for identity %r...' %
                     self._identity)
        http_client = httpclient.AsyncHTTPClient()
        # Google data API uses 1-based start index.
        start_index = 1
        retries = 0
        count = FetchContactsOperation._MAX_FETCH_COUNT
        while True:
            if retries >= FetchContactsOperation._MAX_FETCH_RETRIES:
                raise TooManyRetriesError(
                    'failed to fetch contacts %d times; aborting' % retries)
            logging.info('fetching next %d Google contacts for user %d' %
                         (count, self._user_id))
            url = FetchContactsOperation._GOOGLE_CONTACTS_URL + '?' + \
                urllib.urlencode({'max-results': count,
                                  'start-index': start_index,
                                  'alt': 'json'})
            response = yield gen.Task(http_client.fetch,
                                      url,
                                      method='GET',
                                      headers={
                                          'Authorization':
                                          'OAuth %s' %
                                          self._identity.access_token,
                                          'GData-Version':
                                          3.0
                                      })
            try:
                response_dict = www_util.ParseJSONResponse(response)['feed']
            except Exception as exc:
                logging.warning('failed to fetch Google contacts: %s' % exc)
                retries += 1
                continue

            # Temporarily log additional information to figure out why some responses don't seem to have "entry" fields.
            if 'entry' not in response_dict:
                logging.warning('Missing entry: %s' %
                                json.dumps(response_dict, indent=True))

            for c_dict in response_dict.get('entry', []):
                # Build identities_properties list from all emails/phone numbers associated with this contact.
                identities_properties = []
                # Process emails first so that if there are any emails, one of them will be first in the
                #   identities_properties list.  This will be *the* identity used for down-level client message
                #   migration.
                for email_info in c_dict.get('gd$email', []):
                    email = email_info.get('address', None)
                    if email is not None:
                        email_type = FetchContactsOperation._GOOGLE_TYPE_LOOKUP.get(
                            email_info.get('rel', None), None)
                        identity_properties = (
                            'Email:' + Identity.CanonicalizeEmail(email),
                            email_info.get('label', email_type))
                        if email_info.get('primary', False):
                            # Insert the primary email address at the head of the list.  Older clients will get this
                            #   as the only email address for this contact when they query_contacts.
                            identities_properties.insert(
                                0, identity_properties)
                        else:
                            identities_properties.append(identity_properties)
                for phone_info in c_dict.get('gd$phoneNumber', []):
                    # See RFC3966: "The tel URI for Telephone Numbers" for more information about this format.
                    #   It should be 'tel:' + E.164 format phone number.
                    phone = phone_info.get('uri', None)
                    if phone is not None and phone.startswith(
                            'tel:+') and Identity.CanCanonicalizePhone(
                                phone[4:]):
                        phone_type = FetchContactsOperation._GOOGLE_TYPE_LOOKUP.get(
                            phone_info.get('rel', None), None)
                        identities_properties.append(
                            ('Phone:' + Identity.CanonicalizePhone(phone[4:]),
                             phone_info.get('label', phone_type)))

                if len(identities_properties) == 0:
                    continue

                # Normalize name to None if empty.
                gd_name = c_dict.get('gd$name', None)
                if gd_name is not None:
                    names = {
                        'name':
                        gd_name.get('gd$fullName', {}).get('$t', None),
                        'given_name':
                        gd_name.get('gd$givenName', {}).get('$t', None),
                        'family_name':
                        gd_name.get('gd$familyName', {}).get('$t', None)
                    }
                else:
                    names = {
                        'name': None,
                        'given_name': None,
                        'family_name': None
                    }

                fetched_contact = Contact.CreateFromKeywords(
                    self._user_id,
                    identities_properties,
                    self._notify_timestamp,
                    Contact.GMAIL,
                    rank=None,
                    **names)
                self._fetched_contacts[
                    fetched_contact.contact_id] = fetched_contact

            # Prepare to fetch next batch.
            # Indexes are 1-based, so add 1 to max_index.
            if 'openSearch$totalResults' in response_dict:
                max_index = int(
                    response_dict['openSearch$totalResults']['$t']) + 1
            else:
                max_index = FetchContactsOperation._MAX_GOOGLE_CONTACTS + 1
            next_index = int(
                response_dict['openSearch$startIndex']['$t']) + len(
                    response_dict.get('entry', []))
            count = min(max_index - next_index,
                        FetchContactsOperation._MAX_FETCH_COUNT)
            if len(
                    self._fetched_contacts
            ) < FetchContactsOperation._MAX_GOOGLE_CONTACTS and count > 0:
                start_index = next_index
                retries = 0
                continue
            else:
                raise gen.Return()