Updating of capabilities

master
Bob Mottram 2019-07-09 15:20:23 +01:00
parent 1b10d7ef4b
commit 76e8167ce7
12 changed files with 346 additions and 88 deletions

View File

@ -18,7 +18,7 @@ from utils import getNicknameFromActor
from utils import domainPermitted
from utils import followPerson
def createAcceptReject(baseDir: str,federationList: [],ocapGranted: {}, \
def createAcceptReject(baseDir: str,federationList: [], \
nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,httpPrefix: str, \
objectJson: {},ocapJson,acceptType: str) -> {}:
@ -31,7 +31,7 @@ def createAcceptReject(baseDir: str,federationList: [],ocapGranted: {}, \
if not objectJson.get('actor'):
return None
if not urlPermitted(objectJson['actor'],federationList,ocapGranted,"inbox:write"):
if not urlPermitted(objectJson['actor'],federationList,"inbox:write"):
return None
if port!=80 and port!=443:
@ -52,28 +52,28 @@ def createAcceptReject(baseDir: str,federationList: [],ocapGranted: {}, \
newAccept['capabilities']=ocapJson
return newAccept
def createAccept(baseDir: str,federationList: [],ocapGranted: {}, \
def createAccept(baseDir: str,federationList: [], \
nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,httpPrefix: str, \
objectJson: {}) -> {}:
# create capabilities accept
ocapNew=capabilitiesAccept(baseDir,httpPrefix,nickname,domain,port,toUrl,True)
return createAcceptReject(baseDir,federationList,ocapGranted, \
return createAcceptReject(baseDir,federationList, \
nickname,domain,port, \
toUrl,ccUrl,httpPrefix, \
objectJson,ocapNew,'Accept')
def createReject(baseDir: str,federationList: [],ocapGranted: {}, \
def createReject(baseDir: str,federationList: [], \
nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,httpPrefix: str, \
objectJson: {}) -> {}:
return createAcceptReject(baseDir,federationList,ocapGranted, \
return createAcceptReject(baseDir,federationList, \
nickname,domain,port, \
toUrl,ccUrl, \
httpPrefix,objectJson,None,'Reject')
def acceptFollow(baseDir: str,domain : str,messageJson: {}, \
federationList: [],ocapGranted: {},debug : bool) -> None:
federationList: [],debug : bool) -> None:
if not messageJson.get('object'):
return
if not messageJson['object'].get('type'):
@ -161,7 +161,7 @@ def receiveAcceptReject(session,baseDir: str, \
httpPrefix: str,domain :str,port: int, \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
personCache: {},messageJson: {},federationList: [], \
ocapGranted: {},debug : bool) -> bool:
debug : bool) -> bool:
"""Receives an Accept or Reject within the POST section of HTTPServer
"""
if messageJson['type']!='Accept' and messageJson['type']!='Reject':
@ -185,7 +185,7 @@ def receiveAcceptReject(session,baseDir: str, \
print('DEBUG: '+messageJson['type']+' does not contain a nickname')
return False
handle=nickname.lower()+'@'+domain.lower()
acceptFollow(baseDir,domain,messageJson,federationList,ocapGranted,debug)
acceptFollow(baseDir,domain,messageJson,federationList,debug)
if debug:
print('DEBUG: Uh, '+messageJson['type']+', I guess')
return True

View File

@ -12,7 +12,7 @@ from utils import getStatusNumber
from utils import createOutboxDir
from utils import urlPermitted
def createAnnounce(baseDir: str,federationList: [], ocapGranted: {}, \
def createAnnounce(baseDir: str,federationList: [], \
nickname: str, domain: str, port: int, \
toUrl: str, ccUrl: str, httpPrefix: str, \
objectUrl: str, saveToFile: bool) -> {}:
@ -22,7 +22,7 @@ def createAnnounce(baseDir: str,federationList: [], ocapGranted: {}, \
followers url objectUrl is typically the url of the message,
corresponding to url or atomUri in createPostBase
"""
if not urlPermitted(objectUrl,federationList,ocapGranted,"inbox:write"):
if not urlPermitted(objectUrl,federationList,"inbox:write"):
return None
if port!=80 and port!=443:

View File

@ -16,6 +16,9 @@ from utils import getNicknameFromActor
from utils import getDomainFromActor
def getOcapFilename(baseDir :str,nickname: str,domain: str,actor :str,subdir: str) -> str:
"""Returns the filename for a particular capability accepted or granted
Also creates directories as needed
"""
if ':' in domain:
domain=domain.split(':')[0]
@ -151,3 +154,84 @@ def capabilitiesGrantedSave(baseDir :str,nickname :str,domain :str,ocap: {}) ->
with open(ocapFilename, 'w') as fp:
commentjson.dump(ocap, fp, indent=4, sort_keys=False)
return True
def capabilitiesUpdate(baseDir: str,httpPrefix: str, \
nickname: str,domain: str, port: int, \
updateActor: str, \
updateCaps: []) -> {}:
"""Used to sends an update for a change of object capabilities
Note that the capability id gets changed with a new random token
so that the old capabilities can't continue to be used
"""
# reject excessively long actors
if len(updateActor)>256:
return None
fullDomain=domain
if port!=80 and port !=443:
fullDomain=domain+':'+str(port)
# Get the filename of the capability
ocapFilename=getOcapFilename(baseDir,nickname,fullDomain,updateActor,'accept')
# The capability should already exist for it to be updated
if not os.path.isfile(ocapFilename):
return None
# create an update activity
ocapUpdate = {
'type': 'Update',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'to': [updateActor],
'cc': [],
'object': {}
}
# read the existing capability
with open(ocapFilename, 'r') as fp:
ocapJson=commentjson.load(fp)
# set the new capabilities list. eg. ["inbox:write","objects:read"]
ocapJson['capability']=updateCaps
# change the id, so that the old capabilities can't continue to be used
updateActorNickname=getNicknameFromActor(updateActor)
updateActorDomain,updateActorPort=getDomainFromActor(updateActor)
if updateActorPort:
ocapId=updateActorNickname+'@'+updateActorDomain+':'+str(updateActorPort)+'#'+createPassword(32)
else:
ocapId=updateActorNickname+'@'+updateActorDomain+'#'+createPassword(32)
ocapJson['id']=httpPrefix+"://"+fullDomain+"/caps/"+ocapId
ocapUpdate['object']=ocapJson
# save it again
with open(ocapFilename, 'w') as fp:
commentjson.dump(ocapJson, fp, indent=4, sort_keys=False)
return ocapUpdate
def capabilitiesReceiveUpdate(baseDir :str, \
nickname :str,domain :str,port :int, \
actor :str, \
newCapabilitiesId :str, \
capabilityList :[], debug :bool) -> bool:
"""An update for a capability or the given actor has arrived
"""
ocapFilename= \
getOcapFilename(baseDir,nickname,domain,actor,'granted')
if not os.path.isfile(ocapFilename):
if debug:
print('DEBUG: capabilities file not found during update')
print(ocapFilename)
return False
with open(ocapFilename, 'r') as fp:
ocapJson=commentjson.load(fp)
ocapJson['id']=newCapabilitiesId
ocapJson['capability']=capabilityList
with open(ocapFilename, 'w') as fp:
commentjson.dump(ocapJson, fp, indent=4, sort_keys=False)
return True
return False

View File

@ -450,8 +450,7 @@ class PubServer(BaseHTTPRequestHandler):
if not inboxPermittedMessage(self.server.domain, \
messageJson, \
self.server.federationList, \
self.server.ocapGranted):
self.server.federationList):
if self.server.debug:
# https://www.youtube.com/watch?v=K3PrSj9XEu4
print('DEBUG: Ah Ah Ah')
@ -498,7 +497,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy=False
def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https', \
fedList=[],ocapAlways=False,ocapGranted={}, \
fedList=[],ocapAlways=False, \
useTor=False,debug=False) -> None:
if len(domain)==0:
domain='localhost'
@ -514,7 +513,6 @@ def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https', \
httpd.httpPrefix=httpPrefix
httpd.debug=debug
httpd.federationList=fedList.copy()
httpd.ocapGranted=ocapGranted.copy()
httpd.baseDir=baseDir
httpd.personCache={}
httpd.cachedWebfingers={}
@ -538,6 +536,6 @@ def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https', \
httpd.personCache,httpd.inboxQueue, \
domain,port,useTor,httpd.federationList, \
httpd.ocapAlways, \
httpd.ocapGranted,debug),daemon=True)
debug),daemon=True)
httpd.thrInboxQueue.start()
httpd.serve_forever()

View File

@ -320,8 +320,6 @@ else:
if configFederationList:
federationList=configFederationList
ocapGranted={}
if federationList:
print('Federating with: '+str(federationList))
@ -348,13 +346,13 @@ if args.testdata:
deleteAllPosts(baseDir,nickname,domain,'outbox')
followPerson(baseDir,nickname,domain,'admin',domain,federationList,True)
followerOfPerson(baseDir,nickname,domain,'admin',domain,federationList,True)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"like, this is totally just a test, man",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"Zoiks!!!",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"Hey scoob we need like a hundred more milkshakes",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"Getting kinda spooky around here",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"And they would have gotten away with it too if it wasn't for those pesky hackers",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"man, these centralized sites are, like, the worst!",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"another mystery solved hey",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"let's go bowling",False,True,False,ocapGranted)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"like, this is totally just a test, man",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"Zoiks!!!",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"Hey scoob we need like a hundred more milkshakes",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"Getting kinda spooky around here",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"And they would have gotten away with it too if it wasn't for those pesky hackers",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"man, these centralized sites are, like, the worst!",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"another mystery solved hey",False,True,False)
createPublicPost(baseDir,nickname,domain,port,httpPrefix,"let's go bowling",False,True,False)
runDaemon(baseDir,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,debug)
runDaemon(baseDir,domain,port,httpPrefix,federationList,ocapAlways,useTor,debug)

View File

@ -221,7 +221,7 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
port: int,sendThreads: [],postLog: [], \
cachedWebfingers: {},personCache: {}, \
messageJson: {},federationList: [], \
ocapGranted: {},debug : bool) -> bool:
debug : bool) -> bool:
"""Receives a follow request within the POST section of HTTPServer
"""
if not messageJson['type'].startswith('Follow'):
@ -281,7 +281,7 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
print('DEBUG: sending Accept for follow request which arrived at '+ \
nicknameToFollow+'@'+domainToFollow+' back to '+nickname+'@'+domain)
personUrl=messageJson['actor']
acceptJson=createAccept(baseDir,federationList,ocapGranted, \
acceptJson=createAccept(baseDir,federationList, \
nicknameToFollow,domainToFollow,port, \
personUrl,'',httpPrefix,messageJson)
if debug:
@ -295,7 +295,7 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
nicknameToFollow,domainToFollow,port, \
nickname,domain,fromPort, '', \
httpPrefix,True,clientToServer, \
federationList, ocapGranted, \
federationList, \
sendThreads,postLog,cachedWebfingers, \
personCache,debug)
@ -303,7 +303,7 @@ def sendFollowRequest(session,baseDir: str, \
nickname: str,domain: str,port: int,httpPrefix: str, \
followNickname: str,followDomain: str, \
followPort: bool,followHttpPrefix: str, \
clientToServer: bool,federationList: [],ocapGranted: {}, \
clientToServer: bool,federationList: [], \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
personCache: {},debug : bool) -> {}:
"""Gets the json object for sending a follow request
@ -339,7 +339,7 @@ def sendFollowRequest(session,baseDir: str, \
followNickname,followDomain,followPort, \
'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix,True,clientToServer, \
federationList, ocapGranted, \
federationList, \
sendThreads,postLog,cachedWebfingers,personCache, debug)
return newFollowJson

View File

@ -16,6 +16,9 @@ from shutil import copyfile
from utils import urlPermitted
from utils import createInboxQueueDir
from utils import getStatusNumber
from utils import getDomainFromActor
from utils import getNicknameFromActor
from utils import domainPermitted
from httpsig import verifyPostHeaders
from session import createSession
from session import getJson
@ -27,6 +30,7 @@ from cache import storePersonInCache
from acceptreject import receiveAcceptReject
from capabilities import getOcapFilename
from capabilities import CapablePost
from capabilities import capabilitiesReceiveUpdate
def getPersonPubKey(session,personUrl: str,personCache: {},debug: bool) -> str:
if not personUrl:
@ -68,7 +72,7 @@ def inboxMessageHasParams(messageJson: {}) -> bool:
return False
return True
def inboxPermittedMessage(domain: str,messageJson: {},federationList: [],ocapGranted: {}) -> bool:
def inboxPermittedMessage(domain: str,messageJson: {},federationList: []) -> bool:
""" check that we are receiving from a permitted domain
"""
testParam='actor'
@ -79,14 +83,14 @@ def inboxPermittedMessage(domain: str,messageJson: {},federationList: [],ocapGra
if domain in actor:
return True
if not urlPermitted(actor,federationList,ocapGranted,"inbox:write"):
if not urlPermitted(actor,federationList,"inbox:write"):
return False
if messageJson['type']!='Follow':
if messageJson.get('object'):
if messageJson['object'].get('inReplyTo'):
inReplyTo=messageJson['object']['inReplyTo']
if not urlPermitted(inReplyTo,federationList,ocapGranted):
if not urlPermitted(inReplyTo,federationList):
return False
return True
@ -299,7 +303,62 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{},httpPrefix :str,domain :
return recipientsDict
def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cachedWebfingers: {},personCache: {},queue: [],domain: str,port: int,useTor: bool,federationList: [],ocapAlways: bool,ocapGranted: {},debug: bool) -> None:
def receiveUpdate(session,baseDir: str, \
httpPrefix: str,domain :str,port: int, \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
personCache: {},messageJson: {},federationList: [], \
debug : bool) -> bool:
"""Receives an Update activity within the POST section of HTTPServer
"""
if messageJson['type']!='Update':
return False
if not messageJson.get('actor'):
if debug:
print('DEBUG: '+messageJson['type']+' has no actor')
return False
if not messageJson.get('object'):
if debug:
print('DEBUG: '+messageJson['type']+' has no object')
return False
if not isinstance(messageJson['object'], dict):
if debug:
print('DEBUG: '+messageJson['type']+' object is not a dict')
return False
if not messageJson['object'].get('type'):
if debug:
print('DEBUG: '+messageJson['type']+' object has no type')
return False
if '/users/' not in messageJson['actor']:
if debug:
print('DEBUG: "users" missing from actor in '+messageJson['type'])
return False
domain,tempPort=getDomainFromActor(messageJson['actor'])
if not domainPermitted(domain,federationList):
if debug:
print('DEBUG: '+messageJson['type']+' from domain not permitted - '+domain)
return False
nickname=getNicknameFromActor(messageJson['actor'])
if not nickname:
if debug:
print('DEBUG: '+messageJson['type']+' does not contain a nickname')
return False
handle=nickname.lower()+'@'+domain.lower()
if messageJson['object'].get('capability') and messageJson['object'].get('scope'):
domain,tempPort=getDomainFromActor(messageJson['object']['scope'])
nickname=getNicknameFromActor(messageJson['object']['scope'])
if messageJson['object']['type']=='Capability':
if capabilitiesReceiveUpdate(baseDir,nickname,domain,port,
messageJson['actor'], \
messageJson['object']['id'], \
messageJson['object']['capability'], \
debug):
if debug:
print('DEBUG: An update was received')
return True
return False
def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cachedWebfingers: {},personCache: {},queue: [],domain: str,port: int,useTor: bool,federationList: [],ocapAlways: bool,debug: bool) -> None:
"""Processes received items and moves them to
the appropriate directories
"""
@ -389,7 +448,7 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
cachedWebfingers,
personCache,
queueJson['post'], \
federationList,ocapGranted, \
federationList, \
debug):
if debug:
print('DEBUG: Follow accepted from '+keyId)
@ -403,7 +462,7 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
cachedWebfingers,
personCache,
queueJson['post'], \
federationList,ocapGranted, \
federationList, \
debug):
if debug:
print('DEBUG: Accept/Reject received from '+keyId)
@ -411,6 +470,21 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
queue.pop(0)
continue
if receiveUpdate(session, \
baseDir,httpPrefix, \
domain,port, \
sendThreads,postLog, \
cachedWebfingers,
personCache,
queueJson['post'], \
federationList, \
debug):
if debug:
print('DEBUG: Update accepted from '+keyId)
os.remove(queueFilename)
queue.pop(0)
continue
# get recipients list
recipientsDict=inboxPostRecipients(baseDir,queueJson['post'],httpPrefix,domain,port)

View File

@ -17,7 +17,7 @@ def like(baseDir: str,federationList: [],nickname: str,domain: str,port: int, \
and ccUrl might be a specific person whose post was liked
objectUrl is typically the url of the message, corresponding to url or atomUri in createPostBase
"""
if not urlPermitted(objectUrl,federationList,ocapGranted,"inbox:write"):
if not urlPermitted(objectUrl,federationList,"inbox:write"):
return None
if port!=80 and port!=443:

View File

@ -30,7 +30,10 @@ from httpsig import createSignedHeader
from utils import getStatusNumber
from utils import createPersonDir
from utils import urlPermitted
from utils import getNicknameFromActor
from utils import getDomainFromActor
from capabilities import getOcapFilename
from capabilities import capabilitiesUpdate
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
@ -153,7 +156,7 @@ def getPersonBox(session,wfRequest: {},personCache: {}, \
def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
maxEmoji: int,maxAttachments: int, \
federationList: [], ocapGranted: {},\
federationList: [],\
personCache: {},raw: bool,simple: bool) -> {}:
personPosts={}
if not outboxUrl:
@ -193,7 +196,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if tagItem['icon'].get('url'):
# No emoji from non-permitted domains
if urlPermitted(tagItem['icon']['url'], \
federationList,ocapGranted, \
federationList, \
"objects:read"):
emojiName=tagItem['name']
emojiIcon=tagItem['icon']['url']
@ -217,7 +220,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if item['object']['inReplyTo']:
# No replies to non-permitted domains
if not urlPermitted(item['object']['inReplyTo'], \
federationList,ocapGranted, \
federationList, \
"objects:read"):
continue
inReplyTo = item['object']['inReplyTo']
@ -227,7 +230,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if item['object']['conversation']:
# no conversations originated in non-permitted domains
if urlPermitted(item['object']['conversation'], \
federationList,ocapGranted,"objects:read"):
federationList,"objects:read"):
conversation = item['object']['conversation']
attachment = []
@ -237,7 +240,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if attach.get('name') and attach.get('url'):
# no attachments from non-permitted domains
if urlPermitted(attach['url'], \
federationList,ocapGranted, \
federationList, \
"objects:read"):
attachment.append([attach['name'],attach['url']])
@ -319,7 +322,6 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \
def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
toUrl: str, ccUrl: str, httpPrefix: str, content: str, \
followersOnly: bool, saveToFile: bool, clientToServer: bool, \
ocapGranted: {}, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Creates a message
"""
@ -487,7 +489,7 @@ def postIsAddressedToFollowers(baseDir: str,
def createPublicPost(baseDir: str,
nickname: str, domain: str, port: int,httpPrefix: str, \
content: str, followersOnly: bool, saveToFile: bool,
clientToServer: bool, ocapGranted: {},\
clientToServer: bool,\
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Public post to the outbox
"""
@ -495,10 +497,10 @@ def createPublicPost(baseDir: str,
'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix+'://'+domain+'/users/'+nickname+'/followers', \
httpPrefix, content, followersOnly, saveToFile, \
clientToServer, ocapGranted, \
clientToServer, \
inReplyTo, inReplyToAtomUri, subject)
def threadSendPost(session,postJsonObject: {},federationList: [],ocapGranted: {},\
def threadSendPost(session,postJsonObject: {},federationList: [],\
inboxUrl: str, baseDir: str,signatureHeaderJson: {},postLog: [],
debug :bool) -> None:
"""Sends a post with exponential backoff
@ -507,7 +509,7 @@ def threadSendPost(session,postJsonObject: {},federationList: [],ocapGranted: {}
backoffTime=60
for attempt in range(20):
postResult = postJson(session,postJsonObject,federationList, \
ocapGranted,inboxUrl,signatureHeaderJson, \
inboxUrl,signatureHeaderJson, \
"inbox:write")
if postResult:
if debug:
@ -534,7 +536,7 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
toNickname: str, toDomain: str, toPort: int, cc: str, \
httpPrefix: str, content: str, followersOnly: bool, \
saveToFile: bool, clientToServer: bool, \
federationList: [], ocapGranted: {},\
federationList: [],\
sendThreads: [], postLog: [], cachedWebfingers: {},personCache: {}, \
debug=False,inReplyTo=None,inReplyToAtomUri=None,subject=None) -> int:
"""Post to another inbox
@ -583,7 +585,6 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
createPostBase(baseDir,nickname,domain,port, \
toPersonId,cc,httpPrefix,content, \
followersOnly,saveToFile,clientToServer, \
ocapGranted, \
inReplyTo,inReplyToAtomUri,subject)
# get the senders private key
@ -607,7 +608,6 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
thr = threadWithTrace(target=threadSendPost,args=(session, \
postJsonObject.copy(), \
federationList, \
ocapGranted, \
inboxUrl,baseDir, \
signatureHeaderJson.copy(), \
postLog,
@ -639,7 +639,7 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
nickname: str, domain: str, port: int, \
toNickname: str, toDomain: str, toPort: int, cc: str, \
httpPrefix: str, saveToFile: bool, clientToServer: bool, \
federationList: [], ocapGranted: {}, \
federationList: [], \
sendThreads: [], postLog: [], cachedWebfingers: {}, \
personCache: {}, debug: bool) -> int:
"""Sends a signed json object to an inbox/outbox
@ -715,7 +715,6 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
args=(session, \
postJsonObject.copy(), \
federationList, \
ocapGranted, \
inboxUrl,baseDir, \
signatureHeaderJson.copy(), \
postLog,
@ -754,7 +753,7 @@ def sendToFollowers(session,baseDir: str,
nickname,domain,port, \
toNickname,toDomain,toPort, \
cc,httpPrefix,True,clientToServer, \
federationList,ocapGranted, \
federationList, \
sendThreads,postLog,cachedWebfingers, \
personCache,debug)
@ -900,7 +899,6 @@ def getPublicPostsOfPerson(nickname: str,domain: str, \
personCache={}
cachedWebfingers={}
federationList=[]
ocapGranted={}
httpPrefix='https'
handle=httpPrefix+"://"+domain+"/@"+nickname
@ -917,6 +915,40 @@ def getPublicPostsOfPerson(nickname: str,domain: str, \
maxEmoji=10
maxAttachments=5
userPosts = getPosts(session,personUrl,30,maxMentions,maxEmoji, \
maxAttachments,federationList,ocapGranted, \
maxAttachments,federationList, \
personCache,raw,simple)
#print(str(userPosts))
def sendCapabilitiesUpdate(session,baseDir: str,httpPrefix: str, \
nickname: str,domain: str,port: int, \
followerUrl,updateCaps: [], \
sendThreads: [],postLog: [], \
cachedWebfingers: {},personCache: {}, \
federationList :[],debug :bool) -> int:
"""When the capabilities for a follower are changed this
sends out an update. followerUrl is the actor of the follower.
"""
updateJson=capabilitiesUpdate(baseDir,httpPrefix, \
nickname,domain,port, \
followerUrl, \
updateCaps)
if not updateJson:
return 1
if debug:
pprint(updateJson)
print('DEBUG: sending capabilities update from '+ \
nickname+'@'+domain+' port '+ str(port) + \
' to '+followerUrl)
clientToServer=False
followerNickname=getNicknameFromActor(followerUrl)
followerDomain,followerPort=getDomainFromActor(followerUrl)
return sendSignedJson(updateJson,session,baseDir, \
nickname,domain,port, \
followerNickname,followerDomain,followerPort, '', \
httpPrefix,True,clientToServer, \
federationList, \
sendThreads,postLog,cachedWebfingers, \
personCache,debug)

View File

@ -39,7 +39,7 @@ def getJson(session,url: str,headers: {},params: {}) -> {}:
pass
return None
def postJson(session,postJsonObject: {},federationList: [],ocapGranted: {},inboxUrl: str,headers: {},capability: str) -> str:
def postJson(session,postJsonObject: {},federationList: [],inboxUrl: str,headers: {},capability: str) -> str:
"""Post a json message to the inbox of another person
Supplying a capability, such as "inbox:write"
"""
@ -47,7 +47,7 @@ def postJson(session,postJsonObject: {},federationList: [],ocapGranted: {},inbox
# always allow capability requests
if not capability.startswith('cap'):
# check that we are posting to a permitted domain
if not urlPermitted(inboxUrl,federationList,ocapGranted,capability):
if not urlPermitted(inboxUrl,federationList,capability):
return None
postResult = session.post(url = inboxUrl, data = json.dumps(postJsonObject), headers=headers)

128
tests.py
View File

@ -10,6 +10,8 @@ import base64
import time
import os, os.path
import shutil
import commentjson
from pprint import pprint
from person import createPerson
from Crypto.Hash import SHA256
from httpsig import signPostHeaders
@ -25,6 +27,7 @@ from posts import sendPost
from posts import archivePosts
from posts import noOfFollowersOnDomain
from posts import groupFollowersByDomain
from posts import sendCapabilitiesUpdate
from follow import clearFollows
from follow import clearFollowers
from utils import followPerson
@ -111,7 +114,7 @@ def testThreads():
thr.join()
assert thr.isAlive()==False
def createServerAlice(path: str,domain: str,port: int,federationList: [],ocapGranted: {},hasFollows: bool,hasPosts :bool,ocapAlways: bool):
def createServerAlice(path: str,domain: str,port: int,federationList: [],hasFollows: bool,hasPosts :bool,ocapAlways: bool):
print('Creating test server: Alice on port '+str(port))
if os.path.isdir(path):
shutil.rmtree(path)
@ -129,15 +132,15 @@ def createServerAlice(path: str,domain: str,port: int,federationList: [],ocapGra
followPerson(path,nickname,domain,'bob','127.0.0.100:61936',federationList,True)
followerOfPerson(path,nickname,domain,'bob','127.0.0.100:61936',federationList,True)
if hasPosts:
createPublicPost(path,nickname, domain, port,httpPrefix, "No wise fish would go anywhere without a porpoise", False, True, clientToServer,ocapGranted)
createPublicPost(path,nickname, domain, port,httpPrefix, "Curiouser and curiouser!", False, True, clientToServer,ocapGranted)
createPublicPost(path,nickname, domain, port,httpPrefix, "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", False, True, clientToServer,ocapGranted)
createPublicPost(path,nickname, domain, port,httpPrefix, "No wise fish would go anywhere without a porpoise", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "Curiouser and curiouser!", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", False, True, clientToServer)
global testServerAliceRunning
testServerAliceRunning = True
print('Server running: Alice')
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,True)
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,useTor,True)
def createServerBob(path: str,domain: str,port: int,federationList: [],ocapGranted: {},hasFollows: bool,hasPosts :bool,ocapAlways :bool):
def createServerBob(path: str,domain: str,port: int,federationList: [],hasFollows: bool,hasPosts :bool,ocapAlways :bool):
print('Creating test server: Bob on port '+str(port))
if os.path.isdir(path):
shutil.rmtree(path)
@ -155,15 +158,15 @@ def createServerBob(path: str,domain: str,port: int,federationList: [],ocapGrant
followPerson(path,nickname,domain,'alice','127.0.0.50:61935',federationList,True)
followerOfPerson(path,nickname,domain,'alice','127.0.0.50:61935',federationList,True)
if hasPosts:
createPublicPost(path,nickname, domain, port,httpPrefix, "It's your life, live it your way.", False, True, clientToServer,ocapGranted)
createPublicPost(path,nickname, domain, port,httpPrefix, "One of the things I've realised is that I am very simple", False, True, clientToServer,ocapGranted)
createPublicPost(path,nickname, domain, port,httpPrefix, "Quantum physics is a bit of a passion of mine", False, True, clientToServer,ocapGranted)
createPublicPost(path,nickname, domain, port,httpPrefix, "It's your life, live it your way.", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "One of the things I've realised is that I am very simple", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "Quantum physics is a bit of a passion of mine", False, True, clientToServer)
global testServerBobRunning
testServerBobRunning = True
print('Server running: Bob')
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,True)
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,useTor,True)
def createServerEve(path: str,domain: str,port: int,federationList: [],ocapGranted: {},hasFollows: bool,hasPosts :bool,ocapAlways :bool):
def createServerEve(path: str,domain: str,port: int,federationList: [],hasFollows: bool,hasPosts :bool,ocapAlways :bool):
print('Creating test server: Eve on port '+str(port))
if os.path.isdir(path):
shutil.rmtree(path)
@ -180,7 +183,7 @@ def createServerEve(path: str,domain: str,port: int,federationList: [],ocapGrant
global testServerEveRunning
testServerEveRunning = True
print('Server running: Eve')
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,True)
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,useTor,True)
def testPostMessageBetweenServers():
print('Testing sending message from one server to the inbox of another')
@ -193,7 +196,6 @@ def testPostMessageBetweenServers():
httpPrefix='http'
useTor=False
federationList=['127.0.0.50','127.0.0.100']
ocapGranted={}
baseDir=os.getcwd()
if os.path.isdir(baseDir+'/.tests'):
@ -206,12 +208,12 @@ def testPostMessageBetweenServers():
aliceDir=baseDir+'/.tests/alice'
aliceDomain='127.0.0.50'
alicePort=61935
thrAlice = threadWithTrace(target=createServerAlice,args=(aliceDir,aliceDomain,alicePort,federationList,ocapGranted,True,True,ocapAlways),daemon=True)
thrAlice = threadWithTrace(target=createServerAlice,args=(aliceDir,aliceDomain,alicePort,federationList,True,True,ocapAlways),daemon=True)
bobDir=baseDir+'/.tests/bob'
bobDomain='127.0.0.100'
bobPort=61936
thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobDomain,bobPort,federationList,ocapGranted,True,True,ocapAlways),daemon=True)
thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobDomain,bobPort,federationList,True,True,ocapAlways),daemon=True)
thrAlice.start()
thrBob.start()
@ -238,7 +240,7 @@ def testPostMessageBetweenServers():
ccUrl=None
alicePersonCache={}
aliceCachedWebfingers={}
sendResult = sendPost(sessionAlice,aliceDir,'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Why is a mouse when it spins?', followersOnly, saveToFile, clientToServer, federationList, ocapGranted, aliceSendThreads, alicePostLog, aliceCachedWebfingers,alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
sendResult = sendPost(sessionAlice,aliceDir,'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Why is a mouse when it spins?', followersOnly, saveToFile, clientToServer, federationList, aliceSendThreads, alicePostLog, aliceCachedWebfingers,alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
print('sendResult: '+str(sendResult))
queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
@ -280,7 +282,6 @@ def testFollowBetweenServers():
httpPrefix='http'
useTor=False
federationList=[]
ocapGranted={}
baseDir=os.getcwd()
if os.path.isdir(baseDir+'/.tests'):
@ -293,17 +294,17 @@ def testFollowBetweenServers():
aliceDir=baseDir+'/.tests/alice'
aliceDomain='127.0.0.42'
alicePort=61935
thrAlice = threadWithTrace(target=createServerAlice,args=(aliceDir,aliceDomain,alicePort,federationList,ocapGranted,False,False,ocapAlways),daemon=True)
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,ocapGranted,False,False,ocapAlways),daemon=True)
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,ocapGranted,False,False,False),daemon=True)
thrEve = threadWithTrace(target=createServerEve,args=(eveDir,eveDomain,evePort,federationList,False,False,False),daemon=True)
thrAlice.start()
thrBob.start()
@ -326,7 +327,8 @@ def testFollowBetweenServers():
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)
@ -348,20 +350,31 @@ def testFollowBetweenServers():
sendFollowRequest(sessionAlice,aliceDir, \
'alice',aliceDomain,alicePort,httpPrefix, \
'bob',bobDomain,bobPort,httpPrefix, \
clientToServer,federationList,ocapGranted,
clientToServer,federationList,
aliceSendThreads,alicePostLog, \
aliceCachedWebfingers,alicePersonCache,True)
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(bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json'):
if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json'):
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
print('\n\nEve tries to send to Bob')
print('\n\n*********************************************************')
print('Eve tries to send to Bob')
sessionEve = createSession(eveDomain,evePort,useTor)
eveSendThreads = []
evePostLog = []
@ -369,7 +382,7 @@ def testFollowBetweenServers():
eveCachedWebfingers={}
eveSendThreads=[]
evePostLog=[]
sendResult = sendPost(sessionEve,eveDir,'eve', eveDomain, evePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Eve message', followersOnly, saveToFile, clientToServer, federationList, ocapGranted, eveSendThreads, evePostLog, eveCachedWebfingers,evePersonCache,inReplyTo, inReplyToAtomUri, subject)
sendResult = sendPost(sessionEve,eveDir,'eve', eveDomain, evePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Eve message', followersOnly, saveToFile, clientToServer, federationList, eveSendThreads, evePostLog, eveCachedWebfingers,evePersonCache,inReplyTo, inReplyToAtomUri, subject)
print('sendResult: '+str(sendResult))
queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
@ -387,14 +400,15 @@ def testFollowBetweenServers():
assert eveMessageArrived==False
print('Message from Eve to Bob was correctly rejected by object capabilities')
print('\n\n*********************************************************')
print('Alice sends a message to Bob')
aliceSendThreads = []
alicePostLog = []
alicePersonCache={}
aliceCachedWebfingers={}
aliceSendThreads=[]
alicePostLog=[]
sendResult = sendPost(sessionAlice,aliceDir,'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Alice message', followersOnly, saveToFile, clientToServer, federationList, ocapGranted, aliceSendThreads, alicePostLog, aliceCachedWebfingers,alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
sendResult = sendPost(sessionAlice,aliceDir,'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Alice message', followersOnly, saveToFile, clientToServer, federationList, aliceSendThreads, alicePostLog, aliceCachedWebfingers,alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
print('sendResult: '+str(sendResult))
queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
@ -411,6 +425,64 @@ def testFollowBetweenServers():
assert aliceMessageArrived==True
print('Message from Alice to Bob succeeded, since it was granted capabilities')
print('\n\n*********************************************************')
print("\nBob changes Alice's capabilities so that she can't reply on his posts")
sessionBob = createSession(bobDomain,bobPort,useTor)
bobSendThreads = []
bobPostLog = []
bobPersonCache={}
bobCachedWebfingers={}
print("Bob's capabilities for Alice:")
with open(bobCapsFilename, 'r') as fp:
bobCapsJson=commentjson.load(fp)
pprint(bobCapsJson)
assert "inbox:noreply" not in bobCapsJson['capability']
print("Alice's capabilities granted by Bob")
with open(aliceCapsFilename, 'r') as fp:
aliceCapsJson=commentjson.load(fp)
pprint(aliceCapsJson)
assert "inbox:noreply" not in aliceCapsJson['capability']
newCapabilities=["inbox:write","objects:read","inbox:noreply"]
sendCapabilitiesUpdate(sessionBob,bobDir,httpPrefix, \
'bob',bobDomain,bobPort, \
httpPrefix+'://'+aliceDomain+':'+str(alicePort)+'/users/alice',
newCapabilities, \
bobSendThreads, bobPostLog, \
bobCachedWebfingers,bobPersonCache, \
federationList,True)
bobChanged=False
bobNewCapsJson=None
for i in range(20):
time.sleep(1)
with open(bobCapsFilename, 'r') as fp:
bobNewCapsJson=commentjson.load(fp)
if "inbox:noreply" in bobNewCapsJson['capability']:
print("Bob's capabilities were changed")
pprint(bobNewCapsJson)
bobChanged=True
break
assert bobChanged
aliceChanged=False
aliceNewCapsJson=None
for i in range(20):
time.sleep(1)
with open(aliceCapsFilename, 'r') as fp:
aliceNewCapsJson=commentjson.load(fp)
if "inbox:noreply" in aliceNewCapsJson['capability']:
print("Alice's granted capabilities were changed")
pprint(aliceNewCapsJson)
aliceChanged=True
break
assert aliceChanged
# check that the capabilities id has changed
assert bobNewCapsJson['id']!=bobCapsJson['id']
assert aliceNewCapsJson['id']!=aliceCapsJson['id']
# stop the servers
thrAlice.kill()
thrAlice.join()

View File

@ -48,7 +48,7 @@ def domainPermitted(domain: str, federationList: []):
return True
return False
def urlPermitted(url: str, federationList: [],ocapGranted: {},capability: str):
def urlPermitted(url: str, federationList: [],capability: str):
if len(federationList)==0:
return True
for domain in federationList: