Porównaj commity

...

3 Commity

Autor SHA1 Wiadomość Data
Marnanel Thurman 963a8b2234 basic kepi.users
Tests added.
2023-10-05 19:24:49 +01:00
Marnanel Thurman 7f675c7b00 Key pairs for testing. 2023-10-05 19:24:20 +01:00
Marnanel Thurman 6817216d52 Doing argparse with subcommands, and not daemonising by default
This is a sketch; let's see if it's appropriate
2023-10-03 15:53:49 +01:00
11 zmienionych plików z 368 dodań i 30 usunięć

Wyświetl plik

@ -1,12 +1,16 @@
from kepi.daemon import Daemon
import sys
import os
import argparse
import logging
import json
import kepi.validate
logger = logging.getLogger('kepi')
logging.basicConfig(
level = logging.INFO,
stream = sys.stdout,
)
def daemonise(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
@ -36,47 +40,48 @@ def daemonise(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
logger.info("Running at PID %s", os.getpid())
def get_config():
def load_message(name):
if name=='-':
f = sys.stdin
else:
f = open(name, 'r')
result = json.load(f)
if f!=sys.stdin:
f.close()
return result
def main():
parser = argparse.ArgumentParser(
description='send or receive ActivityPub messages')
parser.add_argument(
'--incoming', '-I', action='store_true',
help='read an incoming message (rather than a message to send)')
parser.add_argument(
'--fifo', '-F', default='/var/run/kepi/kepi.fifo',
help='filename for control pipe')
parser.add_argument(
'--pidfile', '-P', default='/var/run/kepi/kepi.pid',
help='filename for process ID')
parser.add_argument(
'--spool', '-S', default='/var/spool/kepi',
help='directory to store the messages')
parser.add_argument(
subparsers = parser.add_subparsers(
dest = 'command',
required = True,
)
validate_parser = subparsers.add_parser('validate')
validate_parser.add_argument(
'input',
help=(
'the file to read ("-" for stdin)'
),
)
args = parser.parse_args()
if args.input=='-':
f = sys.stdin
if args.command=='validate':
message = load_message(args.input)
kepi.validate.validate(message)
else:
f = open(args.input, 'r')
result = dict(args._get_kwargs())
result['message'] = json.load(f)
return result
def main():
config = get_config()
daemon = Daemon(
config = config,
)
logger.info("Process ended normally.")
raise NotImplementedError()
if __name__=='__main__':
main()

107
kepi/users.py 100644
Wyświetl plik

@ -0,0 +1,107 @@
import os
import glob
import json
import copy
JSON_EXT = '.json'
TEMP_EXT = '.1'
PRIVATE_KEY_FIELD = 'private-key'
class User:
def __init__(self,
filename):
self.filename = filename
self.details = None
self.private_key = None
@property
def name(self):
return os.path.splitext(os.path.basename(self.filename))[0]
def _load_details(self):
if self.details is not None:
return
with open(self.filename, 'r') as f:
self.details = json.load(f)
def _save_details(self):
if self.details is not None:
return
with open(self.filename+TEMP_EXT, 'w') as f:
json.dump(self.details, f)
os.path.move(
self.filename+TEMP_EXT,
self.filename,
)
def __getitem__(self, name):
if name==PRIVATE_KEY_FIELD:
if self.private_key is None:
username = os.path.splitext(
os.path.basename(self.filename))[1]
with open(os.path.join(
os.path.basename(self.filename),
'private',
'username',
), 'r') as f:
self.private_key = f.read()
return self.private_key
self._load_details()
return self.details[name]
def __setitem__(self, name, value):
if name==PRIVATE_KEY_FIELD:
raise KeyError(f"You can't set {PRIVATE_KEY_FIELD}")
self._load_details()
self.details[name] = value
self._save_details()
def as_dict(self):
self._load_details()
result = copy.copy(self.details)
return result
class Users:
def __init__(self,
users_dir,
):
self.users_dir = users_dir
def __getitem__(self, name):
if '/' in name or '\\' in name:
raise ValueError("Forbidden characters in name.")
filename = os.path.join(
self.users_dir,
name,
) + JSON_EXT
try:
os.stat(filename)
except FileNotFoundError:
raise KeyError()
return User(filename)
def __iter__(self):
for someone in glob.glob(os.path.join(
self.users_dir,
'*'+JSON_EXT,
)):
name = os.path.splitext(
os.path.basename(someone)
)[0]
yield self[name]

Wyświetl plik

@ -16,6 +16,10 @@ def validate(
Validates a message.
"""
message = dict([
(k.lower(),v) for k,v in message.items()
])
if 'body' not in message:
raise ValueError("message must contain a body")

Wyświetl plik

@ -0,0 +1 @@
These are public/private key pairs used in testing.

Wyświetl plik

@ -0,0 +1,48 @@
{"@context":["https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers",
"sensitive":"as:sensitive",
"movedTo":{"@id":"as:movedTo",
"@type":"@id"},
"alsoKnownAs":{"@id":"as:alsoKnownAs",
"@type":"@id"},
"Hashtag":"as:Hashtag",
"ostatus":"http://ostatus.org#",
"atomUri":"ostatus:atomUri",
"inReplyToAtomUri":"ostatus:inReplyToAtomUri",
"conversation":"ostatus:conversation",
"toot":"http://joinmastodon.org/ns#",
"Emoji":"toot:Emoji",
"focalPoint":{"@container":"@list",
"@id":"toot:focalPoint"},
"featured":{"@id":"toot:featured",
"@type":"@id"},
"schema":"http://schema.org#",
"PropertyValue":"schema:PropertyValue",
"value":"schema:value"}],
"id":"https://local.example.org/users/fred",
"type":"Person",
"following":"https://local.example.org/users/fred/following",
"followers":"https://local.example.org/users/fred/followers",
"inbox":"https://local.example.org/users/fred/inbox",
"outbox":"https://local.example.org/users/fred/outbox",
"featured":"https://local.example.org/users/fred/collections/featured",
"preferredUsername":"fred",
"name":"",
"summary":"I am not a basset hound.",
"url":"https://local.example.org/@fred",
"manuallyApprovesFollowers":false,
"publicKey":{"id":"https://local.example.org/users/fred#main-key",
"owner":"https://local.example.org/users/fred",
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJtAZt9hgB/7bNozWtv9Er+Pnf\num97oM8cxRlqRqUXZk0wuST9A0eY5EUsN8j3qc6msZjDPSDQELr/U/o+zJLp/B8s\n7x3iHAHGD4LcQ9AbyDqbhX9JZkmwGx6PIVmbMDANmppqLik36V7cov6BuHz1gFpD\nP+iPjem4mph/KLwugQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag":[],
"attachment":[],
"endpoints":{"sharedInbox":"https://local.example.org/inbox"},
"icon":{"type":"Image",
"mediaType":"image/png",
"url":"https://body.local.example.org/media/accounts/avatars/000/015/322/original/data.png"},
"image":{"type":"Image",
"mediaType":"image/png",
"url":"https://body.local.example.org/media/accounts/headers/000/015/322/original/data.png"}
}

Wyświetl plik

@ -0,0 +1,48 @@
{"@context":["https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers",
"sensitive":"as:sensitive",
"movedTo":{"@id":"as:movedTo",
"@type":"@id"},
"alsoKnownAs":{"@id":"as:alsoKnownAs",
"@type":"@id"},
"Hashtag":"as:Hashtag",
"ostatus":"http://ostatus.org#",
"atomUri":"ostatus:atomUri",
"inReplyToAtomUri":"ostatus:inReplyToAtomUri",
"conversation":"ostatus:conversation",
"toot":"http://joinmastodon.org/ns#",
"Emoji":"toot:Emoji",
"focalPoint":{"@container":"@list",
"@id":"toot:focalPoint"},
"featured":{"@id":"toot:featured",
"@type":"@id"},
"schema":"http://schema.org#",
"PropertyValue":"schema:PropertyValue",
"value":"schema:value"}],
"id":"https://local.example.org/users/jim",
"type":"Person",
"following":"https://local.example.org/users/jim/following",
"followers":"https://local.example.org/users/jim/followers",
"inbox":"https://local.example.org/users/jim/inbox",
"outbox":"https://local.example.org/users/jim/outbox",
"featured":"https://local.example.org/users/jim/collections/featured",
"preferredUsername":"jim",
"name":"",
"summary":"His friends were very good to him.",
"url":"https://local.example.org/@jim",
"manuallyApprovesFollowers":false,
"publicKey":{"id":"https://local.example.org/users/jim#main-key",
"owner":"https://local.example.org/users/jim",
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPPJ6uNXzpocC9Z1rue3sgZl/W\nnYHjbtkfQCUpdV9lgmtbOgpZrQos5sIB5QxUx+yRAXmdSRsD2q1Kaeeew5T+pv3h\nJKH4XMNZd2mZf1KAuHjPFBjCRGMUwdEEozSy8ZpDAg+jQ2ro8E7wgZ+wsYatSLbQ\n9SIkceGWqxyhabyIqwIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag":[],
"attachment":[],
"endpoints":{"sharedInbox":"https://local.example.org/inbox"},
"icon":{"type":"Image",
"mediaType":"image/png",
"url":"https://body.local.example.org/media/accounts/avatars/000/015/322/original/data.png"},
"image":{"type":"Image",
"mediaType":"image/png",
"url":"https://body.local.example.org/media/accounts/headers/000/015/322/original/data.png"}
}

Wyświetl plik

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDJtAZt9hgB/7bNozWtv9Er+Pnfum97oM8cxRlqRqUXZk0wuST9
A0eY5EUsN8j3qc6msZjDPSDQELr/U/o+zJLp/B8s7x3iHAHGD4LcQ9AbyDqbhX9J
ZkmwGx6PIVmbMDANmppqLik36V7cov6BuHz1gFpDP+iPjem4mph/KLwugQIDAQAB
AoGBAI6sRaQAYCkBzTeWC8E0HmwhN/Z2NKdZL0clb/3JrLtphI5DWBOT/0/5n6hQ
aVouBdvJYcowcgZa3zr+FtPW9s9EKswd4M6VEg7Kb7yvd7iD+6Hl/KY5YkpRutJF
ZVBt20iJi3xi+5D0BvMImD3nE/Zl2MgAJWIBlRywfDOuKS7BAkEA4hIoq0RAbSgf
VBzzWucpZa4o2Ll35tG9X5zQSmEjWuuIxipsiwcuyHURTEG+45TU/AasyDTvqqm6
HhG4Z9SpaQJBAORoAzTfuF6Z7I3THSDLQjqWiE10K0qSkrcPf7fMlTdzheQdhS5T
A4liwuAkpoRKxpvBw+OvCkgaqr34+Oqo4VkCQQC19kPBxofM1HSS8VJ3IoTRkOLT
vkTiBoPUx5VnqNQaRGasik0fgkKHmqK3rFuHNq5PxNehteoKhd6GgWDaQfOxAkEA
1KV9rsFGrlSR5qyRJtH11AQH3Ex2bZQuod39I0qF9b1I/0r4jltdJJBdLD8TBIF1
jNeGH7j8Uor5QarFW/tk6QJBAMEYab7c1YTagny4nrKoddPfNyjUF2a7HNHTnCQn
rbOt963SPr1/dv602FwOzKHAWVw401PfaHWkclEROZTwwEc=
-----END RSA PRIVATE KEY-----

Wyświetl plik

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDPPJ6uNXzpocC9Z1rue3sgZl/WnYHjbtkfQCUpdV9lgmtbOgpZ
rQos5sIB5QxUx+yRAXmdSRsD2q1Kaeeew5T+pv3hJKH4XMNZd2mZf1KAuHjPFBjC
RGMUwdEEozSy8ZpDAg+jQ2ro8E7wgZ+wsYatSLbQ9SIkceGWqxyhabyIqwIDAQAB
AoGBAMMfZqDMh+JKhHlROVLWPOYSviYKg2Oq2RANi2/vrXScSYzJpzksLip80yqJ
iQTCgME/TEyFqsQEP6mS8ZyQtlUjoz8j6q/9TFvQrWWHRNgiD9bdXFQr0+F9kxr9
hdm1h7F5Q0JTGCPwBmL8GXCO8KmbZUMejITNqsIxx3bvzUoRAkEA1Eul1Mwkb/6C
cBuG0TPfXwluWdzS37XY+ckuQqgA5Jhj+vQTWbGovA/99W5GHHWsHJwGwcqqJJet
c4slKN3GBwJBAPnmXpAO1NiQMlIVohTfjCusypsSiHJXocigJG/DeEuMIHUJebuH
r6M7D2ENk7xh/kr3qA6369WokYT+K5Bqnz0CQQDPvApUVUIeeNwoWTcuBOVBeNgL
hOKv16Cuo6bpwL3G8jt7OFSrAwZKqBdojvR6Ksc045RVEzw0PFuU4YaGG6UHAkBZ
Q9LvfnzFRuzSqWuWLSwyxawxrHMU9PyTX7DkQ1yLD+jgJZxYQmWY1xXtQx5Momxl
dwWPDF+vmGEysl/5XDy5AkEAwbbjj3YstPYPnBsAbI0XYHfXxu7aZ+bwPNtH48r3
3YPAqM+HO0i2ffHjMJCYfbttWiRJRFVHyp3QO/Wtww4DQQ==
-----END RSA PRIVATE KEY-----

Wyświetl plik

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCsGqRZbZV2nljTOW2b77fzpkx9+iqcNgEnlhdgSIjCPvhzwj6c
b0O5RVqPu6krvv+Dgiy89Mb0nregdOstUXzn40Yicg1OOHMitrpAQ+4MsotqspfF
ZF/Q9qSFom3PxDA55lIHYJJmusVM6bSlrdY8msAsL1BieW7065gtjzv6RQIDAQAB
AoGACURx/yLIfpeuPsmD3na9GBCnY805yCmcTE5nudaODq+nX0xhZLkVE3/pjX3U
cTeauLEkyZQAtqFpT+mb1Ffj+t3exqK68k7UwCUI23Gtbr5dRUOivWuN75Sf4xFo
7vQeSwIot/1PyU7JYXZ9Tq9WMBHcFocCdxu85QSBS40cPIECQQC60bIlj+bN7fvU
HoPyQbj1vfjbz6vcoeR6v+YGCiSFrzawOEuJ1xUC7c/uivgJUvKumhrOFfwLac+1
Zay2t7khAkEA69X4ft06mfVNo1oM8ly33CO5TKlpFlVPBxEdKMHyZ2ZO1vIK6DsF
N67YwYI9OoncTvBucZ+Dy2GU0xUeqtSopQJANhtnsjNUUI49onjYFEDutdW4jsk9
6F/HEbokf9lOLJ3LhAw57Ikrn7aKw3biUakBeopNeySo5BFYRBxXgnABoQJBANlf
MVoNo1QAy/zCpahGWZlovASzKY9SNjM3TP8iNMGlhQmNswv2SorWeCd0Wec45n1E
EyhbdOjjGn+sucWPmZkCQD8ekBtzToRvNZjocFJhZdcWMIAo96XsnTAxREKSmbnG
pFHXBWokjDO/rTRbqANocLr0GwFR8UBD70CJLLOCaEg=
-----END RSA PRIVATE KEY-----

Wyświetl plik

@ -0,0 +1,47 @@
{"@context":["https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers",
"sensitive":"as:sensitive",
"movedTo":{"@id":"as:movedTo",
"@type":"@id"},
"alsoKnownAs":{"@id":"as:alsoKnownAs",
"@type":"@id"},
"Hashtag":"as:Hashtag",
"ostatus":"http://ostatus.org#",
"atomUri":"ostatus:atomUri",
"inReplyToAtomUri":"ostatus:inReplyToAtomUri",
"conversation":"ostatus:conversation",
"toot":"http://joinmastodon.org/ns#",
"Emoji":"toot:Emoji",
"focalPoint":{"@container":"@list",
"@id":"toot:focalPoint"},
"featured":{"@id":"toot:featured",
"@type":"@id"},
"schema":"http://schema.org#",
"PropertyValue":"schema:PropertyValue",
"value":"schema:value"}],
"id":"https://local.example.org/users/sheila",
"type":"Person",
"following":"https://local.example.org/users/sheila/following",
"followers":"https://local.example.org/users/sheila/followers",
"inbox":"https://local.example.org/users/sheila/inbox",
"outbox":"https://local.example.org/users/sheila/outbox",
"featured":"https://local.example.org/users/sheila/collections/featured",
"preferredUsername":"sheila",
"name":"",
"summary":"What do you think?",
"url":"https://local.example.org/@sheila",
"manuallyApprovesFollowers":false,
"publicKey":{"id":"https://local.example.org/users/sheila#main-key",
"owner":"https://local.example.org/users/sheila",
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsGqRZbZV2nljTOW2b77fzpkx9\n+iqcNgEnlhdgSIjCPvhzwj6cb0O5RVqPu6krvv+Dgiy89Mb0nregdOstUXzn40Yi\ncg1OOHMitrpAQ+4MsotqspfFZF/Q9qSFom3PxDA55lIHYJJmusVM6bSlrdY8msAs\nL1BieW7065gtjzv6RQIDAQAB\n-----END PUBLIC KEY-----\n"},
"tag":[],
"attachment":[],
"endpoints":{"sharedInbox":"https://local.example.org/inbox"},
"icon":{"type":"Image",
"mediaType":"image/png",
"url":"https://body.local.example.org/media/accounts/avatars/000/015/322/original/data.png"},
"image":{"type":"Image",
"mediaType":"image/png",
"url":"https://body.local.example.org/media/accounts/headers/000/015/322/original/data.png"}
}

33
test/test_users.py 100644
Wyświetl plik

@ -0,0 +1,33 @@
import os
from test import *
from kepi.users import Users
USERS_DIR = os.path.join(
os.path.dirname(__file__),
"example-users",
)
FRED_OUTBOX = 'https://local.example.org/users/fred/outbox'
def test_users_simple():
u = Users(users_dir = USERS_DIR)
fred = u['fred']
assert fred['summary']=='I am not a basset hound.'
def test_users_dir():
u = Users(users_dir = USERS_DIR)
assert sorted([user.name for user in u])==['fred', 'jim', 'sheila']
def test_users_dict():
u = Users(users_dir = USERS_DIR)
fred = u['fred']
assert fred['outbox'] == FRED_OUTBOX
fred_dict = fred.as_dict()
assert not isinstance(fred, dict)
assert isinstance(fred_dict, dict)
assert fred_dict['outbox'] == FRED_OUTBOX