Test for strict capabilities enforcement

master
Bob Mottram 2019-07-07 20:25:38 +01:00
parent 51f36f1427
commit 356877b98c
5 changed files with 141 additions and 56 deletions

View File

@ -14,6 +14,9 @@ import commentjson
from auth import createPassword
def getOcapFilename(baseDir :str,nickname: str,domain: str,actor :str,subdir: str) -> str:
if ':' in domain:
domain=domain.split(':')[0]
if not os.path.isdir(baseDir+'/accounts'):
os.mkdir(baseDir+'/accounts')

View File

@ -6,7 +6,7 @@ __maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
#import socketserver
import commentjson
import json

View File

@ -179,62 +179,54 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
# check that capabilities are accepted
capabilitiesPassed=False
if queueJson['post'].get('capabilities'):
if queueJson['post']['type']!='Accept':
if isinstance(queueJson['post']['capabilities'], dict):
if queueJson['post'].get('capability'):
if isinstance(queueJson['post']['capability'], dict):
if debug:
print('DEBUG: capability is a dictionary when it should be a string')
os.remove(queueFilename)
queue.pop(0)
continue
ocapFilename= \
getOcapFilename(baseDir, \
queueJson['nickname'],queueJson['domain'], \
queueJson['post']['actor'],'accept')
if not os.path.isfile(ocapFilename):
if debug:
print('DEBUG: capabilities for '+ \
queueJson['post']['actor']+' do not exist')
os.remove(queueFilename)
queue.pop(0)
continue
with open(ocapFilename, 'r') as fp:
oc=commentjson.load(fp)
if not oc.get('id'):
if debug:
print('DEBUG: received post capabilities should be a string, not a dict')
pprint(queueJson['post'])
print('DEBUG: capabilities for '+queueJson['post']['actor']+' do not contain an id')
os.remove(queueFilename)
queue.pop(0)
continue
if not queueJson['post'].get('actor'):
if oc['id']!=queueJson['post']['capability']:
if debug:
print('DEBUG: post should have an actor')
print('DEBUG: capability id mismatch')
os.remove(queueFilename)
queue.pop(0)
continue
ocapFilename= \
getOcapFilename(baseDir, \
queueJson['nickname'],queueJson['domain'], \
queueJson['post']['actor'],'accept')
if not os.path.isfile(ocapFilename):
if not oc.get('capability'):
if debug:
print('DEBUG: capabilities for '+ \
queueJson['post']['actor']+' do not exist')
print('DEBUG: missing capability list')
os.remove(queueFilename)
queue.pop(0)
continue
with open(ocapFilename, 'r') as fp:
oc=commentjson.load(fp)
if not oc.get('id'):
if debug:
print('DEBUG: capabilities for '+queueJson['post']['actor']+' do not contain an id')
os.remove(queueFilename)
queue.pop(0)
continue
if oc['id']!=queueJson['post']['capabilities']:
if debug:
print('DEBUG: capabilities id mismatch')
os.remove(queueFilename)
queue.pop(0)
continue
if not queueJson['post']['capabilities'].get('capability'):
if debug:
print('DEBUG: missing capability list')
os.remove(queueFilename)
queue.pop(0)
continue
if 'inbox:write' not in queueJson['post']['capabilities']['capability']:
if debug:
print('DEBUG: insufficient capabilities to write to inbox from '+ \
queueJson['post']['actor'])
os.remove(queueFilename)
queue.pop(0)
continue
if 'inbox:write' not in oc['capability']:
if debug:
print('DEBUG: object capabilities check success')
capabilitiesPassed=True
print('DEBUG: insufficient capabilities to write to inbox from '+ \
queueJson['post']['actor'])
os.remove(queueFilename)
queue.pop(0)
continue
if debug:
print('DEBUG: object capabilities check success')
capabilitiesPassed=True
if ocapAlways and not capabilitiesPassed:
# Allow follow types through

View File

