Ejemplo n.º 1
0
    def _CreateUser(user_dict):
        """Creates a single user from the provided user_dict."""
        identity_key = 'Email:%s' % user_dict['email']
        identity = yield gen.Task(Identity.Query,
                                  client,
                                  identity_key,
                                  None,
                                  must_exist=False)

        # Get existing user id and web device id, if they exist.
        user_id = None
        if identity is not None and identity.user_id is not None:
            user = yield gen.Task(User.Query,
                                  client,
                                  identity.user_id,
                                  None,
                                  must_exist=False)
            if user is not None:
                user_id = user.user_id
                webapp_dev_id = user.webapp_dev_id

        if user_id is None:
            # Allocate new user id and web device id.
            user_id, webapp_dev_id = yield User.AllocateUserAndWebDeviceIds(
                client)

        # Create prospective user.
        user, identity = yield gen.Task(User.CreateProspective, client,
                                        user_id, webapp_dev_id, identity_key,
                                        util.GetCurrentTimestamp())

        # Register the user.
        user_dict = deepcopy(user_dict)
        user_dict['user_id'] = user_id
        user = yield gen.Task(User.Register,
                              client,
                              user_dict, {
                                  'key': identity_key,
                                  'authority': 'Viewfinder'
                              },
                              util.GetCurrentTimestamp(),
                              rewrite_contacts=False)

        # Turn off email alerts.
        settings = AccountSettings.CreateForUser(
            user_id, email_alerts=AccountSettings.EMAIL_NONE)
        yield gen.Task(settings.Update, client)

        # Make this a system user so that client will not add it to contacts.
        yield user.MakeSystemUser(client)

        raise gen.Return(user)
Ejemplo n.º 2
0
def IsConfirmedCookie(confirm_time):
    """Given the confirm time of a cookie, check that the cookie was confirmed no more than an
  hour ago. A recently confirmed cookie is required to perform certain high privilege operations,
  such as updating the password.
  """
    return confirm_time is not None and util.GetCurrentTimestamp(
    ) < confirm_time + _CONFIRM_TIME_LIMIT
Ejemplo n.º 3
0
    def Transform(self, client, contact, callback):
        from viewfinder.backend.db.contact import Contact
        from viewfinder.backend.db.identity import Identity
        contact_dict = contact._asdict()
        # During this upgrade assume that any email identities came from GMail and any facebook identities came from
        #   Facebook.  At this time, there shouldn't be any identities that start with anything else.
        assert contact.identity.startswith(
            'Email:') or contact.identity.startswith('FacebookGraph:'), contact
        contact_dict[
            'contact_source'] = Contact.GMAIL if contact.identity.startswith(
                'Email:') else Contact.FACEBOOK
        contact_dict['identities_properties'] = [
            (Identity.Canonicalize(contact.identity), None)
        ]
        contact_dict['timestamp'] = util.GetCurrentTimestamp()
        # Let Contact.CreateFromKeywords calculate a new sort_key.
        contact_dict.pop('sort_key')
        # Let Contact.CreateFromKeywords determine value for identities column.
        contact_dict.pop('identities')
        # Contact.CreateFromKeywords() will calculate sort_key, contact_id, and identities columns.
        new_contact = Contact.CreateFromKeywords(**contact_dict)

        self._LogUpdate(new_contact)

        if Version._mutate_items:
            yield gen.Task(new_contact.Update, client)
            yield gen.Task(contact.Delete, client)

        callback(new_contact)
Ejemplo n.º 4
0
  def _CreateEpisodeWithPosts(self, sharer_user, parent_ep_id, ph_dicts):
    """Creates a new episode containing the given photos."""
    # Create the episode.
    episode_id = Episode.ConstructEpisodeId(self._op.timestamp, self._new_user.webapp_dev_id, self._unique_id)
    self._unique_id += 1
    episode = yield gen.Task(Episode.CreateNew,
                             self._client,
                             episode_id=episode_id,
                             parent_ep_id=parent_ep_id,
                             user_id=sharer_user.user_id,
                             viewpoint_id=self._viewpoint_id,
                             publish_timestamp=util.GetCurrentTimestamp(),
                             timestamp=self._op.timestamp,
                             location=ph_dicts[0].get('location', None),
                             placemark=ph_dicts[0].get('placemark', None))

    # Create the photos from photo dicts.
    photo_ids = [ph_dict['photo_id'] for ph_dict in ph_dicts]
    for photo_id in photo_ids:
      yield gen.Task(Post.CreateNew, self._client, episode_id=episode_id, photo_id=photo_id)

    # Update accounting, but only apply to the new user, since system users will remove
    # themselves from the viewpoint.
    yield self._acc_accum.SharePhotos(self._client,
                                      sharer_user.user_id,
                                      self._viewpoint_id,
                                      photo_ids,
                                      [self._new_user.user_id])

    # Update viewpoint shared by total for the sharing user.
    self._acc_accum.GetViewpointSharedBy(self._viewpoint_id, sharer_user.user_id).IncrementFromPhotoDicts(ph_dicts)

    raise gen.Return(episode)
