Example #1
0
  def construct_state_param_for_add(self, state=None, **kwargs):
    """Construct the state parameter if one isn't explicitly passed in.

    The following keys are common:
    - operation: 'add' or 'delete'
    - feature: 'listen', 'publish', or 'webmention'
    - callback: an optional external callback, that we will redirect to at
                the end of the authorization handshake
    - source: the source key, only applicable to deletes
    """
    state_obj = util.decode_oauth_state(state)
    if not state_obj:
      state_obj = {field: self.request.get(field) for field in
                   ('callback', 'feature', 'id', 'user_url')}
      state_obj['operation'] = 'add'

    if kwargs:
      state_obj.update(kwargs)

    return util.encode_oauth_state(state_obj)
Example #2
0
  def construct_state_param_for_add(self, state=None, **kwargs):
    """Construct the state parameter if one isn't explicitly passed in.

    The following keys are common:
    - operation: 'add' or 'delete'
    - feature: 'listen', 'publish', or 'webmention'
    - callback: an optional external callback, that we will redirect to at
                the end of the authorization handshake
    - source: the source key, only applicable to deletes
    """
    state_obj = util.decode_oauth_state(state)
    if not state_obj:
      state_obj = {field: self.request.get(field) for field in
                   ('callback', 'feature', 'id', 'user_url')}
      state_obj['operation'] = 'add'

    if kwargs:
      state_obj.update(kwargs)

    return util.encode_oauth_state(state_obj)
Example #3
0
def maybe_add_or_delete_source(source_cls, auth_entity, state, **kwargs):
    """Adds or deletes a source if auth_entity is not None.

  Used in each source's oauth-dropins :meth:`Callback.finish()` and
  :meth:`Callback.get()` methods, respectively.

  Args:
    source_cls: source class, e.g. :class:`instagram.Instagram`
    auth_entity: ouath-dropins auth entity
    state: string, OAuth callback state parameter. a JSON serialized dict
      with operation, feature, and an optional callback URL. For deletes,
      it will also include the source key
    kwargs: passed through to the source_cls constructor

  Returns:
    source entity if it was created or updated, otherwise None
  """
    state_obj = util.decode_oauth_state(state)
    operation = state_obj.get('operation', 'add')
    feature = state_obj.get('feature')
    callback = state_obj.get('callback')
    user_url = state_obj.get('user_url')

    logger.debug(
        'maybe_add_or_delete_source with operation=%s, feature=%s, callback=%s',
        operation, feature, callback)
    logins = None

    if operation == 'add':  # this is an add/update
        if not auth_entity:
            # TODO: only show if we haven't already flashed another message?
            # get_flashed_messages() caches so it's dangerous to call to check;
            # use eg session.get('_flashes', []) instead.
            # https://stackoverflow.com/a/17243946/186123
            flash("OK, you're not signed up. Hope you reconsider!")
            if callback:
                callback = util.add_query_params(callback,
                                                 {'result': 'declined'})
                logger.debug(
                    f'user declined adding source, redirect to external callback {callback}'
                )
                redirect(callback)
            else:
                redirect('/')

        logger.info(
            f'{source_cls.__class__.__name__}.create_new with {auth_entity.key}, {state}, {kwargs}'
        )
        source = source_cls.create_new(
            auth_entity=auth_entity,
            features=feature.split(',') if feature else [],
            user_url=user_url,
            **kwargs)

        if source:
            # if we're normalizing username case to lower case to make the key id, check
            # if there's and old Source with a capitalized key id, and if so, disable it
            # https://github.com/snarfed/bridgy/issues/884
            if source.USERNAME_KEY_ID and source.username != source.key_id():

                @ndb.transactional()
                def maybe_disable_original():
                    orig = source_cls.get_by_id(source.username)
                    if orig:
                        logging.info(
                            f'Disabling {orig.bridgy_url()} for lower case {source.bridgy_url()}'
                        )
                        orig.features = []
                        orig.put()

                maybe_disable_original()

            # add to login cookie
            logins = get_logins()
            logins.append(
                Login(path=source.bridgy_path(),
                      site=source.SHORT_NAME,
                      name=source.label_name()))

            if callback:
                callback = util.add_query_params(
                    callback, {
                        'result': 'success',
                        'user': source.bridgy_url(),
                        'key': source.key.urlsafe().decode(),
                    } if source else {'result': 'failure'})
                logger.debug(
                    'finished adding source, redirect to external callback %s',
                    callback)
                redirect(callback, logins=logins)
            elif not source.domains:
                redirect('/edit-websites?' + urllib.parse.urlencode({
                    'source_key':
                    source.key.urlsafe().decode(),
                }),
                         logins=logins)
            else:
                redirect(source.bridgy_url(), logins=logins)

        # no source
        redirect('/')

    else:  # this is a delete
        if auth_entity:
            # TODO: remove from logins cookie
            redirect(
                f'/delete/finish?auth_entity={auth_entity.key.urlsafe().decode()}&state={state}'
            )
        else:
            flash(
                f'If you want to disable, please approve the {source_cls.GR_CLASS.NAME} prompt.'
            )
            source_key = state_obj.get('source')
            if source_key:
                source = ndb.Key(urlsafe=source_key).get()
                if source:
                    redirect(source.bridgy_url())

            redirect('/')
