Beispiel #1
0
    def test_maybe_add_or_delete_source_delete_declined(self):
        state = {
            'feature': 'webmention',
            'operation': 'delete',
        }
        msg = 'If you want to disable, please approve the FakeSource prompt.'

        # no source
        with app.test_request_context():
            with self.assertRaises(RequestRedirect) as rr:
                util.maybe_add_or_delete_source(FakeSource, None,
                                                util.encode_oauth_state(state))

            self.assert_equals(302, rr.exception.code)
            self.assert_equals('http://localhost/', rr.exception.new_url)
            self.assertEqual([msg], get_flashed_messages())

        # source
        state['source'] = self.sources[0].key.urlsafe().decode()
        with app.test_request_context(), self.assertRaises(
                RequestRedirect) as rr:
            util.maybe_add_or_delete_source(FakeSource, None,
                                            util.encode_oauth_state(state))

            self.assert_equals(302, rr.exception.code)
            self.assert_equals(self.source_bridgy_url, rr.exception.new_url)
            self.assertEqual([msg], get_flashed_messages())
Beispiel #2
0
    def test_maybe_add_or_delete_source(self):
        auth_entity = FakeAuthEntity(id='x',
                                     user_json=json_dumps({
                                         'url': 'http://foo.com/',
                                         'name': UNICODE_STR
                                     }))
        auth_entity.put()

        key = FakeSource.next_key()
        with app.test_request_context(), self.assertRaises(
                RequestRedirect) as rr:
            state = util.construct_state_param_for_add(feature='publish')
            util.maybe_add_or_delete_source(FakeSource, auth_entity, state)
            self.assertIn(UNICODE_STR, get_flashed_messages()[0])

        self.assertEqual(302, rr.exception.code)
        self.assertEqual(['publish'], key.get().features)

        name = urllib.parse.quote_plus(UNICODE_STR.encode())
        self.assertIn(f'logins="/fake/{key.id()}?{name}";',
                      rr.exception.get_response().headers['Set-Cookie'])

        for feature in None, '':
            key = FakeSource.next_key()
            with app.test_request_context(), self.assertRaises(
                    RequestRedirect) as rr:
                state = util.construct_state_param_for_add(feature)
                util.maybe_add_or_delete_source(FakeSource, auth_entity, state)
            self.assertEqual([], key.get().features)
Beispiel #3
0
def blogger_add():
    util.maybe_add_or_delete_source(
        Blogger,
        ndb.Key(urlsafe=request.form['auth_entity_key']).get(),
        request.form['state'],
        blog_id=request.form['blog'],
    )
Beispiel #4
0
    def finish(self, auth_entity, state=None):
        if not auth_entity:
            util.maybe_add_or_delete_source(Tumblr, auth_entity, state)
            return

        vars = {
            'action':
            '/tumblr/add',
            'state':
            state,
            'auth_entity_key':
            auth_entity.key.urlsafe().decode(),
            'blogs': [
                {
                    'id': b['name'],
                    'title': b.get('title', ''),
                    'domain': util.domain_from_link(b['url'])
                }
                # user_json is the user/info response:
                # http://www.tumblr.com/docs/en/api/v2#user-methods
                for b in json_loads(auth_entity.user_json)['user']['blogs']
                if b.get('name') and b.get('url')
            ],
        }
        print(logger.getEffectiveLevel())
        assert logger.isEnabledFor(logging.DEBUG)
        logger.info(f'Rendering choose_blog.html with {vars}')
        return render_template('choose_blog.html', **vars)
Beispiel #5
0
    def test_maybe_add_or_delete_source_username_key_id_disables_other_source(
            self):
        class UKISource(Source):
            USERNAME_KEY_ID = True
            GR_CLASS = FakeGrSource

            @classmethod
            def new(cls, **kwargs):
                del kwargs['auth_entity']
                return UKISource(username='******',
                                 domain_urls=['http://foo/'],
                                 **kwargs)

        # original entity with capitalized key name
        orig_key = UKISource(id='FoO', features=['listen']).put()

        with app.test_request_context(), self.assertRaises(
                RequestRedirect) as rr:
            state = util.construct_state_param_for_add(feature='publish')
            util.maybe_add_or_delete_source(UKISource, FakeAuthEntity(id='x'),
                                            state)

        self.assertEqual(302, rr.exception.code)
        got = UKISource.get_by_id('foo')
        self.assertEqual('FoO', got.username)
        self.assertEqual(['publish'], got.features)

        # original entity with capitalized key should be disabled
        self.assertEqual([], orig_key.get().features)
