def outboxUndoFollow(baseDir: str,messageJson: {},debug: bool) -> None: """When an unfollow request is received by the outbox from c2s This removes the followed handle from the following.txt file of the relevant account TODO the unfollow should also be sent to the previously followed account """ if not messageJson.get('type'): return if not messageJson['type']=='Undo': return if not messageJson.get('object'): return if not isinstance(messageJson['object'], dict): return if not messageJson['object'].get('type'): return if not messageJson['object']['type']=='Follow': return if not messageJson['object'].get('object'): return if not messageJson['object'].get('actor'): return if not isinstance(messageJson['object']['object'], str): return if debug: print('DEBUG: undo follow arrived in outbox') nicknameFollower=getNicknameFromActor(messageJson['object']['actor']) if not nicknameFollower: print('WARN: unable to find nickname in '+messageJson['object']['actor']) return domainFollower,portFollower=getDomainFromActor(messageJson['object']['actor']) domainFollowerFull=domainFollower if portFollower: if portFollower!=80 and portFollower!=443: if ':' not in domainFollower: domainFollowerFull=domainFollower+':'+str(portFollower) nicknameFollowing=getNicknameFromActor(messageJson['object']['object']) if not nicknameFollowing: print('WARN: unable to find nickname in '+messageJson['object']['object']) return domainFollowing,portFollowing=getDomainFromActor(messageJson['object']['object']) domainFollowingFull=domainFollowing if portFollowing: if portFollowing!=80 and portFollowing!=443: if ':' not in domainFollowing: domainFollowingFull=domainFollowing+':'+str(portFollowing) if unfollowPerson(baseDir,nicknameFollower,domainFollowerFull, \ nicknameFollowing,domainFollowingFull): if debug: print('DEBUG: '+nicknameFollower+' unfollowed '+nicknameFollowing+'@'+domainFollowingFull) else: if debug: print('WARN: '+nicknameFollower+' could not unfollow '+nicknameFollowing+'@'+domainFollowingFull)
def outboxSkills(baseDir: str, nickname: str, messageJson: {}, debug: bool) -> bool: """Handles receiving a skills update """ if not messageJson.get('type'): return False if not messageJson['type'] == 'Skill': return False if not messageJson.get('actor'): return False if not messageJson.get('object'): return False if not isinstance(messageJson['object'], str): return False actorNickname = getNicknameFromActor(messageJson['actor']) if actorNickname != nickname: return False domain, port = getDomainFromActor(messageJson['actor']) skill = messageJson['object'].replace('"', '').split(';')[0].strip() skillLevelPercent = int(messageJson['object'].replace( '"', '').split(';')[1].strip()) return setSkillLevel(baseDir,nickname,domain, \ skill,skillLevelPercent)
def isFollowingActor(baseDir: str,nickname: str,domain: str,actor: str) -> bool: """Is the given actor a follower of the given nickname? """ if ':' in domain: domain=domain.split(':')[0] handle=nickname+'@'+domain if not os.path.isdir(baseDir+'/accounts/'+handle): return False followingFile=baseDir+'/accounts/'+handle+'/following.txt' if not os.path.isfile(followingFile): return False if actor in open(followingFile).read(): return True followingNickname=getNicknameFromActor(actor) if not followingNickname: print('WARN: unable to find nickname in '+actor) return False followingDomain,followingPort=getDomainFromActor(actor) followingHandle=followingNickname+'@'+followingDomain if followingPort: if followingPort!=80 and followingPort!=443: if ':' not in followingHandle: followingHandle+=':'+str(followingPort) if followingHandle in open(followingFile).read(): return True return False
def undoAnnounce(session,baseDir: str,federationList: [], \ nickname: str, domain: str, port: int, \ toUrl: str, ccUrl: str, httpPrefix: str, \ objectUrl: str, saveToFile: bool, \ clientToServer: bool, \ sendThreads: [],postLog: [], \ personCache: {},cachedWebfingers: {}, \ debug: bool) -> {}: """Undoes an announce message Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person whose post was repeated and the objectUrl is typically the url of the message which was repeated, corresponding to url or atomUri in createPostBase """ if not urlPermitted(objectUrl,federationList,"inbox:write"): return None if ':' in domain: domain=domain.split(':')[0] fullDomain=domain if port: if port!=80 and port!=443: if ':' not in domain: fullDomain=domain+':'+str(port) newUndoAnnounce = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'type': 'Undo', 'cc': [], 'to': [toUrl], 'object': { 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'cc': [], 'object': objectUrl, 'to': [toUrl], 'type': 'Announce' } } if ccUrl: if len(ccUrl)>0: newUndoAnnounce['object']['cc']=[ccUrl] announceNickname=None announceDomain=None announcePort=None if '/users/' in objectUrl or '/profile/' in objectUrl: announceNickname=getNicknameFromActor(objectUrl) announceDomain,announcePort=getDomainFromActor(objectUrl) if announceNickname and announceDomain: sendSignedJson(newUndoAnnounce,session,baseDir, \ nickname,domain,port, \ announceNickname,announceDomain,announcePort, \ 'https://www.w3.org/ns/activitystreams#Public', \ httpPrefix,True,clientToServer,federationList, \ sendThreads,postLog,cachedWebfingers,personCache,debug) return newUndoAnnounce
def capabilitiesAccept(baseDir: str,httpPrefix: str, \ nickname: str,domain: str, port: int, \ acceptedActor: str, saveToFile: bool, \ acceptedCaps=["inbox:write","objects:read"]) -> {}: # This gets returned to capabilities requester # This could also be added to a follow Accept activity # reject excessively long actors if len(acceptedActor)>256: return None fullDomain=domain if port: if port!=80 and port !=443: if ':' not in domain: fullDomain=domain+':'+str(port) # make directories to store capabilities ocapFilename= \ getOcapFilename(baseDir,nickname,fullDomain,acceptedActor,'accept') if not ocapFilename: return None ocapAccept=None # if the capability already exists then load it from file if os.path.isfile(ocapFilename): with open(ocapFilename, 'r') as fp: ocapAccept=commentjson.load(fp) # otherwise create a new capability if not ocapAccept: acceptedActorNickname=getNicknameFromActor(acceptedActor) if not acceptedActorNickname: print('WARN: unable to find nickname in '+acceptedActor) return None acceptedActorDomain,acceptedActorPort=getDomainFromActor(acceptedActor) if acceptedActorPort: ocapId=acceptedActorNickname+'@'+acceptedActorDomain+':'+ \ str(acceptedActorPort)+'#'+createPassword(32) else: ocapId=acceptedActorNickname+'@'+acceptedActorDomain+'#'+ \ createPassword(32) ocapAccept = { "@context": "https://www.w3.org/ns/activitystreams", "id": httpPrefix+"://"+fullDomain+"/caps/"+ocapId, "type": "Capability", "capability": acceptedCaps, "scope": acceptedActor, "actor": httpPrefix+"://"+fullDomain } if nickname: ocapAccept['actor']=httpPrefix+"://"+fullDomain+'/users/'+nickname if saveToFile: with open(ocapFilename, 'w') as fp: commentjson.dump(ocapAccept, fp, indent=4, sort_keys=False) return ocapAccept
def outboxAnnounce(baseDir: str,messageJson: {},debug: bool) -> bool: """ Adds or removes announce entries from the shares collection within a given post """ if not messageJson.get('actor'): return False if not messageJson.get('type'): return False if not messageJson.get('object'): return False if messageJson['type']=='Announce': if not isinstance(messageJson['object'], str): return False nickname=getNicknameFromActor(messageJson['actor']) if not nickname: print('WARN: no nickname found in '+messageJson['actor']) return False domain,port=getDomainFromActor(messageJson['actor']) postFilename=locatePost(baseDir,nickname,domain,messageJson['object']) if postFilename: updateAnnounceCollection(postFilename,messageJson['actor'],debug) return True if messageJson['type']=='Undo': if not isinstance(messageJson['object'], dict): return False if not messageJson['object'].get('type'): return False if messageJson['object']['type']=='Announce': if not isinstance(messageJson['object']['object'], str): return False nickname=getNicknameFromActor(messageJson['actor']) if not nickname: print('WARN: no nickname found in '+messageJson['actor']) return False domain,port=getDomainFromActor(messageJson['actor']) postFilename=locatePost(baseDir,nickname,domain,messageJson['object']['object']) if postFilename: undoAnnounceCollectionEntry(postFilename,messageJson['actor'],debug) return True return False
def outboxAvailability(baseDir: str,nickname: str,messageJson: {}, \ debug: bool) -> bool: """Handles receiving an availability update """ if not messageJson.get('type'): return False if not messageJson['type'] == 'Availability': return False if not messageJson.get('actor'): return False if not messageJson.get('object'): return False if not isinstance(messageJson['object'], str): return False actorNickname = getNicknameFromActor(messageJson['actor']) if actorNickname != nickname: return False domain, port = getDomainFromActor(messageJson['actor']) status = messageJson['object'].replace('"', '') return setAvailability(baseDir, nickname, domain, status)
def receiveAcceptReject(session,baseDir: str, \ httpPrefix: str,domain :str,port: int, \ sendThreads: [],postLog: [],cachedWebfingers: {}, \ personCache: {},messageJson: {},federationList: [], \ debug : bool) -> bool: """Receives an Accept or Reject within the POST section of HTTPServer """ if messageJson['type'] != 'Accept' and messageJson['type'] != 'Reject': return False if not messageJson.get('actor'): if debug: print('DEBUG: ' + messageJson['type'] + ' has no actor') return False if '/users/' not in messageJson[ 'actor'] and '/profile/' not in messageJson['actor']: if debug: print('DEBUG: "users" or "profile" 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() # receive follow accept acceptFollow(baseDir, domain, messageJson, federationList, debug) if debug: print('DEBUG: Uh, ' + messageJson['type'] + ', I guess') return True
def acceptFollow(baseDir: str,domain : str,messageJson: {}, \ federationList: [],debug : bool) -> None: """Receiving a follow Accept activity """ if not messageJson.get('object'): return if not messageJson['object'].get('type'): return if not messageJson['object'].get('actor'): return # no, this isn't a mistake if not messageJson['object'].get('object'): return if not messageJson['object']['type'] == 'Follow': return if not messageJson.get('to'): if debug: print('DEBUG: No "to" parameter in follow Accept') return #if len(messageJson['object']['to'])!=1: # if debug: # print('DEBUG: "to" does not contain a single recipient') # print(str(messageJson['object']['to'])) # if messageJson['object'].get('object'): # if not isinstance(messageJson['object']['object'], str): # messageJson['object']['to']=messageJson['object']['object'] # else: # return # else: # return if debug: print('DEBUG: follow Accept received') thisActor = messageJson['object']['actor'] nickname = getNicknameFromActor(thisActor) if not nickname: print('WARN: no nickname found in ' + thisActor) return acceptedDomain, acceptedPort = getDomainFromActor(thisActor) if not acceptedDomain: if debug: print('DEBUG: domain not found in ' + thisActor) return #if acceptedDomain != domain: # if debug: # print('DEBUG: domain mismatch '+acceptedDomain+' != '+domain) # return if not nickname: if debug: print('DEBUG: nickname not found in ' + thisActor) return if acceptedPort: if '/' + acceptedDomain + ':' + str( acceptedPort) + '/users/' + nickname not in thisActor: if debug: print('Port: ' + str(acceptedPort)) print('Expected: /' + acceptedDomain + ':' + str(acceptedPort) + '/users/' + nickname) print('Actual: ' + thisActor) print('DEBUG: unrecognized actor ' + thisActor) return else: if '/' + acceptedDomain + '/users/' + nickname not in thisActor: if debug: print('Expected: /' + acceptedDomain + '/users/' + nickname) print('Actual: ' + thisActor) print('DEBUG: unrecognized actor ' + thisActor) return followedActor = messageJson['object']['object'] followedDomain, port = getDomainFromActor(followedActor) if not followedDomain: return followedDomainFull = followedDomain if port: followedDomainFull = followedDomain + ':' + str(port) followedNickname = getNicknameFromActor(followedActor) if not followedNickname: return acceptedDomainFull = acceptedDomain if acceptedPort: acceptedDomainFull = acceptedDomain + ':' + str(acceptedPort) # are capabilities attached? If so then store them if messageJson.get('capabilities'): if isinstance(messageJson['capabilities'], dict): capabilitiesGrantedSave(baseDir, \ nickname,acceptedDomainFull, \ messageJson['capabilities']) if followPerson(baseDir, \ nickname,acceptedDomainFull, \ followedNickname,followedDomainFull, \ federationList,debug): if debug: print('DEBUG: ' + nickname + '@' + acceptedDomainFull + ' followed ' + followedNickname + '@' + followedDomainFull) else: if debug: print('DEBUG: Unable to create follow - ' + nickname + '@' + acceptedDomain + ' -> ' + followedNickname + '@' + followedDomain)
def createDelete(session,baseDir: str,federationList: [], \ nickname: str, domain: str, port: int, \ toUrl: str, ccUrl: str, httpPrefix: str, \ objectUrl: str,clientToServer: bool, \ sendThreads: [],postLog: [], \ personCache: {},cachedWebfingers: {}, \ debug: bool) -> {}: """Creates a delete message Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person whose post is to be deleted 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 ':' in domain: domain = domain.split(':')[0] fullDomain = domain if port: if port != 80 and port != 443: if ':' not in domain: fullDomain = domain + ':' + str(port) statusNumber, published = getStatusNumber() newDeleteId= \ httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber newDelete = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': httpPrefix + '://' + fullDomain + '/users/' + nickname, 'atomUri': httpPrefix + '://' + fullDomain + '/users/' + nickname + '/statuses/' + statusNumber, 'cc': [], 'id': newDeleteId + '/activity', 'object': objectUrl, 'published': published, 'to': [toUrl], 'type': 'Delete' } if ccUrl: if len(ccUrl) > 0: newDelete['cc'] = [ccUrl] deleteNickname = None deleteDomain = None deletePort = None if '/users/' in objectUrl or '/profile/' in objectUrl: deleteNickname = getNicknameFromActor(objectUrl) deleteDomain, deletePort = getDomainFromActor(objectUrl) if deleteNickname and deleteDomain: sendSignedJson(newDelete,session,baseDir, \ nickname,domain,port, \ deleteNickname,deleteDomain,deletePort, \ 'https://www.w3.org/ns/activitystreams#Public', \ httpPrefix,True,clientToServer,federationList, \ sendThreads,postLog,cachedWebfingers,personCache,debug) return newDelete
def outboxDelete(baseDir: str,httpPrefix: str, \ nickname: str,domain: str, \ messageJson: {},debug: bool, allowDeletion: bool) -> None: """ When a delete request is received by the outbox from c2s """ if not messageJson.get('type'): if debug: print('DEBUG: delete - no type') return if not messageJson['type'] == 'Delete': if debug: print('DEBUG: not a delete') return if not messageJson.get('object'): if debug: print('DEBUG: no object in delete') return if not isinstance(messageJson['object'], str): if debug: print('DEBUG: delete object is not string') return if debug: print('DEBUG: c2s delete request arrived in outbox') deletePrefix = httpPrefix + '://' + domain if not allowDeletion and \ (not messageJson['object'].startswith(deletePrefix) or \ not messageJson['actor'].startswith(deletePrefix)): if debug: print('DEBUG: delete not permitted from other instances') return messageId = messageJson['object'].replace('/activity', '') if '/statuses/' not in messageId: if debug: print('DEBUG: c2s delete object is not a status') return if '/users/' not in messageId and '/profile/' not in messageId: if debug: print('DEBUG: c2s delete object has no nickname') return deleteNickname = getNicknameFromActor(messageId) if deleteNickname != nickname: if debug: print( "DEBUG: you can't delete a post which wasn't created by you (nickname does not match)" ) return deleteDomain, deletePort = getDomainFromActor(messageId) if ':' in domain: domain = domain.split(':')[0] if deleteDomain != domain: if debug: print( "DEBUG: you can't delete a post which wasn't created by you (domain does not match)" ) return removeModerationPostFromIndex(baseDir, messageId, debug) postFilename = locatePost(baseDir, deleteNickname, deleteDomain, messageId) if not postFilename: if debug: print('DEBUG: c2s delete post not found in inbox or outbox') print(messageId) return True deletePost(baseDir, httpPrefix, deleteNickname, deleteDomain, postFilename, debug) if debug: print('DEBUG: post deleted via c2s - ' + postFilename)
def createAnnounce(session,baseDir: str,federationList: [], \ nickname: str, domain: str, port: int, \ toUrl: str, ccUrl: str, httpPrefix: str, \ objectUrl: str, saveToFile: bool, \ clientToServer: bool, \ sendThreads: [],postLog: [], \ personCache: {},cachedWebfingers: {}, \ debug: bool,projectVersion: str) -> {}: """Creates an announce message Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person favorited or repeated and the followers url 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 ':' in domain: domain=domain.split(':')[0] fullDomain=domain if port: if port!=80 and port!=443: if ':' not in domain: fullDomain=domain+':'+str(port) statusNumber,published = getStatusNumber() newAnnounceId= \ httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber newAnnounce = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber, 'cc': [], 'id': newAnnounceId+'/activity', 'object': objectUrl, 'published': published, 'to': [toUrl], 'type': 'Announce' } if ccUrl: if len(ccUrl)>0: newAnnounce['cc']=[ccUrl] if saveToFile: outboxDir = createOutboxDir(nickname,domain,baseDir) filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json' with open(filename, 'w') as fp: commentjson.dump(newAnnounce, fp, indent=4, sort_keys=False) announceNickname=None announceDomain=None announcePort=None if '/users/' in objectUrl or '/profile/' in objectUrl: announceNickname=getNicknameFromActor(objectUrl) announceDomain,announcePort=getDomainFromActor(objectUrl) if announceNickname and announceDomain: sendSignedJson(newAnnounce,session,baseDir, \ nickname,domain,port, \ announceNickname,announceDomain,announcePort, \ 'https://www.w3.org/ns/activitystreams#Public', \ httpPrefix,True,clientToServer,federationList, \ sendThreads,postLog,cachedWebfingers,personCache, \ debug,projectVersion) return newAnnounce
def outboxDelegate(baseDir: str, authenticatedNickname: str, messageJson: {}, debug: bool) -> bool: """Handles receiving a delegation request """ if not messageJson.get('type'): return False if not messageJson['type'] == 'Delegate': return False if not messageJson.get('object'): return False if not isinstance(messageJson['object'], dict): return False if not messageJson['object'].get('type'): return False if not messageJson['object']['type'] == 'Role': return False if not messageJson['object'].get('object'): return False if not messageJson['object'].get('actor'): return False if not isinstance(messageJson['object']['object'], str): return False if ';' not in messageJson['object']['object']: print('WARN: No ; separator between project and role') return False delegatorNickname = getNicknameFromActor(messageJson['actor']) if delegatorNickname != authenticatedNickname: return domain, port = getDomainFromActor(messageJson['actor']) project = messageJson['object']['object'].split(';')[0].strip() # instance delegators can delagate to other projects # than their own canDelegate = False delegatorRoles=getRoles(baseDir,delegatorNickname, \ domain,'instance') if delegatorRoles: if 'delegator' in delegatorRoles: canDelegate = True if canDelegate == False: canDelegate = True # non-instance delegators can only delegate within their project delegatorRoles=getRoles(baseDir,delegatorNickname, \ domain,project) if delegatorRoles: if 'delegator' not in delegatorRoles: return False else: return False if canDelegate == False: return False nickname = getNicknameFromActor(messageJson['object']['actor']) if not nickname: print('WARN: unable to find nickname in ' + messageJson['object']['actor']) return False domainFull = domain if port: if port != 80 and port != 443: if ':' not in domain: domainFull = domain + ':' + str(port) role = messageJson['object']['object'].split(';')[1].strip().lower() if not role: setRole(baseDir, nickname, domain, project, None) return True # what roles is this person already assigned to? existingRoles = getRoles(baseDir, nickname, domain, project) if existingRoles: if role in existingRoles: if debug: print(nickname + '@' + domain + ' is already assigned to the role ' + role + ' within the project ' + project) return False setRole(baseDir, nickname, domain, project, role) if debug: print(nickname + '@' + domain + ' assigned to the role ' + role + ' within the project ' + project) 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: if port!=80 and port !=443: if ':' not in domain: fullDomain=domain+':'+str(port) # Get the filename of the capability ocapFilename= \ getOcapFilename(baseDir,nickname,fullDomain,updateActor,'accept') if not ocapFilename: return None # The capability should already exist for it to be updated if not os.path.isfile(ocapFilename): return None # create an update activity ocapUpdate = { "@context": "https://www.w3.org/ns/activitystreams", '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) if not updateActorNickname: print('WARN: unable to find nickname in '+updateActor) return None 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 getFollowersOfActor(baseDir :str,actor :str,debug: bool) -> {}: """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. This returns a list of account handles which follow the given actor and also the corresponding capability id if it exists """ if debug: print('DEBUG: getting followers of '+actor) recipientsDict={} if ':' not in actor: return recipientsDict httpPrefix=actor.split(':')[0] nickname=getNicknameFromActor(actor) if not nickname: if debug: print('DEBUG: no nickname found in '+actor) return recipientsDict domain,port=getDomainFromActor(actor) if not domain: if debug: print('DEBUG: no domain found in '+actor) return recipientsDict actorHandle=nickname+'@'+domain if debug: print('DEBUG: searching for handle '+actorHandle) # for each of the accounts for subdir, dirs, files in os.walk(baseDir+'/accounts'): for account in dirs: if '@' in account and not account.startswith('inbox@'): followingFilename = os.path.join(subdir, account)+'/following.txt' if debug: print('DEBUG: examining follows of '+account) print(followingFilename) if os.path.isfile(followingFilename): # does this account follow the given actor? if debug: print('DEBUG: checking if '+actorHandle+' in '+followingFilename) 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' if debug: print('DEBUG: checking capabilities of'+account) if os.path.isfile(ocapFilename): with open(ocapFilename, 'r') as fp: ocapJson=commentjson.load(fp) if ocapJson.get('id'): if debug: print('DEBUG: capabilities id found for '+account) recipientsDict[account]=ocapJson['id'] else: if debug: print('DEBUG: capabilities has no id attribute') recipientsDict[account]=None else: if debug: print('DEBUG: No capabilities file found for '+account+' granted by '+actorHandle) print(ocapFilename) recipientsDict[account]=None return recipientsDict
def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \ port: int,sendThreads: [],postLog: [], \ cachedWebfingers: {},personCache: {}, \ messageJson: {},federationList: [], \ debug : bool,projectVersion: str, \ acceptedCaps=["inbox:write","objects:read"]) -> bool: """Receives a follow request within the POST section of HTTPServer """ if not messageJson['type'].startswith('Follow'): return False print('Receiving follow request') if not messageJson.get('actor'): if debug: print('DEBUG: follow request has no actor') return False if '/users/' not in messageJson['actor'] and '/profile/' not in messageJson['actor']: if debug: print('DEBUG: "users" or "profile" missing from actor') return False domain,tempPort=getDomainFromActor(messageJson['actor']) fromPort=port domainFull=domain if tempPort: fromPort=tempPort if tempPort!=80 and tempPort!=443: if ':' not in domain: domainFull=domain+':'+str(tempPort) if not domainPermitted(domain,federationList): if debug: print('DEBUG: follower from domain not permitted - '+domain) return False nickname=getNicknameFromActor(messageJson['actor']) if not nickname: if debug: print('DEBUG: follow request does not contain a nickname') return False if not messageJson.get('to'): messageJson['to']=messageJson['object'] handle=nickname.lower()+'@'+domain.lower() if '/users/' not in messageJson['object'] and '/profile/' not in messageJson['object']: if debug: print('DEBUG: "users" or "profile" not found within object') return False domainToFollow,tempPort=getDomainFromActor(messageJson['object']) if not domainPermitted(domainToFollow,federationList): if debug: print('DEBUG: follow domain not permitted '+domainToFollow) return True domainToFollowFull=domainToFollow if tempPort: if tempPort!=80 and tempPort!=443: if ':' not in domainToFollow: domainToFollowFull=domainToFollow+':'+str(tempPort) nicknameToFollow=getNicknameFromActor(messageJson['object']) if not nicknameToFollow: if debug: print('DEBUG: follow request does not contain a nickname for the account followed') return True handleToFollow=nicknameToFollow+'@'+domainToFollow if domainToFollow==domain: if not os.path.isdir(baseDir+'/accounts/'+handleToFollow): if debug: print('DEBUG: followed account not found - '+ \ baseDir+'/accounts/'+handleToFollow) return True if isFollowerOfPerson(baseDir, \ nicknameToFollow,domainToFollowFull, \ nickname,domainFull): if debug: print('DEBUG: '+nickname+'@'+domain+ \ ' is already a follower of '+ \ nicknameToFollow+'@'+domainToFollow) return True # what is the followers policy? if followApprovalRequired(baseDir,nicknameToFollow, \ domainToFollow,debug): rejectedFollowsFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/followrejects.txt' if os.path.isfile(rejectedFollowsFilename): denyHandle=nicknameToFollow+'@'+domainToFollowFull if denyHandle in open(rejectedFollowsFilename).read(): print(denyHandle+' was already denied as a follower of '+nickname) return True print('Storing follow request for approval') return storeFollowRequest(baseDir, \ nicknameToFollow,domainToFollow,port, \ nickname,domain,fromPort, messageJson,debug) else: print('Follow request does not require approval') # update the followers if os.path.isdir(baseDir+'/accounts/'+nicknameToFollow+'@'+domainToFollow): followersFilename=baseDir+'/accounts/'+nicknameToFollow+'@'+domainToFollow+'/followers.txt' approveHandle=nickname+'@'+domain if fromPort: approveHandle=approveHandle+':'+str(fromPort) print('Updating followers file: '+followersFilename+' adding '+approveHandle) if os.path.isfile(followersFilename): if approveHandle not in open(followersFilename).read(): followersFile=open(followersFilename, "a+") followersFile.write(approveHandle+'\n') followersFile.close() else: followersFile=open(followersFilename, "w+") followersFile.write(approveHandle+'\n') followersFile.close() print('Beginning follow accept') return followedAccountAccepts(session,baseDir,httpPrefix, \ nicknameToFollow,domainToFollow,port, \ nickname,domain,fromPort, \ messageJson['actor'],federationList, messageJson,acceptedCaps, \ sendThreads,postLog, \ cachedWebfingers,personCache, \ debug,projectVersion)