kopia lustrzana https://github.com/snarfed/bridgy-fed
in progress checkpoint for webfinger, halfway done
rodzic
5499e06a6b
commit
a0e6d0c5d0
8
app.yaml
8
app.yaml
|
@ -10,6 +10,8 @@ builtins:
|
|||
- remote_api: on
|
||||
|
||||
libraries:
|
||||
- name: jinja2
|
||||
version: latest
|
||||
- name: lxml
|
||||
version: latest
|
||||
- name: pycrypto
|
||||
|
@ -57,10 +59,14 @@ handlers:
|
|||
script: webmention.app
|
||||
secure: always
|
||||
|
||||
- url: /[^/]+/?(inbox)?
|
||||
- url: /[^@/]+/?(inbox)?
|
||||
script: activitypub.app
|
||||
secure: always
|
||||
|
||||
- url: /(acct:)?@[^/]+/?
|
||||
script: webfinger.app
|
||||
secure: always
|
||||
|
||||
skip_files:
|
||||
- ^(.*/)?.*\.py[co]
|
||||
- ^(.*/)?.*/RCS/.*
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"subject": "{{ uri }}",
|
||||
"aliases": ["{{ urls|join('\", \"') }}"],
|
||||
|
||||
{% if magic_public_key %}
|
||||
"magic_keys": [{"value": "{{ magic_public_key }}"}],
|
||||
{% endif %}
|
||||
|
||||
"links": [
|
||||
{% for url in urls %}{
|
||||
"rel": "http://webfinger.net/rel/profile-page",
|
||||
"type": "text/html",
|
||||
"href": "{{ url }}"
|
||||
},{% endfor %}
|
||||
|
||||
{% if magic_public_key %}
|
||||
{
|
||||
"rel": "magic-public-key",
|
||||
"href": "{{ magic_public_key }}"
|
||||
},
|
||||
{% endif %}
|
||||
|
||||
{% for avatar in avatars %}{
|
||||
"rel": "http://webfinger.net/rel/avatar",
|
||||
"href": "{{ avatar }}"
|
||||
}{% endfor %}
|
||||
|
||||
{% if poco_url %}
|
||||
{
|
||||
"rel": "http://portablecontacts.net/spec/1.0",
|
||||
"href": "{{ poco_url }}"
|
||||
},
|
||||
{% endif %}
|
||||
|
||||
{% if activitystreams_url %}
|
||||
{
|
||||
"rel": "http://ns.opensocial.org/2008/opensocial/activitystreams",
|
||||
"href": "{{ activitystreams_url }}"
|
||||
},
|
||||
|
||||
{
|
||||
"rel": "http://activitystrea.ms/spec/1.0",
|
||||
"href": "{{ activitystreams_url }}"
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
|
||||
|
||||
<Subject>{{ uri }}</Subject>
|
||||
<Alias>{{ urls[0] }}</Alias>
|
||||
|
||||
{% for url in urls %}
|
||||
<Link rel='http://webfinger.net/rel/profile-page' type='text/html'
|
||||
href='{{ url }}' />
|
||||
{% endfor %}
|
||||
|
||||
{% if magic_public_key %}
|
||||
<Property xmlns:mk="http://salmon-protocol.org/ns/magic-key"
|
||||
type="http://salmon-protocol.org/ns/magic-key">
|
||||
{{ magic_public_key }}
|
||||
</Property>
|
||||
<Link rel='magic-public-key'
|
||||
href='{{ magic_public_key }}' />
|
||||
{% endif %}
|
||||
|
||||
{% for avatar in avatars %}
|
||||
<Link rel='http://webfinger.net/rel/avatar'
|
||||
href='{{ avatar }}' />
|
||||
{% endfor %}
|
||||
|
||||
{% if poco_url %}
|
||||
<Link rel='http://portablecontacts.net/spec/1.0'
|
||||
href='{{ poco_url }}' />
|
||||
{% endif %}
|
||||
|
||||
{% if activitystreams_url %}
|
||||
<Link rel='http://ns.opensocial.org/2008/opensocial/activitystreams'
|
||||
href='{{ activitystreams_url }}' />
|
||||
<Link rel='http://activitystrea.ms/spec/1.0'
|
||||
href='{{ activitystreams_url }}' />
|
||||
{% endif %}
|
||||
</XRD>
|
|
@ -5,18 +5,31 @@ TODO: test error handling
|
|||
"""
|
||||
import json
|
||||
import unittest
|
||||
import urllib
|
||||
|
||||
from google.appengine.datastore import datastore_stub_util
|
||||
from google.appengine.ext import testbed
|
||||
|
||||
import mock
|
||||
import requests
|
||||
|
||||
import webfinger
|
||||
from webfinger import app
|
||||
import common
|
||||
|
||||
|
||||
class WebFingerTest(unittest.TestCase):
|
||||
|
||||
maxDiff = None
|
||||
|
||||
# TODO: unify with test_models
|
||||
def setUp(self):
|
||||
self.testbed = testbed.Testbed()
|
||||
self.testbed.activate()
|
||||
hrd_policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=.5)
|
||||
self.testbed.init_datastore_v3_stub(consistency_policy=hrd_policy)
|
||||
self.testbed.init_memcache_stub()
|
||||
|
||||
def tearDown(self):
|
||||
self.testbed.deactivate()
|
||||
|
||||
def test_host_meta_handler_xrd(self):
|
||||
got = app.get_response('/.well-known/host-meta')
|
||||
self.assertEquals(200, got.status_int)
|
||||
|
@ -37,3 +50,64 @@ class WebFingerTest(unittest.TestCase):
|
|||
self.assertEquals('application/json; charset=utf-8',
|
||||
got.headers['Content-Type'])
|
||||
self.assertTrue(got.body.startswith('{'), got.body)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_user_handler(self, mock_get):
|
||||
html = u"""
|
||||
<body>
|
||||
<a class="h-card" rel="me" href="/about-me">
|
||||
<img class="u-photo" src="/me.jpg" />
|
||||
Mrs. ☕ Foo
|
||||
</a>
|
||||
</body>
|
||||
"""
|
||||
resp = requests.Response()
|
||||
resp.status_code = 200
|
||||
resp._text = html
|
||||
resp._content = html.encode('utf-8')
|
||||
resp.encoding = 'utf-8'
|
||||
resp.url = 'https://foo.com/'
|
||||
mock_get.return_value = resp
|
||||
|
||||
got = app.get_response('/@foo.com?format=json')
|
||||
self.assertEquals(200, got.status_int)
|
||||
self.assertEquals('application/json; charset=utf-8',
|
||||
got.headers['Content-Type'])
|
||||
print got.body
|
||||
self.assertEquals({
|
||||
'subject': 'acct:@foo.com',
|
||||
'aliases': [
|
||||
'https://foo.com/about-me',
|
||||
'https://foo.com/',
|
||||
],
|
||||
'magic_keys': [{
|
||||
'rel': 'magic-public-key',
|
||||
'href': 'data:application/magic-public-key,RSA. ...',
|
||||
}],
|
||||
'links': [{
|
||||
'rel': 'http://webfinger.net/rel/profile-page',
|
||||
'type': 'text/html',
|
||||
'href': 'https://foo.com/about-me'
|
||||
}, {
|
||||
'rel': 'http://webfinger.net/rel/avatar',
|
||||
'href': 'https://foo.com/me.jpg'
|
||||
}, {
|
||||
'rel': 'salmon',
|
||||
'href': 'http://localhost/salmon/23507'
|
||||
}, {
|
||||
'rel': 'magic-public-key',
|
||||
'href': 'data:application/magic-public-key,RSA. ...'
|
||||
# TODO
|
||||
# }, {
|
||||
# 'rel': 'http://schemas.google.com/g/2010#updates-from',
|
||||
# 'type': 'application/atom+xml',
|
||||
# 'href': 'https://mastodon.technology/users/snarfed.atom'
|
||||
# }, {
|
||||
# 'rel': 'self',
|
||||
# 'type': 'application/activity+json',
|
||||
# 'href': 'https://mastodon.technology/users/snarfed'
|
||||
# }, {
|
||||
# 'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
||||
# 'template': 'https://mastodon.technology/authorize_follow?acct={uri}'
|
||||
}]
|
||||
}, json.loads(got.body))
|
||||
|
|
29
webfinger.py
29
webfinger.py
|
@ -16,15 +16,34 @@ from oauth_dropins.webutil import handlers, util
|
|||
import webapp2
|
||||
|
||||
import common
|
||||
import models
|
||||
|
||||
|
||||
class UserHandler(webapp2.RequestHandler):
|
||||
"""TODO"""
|
||||
class UserHandler(handlers.XrdOrJrdHandler):
|
||||
"""Serves /@[DOMAIN], fetches its mf2, converts to WebFinger, and serves."""
|
||||
|
||||
def get(self, username, domain):
|
||||
pass
|
||||
def template_prefix(self):
|
||||
return 'templates/webfinger_user'
|
||||
|
||||
def template_vars(self, domain):
|
||||
# TODO: unify with activitypub
|
||||
url = 'https://%s/' % domain
|
||||
resp = common.requests_get(url)
|
||||
mf2 = mf2py.parse(resp.text, url=resp.url)
|
||||
logging.info('Parsed mf2 for %s: %s', resp.url, json.dumps(mf2, indent=2))
|
||||
|
||||
hcard = mf2util.representative_hcard(mf2, resp.url)
|
||||
logging.info('Representative h-card: %s', json.dumps(hcard, indent=2))
|
||||
|
||||
key = models.MagicKey.get_or_create('@%s' % domain)
|
||||
props = hcard['properties']
|
||||
return {
|
||||
'urls': props['url'],
|
||||
'avatars': props['photo'],
|
||||
'magic_public_key': key.href(),
|
||||
}
|
||||
|
||||
|
||||
app = webapp2.WSGIApplication([
|
||||
(r'/(?:acct)?([^@/])@%s/?' % common.DOMAIN_RE, UserHandler),
|
||||
(r'/(?:acct)?@%s/?' % common.DOMAIN_RE, UserHandler),
|
||||
] + handlers.HOST_META_ROUTES, debug=appengine_config.DEBUG)
|
||||
|
|
Ładowanie…
Reference in New Issue