Example #4
0
  def maybe_add_or_delete_source(self, source_cls, auth_entity, state, **kwargs):
    """Adds or deletes a source if auth_entity is not None.

    Used in each source's oauth-dropins :meth:`CallbackHandler.finish()` and
    :meth:`CallbackHandler.get()` methods, respectively.

    Args:
      source_cls: source class, e.g. :class:`instagram.Instagram`
      auth_entity: ouath-dropins auth entity
      state: string, OAuth callback state parameter. a JSON serialized dict
        with operation, feature, and an optional callback URL. For deletes,
        it will also include the source key
      kwargs: passed through to the source_cls constructor

    Returns:
      source entity if it was created or updated, otherwise None
    """
    state_obj = util.decode_oauth_state(state)
    operation = state_obj.get('operation', 'add')
    feature = state_obj.get('feature')
    callback = state_obj.get('callback')
    user_url = state_obj.get('user_url')

    logging.debug(
      'maybe_add_or_delete_source with operation=%s, feature=%s, callback=%s',
      operation, feature, callback)

    if operation == 'add':  # this is an add/update
      if not auth_entity:
        if not self.messages:
          self.messages.add("OK, you're not signed up. Hope you reconsider!")
        if callback:
          callback = util.add_query_params(callback, {'result': 'declined'})
          logging.debug(
            'user declined adding source, redirect to external callback %s',
            callback)
          # call super.redirect so the callback url is unmodified
          super(Handler, self).redirect(callback)
        else:
          self.redirect('/')
        return

      CachedPage.invalidate('/users')
      logging.info('%s.create_new with %s', source_cls.__class__.__name__,
                   (auth_entity.key, state, kwargs))
      source = source_cls.create_new(self, auth_entity=auth_entity,
                                     features=feature.split(',') if feature else [],
                                     user_url=user_url, **kwargs)

      if source:
        # add to login cookie
        logins = self.get_logins()
        logins.append(Login(path=source.bridgy_path(), site=source.SHORT_NAME,
                            name=source.label_name()))
        self.set_logins(logins)

      if callback:
        callback = util.add_query_params(callback, {
          'result': 'success',
          'user': source.bridgy_url(self),
          'key': source.key.urlsafe().decode(),
        } if source else {'result': 'failure'})
        logging.debug(
          'finished adding source, redirect to external callback %s', callback)
        # call super.redirect so the callback url is unmodified
        super(Handler, self).redirect(callback)

      elif source and not source.domains:
        self.redirect('/edit-websites?' + urllib.parse.urlencode({
          'source_key': source.key.urlsafe().decode(),
        }))

      else:
        self.redirect(source.bridgy_url(self) if source else '/')

      return source

    else:  # this is a delete
      if auth_entity:
        self.redirect('/delete/finish?auth_entity=%s&state=%s' %
                      (auth_entity.key.urlsafe().decode(), state))
      else:
        self.messages.add('If you want to disable, please approve the %s prompt.' %
                          source_cls.GR_CLASS.NAME)
        source_key = state_obj.get('source')
        if source_key:
          source = ndb.Key(urlsafe=source_key).get()
          if source:
            return self.redirect(source.bridgy_url(self))

        self.redirect('/')
