Move shared inbox account into daemon

master
Bob Mottram 2019-07-11 13:29:31 +01:00
parent cb79ddb760
commit c301f45b33
8 changed files with 294 additions and 117 deletions

View File

@ -18,6 +18,7 @@ from webfinger import webfingerLookup
from webfinger import webfingerHandle from webfinger import webfingerHandle
from person import personLookup from person import personLookup
from person import personBoxJson from person import personBoxJson
from person import createSharedInbox
from posts import outboxMessageCreateWrap from posts import outboxMessageCreateWrap
from posts import savePostToBox from posts import savePostToBox
from inbox import inboxPermittedMessage from inbox import inboxPermittedMessage
@ -540,6 +541,10 @@ def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https', \
httpd.acceptedCaps.append('inbox:noannounce') httpd.acceptedCaps.append('inbox:noannounce')
if cw: if cw:
httpd.acceptedCaps.append('inbox:cw') httpd.acceptedCaps.append('inbox:cw')
print('Creating shared inbox: inbox@'+domain)
createSharedInbox(baseDir,'inbox',domain,port,httpPrefix)
print('Running ActivityPub daemon on ' + domain + ' port ' + str(port)) print('Running ActivityPub daemon on ' + domain + ' port ' + str(port))
httpd.thrInboxQueue= \ httpd.thrInboxQueue= \
threadWithTrace(target=runInboxQueue, \ threadWithTrace(target=runInboxQueue, \

View File

@ -400,13 +400,9 @@ if not os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain):
setConfigParam(baseDir,'adminPassword',adminPassword) setConfigParam(baseDir,'adminPassword',adminPassword)
createPerson(baseDir,nickname,domain,port,httpPrefix,True,adminPassword) createPerson(baseDir,nickname,domain,port,httpPrefix,True,adminPassword)
if not os.path.isdir(baseDir+'/accounts/inbox@'+domain): #if not os.path.isdir(baseDir+'/accounts/capabilities@'+domain):
print('Creating shared inbox: inbox@'+domain) # print('Creating capabilities account which can sign requests')
createSharedInbox(baseDir,'inbox',domain,port,httpPrefix) # createCapabilitiesInbox(baseDir,'capabilities',domain,port,httpPrefix)
if not os.path.isdir(baseDir+'/accounts/capabilities@'+domain):
print('Creating capabilities account which can sign requests')
createCapabilitiesInbox(baseDir,'capabilities',domain,port,httpPrefix)
if args.testdata: if args.testdata:
nickname='testuser567' nickname='testuser567'

View File

@ -27,6 +27,8 @@ def getFollowersOfPerson(baseDir: str, \
Used by the shared inbox to know who to send incoming mail to Used by the shared inbox to know who to send incoming mail to
""" """
followers=[] followers=[]
if ':' in domain:
domain=domain.split(':')[0]
handle=nickname.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
return followers return followers
@ -345,38 +347,64 @@ def sendFollowRequest(session,baseDir: str, \
return newFollowJson return newFollowJson
def getFollowersOfActor(baseDir :str,actor :str,recipientsDict: {}) -> {}: def getFollowersOfActor(baseDir :str,actor :str,debug: bool) -> {}:
"""In a shared inbox if we receive a post we know who it's from """In a shared inbox if we receive a post we know who it's from
and if it's addressed to followers then we need to get a list of those. and if it's addressed to followers then we need to get a list of those.
This returns a list of account handles which follow the given actor This returns a list of account handles which follow the given actor
and also the corresponding capability id if it exists and also the corresponding capability id if it exists
""" """
if debug:
print('DEBUG: getting followers of '+actor)
recipientsDict={}
if ':' not in actor: if ':' not in actor:
return recipientsDict return recipientsDict
httpPrefix=actor.split(':')[0] httpPrefix=actor.split(':')[0]
nickname=getNicknameFromActor(actor) nickname=getNicknameFromActor(actor)
if not nickname: if not nickname:
if debug:
print('DEBUG: no nickname found in '+actor)
return recipientsDict return recipientsDict
domain,port=getDomainFromActor(actor) domain,port=getDomainFromActor(actor)
if not domain: if not domain:
if debug:
print('DEBUG: no domain found in '+actor)
return recipientsDict return recipientsDict
actorHandle=nickname+'@'+domain actorHandle=nickname+'@'+domain
if debug:
print('DEBUG: searching for handle '+actorHandle)
# for each of the accounts # for each of the accounts
for subdir, dirs, files in os.walk(baseDir+'/accounts'): for subdir, dirs, files in os.walk(baseDir+'/accounts'):
for account in dirs: for account in dirs:
if '@' in account and not account.startswith('inbox@'): if '@' in account and not account.startswith('inbox@'):
followingFilename = os.path.join(subdir, account)+'/following.txt' followingFilename = os.path.join(subdir, account)+'/following.txt'
if debug:
print('DEBUG: examining follows of '+account)
print(followingFilename)
if os.path.isfile(followingFilename): if os.path.isfile(followingFilename):
# does this account follow the given actor? # does this account follow the given actor?
if debug:
print('DEBUG: checking if '+actorHandle+' in '+followingFilename)
if actorHandle in open(followingFilename).read(): if actorHandle in open(followingFilename).read():
if debug:
print('DEBUG: '+account+' follows '+actorHandle)
ocapFilename=baseDir+'/accounts/'+account+'/ocap/accept/'+httpPrefix+':##'+domain+':'+str(port)+'#users#'+nickname+'.json' ocapFilename=baseDir+'/accounts/'+account+'/ocap/accept/'+httpPrefix+':##'+domain+':'+str(port)+'#users#'+nickname+'.json'
if debug:
print('DEBUG: checking capabilities of'+account)
if os.path.isfile(ocapFilename): if os.path.isfile(ocapFilename):
with open(ocapFilename, 'r') as fp: with open(ocapFilename, 'r') as fp:
ocapJson=commentjson.load(fp) ocapJson=commentjson.load(fp)
if ocapJson.get('id'): if ocapJson.get('id'):
recipientsDict[account]=ocapJson['id'] if debug:
else: print('DEBUG: capabilities id found for '+account)
recipientsDict[account]=None
recipientsDict[account]=ocapJson['id']
else:
if debug:
print('DEBUG: capabilities has no id attribute')
recipientsDict[account]=None
else: else:
if debug:
print('DEBUG: No capabilities file found for '+account+' granted by '+actorHandle)
print(ocapFilename)
recipientsDict[account]=None recipientsDict[account]=None
return recipientsDict return recipientsDict

View File

@ -19,6 +19,7 @@ from utils import getStatusNumber
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import domainPermitted from utils import domainPermitted
from utils import locatePost
from httpsig import verifyPostHeaders from httpsig import verifyPostHeaders
from session import createSession from session import createSession
from session import getJson from session import getJson
@ -87,7 +88,7 @@ def inboxPermittedMessage(domain: str,messageJson: {},federationList: []) -> boo
if not urlPermitted(actor,federationList,"inbox:write"): if not urlPermitted(actor,federationList,"inbox:write"):
return False return False
if messageJson['type']!='Follow': if messageJson['type']!='Follow' and messageJson['type']!='Like':
if messageJson.get('object'): if messageJson.get('object'):
if messageJson['object'].get('inReplyTo'): if messageJson['object'].get('inReplyTo'):
inReplyTo=messageJson['object']['inReplyTo'] inReplyTo=messageJson['object']['inReplyTo']
@ -210,7 +211,7 @@ def inboxCheckCapabilities(baseDir :str,nickname :str,domain :str, \
def inboxPostRecipientsAdd(baseDir :str,httpPrefix :str,toList :[], \ def inboxPostRecipientsAdd(baseDir :str,httpPrefix :str,toList :[], \
recipientsDict :{}, \ recipientsDict :{}, \
domainMatch: str,domain :str, \ domainMatch: str,domain :str, \
actor :str) -> bool: actor :str,debug: bool) -> bool:
"""Given a list of post recipients (toList) from 'to' or 'cc' parameters """Given a list of post recipients (toList) from 'to' or 'cc' parameters
populate a recipientsDict with the handle and capabilities id for each populate a recipientsDict with the handle and capabilities id for each
""" """
@ -235,12 +236,23 @@ def inboxPostRecipientsAdd(baseDir :str,httpPrefix :str,toList :[], \
else: else:
recipientsDict[handle]=None recipientsDict[handle]=None
else: else:
if debug:
print('DEBUG: '+ocapFilename+' not found')
recipientsDict[handle]=None recipientsDict[handle]=None
else:
if debug:
print('DEBUG: '+baseDir+'/accounts/'+handle+' does not exist')
else:
if debug:
print('DEBUG: '+recipient+' is not local to '+domainMatch)
print(str(toList))
if recipient.endswith('followers'): if recipient.endswith('followers'):
if debug:
print('DEBUG: followers detected as post recipients')
followerRecipients=True followerRecipients=True
return followerRecipients,recipientsDict return followerRecipients,recipientsDict
def inboxPostRecipients(baseDir :str,postJsonObject :{},httpPrefix :str,domain : str,port :int) -> ([],[]): def inboxPostRecipients(baseDir :str,postJsonObject :{},httpPrefix :str,domain : str,port :int, debug :bool) -> ([],[]):
"""Returns dictionaries containing the recipients of the given post """Returns dictionaries containing the recipients of the given post
The shared dictionary contains followers The shared dictionary contains followers
""" """
@ -248,6 +260,9 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{},httpPrefix :str,domain :
recipientsDictFollowers={} recipientsDictFollowers={}
if not postJsonObject.get('actor'): if not postJsonObject.get('actor'):
if debug:
pprint(postJsonObject)
print('WARNING: inbox post has no actor')
return recipientsDict,recipientsDictFollowers return recipientsDict,recipientsDictFollowers
if ':' in domain: if ':' in domain:
@ -264,29 +279,48 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{},httpPrefix :str,domain :
if postJsonObject.get('object'): if postJsonObject.get('object'):
if isinstance(postJsonObject['object'], dict): if isinstance(postJsonObject['object'], dict):
if postJsonObject['object'].get('to'): if postJsonObject['object'].get('to'):
if debug:
print('DEBUG: resolving "to"')
includesFollowers,recipientsDict= \ includesFollowers,recipientsDict= \
inboxPostRecipientsAdd(baseDir,httpPrefix, \ inboxPostRecipientsAdd(baseDir,httpPrefix, \
postJsonObject['object']['to'], \ postJsonObject['object']['to'], \
recipientsDict, \ recipientsDict, \
domainMatch,domainBase,actor) domainMatch,domainBase, \
actor,debug)
if includesFollowers: if includesFollowers:
followerRecipients=True followerRecipients=True
else:
if debug:
print('DEBUG: inbox post has no "to"')
if postJsonObject['object'].get('cc'): if postJsonObject['object'].get('cc'):
includesFollowers,recipientsDict= \ includesFollowers,recipientsDict= \
inboxPostRecipientsAdd(baseDir,httpPrefix, \ inboxPostRecipientsAdd(baseDir,httpPrefix, \
postJsonObject['object']['cc'], \ postJsonObject['object']['cc'], \
recipientsDict, \ recipientsDict, \
domainMatch,domainBase,actor) domainMatch,domainBase, \
actor,debug)
if includesFollowers: if includesFollowers:
followerRecipients=True followerRecipients=True
else:
if debug:
print('DEBUG: inbox post has no cc')
else:
if debug:
if isinstance(postJsonObject['object'], str):
if '/statuses/' in postJsonObject['object']:
print('DEBUG: inbox item is a link to a post')
else:
if '/users/' in postJsonObject['object']:
print('DEBUG: inbox item is a link to an actor')
if postJsonObject.get('to'): if postJsonObject.get('to'):
includesFollowers,recipientsDict= \ includesFollowers,recipientsDict= \
inboxPostRecipientsAdd(baseDir,httpPrefix, \ inboxPostRecipientsAdd(baseDir,httpPrefix, \
postJsonObject['to'], \ postJsonObject['to'], \
recipientsDict, \ recipientsDict, \
domainMatch,domainBase,actor) domainMatch,domainBase, \
actor,debug)
if includesFollowers: if includesFollowers:
followerRecipients=True followerRecipients=True
@ -295,16 +329,19 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{},httpPrefix :str,domain :
inboxPostRecipientsAdd(baseDir,httpPrefix, \ inboxPostRecipientsAdd(baseDir,httpPrefix, \
postJsonObject['cc'], \ postJsonObject['cc'], \
recipientsDict, \ recipientsDict, \
domainMatch,domainBase,actor) domainMatch,domainBase, \
actor,debug)
if includesFollowers: if includesFollowers:
followerRecipients=True followerRecipients=True
if not followerRecipients: if not followerRecipients:
if debug:
print('DEBUG: no followers were resolved')
return recipientsDict,recipientsDictFollowers return recipientsDict,recipientsDictFollowers
# now resolve the followers # now resolve the followers
recipientsDictFollowers= \ recipientsDictFollowers= \
getFollowersOfActor(baseDir,actor,recipientsDict) getFollowersOfActor(baseDir,actor,debug)
return recipientsDict,recipientsDictFollowers return recipientsDict,recipientsDictFollowers
@ -388,18 +425,7 @@ def receiveLike(session,handle: str,baseDir: str, \
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
print('DEBUG: unknown recipient of like - '+handle) print('DEBUG: unknown recipient of like - '+handle)
# if this post in the outbox of the person? # if this post in the outbox of the person?
boxName='outbox' postFilename=locatePost(baseDir,handle.split('@')[0],handle.split('@')[1],messageJson['object'])
postFilename=baseDir+'/accounts/'+handle+'/'+boxName+'/'+messageJson['object'].replace('/','#')+'.json'
if not os.path.isfile(postFilename):
# if this post in the inbox of the person?
boxName='inbox'
postFilename=baseDir+'/accounts/'+handle+'/'+boxName+'/'+messageJson['object'].replace('/','#')+'.json'
if not os.path.isfile(postFilename):
# if this post in the shared inbox?
handle='inbox@'+domain
postFilename=baseDir+'/accounts/'+handle+'/'+boxName+'/'+messageJson['object'].replace('/','#')+'.json'
if not os.path.isfile(postFilename):
postFilename=None
if not postFilename: if not postFilename:
if debug: if debug:
print('DEBUG: post not found in inbox or outbox') print('DEBUG: post not found in inbox or outbox')
@ -569,7 +595,15 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
# get recipients list # get recipients list
recipientsDict,recipientsDictFollowers= \ recipientsDict,recipientsDictFollowers= \
inboxPostRecipients(baseDir,queueJson['post'],httpPrefix,domain,port) inboxPostRecipients(baseDir,queueJson['post'],httpPrefix,domain,port,debug)
if len(recipientsDict.items())==0 and \
len(recipientsDictFollowers.items())==0:
if debug:
pprint(queueJson['post'])
print('DEBUG: no recipients were resolved for post arriving in inbox')
os.remove(queueFilename)
queue.pop(0)
continue
# if there are only a small number of followers then process them as if they # if there are only a small number of followers then process them as if they
# were specifically addresses to particular accounts # were specifically addresses to particular accounts
@ -579,7 +613,7 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
if debug: if debug:
print('DEBUG: moving '+str(noOfFollowItems)+' inbox posts addressed to followers') print('DEBUG: moving '+str(noOfFollowItems)+' inbox posts addressed to followers')
for handle,postItem in recipientsDictFollowers.items(): for handle,postItem in recipientsDictFollowers.items():
recipientsDict['handle']=postItem recipientsDict[handle]=postItem
recipientsDictFollowers={} recipientsDictFollowers={}
recipientsList=[recipientsDict,recipientsDictFollowers] recipientsList=[recipientsDict,recipientsDictFollowers]
@ -587,7 +621,7 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
print('*************************************') print('*************************************')
print('Resolved recipients list:') print('Resolved recipients list:')
pprint(recipientsDict) pprint(recipientsDict)
print('Resolved sollowers list:') print('Resolved followers list:')
pprint(recipientsDictFollowers) pprint(recipientsDictFollowers)
print('*************************************') print('*************************************')

174
like.py
View File

@ -8,80 +8,28 @@ __status__ = "Production"
import json import json
import commentjson import commentjson
from pprint import pprint
from utils import urlPermitted from utils import urlPermitted
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import locatePost
from posts import sendSignedJson
def like(session,baseDir: str,federationList: [],nickname: str,domain: str,port: int, \ def updateLikesCollection(postFilename: str,objectUrl: str, actor: str,debug: bool) -> None:
ccUrl: str,httpPrefix: str,objectUrl: str,clientToServer: bool, \
sendThreads: [],postLog: [],personCache: {},cachedWebfingers: {}) -> {}:
"""Creates a like
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,"inbox:write"):
return None
if port!=80 and port!=443:
domain=domain+':'+str(port)
newLikeJson = {
'type': 'Like',
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'object': objectUrl,
'to': [httpPrefix+'://'+domain+'/users/'+nickname+'/followers'],
'cc': []
}
if ccUrl:
if len(ccUrl)>0:
newLikeJson['cc']=ccUrl
# Extract the domain and nickname from a statuses link
likedPostNickname=None
likedPostDomain=None
likedPostPort=None
if '/users/' in objectUrl:
likedPostNickname=getNicknameFromActor(objectUrl)
likedPostDomain,likedPostPort=getDomainFromActor(objectUrl)
if likedPostNickname:
sendSignedJson(newlikeJson,session,baseDir, \
nickname,domain,port, \
likedPostNickname,likedPostDomain,likedPostPort, \
'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix,True,clientToServer,federationList, \
sendThreads,postLog,cachedWebfingers,personCache,debug)
return newLikeJson
def likePost(session,baseDir: str,federationList: [], \
nickname: str, domain: str, port: int, httpPrefix: str, \
likeNickname: str, likeDomain: str, likePort: int, \
likeHttps: bool, likeStatusNumber: int, \
clientToServer: bool,sendThreads: [],postLog: [], \
personCache: {},cachedWebfingers: {}) -> {}:
"""Likes a given status post
"""
likeDomain=likeDomain
if likePort!=80 and likePort!=443:
likeDomain=likeDomain+':'+str(likePort)
objectUrl = \
httpPrefix + '://'+likeDomain+'/users/'+likeNickname+ \
'/statuses/'+str(likeStatusNumber)
return like(session,baseDir,federationList,nickname,domain,port, \
ccUrl,httpPrefix,objectUrl,clientToServer, \
sendThreads,postLog,personCache,cachedWebfingers)
def updateLikesCollection(postFilename: str,objectUrl: str, actor: str) -> None:
"""Updates the likes collection within a post """Updates the likes collection within a post
""" """
with open(postFilename, 'r') as fp: with open(postFilename, 'r') as fp:
postJson=commentjson.load(fp) postJson=commentjson.load(fp)
if not postJson.get('object'):
if debug:
pprint(postJson)
print('DEBUG: post '+objectUrl+' has no object')
return
if not objectUrl.endswith('/likes'): if not objectUrl.endswith('/likes'):
objectUrl=objectUrl+'/likes' objectUrl=objectUrl+'/likes'
if not postJson.get('likes'): if not postJson['object'].get('likes'):
if debug:
print('DEBUG: Adding initial likes to '+objectUrl)
likesJson = { likesJson = {
'id': objectUrl, 'id': objectUrl,
'type': 'Collection', 'type': 'Collection',
@ -92,17 +40,103 @@ def updateLikesCollection(postFilename: str,objectUrl: str, actor: str) -> None:
}] }]
} }
postJson['likes']=likesJson postJson['object']['likes']=likesJson
else: else:
if postJson['likes'].get('items'): if postJson['object']['likes'].get('items'):
for likeItem in postJson['likes']['items']: for likeItem in postJson['likes']['items']:
if likeItem['actor']==actor: if likeItem.get('actor'):
return if likeItem['actor']==actor:
return
newLike={ newLike={
'type': 'Like', 'type': 'Like',
'actor': actor 'actor': actor
} }
postJson['likes']['items'].append(newLike) postJson['object']['likes']['items'].append(newLike)
postJson['likes']['totalItems']=len(postJson['likes']['items']) postJson['object']['likes']['totalItems']=len(postJson['likes']['items'])
else:
if debug:
print('DEBUG: likes section of post has no items list')
if debug:
print('DEBUG: saving post with likes added')
with open(postFilename, 'w') as fp: with open(postFilename, 'w') as fp:
commentjson.dump(postJson, fp, indent=4, sort_keys=True) commentjson.dump(postJson, fp, indent=4, sort_keys=True)
def like(session,baseDir: str,federationList: [],nickname: str,domain: str,port: int, \
ccList: [],httpPrefix: str,objectUrl: str,clientToServer: bool, \
sendThreads: [],postLog: [],personCache: {},cachedWebfingers: {}, \
debug: bool) -> {}:
"""Creates a like
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,"inbox:write"):
return None
fullDomain=domain
if port!=80 and port!=443:
if ':' not in domain:
fullDomain=domain+':'+str(port)
newLikeJson = {
'type': 'Like',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'object': objectUrl,
'to': [httpPrefix+'://'+fullDomain+'/users/'+nickname+'/followers'],
'cc': []
}
if ccList:
if len(ccList)>0:
newLikeJson['cc']=ccList
# Extract the domain and nickname from a statuses link
likedPostNickname=None
likedPostDomain=None
likedPostPort=None
if '/users/' in objectUrl:
likedPostNickname=getNicknameFromActor(objectUrl)
likedPostDomain,likedPostPort=getDomainFromActor(objectUrl)
if likedPostNickname:
postFilename=locatePost(baseDir,nickname,domain,objectUrl)
if not postFilename:
return None
updateLikesCollection(postFilename,objectUrl,newLikeJson['actor'],debug)
sendSignedJson(newLikeJson,session,baseDir, \
nickname,domain,port, \
likedPostNickname,likedPostDomain,likedPostPort, \
'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix,True,clientToServer,federationList, \
sendThreads,postLog,cachedWebfingers,personCache,debug)
return newLikeJson
def likePost(session,baseDir: str,federationList: [], \
nickname: str,domain: str,port: int,httpPrefix: str, \
likeNickname: str,likeDomain: str,likePort: int, \
ccList: [], \
likeStatusNumber: int,clientToServer: bool, \
sendThreads: [],postLog: [], \
personCache: {},cachedWebfingers: {}, \
debug: bool) -> {}:
"""Likes a given status post
"""
likeDomain=likeDomain
if likePort!=80 and likePort!=443:
likeDomain=likeDomain+':'+str(likePort)
objectUrl = \
httpPrefix + '://'+likeDomain+'/users/'+likeNickname+ \
'/statuses/'+str(likeStatusNumber)
if likePort!=80 and likePort!=443:
ccUrl=httpPrefix+'://'+likeDomain+':'+str(likePort)+'/users/'+likeNickname
else:
ccUrl=httpPrefix+'://'+likeDomain+'/users/'+likeNickname
return like(session,baseDir,federationList,nickname,domain,port, \
ccList,httpPrefix,objectUrl,clientToServer, \
sendThreads,postLog,personCache,cachedWebfingers,debug)

View File

@ -88,6 +88,14 @@ def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \
os.mkdir(baseDir+peopleSubdir) os.mkdir(baseDir+peopleSubdir)
if not os.path.isdir(baseDir+peopleSubdir+'/'+handle): if not os.path.isdir(baseDir+peopleSubdir+'/'+handle):
os.mkdir(baseDir+peopleSubdir+'/'+handle) os.mkdir(baseDir+peopleSubdir+'/'+handle)
if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/inbox'):
os.mkdir(baseDir+peopleSubdir+'/'+handle+'/inbox')
if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/outbox'):
os.mkdir(baseDir+peopleSubdir+'/'+handle+'/outbox')
if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/ocap'):
os.mkdir(baseDir+peopleSubdir+'/'+handle+'/ocap')
if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/queue'):
os.mkdir(baseDir+peopleSubdir+'/'+handle+'/queue')
filename=baseDir+peopleSubdir+'/'+handle+'.json' filename=baseDir+peopleSubdir+'/'+handle+'.json'
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
commentjson.dump(newPerson, fp, indent=4, sort_keys=False) commentjson.dump(newPerson, fp, indent=4, sort_keys=False)

View File

@ -36,13 +36,13 @@ from follow import unfollowPerson
from follow import unfollowerOfPerson from follow import unfollowerOfPerson
from follow import getFollowersOfPerson from follow import getFollowersOfPerson
from follow import sendFollowRequest from follow import sendFollowRequest
from follow import getFollowersOfActor
from person import createPerson from person import createPerson
from person import setPreferredNickname from person import setPreferredNickname
from person import setBio from person import setBio
from auth import createBasicAuthHeader from auth import createBasicAuthHeader
from auth import authorizeBasic from auth import authorizeBasic
from auth import storeBasicCredentials from auth import storeBasicCredentials
from like import likePost
testServerAliceRunning = False testServerAliceRunning = False
testServerBobRunning = False testServerBobRunning = False
@ -210,7 +210,6 @@ def testPostMessageBetweenServers():
httpPrefix='http' httpPrefix='http'
useTor=False useTor=False
federationList=['127.0.0.50','127.0.0.100']
baseDir=os.getcwd() baseDir=os.getcwd()
if os.path.isdir(baseDir+'/.tests'): if os.path.isdir(baseDir+'/.tests'):
@ -223,12 +222,13 @@ def testPostMessageBetweenServers():
aliceDir=baseDir+'/.tests/alice' aliceDir=baseDir+'/.tests/alice'
aliceDomain='127.0.0.50' aliceDomain='127.0.0.50'
alicePort=61935 alicePort=61935
thrAlice = threadWithTrace(target=createServerAlice,args=(aliceDir,aliceDomain,alicePort,federationList,True,True,ocapAlways),daemon=True)
bobDir=baseDir+'/.tests/bob' bobDir=baseDir+'/.tests/bob'
bobDomain='127.0.0.100' bobDomain='127.0.0.100'
bobPort=61936 bobPort=61936
thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobDomain,bobPort,federationList,True,True,ocapAlways),daemon=True) federationList=[bobDomain,aliceDomain]
thrAlice = threadWithTrace(target=createServerAlice,args=(aliceDir,aliceDomain,alicePort,federationList,False,False,ocapAlways),daemon=True)
thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobDomain,bobPort,federationList,False,False,ocapAlways),daemon=True)
thrAlice.start() thrAlice.start()
thrBob.start() thrBob.start()
@ -241,6 +241,7 @@ def testPostMessageBetweenServers():
time.sleep(1) time.sleep(1)
print('\n\n*******************************************************')
print('Alice sends to Bob') print('Alice sends to Bob')
os.chdir(aliceDir) os.chdir(aliceDir)
sessionAlice = createSession(aliceDomain,alicePort,useTor) sessionAlice = createSession(aliceDomain,alicePort,useTor)
@ -255,6 +256,11 @@ def testPostMessageBetweenServers():
ccUrl=None ccUrl=None
alicePersonCache={} alicePersonCache={}
aliceCachedWebfingers={} aliceCachedWebfingers={}
# nothing in Alice's outbox
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 = 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) 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)) print('sendResult: '+str(sendResult))
@ -263,9 +269,53 @@ def testPostMessageBetweenServers():
for i in range(30): for i in range(30):
if os.path.isdir(inboxPath): if os.path.isdir(inboxPath):
if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>0: if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>0:
break if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1:
break
time.sleep(1) time.sleep(1)
# inbox item created
assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
# queue item removed
assert len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))])==0
#print('\n\n*******************************************************')
#print("Bob likes Alice's post")
#followerOfPerson(bobDir,'bob',bobDomain,'alice',aliceDomain+':'+str(alicePort),federationList,True)
#followPerson(aliceDir,'alice',aliceDomain,'bob',bobDomain+':'+str(bobPort),federationList,True)
#followList=getFollowersOfPerson(bobDir,'bob',bobDomain,'followers.txt')
#assert len(followList)==1
#sessionBob = createSession(bobDomain,bobPort,useTor)
#bobSendThreads = []
#bobPostLog = []
#bobPersonCache={}
#bobCachedWebfingers={}
#statusNumber=None
#outboxPostFilename=None
#outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
#for name in os.listdir(outboxPath):
# if '#statuses#' in name:
# statusNumber=int(name.split('#statuses#')[1].replace('.json',''))
# outboxPostFilename=outboxPath+'/'+name
#assert statusNumber
#assert outboxPostFilename
#assert likePost(sessionBob,bobDir,federationList, \
# 'bob',bobDomain,bobPort,httpPrefix, \
# 'alice',aliceDomain,alicePort,[], \
# statusNumber,False,bobSendThreads,bobPostLog, \
# bobPersonCache,bobCachedWebfingers,True)
#for i in range(20):
# if 'likes' in open(outboxPostFilename).read():
# break
# time.sleep(1)
#with open(outboxPostFilename, 'r') as fp:
# alicePostJson=commentjson.load(fp)
# pprint(alicePostJson)
#assert 'likes' in open(outboxPostFilename).read()
# stop the servers # stop the servers
thrAlice.kill() thrAlice.kill()
thrAlice.join() thrAlice.join()
@ -275,11 +325,6 @@ def testPostMessageBetweenServers():
thrBob.join() thrBob.join()
assert thrBob.isAlive()==False assert thrBob.isAlive()==False
# inbox item created
assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
# 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) os.chdir(baseDir)
shutil.rmtree(aliceDir) shutil.rmtree(aliceDir)
shutil.rmtree(bobDir) shutil.rmtree(bobDir)
@ -439,7 +484,7 @@ def testFollowBetweenServers():
assert aliceMessageArrived==True assert aliceMessageArrived==True
print('Message from Alice to Bob succeeded, since it was granted capabilities') print('Message from Alice to Bob succeeded, since it was granted capabilities')
print('\n\n*********************************************************') print('\n\n*********************************************************')
print("\nBob changes Alice's capabilities so that she can't reply on his posts") print("\nBob changes Alice's capabilities so that she can't reply on his posts")
bobCapsFilename=bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json' bobCapsFilename=bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json'