Beispiel #6
0
def tumblr_add():
    util.maybe_add_or_delete_source(
        Tumblr,
        ndb.Key(urlsafe=request.form['auth_entity_key']).get(),
        request.form['state'],
        blog_name=request.form['blog'],
    )
Beispiel #7
0
    def finish(self, auth_entity, state=None):
        if not auth_entity:
            util.maybe_add_or_delete_source(Medium, auth_entity, state)
            return

        user = json_loads(auth_entity.user_json)['data']
        username = user['username']
        if not username.startswith('@'):
            username = '******' + username

        # fetch publications this user contributes or subscribes to.
        # (sadly medium's API doesn't tell us the difference unless we fetch each
        # pub's metadata separately.)
        # https://github.com/Medium/medium-api-docs/#user-content-listing-the-users-publications
        auth_entity.publications_json = auth_entity.get(
            oauth_medium.API_BASE + f'users/{user["id"]}/publications').text
        auth_entity.put()
        pubs = json_loads(auth_entity.publications_json).get('data')
        if not pubs:
            util.maybe_add_or_delete_source(Medium,
                                            auth_entity,
                                            state,
                                            username=username)
            return

        # add user profile to start of pubs list
        user['id'] = username
        pubs.insert(0, user)

        vars = {
            'action':
            '/medium/add',
            'state':
            state,
            'auth_entity_key':
            auth_entity.key.urlsafe().decode(),
            'blogs': [{
                'id': p['id'],
                'title': p.get('name', ''),
                'url': p.get('url', ''),
                'pretty_url': util.pretty_link(str(p.get('url', ''))),
                'image': p.get('imageUrl', ''),
            } for p in pubs if p.get('id')],
        }
        logger.info(f'Rendering choose_blog.html with {vars}')
        return render_template('choose_blog.html', **vars)
Beispiel #8
0
  def finish(self, auth_entity, state=None):
    if auth_entity:
      if int(auth_entity.blog_id) == 0:
        flash('Please try again and choose a blog before clicking Authorize.')
        return redirect('/')

      # Check if this is a self-hosted WordPress blog
      site_info = WordPress.get_site_info(auth_entity)
      if site_info is None:
        return
      elif site_info.get('jetpack'):
        logger.info(f'This is a self-hosted WordPress blog! {auth_entity.key_id()} {auth_entity.blog_id}')
        return render_template('confirm_self_hosted_wordpress.html',
                               auth_entity_key=auth_entity.key.urlsafe().decode(),
                               state=state)

    util.maybe_add_or_delete_source(WordPress, auth_entity, state)
Beispiel #9
0
    def test_maybe_add_or_delete_without_web_site_redirects_to_edit_websites(
            self):
        for bad_url in None, 'not>a<url', 'http://fa.ke/xyz':
            auth_entity = FakeAuthEntity(id='x',
                                         user_json=json_dumps({'url':
                                                               bad_url}))
            auth_entity.put()

            key = FakeSource.next_key().urlsafe().decode()
            with app.test_request_context(), self.assertRaises(
                    RequestRedirect) as rr:
                util.maybe_add_or_delete_source(FakeSource, auth_entity, '{}')

            self.assertEqual(302, rr.exception.code)
            self.assert_equals(
                f'http://localhost/edit-websites?source_key={key}',
                rr.exception.new_url)
Beispiel #10
0
    def finish(self, auth_entity, state=None):
        source = util.maybe_add_or_delete_source(Mastodon, auth_entity, state)

        features = util.decode_oauth_state(state).get('feature', '').split(',')
        if set(features) != set(source.features):
            # override features with whatever we requested scopes for just now, since
            # scopes are per access token. background:
            # https://github.com/snarfed/bridgy/issues/1015
            source.features = features
            source.put()