Ejemplo n.º 5
0
    def testStringSetIndexing(self):
        """Tests indexing of items in string set columns."""

        emails = [
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**'
        ]

        # Create a bunch of contacts with one or two identities.
        timestamp = util.GetCurrentTimestamp()
        for email in emails:
            for email2 in emails:
                contact = Contact.CreateFromKeywords(
                    1, [('Email:' + email, None),
                        ('Email:' + email2, None)], timestamp, Contact.GMAIL)
                self._RunAsync(contact.Update, self._client)

        for email in emails:
            q_contacts = self._RunAsync(Contact.IndexQuery,
                                        self._client,
                                        ('contact.identities={i}', {
                                            'i': 'Email:' + email
                                        }),
                                        col_names=None)
            logging.debug('querying for %s=%s yielded %d matches' %
                          ('identities', 'Email:' + email, len(q_contacts)))
            for contact in q_contacts:
                self.assertTrue('Email:' + email in contact.identities)
            self.assertEqual(len(q_contacts), len(emails) * 2 - 1)
Ejemplo n.º 6
0
    def Register(cls, client, user_id, device_dict, is_first=True):
        """Registers a new device or update an existing device, using the fields in "device_dict".
    If "is_first" is true, then this is the first mobile device to be registered for this
    user.
    """
        assert 'device_id' in device_dict, device_dict

        device = yield gen.Task(Device.Query,
                                client,
                                user_id,
                                device_dict['device_id'],
                                None,
                                must_exist=False)
        if device is None:
            device = Device.Create(user_id=user_id,
                                   timestamp=util.GetCurrentTimestamp(),
                                   **device_dict)
        else:
            device.UpdateFields(**device_dict)

        yield gen.Task(device.Update, client)

        # If this is the first mobile device to be registered, then turn turn off email alerting
        # and turn on full push alerting to mobile devices.
        if is_first:
            settings = AccountSettings.CreateForUser(
                user_id,
                email_alerts=AccountSettings.EMAIL_NONE,
                sms_alerts=AccountSettings.SMS_NONE,
                push_alerts=AccountSettings.PUSH_ALL)
            yield gen.Task(settings.Update, client)

        raise gen.Return(device)
Ejemplo n.º 7
0
    def testUnicodeContactNames(self):
        """Test that contact_id generation works correctly when names include non-ascii characters."""
        name = u'ààà朋友你好abc123\U00010000\U00010000\x00\x01\b\n\t '

        # The following will assert if there are problems when calculating the hash for the contact_id:
        contact_a = Contact.CreateFromKeywords(1, [('Email:[email protected]', None)],
                                               util.GetCurrentTimestamp(),
                                               Contact.GMAIL,
                                               name=name)

        contact_b = Contact.CreateFromKeywords(1, [('Email:[email protected]', None)],
                                               util.GetCurrentTimestamp(),
                                               Contact.GMAIL,
                                               name=u'朋' + name[1:])

        # Check that making a slight change to a unicode
        self.assertNotEqual(contact_a.contact_id, contact_b.contact_id)
Ejemplo n.º 8
0
    def CreateAccessTokenURL(self, client, group_id, use_short_token,
                             **kwargs):
        """Creates a verification access token.

    The token is associated with a ShortURL that will be sent to the identity email address
    or phone number. Following the URL will reveal the access token. The user that presents
    the correct token to Identity.VerifyAccessToken is assumed to be in control of that email
    address or SMS number.

    Returns the ShortURL that was generated.
    """
        identity_type, value = Identity.SplitKey(self.key)
        num_digits, good_for = Identity.GetAccessTokenSettings(
            identity_type, use_short_token)

        now = util.GetCurrentTimestamp()
        access_token = None
        if self.authority == 'Viewfinder' and now < self.expires and \
           self.access_token is not None and len(self.access_token) == num_digits:
            # Re-use the access token.
            access_token = self.access_token

        if access_token is None:
            # Generate new token, which is a random decimal number of 4 or 9 decimal digits.
            format = '%0' + str(num_digits) + 'd'
            access_token = format % random.randint(0, 10**num_digits - 1)

        # Create a ShortURL that contains the access token, along with caller-supplied parameters.
        expires = now + good_for
        short_url = yield ShortURL.Create(client,
                                          group_id,
                                          timestamp=now,
                                          expires=expires,
                                          access_token=access_token,
                                          **kwargs)

        # Update the identity to record the access token and short url information.
        self.authority = 'Viewfinder'
        self.access_token = access_token
        self.expires = expires
        self.json_attrs = {
            'group_id': short_url.group_id,
            'random_key': short_url.random_key
        }
        yield gen.Task(self.Update, client)

        # Check whether user account is locked due to too many guesses.
        guess_id = self._ConstructAccessTokenGuessId(identity_type,
                                                     self.user_id)
        if not (yield Guess.CheckGuessLimit(client, guess_id,
                                            Identity._MAX_GUESSES)):
            raise TooManyGuessesError(TOO_MANY_GUESSES_ERROR)

        raise gen.Return(short_url)
Ejemplo n.º 9
0
    def CheckGuessLimit(cls, client, guess_id, max_guesses):
        """Returns false if the number of incorrect guesses has already exceeded "max_guesses"."""
        guess = yield gen.Task(Guess.Query,
                               client,
                               guess_id,
                               None,
                               must_exist=False)

        # If guess record is expired, ignore it -- it will be re-created in that case.
        now = util.GetCurrentTimestamp()
        if guess is not None and now >= guess.expires:
            guess = None

        raise gen.Return(guess is None or guess.guesses < max_guesses)
Ejemplo n.º 10
0
    def TryClearBadge(cls, client, user_id, device_id, notification_id):
        """Tries to create a "clear_badges" notification with the given id. Returns False if another
    notification with this id has already been created, else returns True.
    """
        notification = Notification(user_id, notification_id)
        notification.name = 'clear_badges'
        notification.timestamp = util.GetCurrentTimestamp()
        notification.sender_id = user_id
        notification.sender_device_id = device_id
        notification.badge = 0

        # If _TryUpdate returns false, then new notifications showed up while the query was running, and so
        # retry creation of the notification.
        success = yield notification._TryUpdate(client)
        raise gen.Return(success)
Ejemplo n.º 11
0
    def ReportIncorrectGuess(cls, client, guess_id):
        """Records an incorrect guess attempt by incrementing the guesses count."""
        guess = yield gen.Task(Guess.Query,
                               client,
                               guess_id,
                               None,
                               must_exist=False)

        # Increment the incorrect guess count.
        now = util.GetCurrentTimestamp()
        if guess is not None and now < guess.expires:
            guess.guesses += 1
        else:
            guess = Guess(guess_id)
            guess.expires = now + constants.SECONDS_PER_DAY
            guess.guesses = 1

        yield gen.Task(guess.Update, client)
Ejemplo n.º 12
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)
Ejemplo n.º 13
0
    def Create(cls, **analytics_dict):
        """Create a new analytics object with fields from 'analytics_dict'. Sets timestamp if not
    specified. Payload may be empty.
    """
        create_dict = analytics_dict
        if 'timestamp' not in create_dict:
            create_dict['timestamp'] = util.GetCurrentTimestamp()

        entity = create_dict['entity']
        entry_type = create_dict['type']
        if entry_type.startswith('User.'):
            assert entity.startswith(
                'us:'), 'Wrong entity string for type User.*: %r' % create_dict

        # Always store as int, floats cause problems as sort keys.
        create_dict['timestamp'] = int(create_dict['timestamp'])
        create_dict['sort_key'] = Analytics.CreateSortKey(
            create_dict['timestamp'], create_dict['type'])

        return cls.CreateFromKeywords(**create_dict)
Ejemplo n.º 14
0
    def CreateInvitationURL(cls, client, user_id, identity_key, viewpoint_id,
                            default_url):
        """Creates and returns a prospective user invitation ShortURL object. The URL is handled
    by an instance of AuthProspectiveHandler, which is "listening" at "/pr/...". The ShortURL
    group is partitioned by user id so that incorrect guesses only affect a single user.
    """
        identity_type, identity_value = Identity.SplitKey(identity_key)
        now = util.GetCurrentTimestamp()
        expires = now + Identity._TIME_TO_INVITIATION_EXPIRATION
        encoded_user_id = base64hex.B64HexEncode(
            util.EncodeVarLengthNumber(user_id), padding=False)
        short_url = yield ShortURL.Create(client,
                                          group_id='pr/%s' % encoded_user_id,
                                          timestamp=now,
                                          expires=expires,
                                          identity_key=identity_key,
                                          viewpoint_id=viewpoint_id,
                                          default_url=default_url,
                                          is_sms=identity_type == 'Phone')

        raise gen.Return(short_url)
