From f8cc1873d49ef9841fb0855a4cc0754ce26dac79 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 18 Aug 2019 10:39:12 +0100 Subject: [PATCH] Fixing tests --- acceptreject.py | 16 +++-- daemon.py | 11 ++- follow.py | 9 +-- inbox.py | 21 ++++-- like.py | 2 +- posts.py | 57 +++++++++++++++- tests.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 272 insertions(+), 20 deletions(-) diff --git a/acceptreject.py b/acceptreject.py index 90c49eb7..fe25ec78 100644 --- a/acceptreject.py +++ b/acceptreject.py @@ -93,11 +93,17 @@ def acceptFollow(baseDir: str,domain : str,messageJson: {}, \ if debug: print('DEBUG: No "to" parameter in follow Accept') return - if len(messageJson['object']['to'])!=1: - if debug: - print('DEBUG: "to" does not contain a single recipient') - print(str(messageJson['object']['to'])) - return + #if len(messageJson['object']['to'])!=1: + # if debug: + # print('DEBUG: "to" does not contain a single recipient') + # print(str(messageJson['object']['to'])) + # if messageJson['object'].get('object'): + # if not isinstance(messageJson['object']['object'], str): + # messageJson['object']['to']=messageJson['object']['object'] + # else: + # return + # else: + # return if debug: print('DEBUG: follow Accept received') thisActor=messageJson['object']['actor'] diff --git a/daemon.py b/daemon.py index 1d6a12af..13cc8b4d 100644 --- a/daemon.py +++ b/daemon.py @@ -42,6 +42,7 @@ from posts import createUnlistedPost from posts import createFollowersOnlyPost from posts import createDirectMessagePost from posts import populateRepliesJson +from posts import addToField from inbox import inboxPermittedMessage from inbox import inboxMessageHasParams from inbox import runInboxQueue @@ -405,7 +406,12 @@ class PubServer(BaseHTTPRequestHandler): headersDict['digest']=self.headers['digest'] if self.headers.get('Content-type'): headersDict['Content-type']=self.headers['Content-type'] - + + # For follow activities add a 'to' field, which is a copy of the object field + messageJson,toFieldExists=addToField('Follow',messageJson,self.server.debug) + + pprint(messageJson) + # save the json for later queue processing queueFilename = \ savePostToInboxQueue(self.server.baseDir, @@ -2460,7 +2466,8 @@ class PubServer(BaseHTTPRequestHandler): if self.server.debug: print(followerNickname+' stops following '+followingActor) followActor=self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+followerNickname - followId=followActor+'#follows/'+followingNickname + statusNumber,published = getStatusNumber() + followId=followActor+'/statuses/'+str(statusNumber) unfollowJson = { '@context': 'https://www.w3.org/ns/activitystreams', 'id': followId+'/undo', diff --git a/follow.py b/follow.py index f8bccd3d..9e0097e3 100644 --- a/follow.py +++ b/follow.py @@ -492,7 +492,7 @@ def sendFollowRequest(session,baseDir: str, \ newFollowJson = { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': followActor+'#follows/'+followNickname, + 'id': followActor+'/statuses/'+str(statusNumber), 'type': 'Follow', 'actor': followActor, 'object': followedId @@ -538,7 +538,7 @@ def sendFollowRequestViaServer(session,fromNickname: str,password: str, statusNumber,published = getStatusNumber() newFollowJson = { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': followActor+'#follows/'+followNickname, + 'id': followActor+'/statuses/'+str(statusNumber), 'type': 'Follow', 'actor': followActor, 'object': followedId @@ -612,14 +612,15 @@ def sendUnfollowRequestViaServer(session,fromNickname: str,password: str, followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname + statusNumber,published = getStatusNumber() unfollowJson = { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': followActor+'#follows/'+followNickname+'/undo', + 'id': followActor+'/statuses/'+str(statusNumber)+'/undo', 'type': 'Undo', 'actor': followActor, 'object': { - 'id': followActor+'#follows/'+followNickname, + 'id': followActor+'/statuses/'+str(statusNumber), 'type': 'Follow', 'actor': followActor, 'object': followedId diff --git a/inbox.py b/inbox.py index 9a7ccd63..31647ef7 100644 --- a/inbox.py +++ b/inbox.py @@ -166,7 +166,12 @@ def validPublishedDate(published) -> bool: return False return True -def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str,postJsonObject: {},messageBytes: str,httpHeaders: {},postPath: str,debug: bool) -> str: +def savePostToInboxQueue(baseDir: str,httpPrefix: str, \ + nickname: str, domain: str, \ + postJsonObject: {}, \ + messageBytes: str, \ + httpHeaders: {}, \ + postPath: str,debug: bool) -> str: """Saves the give json to the inbox queue for the person keyId specifies the actor sending the post """ @@ -183,7 +188,9 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str actor=postJsonObject['actor'] postNickname=getNicknameFromActor(postJsonObject['actor']) postDomain,postPort=getDomainFromActor(postJsonObject['actor']) - if isBlocked(baseDir,nickname,domain,postNickname,postDomain): + if isBlocked(baseDir,nickname,domain,postNickname,postDomain): + if debug: + print('DEBUG: post from '+postNickname+' blocked') return None if postPort: if postPort!=80 and postPort!=443: @@ -195,6 +202,8 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str if postJsonObject['object'].get('content'): if isinstance(postJsonObject['object']['content'], str): if isFiltered(baseDir,nickname,domain,postJsonObject['object']['content']): + if debug: + print('DEBUG: post was filtered out due to content') return None originalPostId=None if postJsonObject.get('id'): @@ -222,6 +231,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str,nickname: str, domain: str destination=baseDir+'/accounts/'+handle+'/inbox/'+postId.replace('/','#')+'.json' if os.path.isfile(destination): if debug: + print(destination) print('DEBUG: inbox item already exists') return None filename=inboxQueueDir+'/'+postId.replace('/','#')+'.json' @@ -480,8 +490,6 @@ def receiveUndoFollow(session,baseDir: str,httpPrefix: str, \ if debug: print('DEBUG: actors do not match') return False - if not messageJson['object'].get('to'): - messageJson['object']['to']=messageJson['object']['object'] nicknameFollower=getNicknameFromActor(messageJson['object']['actor']) domainFollower,portFollower=getDomainFromActor(messageJson['object']['actor']) @@ -1342,7 +1350,7 @@ def runInboxQueue(projectVersion: str, \ maxReplies,allowDeletion) else: if debug: - print('DEBUG: object capabilities check failed') + print('DEBUG: object capabilities check has failed') pprint(queueJson['post']) else: if not ocapAlways: @@ -1358,6 +1366,9 @@ def runInboxQueue(projectVersion: str, \ queueFilename,destination, \ maxReplies,allowDeletion) if debug: + pprint(queueJson['post']) + print('No capability list within post') + print('ocapAlways: '+str(ocapAlways)) print('DEBUG: object capabilities check failed') if debug: diff --git a/like.py b/like.py index fdc1f16a..2619df89 100644 --- a/like.py +++ b/like.py @@ -104,7 +104,7 @@ def updateLikesCollection(postFilename: str,objectUrl: str, actor: str,debug: bo postJsonObject['object']['likes']=likesJson else: if postJsonObject['object']['likes'].get('items'): - for likeItem in postJsonObject['likes']['items']: + for likeItem in postJsonObject['object']['likes']['items']: if likeItem.get('actor'): if likeItem['actor']==actor: return diff --git a/posts.py b/posts.py index 030f44cd..11463f5f 100644 --- a/posts.py +++ b/posts.py @@ -1245,6 +1245,46 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \ thr.start() return 0 +def addToField(activityType: str,postJsonObject: {},debug: bool) -> ({},bool): + """The Follow activity doesn't have a 'to' field and so one + needs to be added so that activity distribution happens in a consistent way + Returns true if a 'to' field exists or was added + """ + if postJsonObject.get('to'): + return postJsonObject,True + + if debug: + pprint(postJsonObject) + print('DEBUG: no "to" field when sending to named addresses 2') + + isSameType=False + toFieldAdded=False + if postJsonObject.get('object'): + if isinstance(postJsonObject['object'], str): + if postJsonObject.get('type'): + if postJsonObject['type']==activityType: + isSameType=True + if debug: + print('DEBUG: "to" field assigned to Follow') + postJsonObject['to']=[postJsonObject['object']] + toFieldAdded=True + elif isinstance(postJsonObject['object'], dict): + if postJsonObject['object'].get('type'): + if postJsonObject['object']['type']==activityType: + isSameType=True + if isinstance(postJsonObject['object']['object'], str): + if debug: + print('DEBUG: "to" field assigned to Follow') + postJsonObject['object']['to']=[postJsonObject['object']['object']] + postJsonObject['to']=[postJsonObject['object']['object']] + toFieldAdded=True + + if not isSameType: + return postJsonObject,True + if toFieldAdded: + return postJsonObject,True + return postJsonObject,False + def sendToNamedAddresses(session,baseDir: str, \ nickname: str, domain: str, port: int, \ httpPrefix: str,federationList: [], \ @@ -1261,16 +1301,29 @@ def sendToNamedAddresses(session,baseDir: str, \ return if isinstance(postJsonObject['object'], dict): if not postJsonObject['object'].get('to'): - return + if debug: + pprint(postJsonObject) + print('DEBUG: no "to" field when sending to named addresses') + if postJsonObject['object'].get('type'): + if postJsonObject['object']['type']=='Follow': + if isinstance(postJsonObject['object']['object'], str): + if debug: + print('DEBUG: "to" field assigned to Follow') + postJsonObject['object']['to']=[postJsonObject['object']['object']] + if not postJsonObject['object'].get('to'): + return recipientsObject=postJsonObject['object'] else: - if not postJsonObject.get('to'): + postJsonObject,fieldAdded=addToField('Follow',postJsonObject,debug) + if not fieldAdded: return recipientsObject=postJsonObject recipients=[] recipientType=['to','cc'] for rType in recipientType: + if not recipientsObject.get(rType): + continue for address in recipientsObject[rType]: if address.endswith('#Public'): continue diff --git a/tests.py b/tests.py index 99ffe556..c1923c84 100644 --- a/tests.py +++ b/tests.py @@ -484,7 +484,7 @@ def testPostMessageBetweenServers(): shutil.rmtree(aliceDir) shutil.rmtree(bobDir) -def testFollowBetweenServers(): +def testFollowBetweenServersOld(): print('Testing sending a follow request from one server to another') global testServerAliceRunning @@ -768,6 +768,180 @@ def testFollowBetweenServers(): os.chdir(baseDir) shutil.rmtree(baseDir+'/.tests') +def testFollowBetweenServers(): + print('Testing sending a follow request from one server to another') + + global testServerAliceRunning + global testServerBobRunning + global testServerEveRunning + testServerAliceRunning = False + testServerBobRunning = False + testServerEveRunning = False + + httpPrefix='http' + useTor=False + federationList=[] + + baseDir=os.getcwd() + if os.path.isdir(baseDir+'/.tests'): + shutil.rmtree(baseDir+'/.tests') + os.mkdir(baseDir+'/.tests') + + ocapAlways=False + + # create the servers + aliceDir=baseDir+'/.tests/alice' + aliceDomain='127.0.0.42' + alicePort=61935 + thrAlice = \ + threadWithTrace(target=createServerAlice, \ + args=(aliceDir,aliceDomain,alicePort, \ + federationList,False,False, \ + ocapAlways),daemon=True) + + bobDir=baseDir+'/.tests/bob' + bobDomain='127.0.0.64' + bobPort=61936 + thrBob = \ + threadWithTrace(target=createServerBob, \ + args=(bobDir,bobDomain,bobPort, \ + federationList,False,False, \ + ocapAlways),daemon=True) + + eveDir=baseDir+'/.tests/eve' + eveDomain='127.0.0.55' + evePort=61937 + thrEve = \ + threadWithTrace(target=createServerEve, \ + args=(eveDir,eveDomain,evePort, \ + federationList,False,False, \ + False),daemon=True) + + thrAlice.start() + thrBob.start() + thrEve.start() + assert thrAlice.isAlive()==True + assert thrBob.isAlive()==True + assert thrEve.isAlive()==True + + # wait for all servers to be running + ctr=0 + while not (testServerAliceRunning and testServerBobRunning and testServerEveRunning): + time.sleep(1) + ctr+=1 + if ctr>60: + break + print('Alice online: '+str(testServerAliceRunning)) + print('Bob online: '+str(testServerBobRunning)) + print('Eve online: '+str(testServerEveRunning)) + assert ctr<=60 + time.sleep(1) + + # In the beginning all was calm and there were no follows + + print('*********************************************************') + print('Alice sends a follow request to Bob') + print('Both are strictly enforcing object capabilities') + os.chdir(aliceDir) + sessionAlice = createSession(aliceDomain,alicePort,useTor) + inReplyTo=None + inReplyToAtomUri=None + subject=None + aliceSendThreads = [] + alicePostLog = [] + followersOnly=False + saveToFile=True + clientToServer=False + ccUrl=None + alicePersonCache={} + aliceCachedWebfingers={} + aliceSendThreads=[] + alicePostLog=[] + sendResult = \ + sendFollowRequest(sessionAlice,aliceDir, \ + 'alice',aliceDomain,alicePort,httpPrefix, \ + 'bob',bobDomain,bobPort,httpPrefix, \ + clientToServer,federationList, \ + aliceSendThreads,alicePostLog, \ + aliceCachedWebfingers,alicePersonCache, \ + True,__version__) + print('sendResult: '+str(sendResult)) + + bobCapsFilename=bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json' + aliceCapsFilename=aliceDir+'/accounts/alice@'+aliceDomain+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json' + + for t in range(10): + if os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt'): + if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt'): + if os.path.isfile(bobCapsFilename): + if os.path.isfile(aliceCapsFilename): + break + time.sleep(1) + + with open(bobCapsFilename, 'r') as fp: + bobCapsJson=commentjson.load(fp) + if not bobCapsJson.get('capability'): + print("Unexpected format for Bob's capabilities") + pprint(bobCapsJson) + assert False + assert validInbox(bobDir,'bob',bobDomain) + assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort) + + print('\n\n*********************************************************') + print('Alice sends a message to Bob') + aliceSendThreads = [] + alicePostLog = [] + alicePersonCache={} + aliceCachedWebfingers={} + aliceSendThreads=[] + alicePostLog=[] + useBlurhash=False + sendResult = \ + sendPost(__version__, \ + sessionAlice,aliceDir,'alice', aliceDomain, alicePort, \ + 'bob', bobDomain, bobPort, ccUrl, \ + httpPrefix, 'Alice message', followersOnly, saveToFile, \ + clientToServer,None,None,useBlurhash, federationList, \ + aliceSendThreads, alicePostLog, aliceCachedWebfingers, \ + alicePersonCache,inReplyTo, inReplyToAtomUri, subject) + print('sendResult: '+str(sendResult)) + + queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue' + inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox' + aliceMessageArrived=False + for i in range(20): + time.sleep(1) + if os.path.isdir(inboxPath): + if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>0: + aliceMessageArrived=True + print('Alice message sent to Bob!') + break + + assert aliceMessageArrived==True + print('Message from Alice to Bob succeeded') + + # stop the servers + thrAlice.kill() + thrAlice.join() + assert thrAlice.isAlive()==False + + thrBob.kill() + thrBob.join() + assert thrBob.isAlive()==False + + thrEve.kill() + thrEve.join() + assert thrEve.isAlive()==False + + assert 'alice@'+aliceDomain in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read() + assert 'bob@'+bobDomain in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read() + + # queue item removed + assert len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))])==0 + + os.chdir(baseDir) + shutil.rmtree(baseDir+'/.tests') + def testFollowersOfPerson(): print('testFollowersOfPerson') currDir=os.getcwd()