From f384bf5529b4e3b202bc11b653b308b439d8606d Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Fri, 18 Nov 2022 18:46:27 -0800 Subject: [PATCH] add User.verify(), checks redirects and home page representative h-card for #276 --- models.py | 27 +++++++++++++++++++++++++ tests/test_models.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/models.py b/models.py index 174ae92..8e2b343 100644 --- a/models.py +++ b/models.py @@ -2,12 +2,16 @@ import logging import urllib.parse +from werkzeug.exceptions import BadRequest, NotFound + from Crypto.PublicKey import RSA from django_salmon import magicsigs from flask import request from google.cloud import ndb from oauth_dropins.webutil.models import StringIdModel +import common + logger = logging.getLogger(__name__) @@ -27,6 +31,8 @@ class User(StringIdModel): mod = ndb.StringProperty(required=True) public_exponent = ndb.StringProperty(required=True) private_exponent = ndb.StringProperty(required=True) + has_redirects = ndb.BooleanProperty() + has_hcard = ndb.BooleanProperty() @classmethod def _get_kind(cls): @@ -65,6 +71,27 @@ class User(StringIdModel): magicsigs.base64_to_long(str(self.private_exponent)))) return rsa.exportKey(format='PEM') + def verify(self): + """Fetches site a couple ways to check for redirects and h-card.""" + domain = self.key.id() + site = f'https://{domain}/' + logger.info(f'Verifying {site}') + + # check webfinger redirect + path = f'/.well-known/webfinger?resource=acct:{domain}@{domain}' + resp = common.requests_get(urllib.parse.urljoin(site, path), + allow_redirects=False) + expected = urllib.parse.urljoin(request.host_url, path) + if resp.is_redirect and resp.headers.get('Location') == expected: + self.has_redirects = True + + # check home page + try: + common.actor(self.key.id()) + self.has_hcard = True + except (BadRequest, NotFound): + pass + class Activity(StringIdModel): """A reply, like, repost, or other interaction that we've relayed. diff --git a/tests/test_models.py b/tests/test_models.py index 9ecf62f..88c5f01 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,9 @@ # coding=utf-8 """Unit tests for models.py.""" +from unittest import mock + +from oauth_dropins.webutil.testutil import requests_response + from app import app from models import User, Activity from . import testutil @@ -35,6 +39,49 @@ class UserTest(testutil.TestCase): self.assertTrue(pem.decode().startswith('-----BEGIN RSA PRIVATE KEY-----\n'), pem) self.assertTrue(pem.decode().endswith('-----END RSA PRIVATE KEY-----'), pem) + @mock.patch('requests.get') + def test_verify(self, mock_get): + self.assertFalse(self.user.has_redirects) + self.assertFalse(self.user.has_hcard) + + def check(redirects, hcard): + with app.test_request_context('/'): + self.user.verify() + with self.subTest(redirects=redirects, hcard=hcard): + self.assertEqual(redirects, bool(self.user.has_redirects)) + self.assertEqual(hcard, bool(self.user.has_hcard)) + + # both fail + empty = requests_response('') + mock_get.side_effect = [empty, empty] + check(False, False) + + # redirect works but strips query params, no h-card + half_redir = requests_response( + status=302, redirected_url='http://localhost/.well-known/webfinger') + no_hcard = requests_response('') + mock_get.side_effect = [half_redir, no_hcard] + check(False, False) + + # redirect works, non-representative h-card + full_redir = requests_response( + status=302, allow_redirects=False, + redirected_url='http://localhost/.well-known/webfinger?resource=acct:y.z@y.z') + bad_hcard = requests_response( + 'me', + url='https://y.z/', + ) + mock_get.side_effect = [full_redir, bad_hcard] + check(True, False) + + # both work + hcard = requests_response( + 'me', + url='https://y.z/', + ) + mock_get.side_effect = [full_redir, hcard] + check(True, True) + class ActivityTest(testutil.TestCase):