Ejemplo n.º 15
0
    def testDerivedAttributes(self):
        """Test that the identity and identities attributes are being properly derived from the
    identities_properties attribute.
    """
        # Create a Peter contact for Spencer with multiple identical and nearly identical identities.
        spencer = self._user
        contact_identity_a = 'Email:[email protected]'
        contact_identity_b = 'Email:[email protected]'
        contact_identity_c = 'Email:[email protected]'
        timestamp = util.GetCurrentTimestamp()

        contact = Contact.CreateFromKeywords(spencer.user_id,
                                             [(contact_identity_a, None),
                                              (contact_identity_b, 'home'),
                                              (contact_identity_c, 'work')],
                                             timestamp,
                                             Contact.GMAIL,
                                             name='Peter Mattis',
                                             given_name='Peter',
                                             family_name='Mattis',
                                             rank=42)

        self.assertEqual(len(contact.identities_properties), 3)
        self.assertEqual(len(contact.identities), 2)
        self.assertFalse(contact_identity_a in contact.identities)
        self.assertFalse(contact_identity_b in contact.identities)
        self.assertFalse(contact_identity_c in contact.identities)
        self.assertTrue(
            Identity.Canonicalize(contact_identity_a) in contact.identities)
        self.assertTrue(
            Identity.Canonicalize(contact_identity_b) in contact.identities)
        self.assertTrue(
            Identity.Canonicalize(contact_identity_c) in contact.identities)
        self.assertTrue(
            [contact_identity_a, None] in contact.identities_properties)
        self.assertTrue(
            [contact_identity_b, 'home'] in contact.identities_properties)
        self.assertTrue(
            [contact_identity_c, 'work'] in contact.identities_properties)
Ejemplo n.º 16
0
    def Update(self, client, callback):
        """Call the base class "Update" method in order to persist modified
    columns to the db. But also ensure that this device has a unique
    push token; two Viewfinder devices might share the same push token
    if a phone has been given or sold to another person without
    re-installing the OS. Also, ensure that the device is added to the
    secondary index used for alerting (alert_user_id), for fast
    enumeration of all devices that need to be alerted for a particular
    user.
    """
        def _DoUpdate():
            super(Device, self).Update(client, callback)

        def _OnQueryByPushToken(devices):
            """Disable alerts for all other devices."""
            with util.Barrier(_DoUpdate) as b:
                for device in devices:
                    if device.device_id != self.device_id:
                        device.push_token = None
                        device.alert_user_id = None
                        super(Device, device).Update(client, b.Callback())

        # Each time the device is updated, update the last_access field.
        self.last_access = util.GetCurrentTimestamp()

        if self._IsModified('push_token'):
            if self.push_token is None:
                self.alert_user_id = None
                _DoUpdate()
            else:
                # Ensure that the device will be alerted.
                self.alert_user_id = self.user_id

                query_expr = ('device.push_token={t}', {'t': self.push_token})
                Device.IndexQuery(client, query_expr, None,
                                  _OnQueryByPushToken)
        else:
            _DoUpdate()
Ejemplo n.º 17
0
 def _OnQuery(devices):
     with util.Barrier(callback) as b:
         now = util.GetCurrentTimestamp()
         for device in devices:
             if device.device_id != exclude_device_id:
                 token = device.push_token
                 assert token, device
                 try:
                     PushNotification.Push(token,
                                           alert=alert,
                                           badge=badge,
                                           sound=sound,
                                           extra=extra)
                 except TypeError as e:
                     logging.error('bad push token %s', token)
                     Device._HandleBadPushToken(client, token,
                                                time.time(),
                                                b.Callback())
                 except Exception as e:
                     logging.warning(
                         'failed to push notification to user %d: %s',
                         user_id, e)
                     raise
Ejemplo n.º 18
0
    def testUnlinkIdentity(self):
        """Verify unlinking an identity causes every referencing contact to be updated."""
        # Create a Peter contact for Spencer.
        timestamp = util.GetCurrentTimestamp()
        spencer = self._user
        contact_identity = 'Email:[email protected]'
        contact_name = 'Peter Mattis'
        contact_given_name = 'Peter'
        contact_family_name = 'Mattis'
        contact_rank = 42
        contact = Contact.CreateFromKeywords(spencer.user_id,
                                             [(contact_identity, None)],
                                             timestamp,
                                             Contact.GMAIL,
                                             name='Peter Mattis',
                                             given_name='Peter',
                                             family_name='Mattis',
                                             rank=42)
        self._RunAsync(contact.Update, self._client)

        peter_ident = self._RunAsync(Identity.Query, self._client,
                                     contact_identity, None)

        # Unlink peter's identity, which should cause Spencer's contact to be updated.
        self._RunAsync(peter_ident.UnlinkIdentity, self._client,
                       self._user2.user_id, contact_identity, timestamp + 1)

        contacts = self._RunAsync(Contact.RangeQuery, self._client,
                                  spencer.user_id, None, None, None)
        self.assertEqual(len(contacts), 1)
        self.assertEqual(
            contacts[0].sort_key,
            Contact.CreateSortKey(contact.contact_id, timestamp + 1))
        self.assertEqual(contacts[0].name, contact_name)
        self.assertEqual(contacts[0].given_name, contact_given_name)
        self.assertEqual(contacts[0].family_name, contact_family_name)
        self.assertEqual(contacts[0].rank, contact_rank)
