move common.error() to webutil.flask_util

corresponds to snarfed/webutil@10c088cebd
pull/79/head
Ryan Barrett 2021-08-06 10:29:25 -07:00
rodzic a27ebc4697
commit 32d9e2bf6c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
11 zmienionych plików z 49 dodań i 51 usunięć

Wyświetl plik

@ -11,13 +11,14 @@ from google.cloud import ndb
from granary import as2, microformats2
import mf2util
from oauth_dropins.webutil import util
from oauth_dropins.webutil.flask_util import error
from oauth_dropins.webutil.handlers import cache_response
from oauth_dropins.webutil.util import json_dumps, json_loads
import webapp2
from app import app, cache
import common
from common import error, redirect_unwrap, redirect_wrap
from common import redirect_unwrap, redirect_wrap
from models import Follower, MagicKey
from httpsig.requests_auth import HTTPSignatureAuth
@ -85,7 +86,7 @@ def actor(domain):
"""Serves /[DOMAIN], fetches its mf2, converts to AS Actor, and serves it."""
tld = domain.split('.')[-1]
if tld in common.TLD_BLOCKLIST:
return error('', status=404)
error('', status=404)
mf2 = util.fetch_mf2('http://%s/' % domain, gateway=True,
headers=common.HEADERS)
@ -94,7 +95,7 @@ def actor(domain):
hcard = mf2util.representative_hcard(mf2, mf2['url'])
logging.info('Representative h-card: %s', json_dumps(hcard, indent=2))
if not hcard:
return error("""\
error("""\
Coul find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on %s""" % mf2['url'])
key = MagicKey.get_or_create(domain)
@ -125,7 +126,7 @@ def inbox(domain):
activity = request.json
assert activity
except (TypeError, ValueError, AssertionError):
return error("Couldn't parse body as JSON", exc_info=True)
error("Couldn't parse body as JSON", exc_info=True)
obj = activity.get('object') or {}
if isinstance(obj, str):
@ -137,7 +138,7 @@ def inbox(domain):
if type == 'Create':
type = obj.get('type')
elif type not in SUPPORTED_TYPES:
return error('Sorry, %s activities are not supported yet.' % type,
error('Sorry, %s activities are not supported yet.' % type,
status=501)
# TODO: verify signature if there is one
@ -196,12 +197,12 @@ def accept_follow(follow, follow_unwrapped):
followee_unwrapped = follow_unwrapped.get('object')
follower = follow.get('actor')
if not followee or not followee_unwrapped or not follower:
return error('Follow activity requires object and actor. Got: %s' % follow)
error('Follow activity requires object and actor. Got: %s' % follow)
inbox = follower.get('inbox')
follower_id = follower.get('id')
if not inbox or not follower_id:
return error('Follow actor requires id and inbox. Got: %s', follower)
error('Follow actor requires id and inbox. Got: %s', follower)
# store Follower
user_domain = util.domain_from_link(followee_unwrapped)
@ -242,7 +243,7 @@ def undo_follow(undo_unwrapped):
follower = follow.get('actor')
followee = follow.get('object')
if not follower or not followee:
return error('Undo of Follow requires object with actor and object. Got: %s' % follow)
error('Undo of Follow requires object with actor and object. Got: %s' % follow)
# deactivate Follower
user_domain = util.domain_from_link(followee)

Wyświetl plik

@ -6,11 +6,11 @@ import urllib.parse
import flask
from flask import request
from oauth_dropins.webutil import flask_util
from oauth_dropins.webutil.flask_util import error
import requests
from app import app, cache
import common
from common import error
LINK_HEADER = '<%s>; rel="webmention"'
CACHE_TIME = datetime.timedelta(seconds=15)
@ -23,14 +23,14 @@ def add_wm(url=None):
"""Proxies HTTP requests and adds Link header to our webmention endpoint."""
url = urllib.parse.unquote(url)
if not url.startswith('http://') and not url.startswith('https://'):
return error('URL must start with http:// or https://')
error('URL must start with http:// or https://')
try:
got = common.requests_get(url)
except requests.exceptions.Timeout as e:
return error(str(e), status=504, exc_info=True)
error(str(e), status=504, exc_info=True)
except requests.exceptions.RequestException as e:
return error(str(e), status=502, exc_info=True)
error(str(e), status=502, exc_info=True)
resp = flask.make_response(got.content, got.status_code, dict(got.headers))
resp.headers.add('Link', LINK_HEADER % (request.args.get('endpoint') or

Wyświetl plik

@ -23,8 +23,6 @@ HEADERS = {
'User-Agent': 'Bridgy Fed (https://fed.brid.gy/)',
}
XML_UTF8 = "<?xml version='1.0' encoding='UTF-8'?>\n"
# USERNAME = 'me'
# USERNAME_EMOJI = '🌎' # globe
LINK_HEADER_RE = re.compile(r""" *< *([^ >]+) *> *; *rel=['"]([^'"]+)['"] *""")
AS2_PUBLIC_AUDIENCE = 'https://www.w3.org/ns/activitystreams#Public'
@ -147,13 +145,6 @@ def content_type(resp):
return type.split(';')[0]
def error(msg, status=None, exc_info=False):
if not status:
status = 400
logging.info('Returning %s: %s' % (status, msg), exc_info=exc_info)
return (msg, status)
def send_webmentions(activity_wrapped, proxy=None, **response_props):
"""Sends webmentions for an incoming Salmon slap or ActivityPub inbox delivery.
Args:
@ -164,7 +155,7 @@ def send_webmentions(activity_wrapped, proxy=None, **response_props):
verb = activity.get('verb')
if verb and verb not in SUPPORTED_VERBS:
return error('%s activities are not supported yet.' % verb)
error('%s activities are not supported yet.' % verb)
# extract source and targets
source = activity.get('url') or activity.get('id')
@ -192,9 +183,9 @@ def send_webmentions(activity_wrapped, proxy=None, **response_props):
targets = util.dedupe_urls(util.get_url(t) for t in targets)
if not source:
return error("Couldn't find original post URL")
error("Couldn't find original post URL")
if not targets:
return error("Couldn't find any target URLs in inReplyTo, object, or mention tags")
error("Couldn't find any target URLs in inReplyTo, object, or mention tags")
# send webmentions and store Responses
errors = []
@ -226,7 +217,7 @@ def send_webmentions(activity_wrapped, proxy=None, **response_props):
if errors:
msg = 'Errors:\n' + '\n'.join(str(e) for e in errors)
return error(msg, status=getattr(errors[0], 'http_status', None))
error(msg, status=getattr(errors[0], 'http_status', None))
def postprocess_as2(activity, target=None, key=None):

Wyświetl plik

@ -15,9 +15,9 @@ import humanize
from oauth_dropins.webutil import flask_util, util
from oauth_dropins.webutil.appengine_info import APP_ID
import webapp2
from oauth_dropins.webutil.flask_util import error
from app import app, cache
from common import error
from models import Response
LEVELS = {

Wyświetl plik

@ -17,12 +17,12 @@ from flask import redirect, request
from granary import as2, microformats2
import mf2util
from oauth_dropins.webutil import flask_util, util
from oauth_dropins.webutil.flask_util import error
from oauth_dropins.webutil.util import json_dumps
from werkzeug.exceptions import abort
from app import app, cache
import common
from common import error
from models import MagicKey
CACHE_TIME = datetime.timedelta(seconds=15)
@ -43,7 +43,7 @@ def redir(to):
to = re.sub(r'^(https?:/)([^/])', r'\1/\2', to)
if not to.startswith('http://') and not to.startswith('https://'):
return error(f'Expected fully qualified URL; got {to}')
error(f'Expected fully qualified URL; got {to}')
# check that we've seen this domain before so we're not an open redirect
domains = set((util.domain_from_link(to),

Wyświetl plik

@ -5,11 +5,11 @@ import datetime
from flask import request
from granary import as2, atom, microformats2
from oauth_dropins.webutil import flask_util
from oauth_dropins.webutil.flask_util import error
from oauth_dropins.webutil.util import json_loads
from app import app, cache
import common
from common import error
from models import Response
CACHE_TIME = datetime.timedelta(minutes=15)
@ -26,7 +26,7 @@ def render():
id = f'{source} {target}'
resp = Response.get_by_id(id)
if not resp:
return error(f'No stored response for {id}', status=404)
error(f'No stored response for {id}', status=404)
if resp.source_mf2:
as1 = microformats2.json_to_object(json_loads(resp.source_mf2))
@ -35,7 +35,7 @@ def render():
elif resp.source_atom:
as1 = atom.atom_to_activity(resp.source_atom)
else:
return error(f'Stored response for {id} has no data', status=404)
error(f'Stored response for {id} has no data', status=404)
# add HTML meta redirect to source page. should trigger for end users in
# browsers but not for webmention receivers (hopefully).

Wyświetl plik

@ -11,10 +11,10 @@ from django_salmon import magicsigs, utils
from flask import request
from granary import atom
from oauth_dropins.webutil import util
from oauth_dropins.webutil.flask_util import error
from app import app
import common
from common import error
# from django_salmon.feeds
ATOM_NS = 'http://www.w3.org/2005/Atom'
@ -41,7 +41,7 @@ def slap(acct):
try:
parsed = utils.parse_magic_envelope(body)
except ParseError as e:
return error('Could not parse POST body as XML', exc_info=True)
error('Could not parse POST body as XML', exc_info=True)
data = parsed['data']
logging.info(f'Decoded: {data}')
@ -49,22 +49,22 @@ def slap(acct):
try:
activity = atom.atom_to_activity(data)
except ParseError as e:
return error('Could not parse envelope data as XML', exc_info=True)
error('Could not parse envelope data as XML', exc_info=True)
verb = activity.get('verb')
if verb and verb not in SUPPORTED_VERBS:
return error(f'Sorry, {verb} activities are not supported yet.', status=501)
error(f'Sorry, {verb} activities are not supported yet.', status=501)
# verify author and signature
author = util.get_url(activity.get('actor'))
if ':' not in author:
author = f'acct:{author}'
elif not author.startswith('acct:'):
return error(f'Author URI {author} has unsupported scheme; expected acct:')
error(f'Author URI {author} has unsupported scheme; expected acct:')
logging.info(f'Fetching Salmon key for {author}')
if not magicsigs.verify(data, parsed['sig'], author_uri=author):
return error('Could not verify magic signature.')
error('Could not verify magic signature.')
logging.info('Verified magic signature.')
# Verify that the timestamp is recent. Required by spec.
@ -73,7 +73,7 @@ def slap(acct):
#
# updated = utils.parse_updated_from_atom(data)
# if not utils.verify_timestamp(updated):
# return error('Timestamp is more than 1h old.')
# error('Timestamp is more than 1h old.')
# send webmentions to each target
activity = atom.atom_to_activity(data)

Wyświetl plik

@ -5,6 +5,7 @@ to test:
* user URL that redirects
* error handling
"""
import html
from unittest import mock
import urllib.parse
@ -179,7 +180,8 @@ class WebfingerTest(testutil.TestCase):
def test_user_handler_bad_tld(self):
got = client.get('/acct:foo.json')
self.assertEqual(404, got.status_code)
self.assertIn("doesn't look like a domain", got.get_data(as_text=True))
self.assertIn("doesn't look like a domain",
html.unescape(got.get_data(as_text=True)))
@mock.patch('requests.get')
def test_webfinger_handler(self, mock_get):

Wyświetl plik

@ -857,7 +857,11 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(400, got.status_code)
self.assertIn('Target post http://orig/url has no Atom link',
got.get_data(as_text=True))
self.assertEqual(0, Response.query().count())
resp = Response.get_by_id('http://a/reply http://orig/url')
self.assertEqual('out', resp.direction)
self.assertEqual('ostatus', resp.protocol)
self.assertEqual('error', resp.status)
def test_salmon_relative_atom_href(self, mock_get, mock_post):
orig_relative = requests_response("""\

Wyświetl plik

@ -12,12 +12,12 @@ from flask import render_template, request
from granary.microformats2 import get_text
import mf2util
from oauth_dropins.webutil import flask_util, handlers, util
from oauth_dropins.webutil.flask_util import error
from oauth_dropins.webutil.util import json_dumps
import webapp2
from app import app, cache
import common
from common import error
import models
CACHE_TIME = datetime.timedelta(seconds=15)
@ -38,7 +38,7 @@ class User(flask_util.XrdOrJrd):
logging.debug(f'Headers: {list(request.headers.items())}')
if domain.split('.')[-1] in NON_TLDS:
return error(f"{domain} doesn't look like a domain", status=404)
error(f"{domain} doesn't look like a domain", status=404)
# find representative h-card. try url, then url's home page, then domain
urls = [f'http://{domain}/']
@ -55,7 +55,7 @@ class User(flask_util.XrdOrJrd):
logging.info(f'Representative h-card: {json_dumps(hcard, indent=2)}')
break
else:
return error(f"didn't find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on {resp.url}")
error(f"didn't find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on {resp.url}")
logging.info(f'Generating WebFinger data for {domain}')
key = models.MagicKey.get_or_create(domain)

Wyświetl plik

@ -17,6 +17,7 @@ from google.cloud.ndb import Key
from granary import as2, atom, microformats2, source
import mf2util
from oauth_dropins.webutil import flask_util, util
from oauth_dropins.webutil.flask_util import error
from oauth_dropins.webutil.util import json_dumps, json_loads
import requests
import webapp2
@ -25,7 +26,6 @@ from webob import exc
import activitypub
from app import app
import common
from common import error
from models import Follower, MagicKey, Response
SKIP_EMAIL_DOMAINS = frozenset(('localhost', 'snarfed.org'))
@ -55,12 +55,12 @@ class Webmention(View):
# source's intent to federate to mastodon)
if (request.host_url not in source_resp.text and
urllib.parse.quote(request.host_url, safe='') not in source_resp.text):
return error("Couldn't find link to {request.host_url}")
error("Couldn't find link to {request.host_url}")
# convert source page to ActivityStreams
entry = mf2util.find_first_entry(self.source_mf2, ['h-entry'])
if not entry:
return error('No microformats2 found on {self.source_url}')
error('No microformats2 found on {self.source_url}')
logging.info(f'First entry: {json_dumps(entry, indent=2)}')
# make sure it has url, since we use that for AS2 id, which is required
@ -191,9 +191,9 @@ class Webmention(View):
inbox_url = actor.get('inbox')
actor = actor.get('url') or actor.get('id')
if not inbox_url and not actor:
return error('Target object has no actor or attributedTo with URL or id.')
error('Target object has no actor or attributedTo with URL or id.')
elif not isinstance(actor, str):
return error(f'Target actor or attributedTo has unexpected url or id object: {actor}')
error(f'Target actor or attributedTo has unexpected url or id object: {actor}')
if not inbox_url:
# fetch actor as AS object
@ -203,7 +203,7 @@ class Webmention(View):
if not inbox_url:
# TODO: probably need a way to save errors like this so that we can
# return them if ostatus fails too.
# return error('Target actor has no inbox')
# error('Target actor has no inbox')
continue
inbox_url = urllib.parse.urljoin(target_url, inbox_url)
@ -255,7 +255,7 @@ class Webmention(View):
parsed = util.parse_html(self.target_resp)
atom_url = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
if not atom_url or not atom_url.get('href'):
return error(f'Target post {target} has no Atom link')
error(f'Target post {target} has no Atom link')
# fetch Atom target post, extract and inject id into source object
base_url = ''
@ -311,7 +311,7 @@ class Webmention(View):
pass
if not endpoint:
return error('No salmon endpoint found!')
error('No salmon endpoint found!')
logging.info(f'Discovered Salmon endpoint {endpoint}')
# construct reply Atom object