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

View File

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

View File

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

117
posts.py
View File

@ -38,6 +38,7 @@ from capabilities import getOcapFilename
from capabilities import capabilitiesUpdate
from media import attachImage
from content import addMentions
from auth import createBasicAuthHeader
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
@ -318,9 +319,10 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Creates a message
"""
# convert content to html
content=addMentions(baseDir,httpPrefix, \
nickname,domain,content)
if not clientToServer:
# convert content to html
content=addMentions(baseDir,httpPrefix, \
nickname,domain,content)
if port!=80 and port!=443:
domain=domain+':'+str(port)
@ -444,12 +446,16 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
nickname,domain,newPost,'outbox')
return newPost
def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str, \
def outboxMessageCreateWrap(httpPrefix: str, \
nickname: str,domain: str,port: int, \
messageJson: {}) -> {}:
"""Wraps a received message in a 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()
if messageJson.get('published'):
published = messageJson['published']
@ -458,7 +464,7 @@ def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str, \
if messageJson.get('cc'):
cc=messageJson['cc']
# TODO
capabilityUrl=''
capabilityUrl=[]
newPost = {
'id': newPostId+'/activity',
'capability': capabilityUrl,
@ -605,7 +611,7 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
getPersonBox(session,wfRequest,personCache,postToBox)
# 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':
inboxUrl=capabilityAcquisition
if not capabilityAcquisition:
@ -658,6 +664,76 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
thr.start()
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) -> {}:
"""Returns a dictionary with followers grouped by domain
"""
@ -686,6 +762,9 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
personCache: {}, debug: bool) -> int:
"""Sends a signed json object to an inbox/outbox
"""
if not session:
print('WARN: No session specified for sendSignedJson')
return 8
withDigest=True
sharedInbox=False
@ -773,13 +852,13 @@ def sendToNamedAddresses(session,baseDir: str, \
postJsonObject: {},debug: bool) -> None:
"""sends a post to the specific named addresses in to/cc
"""
if port!=80 and port!=443:
domain=domain+':'+str(port)
if not session:
print('WARN: No session for sendToNamedAddresses')
return
if not postJsonObject.get('object'):
return False
return
if not postJsonObject['object'].get('to'):
return False
return
recipients=[]
recipientType=['to','cc']
@ -793,7 +872,7 @@ def sendToNamedAddresses(session,baseDir: str, \
if not recipients:
return
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
clientToServer=False
for address in recipients:
@ -804,7 +883,16 @@ def sendToNamedAddresses(session,baseDir: str, \
if not toDomain:
continue
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, \
nickname,domain,port, \
toNickname,toDomain,toPort, \
@ -821,6 +909,9 @@ def sendToFollowers(session,baseDir: str, \
postJsonObject: {},debug: bool) -> None:
"""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, \
port,httpPrefix,postJsonObject):
if debug:

View File

@ -31,6 +31,8 @@ def getJson(session,url: str,headers: {},params: {}) -> {}:
if params:
sessionParams=params
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()
try:
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 sendCapabilitiesUpdate
from posts import archivePostsForPerson
from posts import sendPostViaServer
from follow import clearFollows
from follow import clearFollowers
from utils import followPerson
@ -136,7 +137,6 @@ def createServerAlice(path: str,domain: str,port: int,federationList: [], \
nickname='alice'
httpPrefix='http'
useTor=False
clientToServer=False
password='alicepass'
noreply=False
nolike=False
@ -954,7 +954,94 @@ def testAuthentication():
os.chdir(currDir)
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():
print('Running tests...')
testHttpsig()

View File

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