@ -351,12 +351,15 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
# if capabilities have been granted for this actor
# then get the corresponding id
capabilityId=None
ocapFilename= getOcapFilename(baseDir,nickname,domain,actorUrl,'granted')
ocapFilename= getOcapFilename(baseDir,nickname,domain,toUrl,'granted')
#print('ocapFilename: '+ocapFilename)
if os.path.isfile(ocapFilename):
with open(ocapFilename, 'r') as fp:
oc=commentjson.load(fp)
if oc.get('id'):
capabilityId=oc['id']
#else:
# print('ocapFilename: '+ocapFilename+' not found')
newPost = {
'id': newPostId+'/activity',

101
tests.py
View File

@ -41,6 +41,7 @@ from auth import storeBasicCredentials
testServerAliceRunning = False
testServerBobRunning = False
testServerEveRunning = False
def testHttpsigBase(withDigest):
print('testHttpsig(' + str(withDigest) + ')')
@ -160,6 +161,25 @@ def createServerBob(path: str,domain: str,port: int,federationList: [],ocapGrant
print('Server running: Bob')
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,True)
def createServerEve(path: str,domain: str,port: int,federationList: [],ocapGranted: {},hasFollows: bool,hasPosts :bool,ocapAlways :bool):
print('Creating test server: Eve on port '+str(port))
if os.path.isdir(path):
shutil.rmtree(path)
os.mkdir(path)
os.chdir(path)
nickname='eve'
httpPrefix='http'
useTor=False
clientToServer=False
password='evepass'
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,httpPrefix,True,password)
deleteAllPosts(path,nickname,domain,'inbox')
deleteAllPosts(path,nickname,domain,'outbox')
global testServerEveRunning
testServerEveRunning = True
print('Server running: Eve')
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,True)
def testPostMessageBetweenServers():
print('Testing sending message from one server to the inbox of another')
@ -250,12 +270,14 @@ def testFollowBetweenServers():
global testServerAliceRunning
global testServerBobRunning
global testServerEveRunning
testServerAliceRunning = False
testServerBobRunning = False
testServerEveRunning = False
httpPrefix='http'
useTor=False
federationList=['127.0.0.42','127.0.0.64']
federationList=[]
ocapGranted={}
baseDir=os.getcwd()
@ -276,20 +298,35 @@ def testFollowBetweenServers():
bobPort=61936
thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobDomain,bobPort,federationList,ocapGranted,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)
thrAlice.start()
thrBob.start()
thrEve.start()
assert thrAlice.isAlive()==True
assert thrBob.isAlive()==True
assert thrEve.isAlive()==True
# wait for both servers to be running
while not (testServerAliceRunning and testServerBobRunning):
ctr=0
while not (testServerAliceRunning and testServerBobRunning and testServerEveRunning):
time.sleep(1)
ctr+=1
if ctr>10:
break
print('Alice online: '+str(testServerAliceRunning))
print('Bob online: '+str(testServerBobRunning))
print('Eve online: '+str(testServerEveRunning))
assert ctr<=10
time.sleep(1)
# In the beginning all was calm and there were no follows
print('Alice sends a follow request to Bob')
print('Both are strictly enforcing object capabilities')
os.chdir(aliceDir)
sessionAlice = createSession(aliceDomain,alicePort,useTor)
inReplyTo=None
@ -317,11 +354,61 @@ def testFollowBetweenServers():
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+':'+str(bobPort)+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json'):
if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+':'+str(alicePort)+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json'):
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'):
break
time.sleep(1)
print('\n\nEve tries to send to Bob')
sessionEve = createSession(eveDomain,evePort,useTor)
eveSendThreads = []
evePostLog = []
evePersonCache={}
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)
print('sendResult: '+str(sendResult))
queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
eveMessageArrived=False
for i in range(5):
time.sleep(1)
if os.path.isdir(inboxPath):
if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>1:
eveMessageArrived=True
print('Eve message sent to Bob!')
break
# capabilities should have prevented delivery
assert eveMessageArrived==False
print('Message from Eve to Bob was correctly rejected by object capabilities')
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)
print('sendResult: '+str(sendResult))
queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
aliceMessageArrived=False
for i in range(5):
time.sleep(1)
if os.path.isdir(inboxPath):
if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>1:
aliceMessageArrived=True
print('Alice message sent to Bob!')
break
assert aliceMessageArrived==True
print('Message from Alice to Bob succeeded, since it was granted capabilities')
# stop the servers
thrAlice.kill()
thrAlice.join()
@ -331,8 +418,8 @@ def testFollowBetweenServers():
thrBob.join()
assert thrBob.isAlive()==False
assert os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+':'+str(bobPort)+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json')
assert os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+':'+str(alicePort)+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json')
assert os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json')
assert os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json')
assert 'alice@'+aliceDomain in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read()
assert 'bob@'+bobDomain in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read()