Example #5
0
  def maybe_add_or_delete_source(self, source_cls, auth_entity, state, **kwargs):
    """Adds or deletes a source if auth_entity is not None.

    Used in each source's oauth-dropins :meth:`CallbackHandler.finish()` and
    :meth:`CallbackHandler.get()` methods, respectively.

    Args:
      source_cls: source class, e.g. :class:`instagram.Instagram`
      auth_entity: ouath-dropins auth entity
      state: string, OAuth callback state parameter. a JSON serialized dict
        with operation, feature, and an optional callback URL. For deletes,
        it will also include the source key
      kwargs: passed through to the source_cls constructor

    Returns:
      source entity if it was created or updated, otherwise None
    """
    state_obj = util.decode_oauth_state(state)
    operation = state_obj.get('operation', 'add')
    feature = state_obj.get('feature')
    callback = state_obj.get('callback')
    user_url = state_obj.get('user_url')

    logging.debug(
      'maybe_add_or_delete_source with operation=%s, feature=%s, callback=%s',
      operation, feature, callback)

    if operation == 'add':  # this is an add/update
      if not auth_entity:
        if not self.messages:
          self.messages.add("OK, you're not signed up. Hope you reconsider!")
        if callback:
          callback = util.add_query_params(callback, {'result': 'declined'})
          logging.debug(
            'user declined adding source, redirect to external callback %s',
            callback)
          # call super.redirect so the callback url is unmodified
          super(Handler, self).redirect(callback.encode('utf-8'))
        else:
          self.redirect('/')
        return

      CachedPage.invalidate('/users')
      logging.info('%s.create_new with %s', source_cls.__class__.__name__,
                   (auth_entity.key, state, kwargs))
      source = source_cls.create_new(self, auth_entity=auth_entity,
                                     features=feature.split(',') if feature else [],
                                     user_url=user_url, **kwargs)

      if source:
        # add to login cookie
        logins = self.get_logins()
        logins.append(Login(path=source.bridgy_path(), site=source.SHORT_NAME,
                            name=source.label_name()))
        self.set_logins(logins)

      if callback:
        callback = util.add_query_params(callback, {
          'result': 'success',
          'user': source.bridgy_url(self),
          'key': source.key.urlsafe(),
        } if source else {'result': 'failure'})
        logging.debug(
          'finished adding source, redirect to external callback %s', callback)
        # call super.redirect so the callback url is unmodified
        super(Handler, self).redirect(callback.encode('utf-8'))

      elif source and not source.domains:
        self.redirect('/edit-websites?' + urllib.urlencode({
          'source_key': source.key.urlsafe(),
        }))

      else:
        self.redirect(source.bridgy_url(self) if source else '/')

      return source

    else:  # this is a delete
      if auth_entity:
        self.redirect('/delete/finish?auth_entity=%s&state=%s' %
                      (auth_entity.key.urlsafe(), state))
      else:
        self.messages.add('If you want to disable, please approve the %s prompt.' %
                          source_cls.GR_CLASS.NAME)
        source_key = state_obj.get('source')
        if source_key:
          source = ndb.Key(urlsafe=source_key).get()
          if source:
            return self.redirect(source.bridgy_url(self))

        self.redirect('/')