View File

@ -44,6 +44,8 @@ def createInboxQueueDir(nickname: str,domain: str,baseDir: str) -> str:
def domainPermitted(domain: str, federationList: []): def domainPermitted(domain: str, federationList: []):
if len(federationList)==0: if len(federationList)==0:
return True return True
if ':' in domain:
domain=domain.split(':')[0]
if domain in federationList: if domain in federationList:
return True return True
return False return False
@ -91,6 +93,8 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
if debug: if debug:
print('DEBUG: follow of domain '+followDomain+' not permitted') print('DEBUG: follow of domain '+followDomain+' not permitted')
return False return False
if debug:
print('DEBUG: follow of domain '+followDomain)
handle=nickname.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
handleToFollow=followNickname.lower()+'@'+followDomain.lower() handleToFollow=followNickname.lower()+'@'+followDomain.lower()
if not os.path.isdir(baseDir+'/accounts'): if not os.path.isdir(baseDir+'/accounts'):
@ -100,10 +104,33 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
filename=baseDir+'/accounts/'+handle+'/'+followFile filename=baseDir+'/accounts/'+handle+'/'+followFile
if os.path.isfile(filename): if os.path.isfile(filename):
if handleToFollow in open(filename).read(): if handleToFollow in open(filename).read():
if debug:
print('DEBUG: follow already exists')
return True return True
with open(filename, "a") as followfile: with open(filename, "a") as followfile:
followfile.write(handleToFollow+'\n') followfile.write(handleToFollow+'\n')
if debug:
print('DEBUG: follow added')
return True return True
if debug:
print('DEBUG: creating new following file')
with open(filename, "w") as followfile: with open(filename, "w") as followfile:
followfile.write(handleToFollow+'\n') followfile.write(handleToFollow+'\n')
return True return True
def locatePost(baseDir: str,nickname: str,domain: str,postUrl: str):
"""Returns the filename for the given status post url
"""
boxName='outbox'
postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.json'
if not os.path.isfile(postFilename):
# if this post in the inbox of the person?
boxName='inbox'
postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.json'
if not os.path.isfile(postFilename):
# if this post in the shared inbox?
handle='inbox@'+domain
postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.json'
if not os.path.isfile(postFilename):
postFilename=None
return postFilename