forked from indymedia/epicyon
Test for strict capabilities enforcement
parent
51f36f1427
commit
356877b98c
|
@ -14,6 +14,9 @@ import commentjson
|
||||||
from auth import createPassword
|
from auth import createPassword
|
||||||
|
|
||||||
def getOcapFilename(baseDir :str,nickname: str,domain: str,actor :str,subdir: str) -> str:
|
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'):
|
if not os.path.isdir(baseDir+'/accounts'):
|
||||||
os.mkdir(baseDir+'/accounts')
|
os.mkdir(baseDir+'/accounts')
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ __maintainer__ = "Bob Mottram"
|
||||||
__email__ = "bob@freedombone.net"
|
__email__ = "bob@freedombone.net"
|
||||||
__status__ = "Production"
|
__status__ = "Production"
|
||||||
|
|
||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
|
||||||
#import socketserver
|
#import socketserver
|
||||||
import commentjson
|
import commentjson
|
||||||
import json
|
import json
|
||||||
|
|
82
inbox.py
82
inbox.py
|
@ -179,62 +179,54 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cache
|
||||||
|
|
||||||
# check that capabilities are accepted
|
# check that capabilities are accepted
|
||||||
capabilitiesPassed=False
|
capabilitiesPassed=False
|
||||||
if queueJson['post'].get('capabilities'):
|
if queueJson['post'].get('capability'):
|
||||||
if queueJson['post']['type']!='Accept':
|
if isinstance(queueJson['post']['capability'], dict):
|
||||||
if isinstance(queueJson['post']['capabilities'], 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:
|
if debug:
|
||||||
print('DEBUG: received post capabilities should be a string, not a dict')
|
print('DEBUG: capabilities for '+queueJson['post']['actor']+' do not contain an id')
|
||||||
pprint(queueJson['post'])
|
|
||||||
os.remove(queueFilename)
|
os.remove(queueFilename)
|
||||||
queue.pop(0)
|
queue.pop(0)
|
||||||
continue
|
continue
|
||||||
if not queueJson['post'].get('actor'):
|
if oc['id']!=queueJson['post']['capability']:
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: post should have an actor')
|
print('DEBUG: capability id mismatch')
|
||||||
os.remove(queueFilename)
|
os.remove(queueFilename)
|
||||||
queue.pop(0)
|
queue.pop(0)
|
||||||
continue
|
continue
|
||||||
ocapFilename= \
|
if not oc.get('capability'):
|
||||||
getOcapFilename(baseDir, \
|
|
||||||
queueJson['nickname'],queueJson['domain'], \
|
|
||||||
queueJson['post']['actor'],'accept')
|
|
||||||
if not os.path.isfile(ocapFilename):
|
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: capabilities for '+ \
|
print('DEBUG: missing capability list')
|
||||||
queueJson['post']['actor']+' do not exist')
|
|
||||||
os.remove(queueFilename)
|
os.remove(queueFilename)
|
||||||
queue.pop(0)
|
queue.pop(0)
|
||||||
continue
|
continue
|
||||||
with open(ocapFilename, 'r') as fp:
|
if 'inbox:write' not in oc['capability']:
|
||||||
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 debug:
|
if debug:
|
||||||
print('DEBUG: object capabilities check success')
|
print('DEBUG: insufficient capabilities to write to inbox from '+ \
|
||||||
capabilitiesPassed=True
|
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:
|
if ocapAlways and not capabilitiesPassed:
|
||||||
# Allow follow types through
|
# 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
|
# if capabilities have been granted for this actor
|
||||||
# then get the corresponding id
|
# then get the corresponding id
|
||||||
capabilityId=None
|
capabilityId=None
|
||||||
ocapFilename= getOcapFilename(baseDir,nickname,domain,actorUrl,'granted')
|
ocapFilename= getOcapFilename(baseDir,nickname,domain,toUrl,'granted')
|
||||||
|
#print('ocapFilename: '+ocapFilename)
|
||||||
if os.path.isfile(ocapFilename):
|
if os.path.isfile(ocapFilename):
|
||||||
with open(ocapFilename, 'r') as fp:
|
with open(ocapFilename, 'r') as fp:
|
||||||
oc=commentjson.load(fp)
|
oc=commentjson.load(fp)
|
||||||
if oc.get('id'):
|
if oc.get('id'):
|
||||||
capabilityId=oc['id']
|
capabilityId=oc['id']
|
||||||
|
#else:
|
||||||
|
# print('ocapFilename: '+ocapFilename+' not found')
|
||||||
|
|
||||||
newPost = {
|
newPost = {
|
||||||
'id': newPostId+'/activity',
|
'id': newPostId+'/activity',
|
||||||
|
|
105
tests.py
105
tests.py
|
@ -41,6 +41,7 @@ from auth import storeBasicCredentials
|
||||||
|
|
||||||
testServerAliceRunning = False
|
testServerAliceRunning = False
|
||||||
testServerBobRunning = False
|
testServerBobRunning = False
|
||||||
|
testServerEveRunning = False
|
||||||
|
|
||||||
def testHttpsigBase(withDigest):
|
def testHttpsigBase(withDigest):
|
||||||
print('testHttpsig(' + str(withDigest) + ')')
|
print('testHttpsig(' + str(withDigest) + ')')
|
||||||
|
@ -160,6 +161,25 @@ def createServerBob(path: str,domain: str,port: int,federationList: [],ocapGrant
|
||||||
print('Server running: Bob')
|
print('Server running: Bob')
|
||||||
runDaemon(path,domain,port,httpPrefix,federationList,ocapAlways,ocapGranted,useTor,True)
|
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():
|
def testPostMessageBetweenServers():
|
||||||
print('Testing sending message from one server to the inbox of another')
|
print('Testing sending message from one server to the inbox of another')
|
||||||
|
|
||||||
|
@ -250,12 +270,14 @@ def testFollowBetweenServers():
|
||||||
|
|
||||||
global testServerAliceRunning
|
global testServerAliceRunning
|
||||||
global testServerBobRunning
|
global testServerBobRunning
|
||||||
|
global testServerEveRunning
|
||||||
testServerAliceRunning = False
|
testServerAliceRunning = False
|
||||||
testServerBobRunning = False
|
testServerBobRunning = False
|
||||||
|
testServerEveRunning = False
|
||||||
|
|
||||||
httpPrefix='http'
|
httpPrefix='http'
|
||||||
useTor=False
|
useTor=False
|
||||||
federationList=['127.0.0.42','127.0.0.64']
|
federationList=[]
|
||||||
ocapGranted={}
|
ocapGranted={}
|
||||||
|
|
||||||
baseDir=os.getcwd()
|
baseDir=os.getcwd()
|
||||||
|
@ -276,20 +298,35 @@ def testFollowBetweenServers():
|
||||||
bobPort=61936
|
bobPort=61936
|
||||||
thrBob = threadWithTrace(target=createServerBob,args=(bobDir,bobDomain,bobPort,federationList,ocapGranted,False,False,ocapAlways),daemon=True)
|
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()
|
thrAlice.start()
|
||||||
thrBob.start()
|
thrBob.start()
|
||||||
|
thrEve.start()
|
||||||
assert thrAlice.isAlive()==True
|
assert thrAlice.isAlive()==True
|
||||||
assert thrBob.isAlive()==True
|
assert thrBob.isAlive()==True
|
||||||
|
assert thrEve.isAlive()==True
|
||||||
|
|
||||||
# wait for both servers to be running
|
# wait for both servers to be running
|
||||||
while not (testServerAliceRunning and testServerBobRunning):
|
ctr=0
|
||||||
|
while not (testServerAliceRunning and testServerBobRunning and testServerEveRunning):
|
||||||
time.sleep(1)
|
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)
|
time.sleep(1)
|
||||||
|
|
||||||
# In the beginning all was calm and there were no follows
|
# In the beginning all was calm and there were no follows
|
||||||
|
|
||||||
print('Alice sends a follow request to Bob')
|
print('Alice sends a follow request to Bob')
|
||||||
|
print('Both are strictly enforcing object capabilities')
|
||||||
os.chdir(aliceDir)
|
os.chdir(aliceDir)
|
||||||
sessionAlice = createSession(aliceDomain,alicePort,useTor)
|
sessionAlice = createSession(aliceDomain,alicePort,useTor)
|
||||||
inReplyTo=None
|
inReplyTo=None
|
||||||
|
@ -317,11 +354,61 @@ def testFollowBetweenServers():
|
||||||
for t in range(10):
|
for t in range(10):
|
||||||
if os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt'):
|
if os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt'):
|
||||||
if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/following.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(bobDir+'/accounts/bob@'+bobDomain+'/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(aliceDir+'/accounts/alice@'+aliceDomain+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json'):
|
||||||
break
|
break
|
||||||
time.sleep(1)
|
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
|
# stop the servers
|
||||||
thrAlice.kill()
|
thrAlice.kill()
|
||||||
thrAlice.join()
|
thrAlice.join()
|
||||||
|
@ -330,9 +417,9 @@ def testFollowBetweenServers():
|
||||||
thrBob.kill()
|
thrBob.kill()
|
||||||
thrBob.join()
|
thrBob.join()
|
||||||
assert thrBob.isAlive()==False
|
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(bobDir+'/accounts/bob@'+bobDomain+'/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(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 'alice@'+aliceDomain in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read()
|
||||||
assert 'bob@'+bobDomain in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read()
|
assert 'bob@'+bobDomain in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read()
|
||||||
|
|
Loading…
Reference in New Issue