From eeb74f6e16406d83867de054c5e2a77e72b021c4 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 30 Jun 2019 23:56:37 +0100 Subject: [PATCH] baseDir as parameter --- daemon.py | 12 ++++++------ epicyon.py | 13 ++++++++----- inbox.py | 3 +-- person.py | 15 ++++++-------- posts.py | 55 +++++++++++++++++++++++++--------------------------- tests.py | 39 ++++++++++++++++++++++++++++--------- webfinger.py | 13 +++++++++---- 7 files changed, 86 insertions(+), 64 deletions(-) diff --git a/daemon.py b/daemon.py index b87a0dce..2c71c206 100644 --- a/daemon.py +++ b/daemon.py @@ -69,7 +69,7 @@ class PubServer(BaseHTTPRequestHandler): self.wfile.write(wfResult.encode('utf-8')) return - wfResult=webfingerLookup(self.path) + wfResult=webfingerLookup(self.path,self.server.baseDir) if wfResult: self._set_headers('application/json') self.wfile.write(json.dumps(wfResult).encode('utf-8')) @@ -103,7 +103,7 @@ class PubServer(BaseHTTPRequestHandler): self.GETbusy=False return # get outbox feed for a person - outboxFeed=personOutboxJson(self.server.domain,self.server.port,self.path,self.server.https,maxPostsInFeed) + outboxFeed=personOutboxJson(self.server.baseDir,self.server.domain,self.server.port,self.path,self.server.https,maxPostsInFeed) if outboxFeed: self._set_headers('application/json') self.wfile.write(json.dumps(outboxFeed).encode('utf-8')) @@ -122,13 +122,13 @@ class PubServer(BaseHTTPRequestHandler): self.GETbusy=False return # look up a person - getPerson = personLookup(self.server.domain,self.path) + getPerson = personLookup(self.server.domain,self.path,self.server.baseDir) if getPerson: self._set_headers('application/json') self.wfile.write(json.dumps(getPerson).encode('utf-8')) self.GETbusy=False return - personKey = personKeyLookup(self.server.domain,self.path) + personKey = personKeyLookup(self.server.domain,self.path,self.server.baseDir) if personKey: self._set_headers('text/html; charset=utf-8') self.wfile.write(personKey.encode('utf-8')) @@ -140,8 +140,7 @@ class PubServer(BaseHTTPRequestHandler): self.GETbusy=False return # check that the file exists - baseDir=os.getcwd() - filename=baseDir+self.path + filename=self.server.baseDir+self.path if os.path.isfile(filename): self._set_headers('application/json') with open(filename, 'r', encoding='utf8') as File: @@ -208,5 +207,6 @@ def runDaemon(domain: str,port=80,https=True,fedList=[],useTor=False) -> None: httpd.port=port httpd.https=https httpd.federationList=fedList.copy() + httpd.baseDir=os.getcwd() print('Running ActivityPub daemon on ' + domain + ' port ' + str(port)) httpd.serve_forever() diff --git a/epicyon.py b/epicyon.py index 2ba4c705..c343e9ef 100644 --- a/epicyon.py +++ b/epicyon.py @@ -19,6 +19,7 @@ from posts import sendPost from session import createSession from session import getJson import json +import os import sys import requests from pprint import pprint @@ -43,8 +44,10 @@ domain='127.0.0.1' port=6227 https=False useTor=False +baseDir=os.getcwd() session = createSession(useTor) + clearFollows(username,domain) followPerson(username,domain,'badger','wild.com',federationList) followPerson(username,domain,'squirrel','secret.com',federationList) @@ -70,17 +73,17 @@ followerOfPerson(username,domain,'giraffe','trees.com',federationList) #sys.exit() -privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(username,domain,port,https,True) +privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,username,domain,port,https,True) #deleteAllPosts(username,domain) setPreferredUsername(username,domain,'badger') setBio(username,domain,'Some personal info') #createPublicPost(username, domain, https, "G'day world!", False, True, None, None, 'Not suitable for Vogons') -#archivePosts(username,domain,4) -#outboxJson=createOutbox(username,domain,port,https,2,True,None) +#archivePosts(username,domain,baseDir,4) +#outboxJson=createOutbox(baseDir,username,domain,port,https,2,True,None) #pprint(outboxJson) -testPostMessageBetweenServers() -#runDaemon(domain,port,https,federationList,useTor) +#testPostMessageBetweenServers() +runDaemon(domain,port,https,federationList,useTor) #testHttpsig() sys.exit() diff --git a/inbox.py b/inbox.py index a4a88884..5a4cd2fd 100644 --- a/inbox.py +++ b/inbox.py @@ -53,7 +53,7 @@ def validPublishedDate(published): return False return True -def receiveMessage(message): +def receiveMessage(message,baseDir: str): if not message.get('type'): return if message['type']!='Create': @@ -76,7 +76,6 @@ def receiveMessage(message): domain='' messageId=message['id'].replace('/','_') handle=username.lower()+'@'+domain.lower() - baseDir=os.getcwd() if not os.path.isdir(baseDir+'/accounts/'+handle): os.mkdir(baseDir+'/accounts/'+handle) if not os.path.isdir(baseDir+'/accounts/'+handle+'/inbox'): diff --git a/person.py b/person.py index d3a9ed8e..ba61761b 100644 --- a/person.py +++ b/person.py @@ -21,7 +21,7 @@ def generateRSAKey() -> (str,str): publicKeyPem = key.publickey().exportKey("PEM").decode("utf-8") return privateKeyPem,publicKeyPem -def createPerson(username: str,domain: str,port: int,https: bool, saveToFile: bool) -> (str,str,{},{}): +def createPerson(baseDir: str,username: str,domain: str,port: int,https: bool, saveToFile: bool) -> (str,str,{},{}): """Returns the private key, public key, actor and webfinger endpoint """ prefix='https' @@ -31,7 +31,7 @@ def createPerson(username: str,domain: str,port: int,https: bool, saveToFile: bo privateKeyPem,publicKeyPem=generateRSAKey() webfingerEndpoint=createWebfingerEndpoint(username,domain,port,https,publicKeyPem) if saveToFile: - storeWebfingerEndpoint(username,domain,webfingerEndpoint) + storeWebfingerEndpoint(username,domain,baseDir,webfingerEndpoint) handle=username.lower()+'@'+domain.lower() if port!=80 and port!=443: @@ -79,7 +79,6 @@ def createPerson(username: str,domain: str,port: int,https: bool, saveToFile: bo if saveToFile: # save person to file - baseDir=os.getcwd() peopleSubdir='/accounts' if not os.path.isdir(baseDir+peopleSubdir): os.mkdir(baseDir+peopleSubdir) @@ -114,7 +113,7 @@ def validUsername(username): return False return True -def personKeyLookup(domain: str,path: str) -> str: +def personKeyLookup(domain: str,path: str,baseDir: str) -> str: """Lookup the public key of the person with a given username """ if not path.endswith('/main-key'): @@ -125,7 +124,6 @@ def personKeyLookup(domain: str,path: str) -> str: if not validUsername(username): return None handle=username.lower()+'@'+domain.lower() - baseDir=os.getcwd() filename=baseDir+'/accounts/'+handle.lower()+'.json' if not os.path.isfile(filename): return None @@ -137,7 +135,7 @@ def personKeyLookup(domain: str,path: str) -> str: return personJson['publicKey']['publicKeyPem'] return None -def personLookup(domain: str,path: str) -> {}: +def personLookup(domain: str,path: str,baseDir: str) -> {}: """Lookup the person for an given username """ notPersonLookup=['/inbox','/outbox','/outboxarchive','/followers','/following','/featured','.png','.jpg','.gif','.mpv','#main-key','/main-key'] @@ -154,7 +152,6 @@ def personLookup(domain: str,path: str) -> {}: if not validUsername(username): return None handle=username.lower()+'@'+domain.lower() - baseDir=os.getcwd() filename=baseDir+'/accounts/'+handle.lower()+'.json' if not os.path.isfile(filename): return None @@ -163,7 +160,7 @@ def personLookup(domain: str,path: str) -> {}: personJson=commentjson.load(fp) return personJson -def personOutboxJson(domain: str,port: int,path: str,https: bool,noOfItems: int) -> []: +def personOutboxJson(baseDir: str,domain: str,port: int,path: str,https: bool,noOfItems: int) -> []: """Obtain the outbox feed for the given person """ if not '/outbox' in path: @@ -197,7 +194,7 @@ def personOutboxJson(domain: str,port: int,path: str,https: bool,noOfItems: int) return None if not validUsername(username): return None - return createOutbox(username,domain,port,https,noOfItems,headerOnly,pageNumber) + return createOutbox(baseDir,username,domain,port,https,noOfItems,headerOnly,pageNumber) def setPreferredUsername(username: str, domain: str, preferredName: str) -> bool: if len(preferredName)>32: diff --git a/posts.py b/posts.py index b2510f97..f996683c 100644 --- a/posts.py +++ b/posts.py @@ -23,22 +23,16 @@ from pprint import pprint from random import randint from session import getJson from session import postJson +from webfinger import webfingerHandle try: from BeautifulSoup import BeautifulSoup except ImportError: from bs4 import BeautifulSoup -# Contains threads for posts being sent -sendThreads = [] - -# stores the results from recent post sending attempts -postLog = [] - -def getPersonKey(username: str,domain: str,keyType='public'): +def getPersonKey(username: str,domain: str,baseDir: str,keyType='public'): """Returns the public or private key of a person """ handle=username+'@'+domain - baseDir=os.getcwd() keyFilename=baseDir+'/keys/'+keyType+'/'+handle.lower()+'.key' if not os.path.isfile(keyFilename): return '' @@ -200,11 +194,10 @@ def getUserPosts(session,wfRequest,maxPosts,maxMentions,maxEmoji,maxAttachments, break return userPosts -def createOutboxDir(username: str,domain: str) -> str: +def createOutboxDir(username: str,domain: str,baseDir: str) -> str: """Create an outbox for a person and returns the feed filename and directory """ handle=username.lower()+'@'+domain.lower() - baseDir=os.getcwd() if not os.path.isdir(baseDir+'/accounts/'+handle): os.mkdir(baseDir+'/accounts/'+handle) outboxDir=baseDir+'/accounts/'+handle+'/outbox' @@ -212,11 +205,10 @@ def createOutboxDir(username: str,domain: str) -> str: os.mkdir(outboxDir) return outboxDir -def createOutboxArchive(username: str,domain: str) -> str: +def createOutboxArchive(username: str,domain: str,baseDir: str) -> str: """Creates an archive directory for outbox posts """ handle=username.lower()+'@'+domain.lower() - baseDir=os.getcwd() if not os.path.isdir(baseDir+'/accounts/'+handle): os.mkdir(baseDir+'/accounts/'+handle) outboxArchiveDir=baseDir+'/accounts/'+handle+'/outboxarchive' @@ -224,10 +216,10 @@ def createOutboxArchive(username: str,domain: str) -> str: os.mkdir(outboxArchiveDir) return outboxArchiveDir -def deleteAllPosts(username: str, domain: str) -> None: +def deleteAllPosts(username: str, domain: str,baseDir: str) -> None: """Deletes all posts for a person """ - outboxDir = createOutboxDir(username,domain) + outboxDir = createOutboxDir(username,domain,baseDir) for deleteFilename in os.listdir(outboxDir): filePath = os.path.join(outboxDir, deleteFilename) try: @@ -248,7 +240,7 @@ def getStatusNumber() -> (str,str): conversationDate=currTime.strftime("%Y-%m-%d") return statusNumber,published -def createPostBase(username: str, domain: str, toUrl: str, ccUrl: str, https: bool, content: str, followersOnly: bool, saveToFile: bool, inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}: +def createPostBase(baseDir: str,username: str, domain: str, toUrl: str, ccUrl: str, https: bool, content: str, followersOnly: bool, saveToFile: bool, inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}: """Creates a public post """ prefix='https' @@ -306,7 +298,7 @@ def createPostBase(username: str, domain: str, toUrl: str, ccUrl: str, https: bo } } if saveToFile: - outboxDir = createOutboxDir(username,domain) + outboxDir = createOutboxDir(username,domain,baseDir) filename=outboxDir+'/'+newPostId.replace('/','#')+'.json' with open(filename, 'w') as fp: commentjson.dump(newPost, fp, indent=4, sort_keys=False) @@ -320,7 +312,7 @@ def createPublicPost(username: str, domain: str, https: bool, content: str, foll prefix='http' return createPostBase(username, domain, 'https://www.w3.org/ns/activitystreams#Public', prefix+'://'+domain+'/users/'+username+'/followers', https, content, followersOnly, saveToFile, inReplyTo, inReplyToAtomUri, subject) -def threadSendPost(session,postJsonObject,federationList,inboxUrl: str,signatureHeader) -> None: +def threadSendPost(session,postJsonObject,federationList,inboxUrl: str,baseDir: str,signatureHeader,postLog) -> None: """Sends a post with exponential backoff """ tries=0 @@ -334,7 +326,6 @@ def threadSendPost(session,postJsonObject,federationList,inboxUrl: str,signature while len(postLog)>64: postlog.pop(0) # save the log file - baseDir=os.getcwd() filename=baseDir+'/post.log' with open(filename, "w") as logFile: for line in postLog: @@ -344,19 +335,24 @@ def threadSendPost(session,postJsonObject,federationList,inboxUrl: str,signature time.sleep(backoffTime) backoffTime *= 2 -def sendPost(session,username: str, domain: str, toUsername: str, toDomain: str, cc: str, https: bool, content: str, followersOnly: bool, saveToFile: bool, federationList, inReplyTo=None, inReplyToAtomUri=None, subject=None) -> int: +def sendPost(session,baseDir,username: str, domain: str, port: int, toUsername: str, toDomain: str, toPort: int, cc: str, https: bool, content: str, followersOnly: bool, saveToFile: bool, federationList, sendThreads, postLog, inReplyTo=None, inReplyToAtomUri=None, subject=None) -> int: """Post to another inbox """ prefix='https' if not https: prefix='http' - # lookup the inbox - handle=prefix+'://'+domain+'/@'+username - wfRequest = webfingerHandle(session,handle,True) + if toPort!=80 and toPort!=443: + toDomain=toDomain+':'+str(toPort) + + handle=prefix+'://'+toDomain+'/@'+toUsername + + # lookup the inbox for the To handle + wfRequest = webfingerHandle(session,handle,https) if not wfRequest: return 1 + # get the actor inbox for the To handle inboxUrl,pubKey,toPersonId = getPersonBox(session,wfRequest,'inbox') if not inboxUrl: return 2 @@ -367,7 +363,8 @@ def sendPost(session,username: str, domain: str, toUsername: str, toDomain: str, postJsonObject=createPostBase(username, domain, toPersonId, cc, https, content, followersOnly, saveToFile, inReplyTo, inReplyToAtomUri, subject) - privateKeyPem=getPersonKey(username,domain,'private') + # get the senders private key + privateKeyPem=getPersonKey(username,domain,baseDir,'private') if len(privateKeyPem)==0: return 5 @@ -379,19 +376,19 @@ def sendPost(session,username: str, domain: str, toUsername: str, toDomain: str, while len(sendThreads)>10: sendThreads[0].kill() sendThreads.pop(0) - thr = threadWithTrace(target=threadSendPost,args=(session,postJsonObject.copy(),federationList,inboxUrl,signatureHeader.copy()),daemon=True) + thr = threadWithTrace(target=threadSendPost,args=(session,postJsonObject.copy(),federationList,inboxUrl,baseDir,signatureHeader.copy(),postLog),daemon=True) sendThreads.append(thr) thr.start() return 0 -def createOutbox(username: str,domain: str,port: int,https: bool,itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}: +def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool,itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}: """Constructs the outbox feed """ prefix='https' if not https: prefix='http' - outboxDir = createOutboxDir(username,domain) + outboxDir = createOutboxDir(username,domain,baseDir) if port!=80 and port!=443: domain = domain+':'+str(port) @@ -482,12 +479,12 @@ def createOutbox(username: str,domain: str,port: int,https: bool,itemsPerPage: i return outboxHeader return outboxItems -def archivePosts(username: str,domain: str,maxPostsInOutbox=256) -> None: +def archivePosts(username: str,domain: str,baseDir: str,maxPostsInOutbox=256) -> None: """Retain a maximum number of posts within the outbox Move any others to an archive directory """ - outboxDir = createOutboxDir(username,domain) - archiveDir = createOutboxArchive(username,domain) + outboxDir = createOutboxDir(username,domain,baseDir) + archiveDir = createOutboxArchive(username,domain,baseDir) postsInOutbox=sorted(os.listdir(outboxDir), reverse=False) noOfPosts=len(postsInOutbox) if noOfPosts<=maxPostsInOutbox: diff --git a/tests.py b/tests.py index a1adc688..037ada56 100644 --- a/tests.py +++ b/tests.py @@ -22,6 +22,7 @@ from session import createSession from person import createPerson from posts import deleteAllPosts from posts import createPublicPost +from posts import sendPost from follow import followPerson from follow import followerOfPerson @@ -34,7 +35,8 @@ def testHttpsigBase(withDigest): domain='argumentative.social' https=True port=80 - privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(username,domain,port,https,False) + baseDir=os.getcwd() + privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,username,domain,port,https,False) messageBodyJson = '{"a key": "a value", "another key": "A string"}' if not withDigest: headers = {'host': domain} @@ -96,9 +98,8 @@ def createServerAlice(path: str,port: int): domain='127.0.0.1' https=False useTor=False - session = createSession(useTor) - privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(username,domain,port,https,True) - deleteAllPosts(username,domain) + privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,username,domain,port,https,True) + deleteAllPosts(username,domain,path) followPerson(username,domain,'bob','127.0.0.1:61936',federationList) followerOfPerson(username,domain,'bob','127.0.0.1:61936',federationList) createPublicPost(username, domain, https, "No wise fish would go anywhere without a porpoise", False, True) @@ -120,9 +121,8 @@ def createServerBob(path: str,port: int): domain='127.0.0.1' https=False useTor=False - session = createSession(useTor) - privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(username,domain,port,https,True) - deleteAllPosts(username,domain) + privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,username,domain,port,https,True) + deleteAllPosts(username,domain,path) followPerson(username,domain,'alice','127.0.0.1:61935',federationList) followerOfPerson(username,domain,'alice','127.0.0.1:61935',federationList) createPublicPost(username, domain, https, "It's your life, live it your way.", False, True) @@ -141,16 +141,24 @@ def testPostMessageBetweenServers(): testServerAliceRunning = False testServerBobRunning = False + https=False + useTor=False + federationList=['127.0.0.1'] + baseDir=os.getcwd() if not os.path.isdir(baseDir+'/.tests'): os.mkdir(baseDir+'/.tests') # create the servers - thrAlice = threadWithTrace(target=createServerAlice,args=(baseDir+'/.tests/alice',61935),daemon=True) + aliceDir=baseDir+'/.tests/alice' + alicePort=61935 + thrAlice = threadWithTrace(target=createServerAlice,args=(aliceDir,alicePort),daemon=True) thrAlice.start() assert thrAlice.isAlive()==True - thrBob = threadWithTrace(target=createServerBob,args=(baseDir+'/.tests/bob',61936),daemon=True) + bobDir=baseDir+'/.tests/bob' + bobPort=61936 + thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobPort),daemon=True) thrBob.start() assert thrBob.isAlive()==True @@ -160,6 +168,19 @@ def testPostMessageBetweenServers(): time.sleep(3) + print('Alice sends to Bob') + os.chdir(aliceDir) + sessionAlice = createSession(useTor) + inReplyTo=None + inReplyToAtomUri=None + subject=None + aliceSendThreads = [] + alicePostLog = [] + sendResult = sendPost(sessionAlice,aliceDir,'alice', '127.0.0.1', alicePort, 'bob', '127.0.0.1', bobPort, '', https, 'Why is a mouse when it spins?', False, True, federationList, aliceSendThreads, alicePostLog, inReplyTo, inReplyToAtomUri, subject) + print('sendResult: '+str(sendResult)) + + time.sleep(3) + # stop the servers thrAlice.kill() thrAlice.join() diff --git a/webfinger.py b/webfinger.py index 58cde6b6..52435f12 100644 --- a/webfinger.py +++ b/webfinger.py @@ -45,9 +45,13 @@ def webfingerHandle(session,handle: str,https: bool): if not https: prefix='http' url = '{}://{}/.well-known/webfinger'.format(prefix,domain) + if ':' in domain: + domain=domain.split(':')[0] par = {'resource': 'acct:{}'.format(username+'@'+domain)} hdr = {'Accept': 'application/jrd+json'} #try: + print("webfinger url = "+url) + print("webfinger par = "+str(par)) result = getJson(session, url, hdr, par) #except: # print("Unable to webfinger " + url) @@ -64,11 +68,10 @@ def generateMagicKey(publicKeyPem): pubexp = base64.urlsafe_b64encode(number.long_to_bytes(privkey.e)).decode("utf-8") return f"data:application/magic-public-key,RSA.{mod}.{pubexp}" -def storeWebfingerEndpoint(username: str,domain: str,wfJson) -> bool: +def storeWebfingerEndpoint(username: str,domain: str,baseDir: str,wfJson) -> bool: """Stores webfinger endpoint for a user to a file """ handle=username+'@'+domain - baseDir=os.getcwd() wfSubdir='/wfendpoints' if not os.path.isdir(baseDir+wfSubdir): os.mkdir(baseDir+wfSubdir) @@ -140,25 +143,27 @@ def webfingerMeta() -> str: " " \ "" -def webfingerLookup(path: str): +def webfingerLookup(path: str,baseDir: str): """Lookup the webfinger endpoint for an account """ if not path.startswith('/.well-known/webfinger?'): return None handle=None + print('************** '+path) if 'resource=acct:' in path: handle=path.split('resource=acct:')[1].strip() else: if 'resource=acct%3A' in path: handle=path.split('resource=acct%3A')[1].replace('%40','@').strip() + print('************** handle: '+handle) if not handle: return None if '&' in handle: handle=handle.split('&')[0].strip() if '@' not in handle: return None - baseDir=os.getcwd() filename=baseDir+'/wfendpoints/'+handle.lower()+'.json' + print('************** filename: '+filename) if not os.path.isfile(filename): return None wfJson={"user": "unknown"}