Ejemplo n.º 19
0
 def IsExpired(self):
     """Returns true if this ShortURL has expired."""
     return util.GetCurrentTimestamp() >= self.expires
Ejemplo n.º 20
0
    def CreateAndExecute(
            cls,
            client,
            user_id,
            device_id,
            method,
            args,
            callback,
            message_version=message.MAX_SUPPORTED_MESSAGE_VERSION):
        """Creates a new operation with 'method' and 'args' describing the operation. After
    successfully creating the operation, the operation is asynchronously executed. Returns
    the op that was executed.
    """
        # Get useful headers and strip all else.
        headers = args.pop('headers', {})
        synchronous = headers.pop('synchronous', False)

        # Validate the op_id and op_timestamp fields.
        op_id = headers.pop('op_id', None)
        op_timestamp = headers.pop('op_timestamp', None)
        assert (op_id is not None) == (op_timestamp
                                       is not None), (op_id, op_timestamp)

        # Validate that op_id is correctly formed and is allowed to be generated by the current device.
        # No need to do this if the op_id was generated by the system as part of message upgrade.
        if op_id is not None and headers.get(
                'original_version',
                0) >= message.Message.ADD_OP_HEADER_VERSION:
            yield Operation.VerifyOperationId(client, user_id, device_id,
                                              op_id)

        # Use the op_id provided by the user, or generate a system op-id.
        if op_id is None:
            op_id = yield gen.Task(Operation.AllocateSystemOperationId, client)

        # Possibly migrate backwards to a message version that is compatible with older versions of the
        # server that may still be running.
        op_message = message.Message(
            args, default_version=message.MAX_MESSAGE_VERSION)
        yield gen.Task(op_message.Migrate,
                       client,
                       migrate_version=message_version,
                       migrators=OpManager.Instance().op_map[method].migrators)

        op = Operation(user_id, op_id)
        op.device_id = device_id
        op.method = method
        op.json = json.dumps(args)
        op.attempts = 0

        # Set timestamp to header value if it was specified, or current timestamp if not.
        if op_timestamp is not None:
            op.timestamp = op_timestamp
        else:
            op.timestamp = util.GetCurrentTimestamp()

        # Set expired backoff so that if this process fails before the op can be executed, in the worst
        # case it will eventually get picked up by the OpManager's scan for failed ops. Note that in
        # rare cases, this may mean that the op gets picked up immediately by another server (i.e. even
        # though the current server has *not* failed), but that is fine -- it doesn't really matter what
        # server executes the op, it just matters that the op gets executed in a timely manner.
        op.backoff = 0

        # Try to create the operation if it does not yet exist.
        try:
            yield gen.Task(op.Update, client, expected={'operation_id': False})

            # Execute the op according to the 'synchronous' parameter. If 'synchronous' is True, the
            # callback is invoked only after the operation has completed. Useful during unittests to
            # ensure the mutations wrought by the operation are queryable.
            logging.info('PERSIST: user: %d, device: %d, op: %s, method: %s' %
                         (user_id, device_id, op_id, method))
        except Exception:
            # Return existing op.
            logging.warning('operation "%s" already exists', op_id)
            existing_op = yield gen.Task(Operation.Query,
                                         client,
                                         user_id,
                                         op_id,
                                         None,
                                         must_exist=False)
            if existing_op is not None:
                op = existing_op

        # If not synchronous, we fire the callback, but continue to execute.
        if not synchronous:
            callback(op)

            # Establish new "clean" context in which to execute the operation. The operation should not rely
            # on any context, since it may end up run on a completely different machine. In addition, establish
            # an exception barrier in order to handle any bugs or asserts, rather than letting the context
            # established for the request handle it, since it will have already completed).
            with stack_context.NullContext():
                with util.ExceptionBarrier(util.LogExceptionCallback):
                    OpManager.Instance().MaybeExecuteOp(
                        client, user_id, op.operation_id)
        else:
            # Let exceptions flow up to request context so they'll be put into an error response.
            OpManager.Instance().MaybeExecuteOp(client, user_id,
                                                op.operation_id,
                                                partial(callback, op))
