diff --git a/README.md b/README.md index 5d35aaa4f..41d1d3cc3 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,23 @@ This is one proposed way that OCAP could work. Default capabilities are initially set up when a follow request is made. The Accept activity sent back from a follow request can be received by any instance. A capabilities accept activity is attached to the follow accept. ``` text - Actor A + Alice | V Follow Request | V - Actor B + Bob | V Create/store default Capabilities - for Actor A + for Alice | V Follow Accept + default Capabilities | V - Actor A + Alice | V Store Granted Capabilities @@ -64,18 +64,51 @@ Default capabilities are initially set up when a follow request is made. The Acc The default capabilities could be *any preferred policy* of the instance. They could be no capabilities at all, read only or full access to everything. +Example Follow request from **Alice** to **Bob**: + +``` json +{'actor': 'http://alicedomain.net/users/alice', + 'cc': ['https://www.w3.org/ns/activitystreams#Public'], + 'id': 'http://alicedomain.net/users/alice/statuses/1562507338839876', + 'object': 'http://bobdomain.net/users/bob', + 'published': '2019-07-07T13:48:58Z', + 'to': ['http://bobdomain.net/users/bob'], + 'type': 'Follow'} + ``` + +Follow Accept from **Bob** to **Alice** with attached capabilities. + +``` json +{'actor': 'http://bobdomain.net/users/bob', + 'capabilities': {'actor': 'http://bobdomain.net/users/bob', + 'capability': ['inbox:write', 'objects:read'], + 'id': 'http://bobdomain.net/caps/rOYtHApyr4ZWDUgEE1KqjhTe0kI3T2wJ', + 'scope': 'http://alicedomain.net/users/alice', + 'type': 'Capability'}, + 'cc': [], + 'object': {'actor': 'http://alicedomain.net/users/alice', + 'cc': ['https://www.w3.org/ns/activitystreams#Public'], + 'id': 'http://alicedomain.net/users/alice/statuses/1562507338839876', + 'object': 'http://bobdomain.net/users/bob', + 'published': '2019-07-07T13:48:58Z', + 'to': ['http://bobdomain.net/users/bob'], + 'type': 'Follow'}, + 'to': ['http://alicedomain.net/users/alice'], + 'type': 'Accept'} +``` + When posts are subsequently sent from the following instance (server-to-server) they should have the corresponding capability id string attached within the Create wrapper. ``` text - Actor A + Alice | V Send Post Attach id from Stored Capabilities - granted by Actor B + granted by Bob | V - Actor B + Bob | V Check Capability id matches @@ -88,7 +121,7 @@ When posts are subsequently sent from the following instance (server-to-server) Accept or reject incoming post ``` -Subsequently **Actor B** could change the stored capabilities for **Actor A** in its database, giving the new object a different id. This could be sent back to **Actor A**, perhaps as another **follow Accept** activity with attached capabilities. This could then change the way in which **Actor A** can interact with **Actor B**, for example by adding or removing the ability to like or reply to posts. +Subsequently **Bob** could change the stored capabilities for **Alice** in their database, giving the new object a different id. This could be sent back to **Alice**, perhaps as another **follow Accept** activity with attached capabilities. This could then change the way in which **Alice** can interact with **Bob**, for example by adding or removing the ability to like or reply to posts. ## Install diff --git a/acceptreject.py b/acceptreject.py index ca892aba8..70513833c 100644 --- a/acceptreject.py +++ b/acceptreject.py @@ -57,7 +57,7 @@ def createAccept(baseDir: str,federationList: [],ocapGranted: {}, \ toUrl: str,ccUrl: str,httpPrefix: str, \ objectJson: {}) -> {}: # create capabilities accept - ocapNew=capabilitiesAccept(baseDir,httpPrefix,nickname,domain, toUrl, True) + ocapNew=capabilitiesAccept(baseDir,httpPrefix,nickname,domain,port,toUrl,True) return createAcceptReject(baseDir,federationList,ocapGranted, \ nickname,domain,port, \ toUrl,ccUrl,httpPrefix, \ @@ -85,6 +85,15 @@ def acceptFollow(baseDir: str,domain : str,messageJson: {}, \ 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'])) + return if debug: print('DEBUG: follow Accept received') thisActor=messageJson['object']['actor'] @@ -94,22 +103,27 @@ def acceptFollow(baseDir: str,domain : str,messageJson: {}, \ if debug: print('DEBUG: domain not found in '+thisActor) return - if acceptedDomain != domain: - if debug: - print('DEBUG: domain mismatch '+acceptedDomain+' != '+domain) - 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 not '/'+domain+':'+str(acceptedPort)+'/users/'+nickname in thisActor: + if not '/'+acceptedDomain+':'+str(acceptedPort)+'/users/'+nickname 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 not '/'+domain+'/users/'+nickname in thisActor: + if not '/'+acceptedDomain+'/users/'+nickname in thisActor: if debug: + print('Expected: /'+acceptedDomain+'/users/'+nickname) + print('Actual: '+thisActor) print('DEBUG: unrecognized actor '+thisActor) return followedActor=messageJson['object']['object'] @@ -128,14 +142,15 @@ def acceptFollow(baseDir: str,domain : str,messageJson: {}, \ if isinstance(messageJson['object']['capabilities'], dict): capabilitiesGrantedSave(baseDir,messageJson['object']['capabilities']) - if followPerson(baseDir,nickname,domain, \ + if followPerson(baseDir, \ + nickname,acceptedDomain, \ followedNickname,followedDomain, \ federationList,debug): if debug: - print('DEBUG: '+nickname+'@'+domain+' followed '+followedNickname+'@'+followedDomain) + print('DEBUG: '+nickname+'@'+acceptedDomain+' followed '+followedNickname+'@'+followedDomain) else: if debug: - print('DEBUG: Unable to create follow - '+nickname+'@'+domain+' -> '+followedNickname+'@'+followedDomain) + print('DEBUG: Unable to create follow - '+nickname+'@'+acceptedDomain+' -> '+followedNickname+'@'+followedDomain) def receiveAcceptReject(session,baseDir: str, \ httpPrefix: str,domain :str,port: int, \ diff --git a/capabilities.py b/capabilities.py index 83fe79949..2ac45dc8c 100644 --- a/capabilities.py +++ b/capabilities.py @@ -40,7 +40,8 @@ def capabilitiesRequest(baseDir: str,httpPrefix: str,domain: str, \ } return ocapRequest -def capabilitiesAccept(baseDir: str,httpPrefix: str,nickname: str,domain: str, \ +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 @@ -50,6 +51,10 @@ def capabilitiesAccept(baseDir: str,httpPrefix: str,nickname: str,domain: str, \ if len(acceptedActor)>256: return None + fullDomain=domain + if port!=80 and port !=443: + fullDomain=domain+':'+str(port) + # make directories to store capabilities capabilitiesMakeDirs(baseDir) filename=baseDir+'/ocap/accept/'+acceptedActor.replace('/','#')+'.json' @@ -63,14 +68,14 @@ def capabilitiesAccept(baseDir: str,httpPrefix: str,nickname: str,domain: str, \ if not ocapAccept: ocapId=createPassword(32) ocapAccept = { - "id": httpPrefix+"://"+domain+"/caps/"+ocapId, + "id": httpPrefix+"://"+fullDomain+"/caps/"+ocapId, "type": "Capability", "capability": acceptedCaps, "scope": acceptedActor, - "actor": httpPrefix+"://"+domain + "actor": httpPrefix+"://"+fullDomain } if nickname: - ocapAccept['actor']=httpPrefix+"://"+domain+'/users/'+nickname + ocapAccept['actor']=httpPrefix+"://"+fullDomain+'/users/'+nickname if saveToFile: with open(filename, 'w') as fp: diff --git a/follow.py b/follow.py index 1e13dcf6b..a8c037695 100644 --- a/follow.py +++ b/follow.py @@ -282,7 +282,7 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \ nicknameToFollow+'@'+domainToFollow+' back to '+nickname+'@'+domain) personUrl=messageJson['actor'] acceptJson=createAccept(baseDir,federationList,ocapGranted, \ - nickname,domain,port, \ + nicknameToFollow,domainToFollow,port, \ personUrl,'',httpPrefix,messageJson) if debug: pprint(acceptJson) @@ -310,9 +310,11 @@ def sendFollowRequest(session,baseDir: str, \ """ if not domainPermitted(followDomain,federationList): return None - + + fullDomain=domain followActor=httpPrefix+'://'+domain+'/users/'+nickname if port!=80 and port!=443: + fullDomain=domain+':'+str(port) followActor=httpPrefix+'://'+domain+':'+str(port)+'/users/'+nickname requestDomain=followDomain @@ -329,7 +331,7 @@ def sendFollowRequest(session,baseDir: str, \ followedId=followHttpPrefix+'://'+requestDomain+'/users/'+followNickname newFollowJson = { - 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber, + 'id': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber, 'type': 'Follow', 'actor': followActor, 'object': followedId,