forked from indymedia/epicyon
Test for strict capabilities enforcement
parent
51f36f1427
commit
356877b98c
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
78
inbox.py
78
inbox.py
|
@ -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
|
||||
|
|
5
posts.py
5
posts.py
|
@ -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
101
tests.py
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue