kopia lustrzana https://gitlab.com/marnanel/chapeau
Porównaj commity
No commits in common. "4787f7c09aa8fd9c146ee6f47df736020c1dfe75" and "0918382eae8a8d580762df8c55f2de77dfb316e3" have entirely different histories.
4787f7c09a
...
0918382eae
|
@ -1,46 +1,28 @@
|
|||
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
|
||||
|
@ -65,16 +47,5 @@ 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}'
|
||||
|
|
132
kepi/fastcgi.py
132
kepi/fastcgi.py
|
@ -5,7 +5,7 @@ from json import dumps
|
|||
import logging
|
||||
import kepi
|
||||
import urllib.parse
|
||||
from kepi.config import config, subcommand, normalise_url
|
||||
from kepi.config import config, subcommand
|
||||
|
||||
logger = logging.getLogger('kepi.fastcgi')
|
||||
|
||||
|
@ -20,7 +20,7 @@ CONTENTTYPE_NODEINFO = (
|
|||
'profile=http://nodeinfo.diaspora.software/ns/schema/2.0#'
|
||||
)
|
||||
|
||||
USER_RE = r'/users/([a-z0-9-]+)/?'
|
||||
USER_PAGE_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,6 +28,12 @@ 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
|
||||
|
||||
|
@ -63,11 +69,15 @@ def fastcgi_command(subparsers):
|
|||
def _encode(s):
|
||||
return s.replace('\n','\r\n').encode('UTF-8')
|
||||
|
||||
def despatch_user(env, match):
|
||||
result = {
|
||||
'mimetype': CONTENTTYPE_ACTIVITY,
|
||||
}
|
||||
raise NotImplementedError()
|
||||
def despatch_user_page(env, match):
|
||||
|
||||
result = f"""Content-Type: {CONTENTTYPE_ACTIVITY}
|
||||
Status: 200 OK
|
||||
|
||||
Hello world.
|
||||
"""
|
||||
|
||||
|
||||
return result
|
||||
|
||||
def despatch_host_meta(env, match):
|
||||
|
@ -76,7 +86,7 @@ def despatch_host_meta(env, match):
|
|||
|
||||
result = {
|
||||
'mimetype': CONTENTTYPE_HOST_META,
|
||||
'text': f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
'text': """<?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}}"/>
|
||||
|
@ -133,33 +143,35 @@ def despatch_nodeinfo_part_2(env, match):
|
|||
|
||||
return result
|
||||
|
||||
def _despatch_webfinger_inner(env, match):
|
||||
def despatch_webfinger(env, match):
|
||||
|
||||
username = urllib.parse.parse_qs(
|
||||
user = urllib.parse.parse_qs(
|
||||
qs = env['QUERY_STRING'],
|
||||
).get('resource', '')
|
||||
|
||||
if not username:
|
||||
print("9100", user)
|
||||
if not user:
|
||||
return {
|
||||
'status': 400,
|
||||
'reason': 'No resource specified for webfinger',
|
||||
}
|
||||
username = username[0]
|
||||
user = user[0]
|
||||
|
||||
# Generally, username resources should be prefaced with "acct:",
|
||||
# Generally, user resources should be prefaced with "acct:",
|
||||
# per RFC7565. We support this, but we don't enforce it.
|
||||
username = re.sub(r'^acct:', '', username)
|
||||
user = re.sub(r'^acct:', '', user)
|
||||
|
||||
print("9110", user)
|
||||
|
||||
if '@' not in username:
|
||||
if '@' not in user:
|
||||
return {
|
||||
'status': 400,
|
||||
'reason': 'Absolute name required',
|
||||
}
|
||||
|
||||
user_part, host_part = username.split('@', 2)
|
||||
username, hostname = user.split('@', 2)
|
||||
|
||||
if host_part not in [
|
||||
if hostname not in [
|
||||
env['SERVER_NAME'],
|
||||
]:
|
||||
return {
|
||||
|
@ -167,75 +179,38 @@ def _despatch_webfinger_inner(env, match):
|
|||
'reason': 'Not local',
|
||||
}
|
||||
|
||||
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" : f"acct:{user_part}@{host_part}",
|
||||
"subject" : "acct:{}@{}".format(username, hostname),
|
||||
"aliases" : [
|
||||
user_url,
|
||||
actor_url,
|
||||
],
|
||||
|
||||
"links":[
|
||||
{
|
||||
'rel': 'http://webfinger.net/rel/profile-page',
|
||||
'type': 'text/html',
|
||||
'href': user_url,
|
||||
'href': actor_url,
|
||||
},
|
||||
# TODO
|
||||
#{
|
||||
# 'rel': 'http://schemas.google.com/g/2010#updates-from',
|
||||
# 'type': 'application/atom+xml',
|
||||
# 'href': 'FIXME',
|
||||
#},
|
||||
{
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': user_url,
|
||||
'href': actor_url,
|
||||
},
|
||||
{
|
||||
'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
||||
'template': normalise_url(
|
||||
address = config.authorise_follow_uri,
|
||||
env=env,
|
||||
),
|
||||
'template': configured_url('AUTHORIZE_FOLLOW_LINK'),
|
||||
},
|
||||
]},
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -244,12 +219,11 @@ def format_message_for_http(
|
|||
reason = 'OK',
|
||||
text = None,
|
||||
json = None,
|
||||
extra_headers = {},
|
||||
):
|
||||
|
||||
if json is not None:
|
||||
assert text is None
|
||||
body = dumps(json, indent=2, sort_keys=True)
|
||||
body = dumps(text, indent=2, sort_keys=True)
|
||||
elif text is not None:
|
||||
body = text
|
||||
else:
|
||||
|
@ -262,13 +236,9 @@ 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
|
||||
|
@ -278,12 +248,23 @@ 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_RE, despatch_user),
|
||||
(USER_PAGE_RE, despatch_user_page),
|
||||
(HOST_META_URI, despatch_host_meta),
|
||||
(NODEINFO_PART_1_URI, despatch_nodeinfo_part_1),
|
||||
(NODEINFO_PART_2_URI, despatch_nodeinfo_part_2),
|
||||
|
@ -291,6 +272,7 @@ def despatch(env):
|
|||
]:
|
||||
|
||||
match = re.match(regex, uri)
|
||||
print(regex, uri, match)
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
|
@ -303,9 +285,7 @@ def despatch(env):
|
|||
result = format_message_for_http(**result)
|
||||
return result
|
||||
|
||||
return format_message_for_http(
|
||||
status = 404,
|
||||
)
|
||||
return ERROR_404
|
||||
|
||||
class KepiHandler(FcgiHandler):
|
||||
|
||||
|
|
|
@ -19,10 +19,6 @@ 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
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# 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')
|
||||
|
||||
|
@ -26,8 +25,6 @@ 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)
|
||||
|
@ -40,6 +37,7 @@ def test_fastcgi_simple():
|
|||
},
|
||||
)
|
||||
assert found['Status'].startswith('404 ')
|
||||
assert found['Content-Type']==HTML_MIMETYPE
|
||||
|
||||
def test_fastcgi_host_meta():
|
||||
|
||||
|
@ -102,17 +100,24 @@ def test_fastcgi_nodeinfo_part_2():
|
|||
assert 'activitypub' in response['protocols']
|
||||
|
||||
"""
|
||||
class _TestUsers:
|
||||
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'))
|
||||
|
||||
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__()
|
||||
create_local_person(
|
||||
name='alice',
|
||||
publicKey=keys['public'],
|
||||
privateKey=keys['private'],
|
||||
)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
return self.tempd.__exit__(exc_type, exc_value, exc_tb)
|
||||
self._alice_keys = keys
|
||||
|
||||
settings.ALLOWED_HOSTS = [
|
||||
'altair.example.com',
|
||||
'testserver',
|
||||
]
|
||||
|
||||
settings.KEPI['LOCAL_OBJECT_HOSTNAME'] = 'testserver'
|
||||
"""
|
||||
|
||||
def test_fastcgi_webfinger():
|
||||
|
@ -136,16 +141,6 @@ 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,
|
||||
|
@ -167,7 +162,7 @@ def test_fastcgi_webfinger():
|
|||
)
|
||||
|
||||
response = webfinger(
|
||||
who = 'sheila@wombles.example.org',
|
||||
who = 'alice@wombles.example.org',
|
||||
expected_status = 200,
|
||||
)
|
||||
|
||||
|
@ -177,23 +172,12 @@ def test_fastcgi_webfinger():
|
|||
'ACAO is *, per RFC'
|
||||
)
|
||||
|
||||
parsed = json.loads(response.get_payload())
|
||||
parsed = json.loads(response.content)
|
||||
|
||||
assert parsed['subject']=='acct:sheila@wombles.example.org'
|
||||
assert 'https://wombles.example.org/users/sheila' in parsed['aliases']
|
||||
assert parsed['subject']=='acct:alice@testserver'
|
||||
assert 'https://testserver/users/alice' in parsed['aliases']
|
||||
assert {
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': 'https://wombles.example.org/users/sheila',
|
||||
'href': 'https://testserver/users/alice',
|
||||
} 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'
|
||||
|
|
Ładowanie…
Reference in New Issue