Ejemplo n.º 21
0
    def _AuthUser(self, user_dict, ident_dict, device_dict, confirmed=False):
        """Called when a requester has been authenticated as a Viewfinder user by a trusted authority
    that provides additional information about the user, such as name, email, gender, etc. At
    this point, we can trust that the identity key provided in "ident_dict" is controlled by the
    calling user.

    Completes the authentication action in two steps: first, makes sure the user id and device
    id are retrieved or allocated as necessary; second, starts a user registration operation and
    returns a login cookie. If "confirmed" is True, then the "confirm_time" field in the user
    cookie is set, indicating the time at which the user confirmed their control of the identity
    via email or SMS. This type of cookie is necessary to perform certain high-privilege
    operations, such as updating the password.

    Registration is synchronous, meaning that the caller will wait until it is complete. This
    ensures that when the caller tries to login immediately following this call, the new user
    will be created and ready.
    """
        before_user = yield gen.Task(self._PrepareAuthUser, user_dict,
                                     ident_dict, device_dict)

        # Map auth attribute names to those used by User schema and exclude any attributes that are not yet stored
        # in the User table.
        scratch_user_dict = {'user_id': user_dict['user_id']}
        for k, v in user_dict.items():
            user_key = AuthHandler._AUTH_ATTRIBUTE_MAP.get(k, None)
            if user_key is not None:
                if getattr(before_user, user_key) is None:
                    scratch_user_dict[user_key] = v

                # Set facebook email if it has not yet been set.
                if user_key == 'email' and ident_dict[
                        'authority'] == 'Facebook':
                    if getattr(before_user, 'facebook_email') is None:
                        scratch_user_dict['facebook_email'] = v

        # If the device id is not present, then allocate it now.
        if device_dict is not None and 'device_id' not in device_dict:
            device_dict['device_id'] = yield gen.Task(Device._allocator.NextId,
                                                      self._client)

        # Make synchronous request to ensure user is fully created before returning.
        request = {
            'headers': {
                'synchronous': True
            },
            'user_dict': scratch_user_dict,
            'ident_dict': ident_dict,
            'device_dict': device_dict
        }
        op = yield gen.Task(
            Operation.CreateAndExecute, self._client, user_dict['user_id'],
            device_dict['device_id']
            if device_dict is not None else before_user.webapp_dev_id,
            'RegisterUserOperation.Execute', request)

        if self._action == 'link':
            # Now make asynchronous request (or synchronous if requested by client) to fetch contacts.
            # Fetching contacts can take a long time, so best to do this in the background by default.
            request = {
                'key': ident_dict['key'],
                'user_id': user_dict['user_id']
            }
            if self._IsInteractiveRequest(
            ) or self._request_message.dict['headers'].get(
                    'synchronous', False):
                request['headers'] = {'synchronous': True}

            op = yield gen.Task(
                Operation.CreateAndExecute, self._client, user_dict['user_id'],
                device_dict['device_id']
                if device_dict is not None else before_user.webapp_dev_id,
                'FetchContactsOperation.Execute', request)

        # Get the user that was registered by the operation.
        after_user = yield gen.Task(User.Query, self._client,
                                    user_dict['user_id'], None)

        # If the identity was confirmed via email/SMS, set the cookie "confirm_time", which allows
        # the cookie to authorize higher privilege operations, such as setting the user password.
        confirm_time = util.GetCurrentTimestamp() if confirmed else None

        # Create the user cookie dict that will be returned to the caller.
        device_id = after_user.webapp_dev_id if device_dict is None else device_dict[
            'device_id']
        user_cookie_dict = self.CreateUserCookieDict(after_user.user_id,
                                                     device_id,
                                                     after_user.name,
                                                     confirm_time=confirm_time)

        # Sets the user cookie and finishes the request.
        if self._IsInteractiveRequest():
            self.SetUserCookie(user_cookie_dict)
            self._FinishInteractiveRequest()
        else:
            response_dict = {'user_id': user_dict['user_id']}
            if device_dict is not None:
                response_dict['device_id'] = device_dict['device_id']

            use_session_cookie = self._request_message.dict.get(
                'use_session_cookie', None)
            util.SetIfNotNone(user_cookie_dict, 'is_session_cookie',
                              use_session_cookie)

            self.SetUserCookie(user_cookie_dict)
            self._FinishJSONRequest(op, response_dict,
                                    json_schema.AUTH_RESPONSE)
