Fixing c2s

master
Bob Mottram 2019-07-16 11:19:04 +01:00
parent 18629c88a8
commit a98facaf33
7 changed files with 244 additions and 35 deletions

View File

@ -124,15 +124,17 @@ class PubServer(BaseHTTPRequestHandler):
messageJson= \ messageJson= \
outboxMessageCreateWrap(self.server.httpPrefix, \ outboxMessageCreateWrap(self.server.httpPrefix, \
self.postToNickname, \ self.postToNickname, \
self.server.domain,messageJson) self.server.domain, \
self.server.port, \
messageJson)
if messageJson['type']=='Create': if messageJson['type']=='Create':
if not (messageJson.get('id') and \ if not (messageJson.get('id') and \
messageJson.get('type') and \ messageJson.get('type') and \
messageJson.get('actor') and \ messageJson.get('actor') and \
messageJson.get('object') and \ messageJson.get('object') and \
messageJson.get('atomUri') and \
messageJson.get('to')): messageJson.get('to')):
if self.server.debug: if self.server.debug:
pprint(messageJson)
print('DEBUG: POST to outbox - Create does not have the required parameters') print('DEBUG: POST to outbox - Create does not have the required parameters')
return False return False
# https://www.w3.org/TR/activitypub/#create-activity-outbox # https://www.w3.org/TR/activitypub/#create-activity-outbox
@ -150,23 +152,31 @@ class PubServer(BaseHTTPRequestHandler):
postId=messageJson['id'] postId=messageJson['id']
else: else:
postId=None postId=None
savePostToBox(self.server.baseDir,postId, \ if self.server.debug:
pprint(messageJson)
savePostToBox(self.server.baseDir, \
self.server.httpPrefix, \
postId, \
self.postToNickname, \ self.postToNickname, \
self.server.domain,messageJson,'outbox') self.server.domain,messageJson,'outbox')
if not self.server.session:
self.server.session= \
createSession(self.server.domain,self.server.port,self.server.useTor)
if self.server.debug: if self.server.debug:
print('DEBUG: sending c2s post to followers') print('DEBUG: sending c2s post to followers')
sendToFollowers(self.server.session,self.server.baseDir, \ sendToFollowers(self.server.session,self.server.baseDir, \
self.postToNickname,self.server.domain, \ self.postToNickname,self.server.domain, \
self.server.port, \ self.server.port, \
self.server.httpPrefix, \ self.server.httpPrefix, \
self.server.federationList, \ self.server.federationList, \
self.server.sendThreads, \ self.server.sendThreads, \
self.server.postLog, \ self.server.postLog, \
self.server.cachedWebfingers, \ self.server.cachedWebfingers, \
self.server.personCache, \ self.server.personCache, \
messageJson,self.server.debug) messageJson,self.server.debug)
if self.server.debug: if self.server.debug:
print('DEBUG: sending c2s post to named addresses') print('DEBUG: sending c2s post to named addresses')
print('c2s sender: '+self.postToNickname+'@'+self.server.domain+':'+str(self.server.port))
sendToNamedAddresses(self.server.session,self.server.baseDir, \ sendToNamedAddresses(self.server.session,self.server.baseDir, \
self.postToNickname,self.server.domain, \ self.postToNickname,self.server.domain, \
self.server.port, \ self.server.port, \
@ -580,7 +590,7 @@ class PubServer(BaseHTTPRequestHandler):
if '/users/' in self.path: if '/users/' in self.path:
if self._isAuthorized(): if self._isAuthorized():
self.outboxAuthenticated=True self.outboxAuthenticated=True
pathUsersSection=path.split('/users/')[1] pathUsersSection=self.path.split('/users/')[1]
self.postToNickname=pathUsersSection.split('/')[0] self.postToNickname=pathUsersSection.split('/')[0]
if not self.outboxAuthenticated: if not self.outboxAuthenticated:
self.send_response(405) self.send_response(405)
@ -618,8 +628,15 @@ class PubServer(BaseHTTPRequestHandler):
# https://www.w3.org/TR/activitypub/#object-without-create # https://www.w3.org/TR/activitypub/#object-without-create
if self.outboxAuthenticated: if self.outboxAuthenticated:
if self._postToOutbox(messageJson): if self._postToOutbox(messageJson):
self.send_header('Location', \ if messageJson.get('object'):
messageJson['object']['atomUri']) #self.send_header('Location', \
self.headers['Location']= \
messageJson['object']['id'].replace('/activity','')
else:
if messageJson.get('id'):
#self.send_header('Location', \
self.headers['Location']= \
messageJson['id'].replace('/activity','')
self.send_response(201) self.send_response(201)
self.end_headers() self.end_headers()
self.server.POSTbusy=False self.server.POSTbusy=False
@ -638,6 +655,7 @@ class PubServer(BaseHTTPRequestHandler):
self.path=='/sharedInbox': self.path=='/sharedInbox':
if not inboxMessageHasParams(messageJson): if not inboxMessageHasParams(messageJson):
if self.server.debug: if self.server.debug:
pprint(messageJson)
print("DEBUG: inbox message doesn't have the required parameters") print("DEBUG: inbox message doesn't have the required parameters")
self.send_response(403) self.send_response(403)
self.end_headers() self.end_headers()

View File

@ -52,6 +52,7 @@ from follow import unfollowerOfPerson
from follow import getFollowersOfPerson from follow import getFollowersOfPerson
from tests import testPostMessageBetweenServers from tests import testPostMessageBetweenServers
from tests import testFollowBetweenServers from tests import testFollowBetweenServers
from tests import testClientToServer
from tests import runAllTests from tests import runAllTests
from config import setConfigParam from config import setConfigParam
from config import getConfigParam from config import getConfigParam
@ -213,8 +214,9 @@ if args.tests:
if args.testsnetwork: if args.testsnetwork:
print('Network Tests') print('Network Tests')
testPostMessageBetweenServers() testClientToServer()
testFollowBetweenServers() #testPostMessageBetweenServers()
#testFollowBetweenServers()
sys.exit() sys.exit()
if args.posts: if args.posts:

View File

@ -64,9 +64,10 @@ def attachImage(baseDir: str,httpPrefix: str,domain: str,port: int, \
domain=domain+':'+str(port) domain=domain+':'+str(port)
mPath=getMediaPath() mPath=getMediaPath()
createMediaDirs(baseDir,mPath)
mediaPath=mPath+'/'+createPassword(32)+'.'+fileExtension mediaPath=mPath+'/'+createPassword(32)+'.'+fileExtension
mediaFilename=baseDir+'/'+mediaPath if baseDir:
createMediaDirs(baseDir,mPath)
mediaFilename=baseDir+'/'+mediaPath
attachmentJson={ attachmentJson={
'mediaType': mediaType, 'mediaType': mediaType,
@ -78,7 +79,8 @@ def attachImage(baseDir: str,httpPrefix: str,domain: str,port: int, \
attachmentJson['blurhash']=getImageHash(imageFilename) attachmentJson['blurhash']=getImageHash(imageFilename)
postJson['attachment']=[attachmentJson] postJson['attachment']=[attachmentJson]
copyfile(imageFilename,mediaFilename) if baseDir:
copyfile(imageFilename,mediaFilename)
return postJson return postJson

117
posts.py
View File

@ -38,6 +38,7 @@ from capabilities import getOcapFilename
from capabilities import capabilitiesUpdate from capabilities import capabilitiesUpdate
from media import attachImage from media import attachImage
from content import addMentions from content import addMentions
from auth import createBasicAuthHeader
try: try:
from BeautifulSoup import BeautifulSoup from BeautifulSoup import BeautifulSoup
except ImportError: except ImportError:
@ -318,9 +319,10 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}: inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Creates a message """Creates a message
""" """
# convert content to html if not clientToServer:
content=addMentions(baseDir,httpPrefix, \ # convert content to html
nickname,domain,content) content=addMentions(baseDir,httpPrefix, \
nickname,domain,content)
if port!=80 and port!=443: if port!=80 and port!=443:
domain=domain+':'+str(port) domain=domain+':'+str(port)
@ -444,12 +446,16 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
nickname,domain,newPost,'outbox') nickname,domain,newPost,'outbox')
return newPost return newPost
def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str, \ def outboxMessageCreateWrap(httpPrefix: str, \
nickname: str,domain: str,port: int, \
messageJson: {}) -> {}: messageJson: {}) -> {}:
"""Wraps a received message in a Create """Wraps a received message in a Create
https://www.w3.org/TR/activitypub/#object-without-create https://www.w3.org/TR/activitypub/#object-without-create
""" """
if port!=80 and port!=443:
if ':' not in domain:
domain=domain+':'+str(port)
statusNumber,published = getStatusNumber() statusNumber,published = getStatusNumber()
if messageJson.get('published'): if messageJson.get('published'):
published = messageJson['published'] published = messageJson['published']
@ -458,7 +464,7 @@ def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str, \
if messageJson.get('cc'): if messageJson.get('cc'):
cc=messageJson['cc'] cc=messageJson['cc']
# TODO # TODO
capabilityUrl='' capabilityUrl=[]
newPost = { newPost = {
'id': newPostId+'/activity', 'id': newPostId+'/activity',
'capability': capabilityUrl, 'capability': capabilityUrl,
@ -605,7 +611,7 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
getPersonBox(session,wfRequest,personCache,postToBox) getPersonBox(session,wfRequest,personCache,postToBox)
# If there are more than one followers on the target domain # If there are more than one followers on the target domain
# then send to teh shared inbox indead of the individual inbox # then send to the shared inbox indead of the individual inbox
if nickname=='capabilities': if nickname=='capabilities':
inboxUrl=capabilityAcquisition inboxUrl=capabilityAcquisition
if not capabilityAcquisition: if not capabilityAcquisition:
@ -658,6 +664,76 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
thr.start() thr.start()
return 0 return 0
def sendPostViaServer(session,fromNickname: str,password: str, \
fromDomain: str, fromPort: int, \
toNickname: str, toDomain: str, toPort: int, cc: str, \
httpPrefix: str, content: str, followersOnly: bool, \
attachImageFilename: str,imageDescription: str,useBlurhash: bool, \
cachedWebfingers: {},personCache: {}, \
debug=False,inReplyTo=None,inReplyToAtomUri=None,subject=None) -> int:
"""Send a post via a proxy (c2s)
"""
withDigest=True
if toPort!=80 and toPort!=443:
if ':' not in fromDomain:
fromDomain=fromDomain+':'+str(fromPort)
handle=httpPrefix+'://'+fromDomain+'/@'+fromNickname
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers)
if not wfRequest:
if debug:
print('DEBUG: 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
# Get the json for the c2s post, not saving anything to file
# Note that baseDir is set to None
saveToFile=False
clientToServer=True
toDomainFull=toDomain
if toPort!=80 and toDomain!=443:
toDomainFull=toDomain+':'+str(toPort)
toPersonId=httpPrefix+'://'+toDomainFull+'/users/'+toNickname
postJsonObject = \
createPostBase(None, \
fromNickname,fromDomain,fromPort, \
toPersonId,cc,httpPrefix,content, \
followersOnly,saveToFile,clientToServer, \
attachImageFilename,imageDescription,useBlurhash, \
inReplyTo,inReplyToAtomUri,subject)
authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \
'Content-type': 'application/json', \
'Authorization': authHeader}
postResult = \
postJson(session,postJsonObject,[],inboxUrl,headers,"inbox:write")
if not postResult:
if debug:
print('DEBUG: POST failed for c2s to '+inboxUrl)
return 5
if debug:
print('DEBUG: c2s POST success')
return 0
def groupFollowersByDomain(baseDir :str,nickname :str,domain :str) -> {}: def groupFollowersByDomain(baseDir :str,nickname :str,domain :str) -> {}:
"""Returns a dictionary with followers grouped by domain """Returns a dictionary with followers grouped by domain
""" """
@ -686,6 +762,9 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
personCache: {}, debug: bool) -> int: personCache: {}, debug: bool) -> int:
"""Sends a signed json object to an inbox/outbox """Sends a signed json object to an inbox/outbox
""" """
if not session:
print('WARN: No session specified for sendSignedJson')
return 8
withDigest=True withDigest=True
sharedInbox=False sharedInbox=False
@ -773,13 +852,13 @@ def sendToNamedAddresses(session,baseDir: str, \
postJsonObject: {},debug: bool) -> None: postJsonObject: {},debug: bool) -> None:
"""sends a post to the specific named addresses in to/cc """sends a post to the specific named addresses in to/cc
""" """
if port!=80 and port!=443: if not session:
domain=domain+':'+str(port) print('WARN: No session for sendToNamedAddresses')
return
if not postJsonObject.get('object'): if not postJsonObject.get('object'):
return False return
if not postJsonObject['object'].get('to'): if not postJsonObject['object'].get('to'):
return False return
recipients=[] recipients=[]
recipientType=['to','cc'] recipientType=['to','cc']
@ -793,7 +872,7 @@ def sendToNamedAddresses(session,baseDir: str, \
if not recipients: if not recipients:
return return
if debug: if debug:
print('c2s sending to addresses: '+str(recipients)) print('Sending individually addressed posts: '+str(recipients))
# this is after the message has arrived at the server # this is after the message has arrived at the server
clientToServer=False clientToServer=False
for address in recipients: for address in recipients:
@ -804,7 +883,16 @@ def sendToNamedAddresses(session,baseDir: str, \
if not toDomain: if not toDomain:
continue continue
if debug: if debug:
print('c2s sending from '+nickname+'@'+domain+' to '+toNickname+'@'+toDomain) domainFull=domain
if port:
if port!=80 and port!=443:
domainFull=domain+':'+str(port)
toDomainFull=toDomain
if toPort:
if toPort!=80 and toPort!=443:
toDomainFull=toDomain+':'+str(toPort)
print('Post sending s2s: '+nickname+'@'+domainFull+' to '+toNickname+'@'+toDomainFull)
cc=[]
sendSignedJson(postJsonObject,session,baseDir, \ sendSignedJson(postJsonObject,session,baseDir, \
nickname,domain,port, \ nickname,domain,port, \
toNickname,toDomain,toPort, \ toNickname,toDomain,toPort, \
@ -821,6 +909,9 @@ def sendToFollowers(session,baseDir: str, \
postJsonObject: {},debug: bool) -> None: postJsonObject: {},debug: bool) -> None:
"""sends a post to the followers of the given nickname """sends a post to the followers of the given nickname
""" """
if not session:
print('WARN: No session for sendToFollowers')
return
if not postIsAddressedToFollowers(baseDir,nickname,domain, \ if not postIsAddressedToFollowers(baseDir,nickname,domain, \
port,httpPrefix,postJsonObject): port,httpPrefix,postJsonObject):
if debug: if debug:

View File

@ -31,6 +31,8 @@ def getJson(session,url: str,headers: {},params: {}) -> {}:
if params: if params:
sessionParams=params sessionParams=params
sessionHeaders['User-agent'] = "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv)" sessionHeaders['User-agent'] = "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv)"
if not session:
print('WARN: no session specified for getJson')
session.cookies.clear() session.cookies.clear()
try: try:
result=session.get(url, headers=sessionHeaders, params=sessionParams) result=session.get(url, headers=sessionHeaders, params=sessionParams)

View File

@ -29,6 +29,7 @@ from posts import noOfFollowersOnDomain
from posts import groupFollowersByDomain from posts import groupFollowersByDomain
from posts import sendCapabilitiesUpdate from posts import sendCapabilitiesUpdate
from posts import archivePostsForPerson from posts import archivePostsForPerson
from posts import sendPostViaServer
from follow import clearFollows from follow import clearFollows
from follow import clearFollowers from follow import clearFollowers
from utils import followPerson from utils import followPerson
@ -136,7 +137,6 @@ def createServerAlice(path: str,domain: str,port: int,federationList: [], \
nickname='alice' nickname='alice'
httpPrefix='http' httpPrefix='http'
useTor=False useTor=False
clientToServer=False
password='alicepass' password='alicepass'
noreply=False noreply=False
nolike=False nolike=False
@ -955,6 +955,93 @@ def testAuthentication():
os.chdir(currDir) os.chdir(currDir)
shutil.rmtree(baseDir) shutil.rmtree(baseDir)
def testClientToServer():
print('Testing sending a post via c2s')
global testServerAliceRunning
global testServerBobRunning
testServerAliceRunning = False
testServerBobRunning = 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)
thrAlice.start()
thrBob.start()
assert thrAlice.isAlive()==True
assert thrBob.isAlive()==True
# wait for both servers to be running
ctr=0
while not (testServerAliceRunning and testServerBobRunning):
time.sleep(1)
ctr+=1
if ctr>60:
break
print('Alice online: '+str(testServerAliceRunning))
print('Bob online: '+str(testServerBobRunning))
time.sleep(1)
print('\n\n*******************************************************')
print('Alice sends to Bob via c2s')
sessionAlice = createSession(aliceDomain,alicePort,useTor)
followersOnly=False
attachImageFilename=None
imageDescription=None
useBlurhash=False
cachedWebfingers={}
personCache={}
password='alicepass'
outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==0
sendResult= \
sendPostViaServer(sessionAlice,'alice',password, \
aliceDomain,alicePort, \
'bob',bobDomain,bobPort,None, \
httpPrefix,'Sent from my ActivityPub client',followersOnly, \
attachImageFilename,imageDescription,useBlurhash, \
cachedWebfingers,personCache, \
True,None,None,None)
print('sendResult: '+str(sendResult))
assert sendResult==0
for i in range(30):
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
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testHttpsig() testHttpsig()

View File

@ -37,6 +37,10 @@ def parseHandle(handle: str) -> (str,str):
def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}) -> {}: def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}) -> {}:
if not session:
print('WARN: No session specified for webfingerHandle')
return None
nickname, domain = parseHandle(handle) nickname, domain = parseHandle(handle)
if not nickname: if not nickname:
return None return None
@ -49,6 +53,9 @@ def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}) ->
url = '{}://{}/.well-known/webfinger'.format(httpPrefix,domain) url = '{}://{}/.well-known/webfinger'.format(httpPrefix,domain)
par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)} par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)}
hdr = {'Accept': 'application/jrd+json'} hdr = {'Accept': 'application/jrd+json'}
#print('webfinger url: '+url)
#print('webfinger par: '+str(par))
#print('webfinger hdr: '+str(hdr))
try: try:
result = getJson(session, url, hdr, par) result = getJson(session, url, hdr, par)
except: except: