diff --git a/announce.py b/announce.py index d6adba39..e5539d71 100644 --- a/announce.py +++ b/announce.py @@ -14,6 +14,10 @@ from utils import urlPermitted from utils import getNicknameFromActor from utils import getDomainFromActor from posts import sendSignedJson +from posts import getPersonBox +from session import postJson +from webfinger import webfingerHandle +from auth import createBasicAuthHeader def createAnnounce(session,baseDir: str,federationList: [], \ nickname: str, domain: str, port: int, \ @@ -229,3 +233,78 @@ def undoRepeatPost(session,baseDir: str,federationList: [], \ sendThreads,postLog, \ personCache,cachedWebfingers, \ debug) + +def sendAnnounceViaServer(session,fromNickname: str,password: str, + fromDomain: str,fromPort: int, \ + httpPrefix: str,repeatObjectUrl: str, \ + cachedWebfingers: {},personCache: {}, \ + debug: bool) -> {}: + """Creates an announce message via c2s + """ + if not session: + print('WARN: No session for sendAnnounceViaServer') + return 6 + + withDigest=True + + fromDomainFull=fromDomain + if fromPort!=80 and fromPort!=443: + fromDomainFull=fromDomain+':'+str(fromPort) + + toUrl = 'https://www.w3.org/ns/activitystreams#Public' + ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' + + statusNumber,published = getStatusNumber() + newAnnounceId= \ + httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/statuses/'+statusNumber + newAnnounceJson = { + 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, + 'atomUri': newAnnounceId, + 'cc': [ccUrl], + 'id': newAnnounceId+'/activity', + 'object': repeatObjectUrl, + 'published': published, + 'to': [toUrl], + 'type': 'Announce' + } + + handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname + + # lookup the inbox for the To handle + wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers) + if not wfRequest: + if debug: + print('DEBUG: announce webfinger failed for '+handle) + return 1 + + postToBox='outbox' + + # get the actor inbox for the To handle + inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition = \ + getPersonBox(session,wfRequest,personCache,postToBox) + + if not inboxUrl: + if debug: + print('DEBUG: No '+postToBox+' was found for '+handle) + return 3 + if not fromPersonId: + if debug: + print('DEBUG: No actor was found for '+handle) + return 4 + + authHeader=createBasicAuthHeader(fromNickname,password) + + headers = {'host': fromDomain, \ + 'Content-type': 'application/json', \ + 'Authorization': authHeader} + postResult = \ + postJson(session,newAnnounceJson,[],inboxUrl,headers,"inbox:write") + #if not postResult: + # if debug: + # print('DEBUG: POST announce failed for c2s to '+inboxUrl) + # return 5 + + if debug: + print('DEBUG: c2s POST announce success') + + return newAnnounceJson diff --git a/daemon.py b/daemon.py index 11ebf854..f4253ab2 100644 --- a/daemon.py +++ b/daemon.py @@ -703,15 +703,9 @@ class PubServer(BaseHTTPRequestHandler): # https://www.w3.org/TR/activitypub/#object-without-create if self.outboxAuthenticated: if self._postToOutbox(messageJson): - if messageJson.get('object'): - #self.send_header('Location', \ + if messageJson.get('id'): self.headers['Location']= \ - messageJson['object']['id'].replace('/activity','') - else: - if messageJson.get('id'): - #self.send_header('Location', \ - self.headers['Location']= \ - messageJson['id'].replace('/activity','') + messageJson['id'].replace('/activity','') self.send_response(201) self.end_headers() self.server.POSTbusy=False diff --git a/epicyon.py b/epicyon.py index ff31eac6..502fd3ec 100644 --- a/epicyon.py +++ b/epicyon.py @@ -214,8 +214,8 @@ if args.tests: if args.testsnetwork: print('Network Tests') - testPostMessageBetweenServers() - testFollowBetweenServers() + #testPostMessageBetweenServers() + #testFollowBetweenServers() testClientToServer() sys.exit() diff --git a/posts.py b/posts.py index f5e9236c..b62f23c0 100644 --- a/posts.py +++ b/posts.py @@ -305,8 +305,9 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \ '/statuses/'+statusNumber postJsonObject['id']=postId+'/activity' if postJsonObject.get('object'): - postJsonObject['object']['id']=postId - postJsonObject['object']['atomUri']=postId + if isinstance(postJsonObject['object'], dict): + postJsonObject['object']['id']=postId + postJsonObject['object']['atomUri']=postId boxDir = createPersonDir(nickname,domain,baseDir,boxname) filename=boxDir+'/'+postId.replace('/','#')+'.json' @@ -493,19 +494,29 @@ def postIsAddressedToFollowers(baseDir: str, if not postJsonObject.get('object'): return False - if not postJsonObject['object'].get('to'): - return False + toList=[] + ccList=[] + if isinstance(postJsonObject['object'], dict): + if not postJsonObject['object'].get('to'): + return False + toList=postJsonObject['object']['to'] + if postJsonObject['object'].get('cc'): + ccList=postJsonObject['object']['cc'] + else: + if not postJsonObject.get('to'): + return False + toList=postJsonObject['to'] + if postJsonObject.get('cc'): + ccList=postJsonObject['cc'] followersUrl=httpPrefix+'://'+domain+'/users/'+nickname+'/followers' # does the followers url exist in 'to' or 'cc' lists? addressedToFollowers=False - if followersUrl in postJsonObject['object']['to']: + if followersUrl in toList: addressedToFollowers=True if not addressedToFollowers: - if not postJsonObject['object'].get('cc'): - return False - if followersUrl in postJsonObject['object']['cc']: + if followersUrl in ccList: addressedToFollowers=True return addressedToFollowers @@ -872,13 +883,22 @@ def sendToNamedAddresses(session,baseDir: str, \ return if not postJsonObject.get('object'): return - if not postJsonObject['object'].get('to'): - return + toList=[] + if isinstance(postJsonObject['object'], dict): + if not postJsonObject['object'].get('to'): + return + toList=postJsonObject['object']['to'] + recipientsObject=postJsonObject['object'] + else: + if not postJsonObject.get('to'): + return + toList=postJsonObject['to'] + recipientsObject=postJsonObject recipients=[] recipientType=['to','cc'] for rType in recipientType: - for address in postJsonObject['object'][rType]: + for address in recipientsObject[rType]: if address.endswith('#Public'): continue if address.endswith('/followers'): diff --git a/tests.py b/tests.py index 16c5efcd..7062f5cb 100644 --- a/tests.py +++ b/tests.py @@ -48,6 +48,7 @@ from auth import authorizeBasic from auth import storeBasicCredentials from like import likePost from announce import announcePublic +from announce import sendAnnounceViaServer from media import getMediaPath testServerAliceRunning = False @@ -1043,7 +1044,7 @@ def testClientToServer(): assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1 print(">>> c2s post arrived in Alice's outbox") - + for i in range(30): if os.path.isdir(inboxPath): if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1: @@ -1054,6 +1055,39 @@ def testClientToServer(): print(">>> s2s post arrived in Bob's inbox") print("c2s send success") + print('\n\nGetting message id for the post') + statusNumber=0 + outboxPostFilename=None + outboxPostId=None + for name in os.listdir(outboxPath): + if '#statuses#' in name: + statusNumber=int(name.split('#statuses#')[1].replace('.json','').replace('#activity','')) + outboxPostFilename=outboxPath+'/'+name + with open(outboxPostFilename, 'r') as fp: + postJsonObject=commentjson.load(fp) + outboxPostId=postJsonObject['id'].replace('/activity','') + assert outboxPostId + print('message id obtained: '+outboxPostId) + + print('\n\nBob repeats the post') + sessionBob = createSession(bobDomain,bobPort,useTor) + password='bobpass' + outboxPath=bobDir+'/accounts/bob@'+bobDomain+'/outbox' + assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==0 + sendAnnounceViaServer(sessionBob,'bob',password, + bobDomain,bobPort, \ + httpPrefix,outboxPostId, \ + cachedWebfingers,personCache, \ + True) + for i in range(10): + if os.path.isdir(outboxPath): + if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1: + break + time.sleep(1) + + assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1 + print('Post repeated') + # stop the servers thrAlice.kill() thrAlice.join()