Beispiel #11
0
def oauth_callback():
    """OAuth callback handler.

  Both the add and delete flows have to share this because Blogger's
  oauth-dropin doesn't yet allow multiple callback handlers. :/
  """
    auth_entity = None
    auth_entity_str_key = request.values.get('auth_entity')
    if auth_entity_str_key:
        auth_entity = ndb.Key(urlsafe=auth_entity_str_key).get()
        if not auth_entity.blog_ids or not auth_entity.blog_hostnames:
            auth_entity = None

    if not auth_entity:
        flash("Couldn't fetch your blogs. Maybe you're not a Blogger user?")

    state = request.values.get('state')
    if not state:
        state = util.construct_state_param_for_add(feature='webmention')

    if not auth_entity:
        util.maybe_add_or_delete_source(Blogger, auth_entity, state)
        return

    vars = {
        'action':
        '/blogger/add',
        'state':
        state,
        'operation':
        util.decode_oauth_state(state).get('operation'),
        'auth_entity_key':
        auth_entity.key.urlsafe().decode(),
        'blogs': [{
            'id': id,
            'title': title,
            'domain': host
        } for id, title, host in zip(auth_entity.blog_ids, auth_entity.
                                     blog_titles, auth_entity.blog_hostnames)],
    }
    logger.info(f'Rendering choose_blog.html with {vars}')
    return render_template('choose_blog.html', **vars)
Beispiel #12
0
 def finish(self, auth_entity, state=None):
     logger.debug(f'finish with {auth_entity}, {state}')
     source = util.maybe_add_or_delete_source(Flickr, auth_entity, state)
     feature = util.decode_oauth_state(state).get('feature')
     if source and feature == 'listen' and 'publish' in source.features:
         # we had signed up previously with publish, so we'll reauth to
         # avoid losing that permission
         logger.info('Restarting OAuth flow to get publish permissions.')
         source.features.remove('publish')
         source.put()
         return self.start_oauth_flow('publish')
Beispiel #13
0
  def finish(self, auth_entity, state=None):
    source = util.maybe_add_or_delete_source(Twitter, auth_entity, state)
    feature = util.decode_oauth_state(state).get('feature')

    if source is not None and feature == 'listen' and 'publish' in source.features:
      # if we were already signed up for publish, we had a read/write token.
      # when we sign up for listen, we use x_auth_access_type=read to request
      # just read permissions, which *demotes* us to a read only token! ugh.
      # so, do the whole oauth flow again to get a read/write token.
      logger.info('Restarting OAuth flow to get publish permissions.')
      source.features.remove('publish')
      source.put()
      return self.start_oauth_flow('publish')
Beispiel #14
0
    def test_add_to_logins_cookie(self):
        with app.test_request_context(
                headers={'Cookie': 'logins=/other/1?bob'}):
            listen = util.construct_state_param_for_add(feature='listen')
            auth_entity = FakeAuthEntity(id='x', user_json='{}')
            auth_entity.put()

            id = FakeSource.next_key().id()
            with self.assertRaises(RequestRedirect) as rr:
                util.maybe_add_or_delete_source(FakeSource, auth_entity,
                                                listen)

            cookie = 'logins="/fake/{id}?fake|/other/1?bob";'
            self.assertIn(cookie.format(id=id),
                          rr.exception.get_response().headers['Set-Cookie'])

            id = FakeSource.next_key().id()
            with self.assertRaises(RequestRedirect) as rr:
                util.maybe_add_or_delete_source(FakeSource, auth_entity,
                                                listen)
            self.assertIn(cookie.format(id=id),
                          rr.exception.get_response().headers['Set-Cookie'])
Beispiel #15
0
 def dispatch_request(self):
     util.maybe_add_or_delete_source(FakeSource, self.auth_entity,
                                     request.values.get('state'))
     return ''
Beispiel #16
0
 def finish(self, auth_entity, state=None):
     logger.debug(f'finish with {auth_entity}, {state}')
     util.maybe_add_or_delete_source(GitHub, auth_entity, state)
Beispiel #17
0
 def finish(self, auth_entity, state=None):
   util.maybe_add_or_delete_source(Reddit, auth_entity, state)
Beispiel #18
0
def medium_add():
    auth_entity = ndb.Key(urlsafe=request.values['auth_entity_key']).get()
    util.maybe_add_or_delete_source(Medium,
                                    auth_entity,
                                    request.values['state'],
                                    username=request.values['blog'])
Beispiel #19
0
 def test_maybe_add_or_delete_source_bad_state(self):
     auth_entity = FakeAuthEntity(id='x', user_json='{}')
     auth_entity.put()
     with self.assertRaises(BadRequest):
         util.maybe_add_or_delete_source(FakeSource, auth_entity, 'bad')
Beispiel #20
0
def confirm_self_hosted():
  util.maybe_add_or_delete_source(
    WordPress,
    ndb.Key(urlsafe=request.form['auth_entity_key']).get(),
    request.form['state'])