Ejemplo n.º 22
0
    def _Check(self):
        """Gathers pre-mutation information:
       1. Queries for existing followers and viewpoint.
       2. Checkpoints list of followers that need to have REMOVED label added.

       Validates the following:
       1. Viewpoint exists and is not a default viewpoint.
       2. Permission to modify viewpoint.
       3. Permission to remove the requested followers.
    """
        # Get all existing followers.
        self._followers, _ = yield gen.Task(Viewpoint.QueryFollowers,
                                            self._client,
                                            self._viewpoint_id,
                                            limit=Viewpoint.MAX_FOLLOWERS)

        # Get the viewpoint to be modified, along with the follower that is removing the users.
        # This state will not be changed by remove followers, and so doesn't need to be part of
        # the checkpoint.
        self._viewpoint, self._removing_follower = yield gen.Task(
            Viewpoint.QueryWithFollower, self._client, self._user_id,
            self._viewpoint_id)

        # Raise error if viewpoint is not found.
        if self._viewpoint is None:
            raise NotFoundError(VIEWPOINT_NOT_FOUND,
                                viewpoint_id=self._viewpoint_id)

        # Don't allow removal of followers from a default viewpoint.
        if self._viewpoint.IsDefault():
            raise PermissionError(CANNOT_REMOVE_DEFAULT_FOLLOWER)

        # Check permission to remove followers from the viewpoint.
        if self._removing_follower is None or not self._removing_follower.CanContribute(
        ):
            raise PermissionError(CANNOT_REMOVE_FOLLOWERS,
                                  user_id=self._user_id,
                                  viewpoint_id=self._viewpoint.viewpoint_id)

        # Check permission to remove each of the followers.
        for follower in self._followers:
            # Only consider followers to be removed.
            if follower.user_id not in self._remove_id_set:
                continue

            # User can always remove himself from the viewpoint.
            if follower.user_id == self._user_id:
                continue

            # User can only remove other user if he originally added that user.
            if follower.adding_user_id != self._user_id:
                raise PermissionError(
                    CANNOT_REMOVE_THIS_FOLLOWER,
                    remove_id=follower.user_id,
                    viewpoint_id=self._viewpoint.viewpoint_id)

            # Follower can only be removed if he was added less than 7 days ago.
            if util.GetCurrentTimestamp(
            ) - follower.timestamp > RemoveFollowersOperation.MAX_REMOVE_PERIOD:
                raise PermissionError(
                    CANNOT_REMOVE_OLD_FOLLOWER,
                    remove_id=follower.user_id,
                    viewpoint_id=self._viewpoint.viewpoint_id)

        # Get followers to make un-revivable.
        self._unrevivable_followers = [
            follower for follower in self._followers
            if follower.user_id in self._remove_id_set
            and not follower.IsUnrevivable()
        ]

        # Start populating the checkpoint if this the first time the operation has been run.
        if self._op.checkpoint is None:
            # Trim down remove set to include only those which are not already removed. Note that
            # some of the discarded followers still need to be made un-revivable.
            for follower in self._followers:
                if follower.IsRemoved():
                    self._remove_id_set.discard(follower.user_id)

            # Set checkpoint.
            # The list of followers that need to be removed from the viewpoint need to be check-pointed
            # because it is changed in the UPDATE phase. If we fail after UPDATE, but before NOTIFY,
            # we would not send correct notifications on retry.
            checkpoint = {'remove': list(self._remove_id_set)}
            yield self._op.SetCheckpoint(self._client, checkpoint)
        else:
            # Restore state from checkpoint.
            self._remove_id_set = set(self._op.checkpoint['remove'])
Ejemplo n.º 23
0
    def _HandleGet(self, short_url, action, identity_key, user_name,
                   access_token, **kwargs):
        """This handler is invoked in two cases:

      1. The user clicks a ShortURL link in a verification email that was sent to them. In
         this case, we return a page that tries to redirect to the mobile app in order to
         provide it the access code.

      2. Once the redirect has been attempted, the page calls here with a redirected=True
         query parameter. We then poll the server to determine whether the redirect
         succeeded (i.e. if access token was redeemed). If redirect was not successful,
         we return a page to the user that prompts them to manually enter the access code.
    """
        action_info = VerifyIdBaseHandler.ACTION_MAP[action]
        identity = yield gen.Task(Identity.Query, self._client, identity_key,
                                  None)

        # The "redirected" flag indicates whether the app redirect has already been attempted.
        redirected = self.get_argument('redirected', None) == 'True'
        if not redirected:
            # If we haven't yet tried to re-direct to the app, verify the access token before giving
            # it back to the client, in order to detect if it has expired.
            try:
                yield identity.VerifyAccessToken(self._client, access_token)
            except Exception as ex:
                logging.info('error during access token verification: %s', ex)
                raise ExpiredError(EXPIRED_EMAIL_LINK_ERROR)

            # If user agent is mobile IOS device, then returned page should try to switch to app.
            user_agent_info = mdetect.UAgentInfo(
                self.request.headers.get("User-Agent"),
                self.request.headers.get("Accept"))
            may_have_app = user_agent_info.detectIos()
        else:
            # The app re-direct attempt is done, so wait several seconds to see if it was successful.
            for i in xrange(VerifyIdMobileHandler._ACCESS_TOKEN_TRIES):
                now = util.GetCurrentTimestamp()
                if now >= identity.expires:
                    # Token was redeemed, so assume the app re-direct was successful.
                    logging.info('verification of %s succeeded', identity.key)
                    self.render('verify_id_success.html',
                                title=action_info.title)
                    return

                # Wait and try again.
                logging.info(
                    'waiting, then re-checking access token for %s...',
                    identity.key)
                yield gen.Task(IOLoop.current().add_timeout,
                               now + VerifyIdMobileHandler._ACCESS_TOKEN_WAIT)

            # Redirect was attempted, but failed, so user needs to enter access token.
            may_have_app = False

        # Separate groups of three digits.
        access_token_re = re.match(r'(\d\d\d)(\d\d\d)(\d\d\d)', access_token)

        self.render('verify_id.html',
                    redirect_failed=redirected,
                    may_have_app=may_have_app,
                    title=action_info.title,
                    email_type=action_info.email_type,
                    identity_key=identity.key,
                    access_token=access_token,
                    code_1=access_token_re.group(1),
                    code_2=access_token_re.group(2),
                    code_3=access_token_re.group(3))
