Porównaj commity

...

3 Commity

Autor SHA1 Wiadomość Data
Marnanel Thurman 4787f7c09a intermediate 2023-11-06 01:37:42 +00:00
Marnanel Thurman fb9fe9aaa7 fix 404 to the modern system 2023-11-06 01:26:15 +00:00
Marnanel Thurman de8ca26303 webfinger tests pass 2023-11-04 18:09:24 +00:00
7 zmienionych plików z 154 dodań i 85 usunięć

Wyświetl plik

@ -1,28 +1,46 @@
import argparse
import os
import kepi.users
DEFAULT_CONFIG_FILENAME = '/etc/kepi/kepi.conf'
CONFIG_SECTION = 'kepi'
class ConfigUsers:
def __init__(self):
raise NotImplementedError()
def __getitem__(self, who):
assert '/' not in who
assert not who.startswith('.')
filename = os.path.join(
config.users_dir,
who,
f'{who}.json',
)
return kepi.users.User(filename)
class Config:
settings = {}
subcommands = []
argparser = None
users = ConfigUsers.__new__(ConfigUsers)
def __init__(self):
raise NotImplementedError()
def users(self):
raise ValueError()
def __getattr__(self, field):
if field in self.settings:
print("9199", self.settings, field)
return self.settings[field]
else:
raise AttributeError(field)
def __setattr__(self, field, value):
self.settings[field] = value
def parse_args(self, argparser):
assert self.subcommands is not None
@ -47,5 +65,16 @@ class Config:
config = Config.__new__(Config)
def subcommand(fn):
"""
Decorator
"""
config.subcommands.append(fn)
return None
def normalise_url(
address,
env = {},
):
# For now, a naive implementation
return f'https://{env["SERVER_NAME"]}{address}'

Wyświetl plik

@ -5,7 +5,7 @@ from json import dumps
import logging
import kepi
import urllib.parse
from kepi.config import config, subcommand
from kepi.config import config, subcommand, normalise_url
logger = logging.getLogger('kepi.fastcgi')
@ -20,7 +20,7 @@ CONTENTTYPE_NODEINFO = (
'profile=http://nodeinfo.diaspora.software/ns/schema/2.0#'
)
USER_PAGE_RE = r'users/([a-z0-9-]+)/?'
USER_RE = r'/users/([a-z0-9-]+)/?'
HOST_META_URI = '/.well-known/host-meta'
NODEINFO_PART_1_URI = '/.well-known/nodeinfo'
NODEINFO_PART_2_URI = '/nodeinfo.json'
@ -28,12 +28,6 @@ NODEINFO_PART_2_URI = '/nodeinfo.json'
WEBFINGER_URI = '/.well-known/webfinger'
WEBFINGER_MIMETYPE = 'application/jrd+json; charset=utf-8'
ERROR_404 = """Content-Type: text/html
Status: 404 Not found
That resource does not exist here.
"""
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 17177
@ -69,15 +63,11 @@ def fastcgi_command(subparsers):
def _encode(s):
return s.replace('\n','\r\n').encode('UTF-8')
def despatch_user_page(env, match):
result = f"""Content-Type: {CONTENTTYPE_ACTIVITY}
Status: 200 OK
Hello world.
"""
def despatch_user(env, match):
result = {
'mimetype': CONTENTTYPE_ACTIVITY,
}
raise NotImplementedError()
return result
def despatch_host_meta(env, match):
@ -86,7 +76,7 @@ def despatch_host_meta(env, match):
result = {
'mimetype': CONTENTTYPE_HOST_META,
'text': """<?xml version="1.0" encoding="UTF-8"?>
'text': f"""<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/xrd+xml"
template="https://{env['SERVER_NAME']}/.well-known/webfinger?resource={{uri}}"/>
@ -143,35 +133,33 @@ def despatch_nodeinfo_part_2(env, match):
return result
def despatch_webfinger(env, match):
def _despatch_webfinger_inner(env, match):
user = urllib.parse.parse_qs(
username = urllib.parse.parse_qs(
qs = env['QUERY_STRING'],
).get('resource', '')
print("9100", user)
if not user:
if not username:
return {
'status': 400,
'reason': 'No resource specified for webfinger',
}
user = user[0]
username = username[0]
# Generally, user resources should be prefaced with "acct:",
# Generally, username resources should be prefaced with "acct:",
# per RFC7565. We support this, but we don't enforce it.
user = re.sub(r'^acct:', '', user)
username = re.sub(r'^acct:', '', username)
print("9110", user)
if '@' not in user:
if '@' not in username:
return {
'status': 400,
'reason': 'Absolute name required',
}
username, hostname = user.split('@', 2)
user_part, host_part = username.split('@', 2)
if hostname not in [
if host_part not in [
env['SERVER_NAME'],
]:
return {
@ -179,38 +167,75 @@ def despatch_webfinger(env, match):
'reason': 'Not local',
}
return {
user = config.users[user_part]
if not user.exists:
return {
'status': 404,
'reason': 'Not known',
}
user_url = normalise_url(
address = config.user_uri % {'username':user_part},
env = env,
)
try:
atom_uri = config.atom_uri
except AttributeError:
atom_uri = None
result = {
'mimetype': WEBFINGER_MIMETYPE,
'json': {
"subject" : "acct:{}@{}".format(username, hostname),
"subject" : f"acct:{user_part}@{host_part}",
"aliases" : [
actor_url,
user_url,
],
"links":[
{
'rel': 'http://webfinger.net/rel/profile-page',
'type': 'text/html',
'href': actor_url,
'href': user_url,
},
# TODO
#{
# 'rel': 'http://schemas.google.com/g/2010#updates-from',
# 'type': 'application/atom+xml',
# 'href': 'FIXME',
#},
{
'rel': 'self',
'type': 'application/activity+json',
'href': actor_url,
'href': user_url,
},
{
'rel': 'http://ostatus.org/schema/1.0/subscribe',
'template': configured_url('AUTHORIZE_FOLLOW_LINK'),
'template': normalise_url(
address = config.authorise_follow_uri,
env=env,
),
},
]},
}
if atom_uri is not None:
result['json']['links'].append(
{
'rel': 'http://schemas.google.com/g/2010#updates-from',
'type': 'application/atom+xml',
'href': normalise_url(
address = atom_uri,
env = env,
),
},
)
return result
def despatch_webfinger(env, match):
result = _despatch_webfinger_inner(env, match)
result['extra_headers'] = {
'Access-Control-Allow-Origin': '*',
}
return result
def format_message_for_http(
@ -219,11 +244,12 @@ def format_message_for_http(
reason = 'OK',
text = None,
json = None,
extra_headers = {},
):
if json is not None:
assert text is None
body = dumps(text, indent=2, sort_keys=True)
body = dumps(json, indent=2, sort_keys=True)
elif text is not None:
body = text
else:
@ -236,9 +262,13 @@ def format_message_for_http(
f"Content-Type: {mimetype}\r\n"
f"Status: {status} {reason}\r\n"
f"Content-Length: {len(body)}\r\n"
"\r\n"
)
for f,v in extra_headers.items():
headers += f'{f}: {v}\r\n'
headers += "\r\n"
headers = headers.encode('UTF-8')
return headers+body
@ -248,23 +278,12 @@ def despatch(env):
# XXX and DOCUMENT_URI and possibly QUERY_STRING
# XXX and despatch as appropriate
"""
response_headers = ''
response_headers += f'Content-Type: {CONTENTTYPE_ACTIVITY}\r\n'
response_headers += '\r\n'
response_body = response_body.encode('UTF-8')
response_headers = response_headers.encode('UTF-8')
"""
logger.debug('query: %s', env)
uri = env['DOCUMENT_URI']
print(env)
for regex, handler in [
(USER_PAGE_RE, despatch_user_page),
(USER_RE, despatch_user),
(HOST_META_URI, despatch_host_meta),
(NODEINFO_PART_1_URI, despatch_nodeinfo_part_1),
(NODEINFO_PART_2_URI, despatch_nodeinfo_part_2),
@ -272,7 +291,6 @@ def despatch(env):
]:
match = re.match(regex, uri)
print(regex, uri, match)
if match is None:
continue
@ -285,7 +303,9 @@ def despatch(env):
result = format_message_for_http(**result)
return result
return ERROR_404
return format_message_for_http(
status = 404,
)
class KepiHandler(FcgiHandler):

Wyświetl plik

@ -19,6 +19,10 @@ class User:
def name(self):
return os.path.splitext(os.path.basename(self.filename))[0]
@property
def exists(self):
return os.path.exists(self.filename)
def _load_details(self):
if self.details is not None:
return

Wyświetl plik

@ -1,11 +1,12 @@
# Many of these tests are based on tests from busby, and we should
# refactor those when the dust settles.
from kepi.fastcgi import despatch
from test import *
import logging
import email
import json
from kepi.fastcgi import despatch
from kepi.config import config
logger = logging.getLogger(name='kepi')
@ -25,6 +26,8 @@ WEBFINGER_BASE_URI = '/.well-known/webfinger'
WEBFINGER_URI = WEBFINGER_BASE_URI + '?resource={}'
WEBFINGER_MIMETYPE = 'application/jrd+json; charset=utf-8'
ACTIVITY_MIMETYPE = 'application/activity+json'
def call_despatch_and_parse_result(*args, **kwargs):
found = despatch(*args, **kwargs).decode('UTF-8')
result = email.message_from_string(found)
@ -37,7 +40,6 @@ def test_fastcgi_simple():
},
)
assert found['Status'].startswith('404 ')
assert found['Content-Type']==HTML_MIMETYPE
def test_fastcgi_host_meta():
@ -100,25 +102,18 @@ def test_fastcgi_nodeinfo_part_2():
assert 'activitypub' in response['protocols']
"""
From the original: we need to do this before the tests which get 200
def setUp():
keys = json.load(open('kepi/bowler_pub/tests/keys/keys-0001.json', 'r'))
class _TestUsers:
create_local_person(
name='alice',
publicKey=keys['public'],
privateKey=keys['private'],
)
def __enter__(self):
self.tempd = tempfile.TemporaryDirectory()
print("9444", "================", self.tempd)
config.users_dir = self.tempd.name
print("9445", "================", config.settings)
return self.tempd.__enter__()
self._alice_keys = keys
settings.ALLOWED_HOSTS = [
'altair.example.com',
'testserver',
]
settings.KEPI['LOCAL_OBJECT_HOSTNAME'] = 'testserver'
"""
def __exit__(self, exc_type, exc_value, exc_tb):
return self.tempd.__exit__(exc_type, exc_value, exc_tb)
"""
def test_fastcgi_webfinger():
@ -141,6 +136,16 @@ def test_fastcgi_webfinger():
assert found['Status'].startswith(
f'{expected_status} '), 'Status is correct'
return found
config.authorise_follow_uri = '/authorise-follow'
config.user_uri = '/users/%(username)s'
config.users_dir = os.path.join(
os.path.dirname(__file__),
'example-users',
)
response = webfinger(
who = None,
expected_status = 400,
@ -162,7 +167,7 @@ def test_fastcgi_webfinger():
)
response = webfinger(
who = 'alice@wombles.example.org',
who = 'sheila@wombles.example.org',
expected_status = 200,
)
@ -172,12 +177,23 @@ def test_fastcgi_webfinger():
'ACAO is *, per RFC'
)
parsed = json.loads(response.content)
parsed = json.loads(response.get_payload())
assert parsed['subject']=='acct:alice@testserver'
assert 'https://testserver/users/alice' in parsed['aliases']
assert parsed['subject']=='acct:sheila@wombles.example.org'
assert 'https://wombles.example.org/users/sheila' in parsed['aliases']
assert {
'rel': 'self',
'type': 'application/activity+json',
'href': 'https://testserver/users/alice',
'href': 'https://wombles.example.org/users/sheila',
} in parsed['links']
def test_fastcgi_user():
found = call_despatch_and_parse_result(
env = {
'DOCUMENT_URI': '/users/alice',
'ACCEPT': ACTIVITY_MIMETYPE,
'SERVER_NAME': 'wombles.example.org',
},
)
assert found['Status'].startswith('200 ')
assert found == 'womle'