Ejemplo n.º 24
0
    def _HandleGet(self,
                   short_url,
                   identity_key,
                   viewpoint_id,
                   default_url,
                   is_sms=False,
                   is_first_click=True):
        """Invoked when a user follows a prospective user invitation URL. Sets a prospective user
    cookie that identifies the user and restricts access to a single viewpoint. Typically
    redirects the user to the corresponding website conversation page.
    """
        identity = yield gen.Task(Identity.Query,
                                  self._client,
                                  identity_key,
                                  None,
                                  must_exist=False)

        # Check for rare case where the identity has been unlinked since issuing the prospective user link.
        if identity is None or identity.user_id is None:
            raise ExpiredError(
                'The requested link has expired and can no longer be used.')

        # If the "next" query argument is specified, redirect to that, otherwise fall back on default_url.
        next_url = self.get_argument('next', default_url)
        if urlparse.urlparse(next_url).hostname is not None:
            raise InvalidRequestError('Cannot redirect to absolute URL: %s' %
                                      next_url)

        # Detect photo store redirect, as we should not set a cookie or return redirection to photo store in this case.
        photostore_re = re.match(r'.*/episodes/(.*)/photos/(.*)(\..)',
                                 next_url)

        # If the link was sent via SMS, then reset the SMS alert count (since the link was followed).
        if is_sms:
            settings = AccountSettings.CreateForUser(identity.user_id,
                                                     sms_count=0)
            yield gen.Task(settings.Update, self._client)

        # A registered user can no longer use prospective user links.
        user = yield gen.Task(User.Query, self._client, identity.user_id, None)
        if user.IsRegistered():
            # If not already logged in with the same user with full access, redirect to the auth page.
            context = ViewfinderContext.current()
            if context is None:
                current_user = None
                current_viewpoint_id = None
            else:
                current_user = context.user
                current_viewpoint_id = context.viewpoint_id

            if current_user is None or current_user.user_id != identity.user_id or current_viewpoint_id is not None:
                self.ClearUserCookie()
                self.redirect('/auth?%s' % urlencode(dict(next=next_url)))
                return
        else:
            # If this is the first time the link was clicked, then create a confirmed cookie.
            if is_first_click:
                confirm_time = util.GetCurrentTimestamp()

                # Update is_first_click.
                new_json = deepcopy(short_url.json)
                new_json['is_first_click'] = False
                short_url.json = new_json
                yield gen.Task(short_url.Update, self._client)
            else:
                confirm_time = None

            # Set the prospective user cookie. Make it a session cookie so that it will go away when
            # browser is closed.
            user_cookie_dict = self.CreateUserCookieDict(
                user.user_id,
                user.webapp_dev_id,
                user_name=user.name,
                viewpoint_id=viewpoint_id,
                confirm_time=confirm_time,
                is_session_cookie=True)

            # Do not set the user cookie if this is a photo view request.
            self.LoginUser(user,
                           user_cookie_dict,
                           set_cookie=photostore_re is None)

        # Handle photostore redirect request internally rather than returning the redirect to the
        # client. Email clients do not keep cookies, so it is not possible to redirect to an
        # authenticated URL.
        if photostore_re:
            episode_id = photostore_re.group(1)
            photo_id = photostore_re.group(2)
            suffix = photostore_re.group(3)
            next_url = yield PhotoStoreHandler.GetPhotoUrl(
                self._client, self._obj_store, episode_id, photo_id, suffix)

        # Redirect to the URL of the next page.
        self.redirect(next_url)