tests.py 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392
  1. __filename__ = "tests.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.0.0"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. import base64
  9. import time
  10. import os, os.path
  11. import shutil
  12. import commentjson
  13. import json
  14. from time import gmtime, strftime
  15. from pprint import pprint
  16. from person import createPerson
  17. from Crypto.Hash import SHA256
  18. from httpsig import signPostHeaders
  19. from httpsig import verifyPostHeaders
  20. from httpsig import messageContentDigest
  21. from cache import storePersonInCache
  22. from cache import getPersonFromCache
  23. from threads import threadWithTrace
  24. from daemon import runDaemon
  25. from session import createSession
  26. from posts import deleteAllPosts
  27. from posts import createPublicPost
  28. from posts import sendPost
  29. from posts import archivePosts
  30. from posts import noOfFollowersOnDomain
  31. from posts import groupFollowersByDomain
  32. from posts import sendCapabilitiesUpdate
  33. from posts import archivePostsForPerson
  34. from posts import sendPostViaServer
  35. from follow import clearFollows
  36. from follow import clearFollowers
  37. from follow import sendFollowRequestViaServer
  38. from follow import sendUnfollowRequestViaServer
  39. from utils import followPerson
  40. from utils import getNicknameFromActor
  41. from utils import getDomainFromActor
  42. from follow import followerOfPerson
  43. from follow import unfollowPerson
  44. from follow import unfollowerOfPerson
  45. from follow import getFollowersOfPerson
  46. from follow import sendFollowRequest
  47. from person import createPerson
  48. from person import setDisplayNickname
  49. from person import setBio
  50. from skills import setSkillLevel
  51. from roles import setRole
  52. from roles import getRoles
  53. from roles import outboxDelegate
  54. from auth import createBasicAuthHeader
  55. from auth import authorizeBasic
  56. from auth import storeBasicCredentials
  57. from like import likePost
  58. from like import sendLikeViaServer
  59. from announce import announcePublic
  60. from announce import sendAnnounceViaServer
  61. from media import getMediaPath
  62. from media import getAttachmentMediaType
  63. from delete import sendDeleteViaServer
  64. from inbox import validInbox
  65. from inbox import validInboxFilenames
  66. from content import addWebLinks
  67. testServerAliceRunning = False
  68. testServerBobRunning = False
  69. testServerEveRunning = False
  70. def testHttpsigBase(withDigest):
  71. print('testHttpsig(' + str(withDigest) + ')')
  72. baseDir=os.getcwd()
  73. path=baseDir+'/.testHttpsigBase'
  74. if os.path.isdir(path):
  75. shutil.rmtree(path)
  76. os.mkdir(path)
  77. os.chdir(path)
  78. contentType='application/activity+json'
  79. nickname='socrates'
  80. domain='argumentative.social'
  81. httpPrefix='https'
  82. port=5576
  83. password='SuperSecretPassword'
  84. privateKeyPem,publicKeyPem,person,wfEndpoint= \
  85. createPerson(path,nickname,domain,port,httpPrefix,False,password)
  86. assert privateKeyPem
  87. messageBodyJson = {"a key": "a value", "another key": "A string","yet another key": "Another string"}
  88. messageBodyJsonStr=json.dumps(messageBodyJson)
  89. headersDomain=domain
  90. if port:
  91. if port!=80 and port !=443:
  92. if ':' not in domain:
  93. headersDomain=domain+':'+str(port)
  94. dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
  95. boxpath='/inbox'
  96. if not withDigest:
  97. headers = {'host': headersDomain,'date': dateStr,'content-type': 'application/json'}
  98. signatureHeader = \
  99. signPostHeaders(dateStr,privateKeyPem, nickname, \
  100. domain, port, \
  101. domain, port, \
  102. boxpath, httpPrefix, None)
  103. else:
  104. bodyDigest = messageContentDigest(messageBodyJsonStr)
  105. headers = {'host': headersDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType}
  106. signatureHeader = \
  107. signPostHeaders(dateStr,privateKeyPem, nickname, \
  108. domain, port, \
  109. domain, port, \
  110. boxpath, httpPrefix, messageBodyJsonStr)
  111. headers['signature'] = signatureHeader
  112. assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
  113. boxpath,False,None, \
  114. messageBodyJsonStr)
  115. assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
  116. '/parambulator'+boxpath,False,None, \
  117. messageBodyJsonStr) == False
  118. assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
  119. boxpath,True,None, \
  120. messageBodyJsonStr) == False
  121. if not withDigest:
  122. # fake domain
  123. headers = {'host': 'bogon.domain','date': dateStr,'content-type': 'application/json'}
  124. else:
  125. # correct domain but fake message
  126. messageBodyJsonStr = '{"a key": "a value", "another key": "Fake GNUs", "yet another key": "More Fake GNUs"}'
  127. bodyDigest = messageContentDigest(messageBodyJsonStr)
  128. headers = {'host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType}
  129. headers['signature'] = signatureHeader
  130. assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
  131. boxpath,True,None, \
  132. messageBodyJsonStr) == False
  133. os.chdir(baseDir)
  134. shutil.rmtree(path)
  135. def testHttpsig():
  136. testHttpsigBase(True)
  137. testHttpsigBase(False)
  138. def testCache():
  139. print('testCache')
  140. personUrl="cat@cardboard.box"
  141. personJson={ "id": 123456, "test": "This is a test" }
  142. personCache={}
  143. storePersonInCache(None,personUrl,personJson,personCache)
  144. result=getPersonFromCache(None,personUrl,personCache)
  145. assert result['id']==123456
  146. assert result['test']=='This is a test'
  147. def testThreadsFunction(param: str):
  148. for i in range(10000):
  149. time.sleep(2)
  150. def testThreads():
  151. print('testThreads')
  152. thr = threadWithTrace(target=testThreadsFunction,args=('test',),daemon=True)
  153. thr.start()
  154. assert thr.isAlive()==True
  155. time.sleep(1)
  156. thr.kill()
  157. thr.join()
  158. assert thr.isAlive()==False
  159. def createServerAlice(path: str,domain: str,port: int,federationList: [], \
  160. hasFollows: bool,hasPosts :bool,ocapAlways: bool):
  161. print('Creating test server: Alice on port '+str(port))
  162. if os.path.isdir(path):
  163. shutil.rmtree(path)
  164. os.mkdir(path)
  165. os.chdir(path)
  166. nickname='alice'
  167. httpPrefix='http'
  168. useTor=False
  169. password='alicepass'
  170. noreply=False
  171. nolike=False
  172. nopics=False
  173. noannounce=False
  174. cw=False
  175. useBlurhash=True
  176. maxReplies=64
  177. domainMaxPostsPerDay=1000
  178. accountMaxPostsPerDay=1000
  179. allowDeletion=True
  180. privateKeyPem,publicKeyPem,person,wfEndpoint= \
  181. createPerson(path,nickname,domain,port,httpPrefix,True,password)
  182. deleteAllPosts(path,nickname,domain,'inbox')
  183. deleteAllPosts(path,nickname,domain,'outbox')
  184. assert setSkillLevel(path,nickname,domain,'hacking',90)
  185. assert setRole(path,nickname,domain,'someproject','guru')
  186. if hasFollows:
  187. followPerson(path,nickname,domain,'bob','127.0.0.100:61936', \
  188. federationList,False)
  189. followerOfPerson(path,nickname,domain,'bob','127.0.0.100:61936', \
  190. federationList,False)
  191. if hasPosts:
  192. createPublicPost(path,nickname, domain, port,httpPrefix, \
  193. "No wise fish would go anywhere without a porpoise", \
  194. False, True, clientToServer,None,None,useBlurhash)
  195. createPublicPost(path,nickname, domain, port,httpPrefix, \
  196. "Curiouser and curiouser!", False, True, \
  197. clientToServer,None,None,useBlurhash)
  198. createPublicPost(path,nickname, domain, port,httpPrefix, \
  199. "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", \
  200. False, True, clientToServer,None,None,useBlurhash)
  201. global testServerAliceRunning
  202. testServerAliceRunning = True
  203. print('Server running: Alice')
  204. runDaemon(__version__,"instanceId",False,path,domain,port,port, \
  205. httpPrefix,federationList, \
  206. noreply,nolike,nopics,noannounce,cw,ocapAlways, \
  207. useTor,maxReplies, \
  208. domainMaxPostsPerDay,accountMaxPostsPerDay, \
  209. allowDeletion,True,True,False)
  210. def createServerBob(path: str,domain: str,port: int,federationList: [], \
  211. hasFollows: bool,hasPosts :bool,ocapAlways :bool):
  212. print('Creating test server: Bob on port '+str(port))
  213. if os.path.isdir(path):
  214. shutil.rmtree(path)
  215. os.mkdir(path)
  216. os.chdir(path)
  217. nickname='bob'
  218. httpPrefix='http'
  219. useTor=False
  220. clientToServer=False
  221. password='bobpass'
  222. noreply=False
  223. nolike=False
  224. nopics=False
  225. noannounce=False
  226. cw=False
  227. useBlurhash=False
  228. maxReplies=64
  229. domainMaxPostsPerDay=1000
  230. accountMaxPostsPerDay=1000
  231. allowDeletion=True
  232. privateKeyPem,publicKeyPem,person,wfEndpoint= \
  233. createPerson(path,nickname,domain,port,httpPrefix,True,password)
  234. deleteAllPosts(path,nickname,domain,'inbox')
  235. deleteAllPosts(path,nickname,domain,'outbox')
  236. assert setRole(path,nickname,domain,'bandname','bass player')
  237. assert setRole(path,nickname,domain,'bandname','publicist')
  238. if hasFollows:
  239. followPerson(path,nickname,domain, \
  240. 'alice','127.0.0.50:61935',federationList,False)
  241. followerOfPerson(path,nickname,domain, \
  242. 'alice','127.0.0.50:61935',federationList,False)
  243. if hasPosts:
  244. createPublicPost(path,nickname, domain, port,httpPrefix, \
  245. "It's your life, live it your way.", \
  246. False, True, clientToServer,None,None,useBlurhash)
  247. createPublicPost(path,nickname, domain, port,httpPrefix, \
  248. "One of the things I've realised is that I am very simple", \
  249. False, True, clientToServer,None,None,useBlurhash)
  250. createPublicPost(path,nickname, domain, port,httpPrefix, \
  251. "Quantum physics is a bit of a passion of mine", \
  252. False, True, clientToServer,None,None,useBlurhash)
  253. global testServerBobRunning
  254. testServerBobRunning = True
  255. print('Server running: Bob')
  256. runDaemon(__version__,"instanceId",False,path,domain,port,port, \
  257. httpPrefix,federationList, \
  258. noreply,nolike,nopics,noannounce,cw,ocapAlways, \
  259. useTor,maxReplies, \
  260. domainMaxPostsPerDay,accountMaxPostsPerDay, \
  261. allowDeletion,True,True,False)
  262. def createServerEve(path: str,domain: str,port: int,federationList: [], \
  263. hasFollows: bool,hasPosts :bool,ocapAlways :bool):
  264. print('Creating test server: Eve on port '+str(port))
  265. if os.path.isdir(path):
  266. shutil.rmtree(path)
  267. os.mkdir(path)
  268. os.chdir(path)
  269. nickname='eve'
  270. httpPrefix='http'
  271. useTor=False
  272. clientToServer=False
  273. password='evepass'
  274. noreply=False
  275. nolike=False
  276. nopics=False
  277. noannounce=False
  278. cw=False
  279. maxReplies=64
  280. allowDeletion=True
  281. privateKeyPem,publicKeyPem,person,wfEndpoint= \
  282. createPerson(path,nickname,domain,port,httpPrefix,True,password)
  283. deleteAllPosts(path,nickname,domain,'inbox')
  284. deleteAllPosts(path,nickname,domain,'outbox')
  285. global testServerEveRunning
  286. testServerEveRunning = True
  287. print('Server running: Eve')
  288. runDaemon(__version__,"instanceId",False,path,domain,port,port, \
  289. httpPrefix,federationList, \
  290. noreply,nolike,nopics,noannounce,cw,ocapAlways, \
  291. useTor,maxReplies,allowDeletion,True,True,False)
  292. def testPostMessageBetweenServers():
  293. print('Testing sending message from one server to the inbox of another')
  294. global testServerAliceRunning
  295. global testServerBobRunning
  296. testServerAliceRunning = False
  297. testServerBobRunning = False
  298. httpPrefix='http'
  299. useTor=False
  300. baseDir=os.getcwd()
  301. if os.path.isdir(baseDir+'/.tests'):
  302. shutil.rmtree(baseDir+'/.tests')
  303. os.mkdir(baseDir+'/.tests')
  304. ocapAlways=False
  305. # create the servers
  306. aliceDir=baseDir+'/.tests/alice'
  307. aliceDomain='127.0.0.50'
  308. alicePort=61935
  309. bobDir=baseDir+'/.tests/bob'
  310. bobDomain='127.0.0.100'
  311. bobPort=61936
  312. federationList=[bobDomain,aliceDomain]
  313. thrAlice = \
  314. threadWithTrace(target=createServerAlice, \
  315. args=(aliceDir,aliceDomain,alicePort, \
  316. federationList,False,False, \
  317. ocapAlways),daemon=True)
  318. thrBob = \
  319. threadWithTrace(target=createServerBob, \
  320. args=(bobDir,bobDomain,bobPort, \
  321. federationList,False,False, \
  322. ocapAlways),daemon=True)
  323. thrAlice.start()
  324. thrBob.start()
  325. assert thrAlice.isAlive()==True
  326. assert thrBob.isAlive()==True
  327. # wait for both servers to be running
  328. while not (testServerAliceRunning and testServerBobRunning):
  329. time.sleep(1)
  330. time.sleep(1)
  331. print('\n\n*******************************************************')
  332. print('Alice sends to Bob')
  333. os.chdir(aliceDir)
  334. sessionAlice = createSession(aliceDomain,alicePort,useTor)
  335. inReplyTo=None
  336. inReplyToAtomUri=None
  337. subject=None
  338. aliceSendThreads = []
  339. alicePostLog = []
  340. followersOnly=False
  341. saveToFile=True
  342. clientToServer=False
  343. ccUrl=None
  344. alicePersonCache={}
  345. aliceCachedWebfingers={}
  346. attachedImageFilename=baseDir+'/img/logo.png'
  347. mediaType=getAttachmentMediaType(attachedImageFilename)
  348. attachedImageDescription='Logo'
  349. useBlurhash=True
  350. # nothing in Alice's outbox
  351. outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
  352. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==0
  353. sendResult = \
  354. sendPost(__version__, \
  355. sessionAlice,aliceDir,'alice', aliceDomain, alicePort, \
  356. 'bob', bobDomain, bobPort, ccUrl, httpPrefix, \
  357. 'Why is a mouse when it spins? #sillyquestion', followersOnly, \
  358. saveToFile, clientToServer,attachedImageFilename,mediaType, \
  359. attachedImageDescription,useBlurhash, federationList, \
  360. aliceSendThreads, alicePostLog, aliceCachedWebfingers, \
  361. alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
  362. print('sendResult: '+str(sendResult))
  363. queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
  364. inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
  365. mPath=getMediaPath()
  366. mediaPath=aliceDir+'/'+mPath
  367. for i in range(30):
  368. if os.path.isdir(inboxPath):
  369. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>0:
  370. if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1:
  371. if len([name for name in os.listdir(mediaPath) if os.path.isfile(os.path.join(mediaPath, name))])>0:
  372. break
  373. time.sleep(1)
  374. # Image attachment created
  375. assert len([name for name in os.listdir(mediaPath) if os.path.isfile(os.path.join(mediaPath, name))])>0
  376. # inbox item created
  377. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
  378. # queue item removed
  379. assert len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))])==0
  380. assert validInbox(bobDir,'bob',bobDomain)
  381. assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort)
  382. print('\n\n*******************************************************')
  383. print("Bob likes Alice's post")
  384. followerOfPerson(bobDir,'bob',bobDomain,'alice', \
  385. aliceDomain+':'+str(alicePort),federationList,False)
  386. followPerson(aliceDir,'alice',aliceDomain,'bob', \
  387. bobDomain+':'+str(bobPort),federationList,False)
  388. sessionBob = createSession(bobDomain,bobPort,useTor)
  389. bobSendThreads = []
  390. bobPostLog = []
  391. bobPersonCache={}
  392. bobCachedWebfingers={}
  393. statusNumber=None
  394. outboxPostFilename=None
  395. outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
  396. for name in os.listdir(outboxPath):
  397. if '#statuses#' in name:
  398. statusNumber=int(name.split('#statuses#')[1].replace('.json',''))
  399. outboxPostFilename=outboxPath+'/'+name
  400. assert statusNumber>0
  401. assert outboxPostFilename
  402. assert likePost(sessionBob,bobDir,federationList, \
  403. 'bob',bobDomain,bobPort,httpPrefix, \
  404. 'alice',aliceDomain,alicePort,[], \
  405. statusNumber,False,bobSendThreads,bobPostLog, \
  406. bobPersonCache,bobCachedWebfingers, \
  407. True,__version__)
  408. for i in range(20):
  409. if 'likes' in open(outboxPostFilename).read():
  410. break
  411. time.sleep(1)
  412. with open(outboxPostFilename, 'r') as fp:
  413. alicePostJson=commentjson.load(fp)
  414. pprint(alicePostJson)
  415. assert 'likes' in open(outboxPostFilename).read()
  416. print('\n\n*******************************************************')
  417. print("Bob repeats Alice's post")
  418. objectUrl=httpPrefix+'://'+aliceDomain+':'+str(alicePort)+'/users/alice/statuses/'+str(statusNumber)
  419. inboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/inbox'
  420. outboxPath=bobDir+'/accounts/bob@'+bobDomain+'/outbox'
  421. outboxBeforeAnnounceCount=len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])
  422. beforeAnnounceCount=len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])
  423. assert beforeAnnounceCount==0
  424. print('inbox items before announce: '+str(beforeAnnounceCount))
  425. announcePublic(sessionBob,bobDir,federationList, \
  426. 'bob',bobDomain,bobPort,httpPrefix, \
  427. objectUrl, \
  428. False,bobSendThreads,bobPostLog, \
  429. bobPersonCache,bobCachedWebfingers, \
  430. True,__version__)
  431. announceMessageArrived=False
  432. for i in range(10):
  433. time.sleep(1)
  434. if os.path.isdir(inboxPath):
  435. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>0:
  436. announceMessageArrived=True
  437. print('Announce message sent to Alice!')
  438. break
  439. afterAnnounceCount=len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])
  440. outboxAfterAnnounceCount=len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])
  441. print('inbox items after announce: '+str(afterAnnounceCount))
  442. assert afterAnnounceCount==beforeAnnounceCount+1
  443. assert outboxAfterAnnounceCount==outboxBeforeAnnounceCount+1
  444. # stop the servers
  445. thrAlice.kill()
  446. thrAlice.join()
  447. assert thrAlice.isAlive()==False
  448. thrBob.kill()
  449. thrBob.join()
  450. assert thrBob.isAlive()==False
  451. os.chdir(baseDir)
  452. shutil.rmtree(aliceDir)
  453. shutil.rmtree(bobDir)
  454. def testFollowBetweenServers():
  455. print('Testing sending a follow request from one server to another')
  456. global testServerAliceRunning
  457. global testServerBobRunning
  458. global testServerEveRunning
  459. testServerAliceRunning = False
  460. testServerBobRunning = False
  461. testServerEveRunning = False
  462. httpPrefix='http'
  463. useTor=False
  464. federationList=[]
  465. baseDir=os.getcwd()
  466. if os.path.isdir(baseDir+'/.tests'):
  467. shutil.rmtree(baseDir+'/.tests')
  468. os.mkdir(baseDir+'/.tests')
  469. ocapAlways=True
  470. # create the servers
  471. aliceDir=baseDir+'/.tests/alice'
  472. aliceDomain='127.0.0.42'
  473. alicePort=61935
  474. thrAlice = \
  475. threadWithTrace(target=createServerAlice, \
  476. args=(aliceDir,aliceDomain,alicePort, \
  477. federationList,False,False, \
  478. ocapAlways),daemon=True)
  479. bobDir=baseDir+'/.tests/bob'
  480. bobDomain='127.0.0.64'
  481. bobPort=61936
  482. thrBob = \
  483. threadWithTrace(target=createServerBob, \
  484. args=(bobDir,bobDomain,bobPort, \
  485. federationList,False,False, \
  486. ocapAlways),daemon=True)
  487. eveDir=baseDir+'/.tests/eve'
  488. eveDomain='127.0.0.55'
  489. evePort=61937
  490. thrEve = \
  491. threadWithTrace(target=createServerEve, \
  492. args=(eveDir,eveDomain,evePort, \
  493. federationList,False,False, \
  494. False),daemon=True)
  495. thrAlice.start()
  496. thrBob.start()
  497. thrEve.start()
  498. assert thrAlice.isAlive()==True
  499. assert thrBob.isAlive()==True
  500. assert thrEve.isAlive()==True
  501. # wait for all servers to be running
  502. ctr=0
  503. while not (testServerAliceRunning and testServerBobRunning and testServerEveRunning):
  504. time.sleep(1)
  505. ctr+=1
  506. if ctr>60:
  507. break
  508. print('Alice online: '+str(testServerAliceRunning))
  509. print('Bob online: '+str(testServerBobRunning))
  510. print('Eve online: '+str(testServerEveRunning))
  511. assert ctr<=60
  512. time.sleep(1)
  513. # In the beginning all was calm and there were no follows
  514. print('*********************************************************')
  515. print('Alice sends a follow request to Bob')
  516. print('Both are strictly enforcing object capabilities')
  517. os.chdir(aliceDir)
  518. sessionAlice = createSession(aliceDomain,alicePort,useTor)
  519. inReplyTo=None
  520. inReplyToAtomUri=None
  521. subject=None
  522. aliceSendThreads = []
  523. alicePostLog = []
  524. followersOnly=False
  525. saveToFile=True
  526. clientToServer=False
  527. ccUrl=None
  528. alicePersonCache={}
  529. aliceCachedWebfingers={}
  530. aliceSendThreads=[]
  531. alicePostLog=[]
  532. sendResult = \
  533. sendFollowRequest(sessionAlice,aliceDir, \
  534. 'alice',aliceDomain,alicePort,httpPrefix, \
  535. 'bob',bobDomain,bobPort,httpPrefix, \
  536. clientToServer,federationList, \
  537. aliceSendThreads,alicePostLog, \
  538. aliceCachedWebfingers,alicePersonCache, \
  539. True,__version__)
  540. print('sendResult: '+str(sendResult))
  541. bobCapsFilename=bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json'
  542. aliceCapsFilename=aliceDir+'/accounts/alice@'+aliceDomain+'/ocap/granted/'+httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json'
  543. for t in range(10):
  544. if os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt'):
  545. if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt'):
  546. if os.path.isfile(bobCapsFilename):
  547. if os.path.isfile(aliceCapsFilename):
  548. break
  549. time.sleep(1)
  550. with open(bobCapsFilename, 'r') as fp:
  551. bobCapsJson=commentjson.load(fp)
  552. if not bobCapsJson.get('capability'):
  553. print("Unexpected format for Bob's capabilities")
  554. pprint(bobCapsJson)
  555. assert False
  556. assert validInbox(bobDir,'bob',bobDomain)
  557. assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort)
  558. print('\n\n*********************************************************')
  559. print('Eve tries to send to Bob')
  560. sessionEve = createSession(eveDomain,evePort,useTor)
  561. eveSendThreads = []
  562. evePostLog = []
  563. evePersonCache={}
  564. eveCachedWebfingers={}
  565. eveSendThreads=[]
  566. evePostLog=[]
  567. useBlurhash=False
  568. sendResult = \
  569. sendPost(__version__, \
  570. sessionEve,eveDir,'eve', eveDomain, evePort, \
  571. 'bob', bobDomain, bobPort, ccUrl, \
  572. httpPrefix, 'Eve message', followersOnly, \
  573. saveToFile, clientToServer,None,None,None, \
  574. useBlurhash, federationList, eveSendThreads, \
  575. evePostLog, eveCachedWebfingers, \
  576. evePersonCache,inReplyTo, inReplyToAtomUri, subject)
  577. print('sendResult: '+str(sendResult))
  578. queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
  579. inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
  580. eveMessageArrived=False
  581. for i in range(10):
  582. time.sleep(1)
  583. if os.path.isdir(inboxPath):
  584. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>1:
  585. eveMessageArrived=True
  586. print('Eve message sent to Bob!')
  587. break
  588. # capabilities should have prevented delivery
  589. assert eveMessageArrived==False
  590. print('Message from Eve to Bob was correctly rejected by object capabilities')
  591. print('\n\n*********************************************************')
  592. print('Alice sends a message to Bob')
  593. aliceSendThreads = []
  594. alicePostLog = []
  595. alicePersonCache={}
  596. aliceCachedWebfingers={}
  597. aliceSendThreads=[]
  598. alicePostLog=[]
  599. useBlurhash=False
  600. sendResult = \
  601. sendPost(__version__, \
  602. sessionAlice,aliceDir,'alice', aliceDomain, alicePort, \
  603. 'bob', bobDomain, bobPort, ccUrl, \
  604. httpPrefix, 'Alice message', followersOnly, saveToFile, \
  605. clientToServer,None,None,None,useBlurhash, federationList, \
  606. aliceSendThreads, alicePostLog, aliceCachedWebfingers, \
  607. alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
  608. print('sendResult: '+str(sendResult))
  609. queuePath=bobDir+'/accounts/bob@'+bobDomain+'/queue'
  610. inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
  611. aliceMessageArrived=False
  612. for i in range(20):
  613. time.sleep(1)
  614. if os.path.isdir(inboxPath):
  615. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])>0:
  616. aliceMessageArrived=True
  617. print('Alice message sent to Bob!')
  618. break
  619. assert aliceMessageArrived==True
  620. print('Message from Alice to Bob succeeded, since it was granted capabilities')
  621. print('\n\n*********************************************************')
  622. print("\nBob changes Alice's capabilities so that she can't reply on his posts")
  623. bobCapsFilename= \
  624. bobDir+'/accounts/bob@'+bobDomain+'/ocap/accept/'+ \
  625. httpPrefix+':##'+aliceDomain+':'+str(alicePort)+'#users#alice.json'
  626. aliceCapsFilename= \
  627. aliceDir+'/accounts/alice@'+aliceDomain+'/ocap/granted/'+ \
  628. httpPrefix+':##'+bobDomain+':'+str(bobPort)+'#users#bob.json'
  629. sessionBob = createSession(bobDomain,bobPort,useTor)
  630. bobSendThreads = []
  631. bobPostLog = []
  632. bobPersonCache={}
  633. bobCachedWebfingers={}
  634. print("Bob's capabilities for Alice:")
  635. with open(bobCapsFilename, 'r') as fp:
  636. bobCapsJson=commentjson.load(fp)
  637. pprint(bobCapsJson)
  638. assert "inbox:noreply" not in bobCapsJson['capability']
  639. print("Alice's capabilities granted by Bob")
  640. with open(aliceCapsFilename, 'r') as fp:
  641. aliceCapsJson=commentjson.load(fp)
  642. pprint(aliceCapsJson)
  643. assert "inbox:noreply" not in aliceCapsJson['capability']
  644. newCapabilities=["inbox:write","objects:read","inbox:noreply"]
  645. sendCapabilitiesUpdate(sessionBob,bobDir,httpPrefix, \
  646. 'bob',bobDomain,bobPort, \
  647. httpPrefix+'://'+aliceDomain+':'+\
  648. str(alicePort)+'/users/alice',
  649. newCapabilities, \
  650. bobSendThreads, bobPostLog, \
  651. bobCachedWebfingers,bobPersonCache, \
  652. federationList,True,__version__)
  653. bobChanged=False
  654. bobNewCapsJson=None
  655. for i in range(20):
  656. time.sleep(1)
  657. with open(bobCapsFilename, 'r') as fp:
  658. bobNewCapsJson=commentjson.load(fp)
  659. if "inbox:noreply" in bobNewCapsJson['capability']:
  660. print("Bob's capabilities were changed")
  661. pprint(bobNewCapsJson)
  662. bobChanged=True
  663. break
  664. assert bobChanged
  665. aliceChanged=False
  666. aliceNewCapsJson=None
  667. for i in range(20):
  668. time.sleep(1)
  669. with open(aliceCapsFilename, 'r') as fp:
  670. aliceNewCapsJson=commentjson.load(fp)
  671. if "inbox:noreply" in aliceNewCapsJson['capability']:
  672. print("Alice's granted capabilities were changed")
  673. pprint(aliceNewCapsJson)
  674. aliceChanged=True
  675. break
  676. assert aliceChanged
  677. # check that the capabilities id has changed
  678. assert bobNewCapsJson['id']!=bobCapsJson['id']
  679. assert aliceNewCapsJson['id']!=aliceCapsJson['id']
  680. # stop the servers
  681. thrAlice.kill()
  682. thrAlice.join()
  683. assert thrAlice.isAlive()==False
  684. thrBob.kill()
  685. thrBob.join()
  686. assert thrBob.isAlive()==False
  687. thrEve.kill()
  688. thrEve.join()
  689. assert thrEve.isAlive()==False
  690. assert os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+ \
  691. '/ocap/accept/'+httpPrefix+':##'+ \
  692. aliceDomain+':'+str(alicePort)+ \
  693. '#users#alice.json')
  694. assert os.path.isfile(aliceDir+'/accounts/alice@'+ \
  695. aliceDomain+'/ocap/granted/'+ \
  696. httpPrefix+':##'+bobDomain+':'+ \
  697. str(bobPort)+'#users#bob.json')
  698. assert 'alice@'+aliceDomain in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read()
  699. assert 'bob@'+bobDomain in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read()
  700. # queue item removed
  701. assert len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))])==0
  702. os.chdir(baseDir)
  703. shutil.rmtree(baseDir+'/.tests')
  704. def testFollowersOfPerson():
  705. print('testFollowersOfPerson')
  706. currDir=os.getcwd()
  707. nickname='mxpop'
  708. domain='diva.domain'
  709. password='birb'
  710. port=80
  711. httpPrefix='https'
  712. federationList=[]
  713. baseDir=currDir+'/.tests_followersofperson'
  714. if os.path.isdir(baseDir):
  715. shutil.rmtree(baseDir)
  716. os.mkdir(baseDir)
  717. os.chdir(baseDir)
  718. createPerson(baseDir,nickname,domain,port,httpPrefix,True,password)
  719. createPerson(baseDir,'maxboardroom',domain,port,httpPrefix,True,password)
  720. createPerson(baseDir,'ultrapancake',domain,port,httpPrefix,True,password)
  721. createPerson(baseDir,'drokk',domain,port,httpPrefix,True,password)
  722. createPerson(baseDir,'sausagedog',domain,port,httpPrefix,True,password)
  723. clearFollows(baseDir,nickname,domain)
  724. followPerson(baseDir,nickname,domain,'maxboardroom',domain,federationList,False)
  725. followPerson(baseDir,'drokk',domain,'ultrapancake',domain,federationList,False)
  726. # deliberate duplication
  727. followPerson(baseDir,'drokk',domain,'ultrapancake',domain,federationList,False)
  728. followPerson(baseDir,'sausagedog',domain,'ultrapancake',domain,federationList,False)
  729. followPerson(baseDir,nickname,domain,'ultrapancake',domain,federationList,False)
  730. followPerson(baseDir,nickname,domain,'someother','randodomain.net',federationList,False)
  731. followList=getFollowersOfPerson(baseDir,'ultrapancake',domain)
  732. assert len(followList)==3
  733. assert 'mxpop@'+domain in followList
  734. assert 'drokk@'+domain in followList
  735. assert 'sausagedog@'+domain in followList
  736. os.chdir(currDir)
  737. shutil.rmtree(baseDir)
  738. def testNoOfFollowersOnDomain():
  739. print('testNoOfFollowersOnDomain')
  740. currDir=os.getcwd()
  741. nickname='mxpop'
  742. domain='diva.domain'
  743. otherdomain='soup.dragon'
  744. password='birb'
  745. port=80
  746. httpPrefix='https'
  747. federationList=[]
  748. baseDir=currDir+'/.tests_nooffollowersOndomain'
  749. if os.path.isdir(baseDir):
  750. shutil.rmtree(baseDir)
  751. os.mkdir(baseDir)
  752. os.chdir(baseDir)
  753. createPerson(baseDir,nickname,domain,port,httpPrefix,True,password)
  754. createPerson(baseDir,'maxboardroom',otherdomain,port,httpPrefix,True,password)
  755. createPerson(baseDir,'ultrapancake',otherdomain,port,httpPrefix,True,password)
  756. createPerson(baseDir,'drokk',otherdomain,port,httpPrefix,True,password)
  757. createPerson(baseDir,'sausagedog',otherdomain,port,httpPrefix,True,password)
  758. followPerson(baseDir,'drokk',otherdomain,nickname,domain,federationList,False)
  759. followPerson(baseDir,'sausagedog',otherdomain,nickname,domain,federationList,False)
  760. followPerson(baseDir,'maxboardroom',otherdomain,nickname,domain,federationList,False)
  761. followerOfPerson(baseDir,nickname,domain,'cucumber','sandwiches.party',federationList,False)
  762. followerOfPerson(baseDir,nickname,domain,'captainsensible','damned.zone',federationList,False)
  763. followerOfPerson(baseDir,nickname,domain,'pilchard','zombies.attack',federationList,False)
  764. followerOfPerson(baseDir,nickname,domain,'drokk',otherdomain,federationList,False)
  765. followerOfPerson(baseDir,nickname,domain,'sausagedog',otherdomain,federationList,False)
  766. followerOfPerson(baseDir,nickname,domain,'maxboardroom',otherdomain,federationList,False)
  767. followersOnOtherDomain=noOfFollowersOnDomain(baseDir,nickname+'@'+domain, otherdomain)
  768. assert followersOnOtherDomain==3
  769. unfollowerOfPerson(baseDir,nickname,domain,'sausagedog',otherdomain)
  770. followersOnOtherDomain=noOfFollowersOnDomain(baseDir,nickname+'@'+domain, otherdomain)
  771. assert followersOnOtherDomain==2
  772. os.chdir(currDir)
  773. shutil.rmtree(baseDir)
  774. def testGroupFollowers():
  775. print('testGroupFollowers')
  776. currDir=os.getcwd()
  777. nickname='test735'
  778. domain='mydomain.com'
  779. password='somepass'
  780. port=80
  781. httpPrefix='https'
  782. federationList=[]
  783. baseDir=currDir+'/.tests_testgroupfollowers'
  784. if os.path.isdir(baseDir):
  785. shutil.rmtree(baseDir)
  786. os.mkdir(baseDir)
  787. os.chdir(baseDir)
  788. createPerson(baseDir,nickname,domain,port,httpPrefix,True,password)
  789. clearFollowers(baseDir,nickname,domain)
  790. followerOfPerson(baseDir,nickname,domain,'badger','wild.domain',federationList,False)
  791. followerOfPerson(baseDir,nickname,domain,'squirrel','wild.domain',federationList,False)
  792. followerOfPerson(baseDir,nickname,domain,'rodent','wild.domain',federationList,False)
  793. followerOfPerson(baseDir,nickname,domain,'utterly','clutterly.domain',federationList,False)
  794. followerOfPerson(baseDir,nickname,domain,'zonked','zzz.domain',federationList,False)
  795. followerOfPerson(baseDir,nickname,domain,'nap','zzz.domain',federationList,False)
  796. grouped=groupFollowersByDomain(baseDir,nickname,domain)
  797. assert len(grouped.items())==3
  798. assert grouped.get('zzz.domain')
  799. assert grouped.get('clutterly.domain')
  800. assert grouped.get('wild.domain')
  801. assert len(grouped['zzz.domain'])==2
  802. assert len(grouped['wild.domain'])==3
  803. assert len(grouped['clutterly.domain'])==1
  804. os.chdir(currDir)
  805. shutil.rmtree(baseDir)
  806. def testFollows():
  807. print('testFollows')
  808. currDir=os.getcwd()
  809. nickname='test529'
  810. domain='testdomain.com'
  811. password='mypass'
  812. port=80
  813. httpPrefix='https'
  814. federationList=['wild.com','mesh.com']
  815. baseDir=currDir+'/.tests_testfollows'
  816. if os.path.isdir(baseDir):
  817. shutil.rmtree(baseDir)
  818. os.mkdir(baseDir)
  819. os.chdir(baseDir)
  820. createPerson(baseDir,nickname,domain,port,httpPrefix,True,password)
  821. clearFollows(baseDir,nickname,domain)
  822. followPerson(baseDir,nickname,domain,'badger','wild.com',federationList,False)
  823. followPerson(baseDir,nickname,domain,'squirrel','secret.com',federationList,False)
  824. followPerson(baseDir,nickname,domain,'rodent','drainpipe.com',federationList,False)
  825. followPerson(baseDir,nickname,domain,'batman','mesh.com',federationList,False)
  826. followPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList,False)
  827. f = open(baseDir+'/accounts/'+nickname+'@'+domain+'/following.txt', "r")
  828. domainFound=False
  829. for followingDomain in f:
  830. testDomain=followingDomain.split('@')[1].replace('\n','')
  831. if testDomain=='mesh.com':
  832. domainFound=True
  833. if testDomain not in federationList:
  834. print(testDomain)
  835. assert(False)
  836. assert(domainFound)
  837. unfollowPerson(baseDir,nickname,domain,'batman','mesh.com')
  838. domainFound=False
  839. for followingDomain in f:
  840. testDomain=followingDomain.split('@')[1].replace('\n','')
  841. if testDomain=='mesh.com':
  842. domainFound=True
  843. assert(domainFound==False)
  844. clearFollowers(baseDir,nickname,domain)
  845. followerOfPerson(baseDir,nickname,domain,'badger','wild.com',federationList,False)
  846. followerOfPerson(baseDir,nickname,domain,'squirrel','secret.com',federationList,False)
  847. followerOfPerson(baseDir,nickname,domain,'rodent','drainpipe.com',federationList,False)
  848. followerOfPerson(baseDir,nickname,domain,'batman','mesh.com',federationList,False)
  849. followerOfPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList,False)
  850. f = open(baseDir+'/accounts/'+nickname+'@'+domain+'/followers.txt', "r")
  851. for followerDomain in f:
  852. testDomain=followerDomain.split('@')[1].replace('\n','')
  853. if testDomain not in federationList:
  854. print(testDomain)
  855. assert(False)
  856. os.chdir(currDir)
  857. shutil.rmtree(baseDir)
  858. def testCreatePerson():
  859. print('testCreatePerson')
  860. currDir=os.getcwd()
  861. nickname='test382'
  862. domain='badgerdomain.com'
  863. password='mypass'
  864. port=80
  865. httpPrefix='https'
  866. clientToServer=False
  867. useBlurhash=False
  868. baseDir=currDir+'/.tests_createperson'
  869. if os.path.isdir(baseDir):
  870. shutil.rmtree(baseDir)
  871. os.mkdir(baseDir)
  872. os.chdir(baseDir)
  873. privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,True,password)
  874. assert os.path.isfile(baseDir+'/accounts/passwords')
  875. deleteAllPosts(baseDir,nickname,domain,'inbox')
  876. deleteAllPosts(baseDir,nickname,domain,'outbox')
  877. setDisplayNickname(baseDir,nickname,domain,'badger')
  878. setBio(baseDir,nickname,domain,'Randomly roaming in your backyard')
  879. archivePostsForPerson(nickname,domain,baseDir,'inbox',None,4)
  880. archivePostsForPerson(nickname,domain,baseDir,'outbox',None,4)
  881. createPublicPost(baseDir,nickname, domain, port,httpPrefix, "G'day world!", False, True, clientToServer,None,None,useBlurhash, None, None, 'Not suitable for Vogons')
  882. os.chdir(currDir)
  883. shutil.rmtree(baseDir)
  884. def testDelegateRoles():
  885. print('testDelegateRoles')
  886. currDir=os.getcwd()
  887. nickname='test382'
  888. nicknameDelegated='test383'
  889. domain='badgerdomain.com'
  890. password='mypass'
  891. port=80
  892. httpPrefix='https'
  893. clientToServer=False
  894. useBlurhash=False
  895. baseDir=currDir+'/.tests_delegaterole'
  896. if os.path.isdir(baseDir):
  897. shutil.rmtree(baseDir)
  898. os.mkdir(baseDir)
  899. os.chdir(baseDir)
  900. privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,True,password)
  901. privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nicknameDelegated,domain,port,httpPrefix,True,'insecure')
  902. httpPrefix='http'
  903. project='artechoke'
  904. role='delegator'
  905. newRoleJson = {
  906. 'type': 'Delegate',
  907. 'actor': httpPrefix+'://'+domain+'/users/'+nickname,
  908. 'object': {
  909. 'type': 'Role',
  910. 'actor': httpPrefix+'://'+domain+'/users/'+nicknameDelegated,
  911. 'object': project+';'+role,
  912. 'to': [],
  913. 'cc': []
  914. },
  915. 'to': [],
  916. 'cc': []
  917. }
  918. assert outboxDelegate(baseDir,nickname,newRoleJson,False)
  919. # second time delegation has already happened so should return false
  920. assert outboxDelegate(baseDir,nickname,newRoleJson,False)==False
  921. assert '"delegator"' in open(baseDir+'/accounts/'+nickname+'@'+domain+'.json').read()
  922. assert '"delegator"' in open(baseDir+'/accounts/'+nicknameDelegated+'@'+domain+'.json').read()
  923. newRoleJson = {
  924. 'type': 'Delegate',
  925. 'actor': httpPrefix+'://'+domain+'/users/'+nicknameDelegated,
  926. 'object': {
  927. 'type': 'Role',
  928. 'actor': httpPrefix+'://'+domain+'/users/'+nickname,
  929. 'object': 'otherproject;otherrole',
  930. 'to': [],
  931. 'cc': []
  932. },
  933. 'to': [],
  934. 'cc': []
  935. }
  936. # non-delegators cannot assign roles
  937. assert outboxDelegate(baseDir,nicknameDelegated,newRoleJson,False)==False
  938. assert '"otherrole"' not in open(baseDir+'/accounts/'+nickname+'@'+domain+'.json').read()
  939. os.chdir(currDir)
  940. shutil.rmtree(baseDir)
  941. def testAuthentication():
  942. print('testAuthentication')
  943. currDir=os.getcwd()
  944. nickname='test8743'
  945. password='SuperSecretPassword12345'
  946. baseDir=currDir+'/.tests_authentication'
  947. if os.path.isdir(baseDir):
  948. shutil.rmtree(baseDir)
  949. os.mkdir(baseDir)
  950. os.chdir(baseDir)
  951. assert storeBasicCredentials(baseDir,'othernick','otherpass')
  952. assert storeBasicCredentials(baseDir,'bad:nick','otherpass')==False
  953. assert storeBasicCredentials(baseDir,'badnick','otherpa:ss')==False
  954. assert storeBasicCredentials(baseDir,nickname,password)
  955. authHeader=createBasicAuthHeader(nickname,password)
  956. assert authorizeBasic(baseDir,'/users/'+nickname+'/inbox',authHeader,False)
  957. assert authorizeBasic(baseDir,'/users/'+nickname,authHeader,False)==False
  958. assert authorizeBasic(baseDir,'/users/othernick/inbox',authHeader,False)==False
  959. authHeader=createBasicAuthHeader(nickname,password+'1')
  960. assert authorizeBasic(baseDir,'/users/'+nickname+'/inbox',authHeader,False)==False
  961. password='someOtherPassword'
  962. assert storeBasicCredentials(baseDir,nickname,password)
  963. authHeader=createBasicAuthHeader(nickname,password)
  964. assert authorizeBasic(baseDir,'/users/'+nickname+'/inbox',authHeader,False)
  965. os.chdir(currDir)
  966. shutil.rmtree(baseDir)
  967. def testClientToServer():
  968. print('Testing sending a post via c2s')
  969. global testServerAliceRunning
  970. global testServerBobRunning
  971. testServerAliceRunning = False
  972. testServerBobRunning = False
  973. httpPrefix='http'
  974. useTor=False
  975. federationList=[]
  976. baseDir=os.getcwd()
  977. if os.path.isdir(baseDir+'/.tests'):
  978. shutil.rmtree(baseDir+'/.tests')
  979. os.mkdir(baseDir+'/.tests')
  980. ocapAlways=False
  981. # create the servers
  982. aliceDir=baseDir+'/.tests/alice'
  983. aliceDomain='127.0.0.42'
  984. alicePort=61935
  985. thrAlice = \
  986. threadWithTrace(target=createServerAlice, \
  987. args=(aliceDir,aliceDomain,alicePort, \
  988. federationList,False,False, \
  989. ocapAlways),daemon=True)
  990. bobDir=baseDir+'/.tests/bob'
  991. bobDomain='127.0.0.64'
  992. bobPort=61936
  993. thrBob = \
  994. threadWithTrace(target=createServerBob, \
  995. args=(bobDir,bobDomain,bobPort, \
  996. federationList,False,False, \
  997. ocapAlways),daemon=True)
  998. thrAlice.start()
  999. thrBob.start()
  1000. assert thrAlice.isAlive()==True
  1001. assert thrBob.isAlive()==True
  1002. # wait for both servers to be running
  1003. ctr=0
  1004. while not (testServerAliceRunning and testServerBobRunning):
  1005. time.sleep(1)
  1006. ctr+=1
  1007. if ctr>60:
  1008. break
  1009. print('Alice online: '+str(testServerAliceRunning))
  1010. print('Bob online: '+str(testServerBobRunning))
  1011. time.sleep(1)
  1012. print('\n\n*******************************************************')
  1013. print('Alice sends to Bob via c2s')
  1014. sessionAlice = createSession(aliceDomain,alicePort,useTor)
  1015. followersOnly=False
  1016. attachedImageFilename=baseDir+'/img/logo.png'
  1017. mediaType=getAttachmentMediaType(attachedImageFilename)
  1018. attachedImageDescription='Logo'
  1019. useBlurhash=False
  1020. cachedWebfingers={}
  1021. personCache={}
  1022. password='alicepass'
  1023. outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
  1024. inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
  1025. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==0
  1026. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==0
  1027. sendResult= \
  1028. sendPostViaServer(__version__, \
  1029. aliceDir,sessionAlice,'alice',password, \
  1030. aliceDomain,alicePort, \
  1031. 'bob',bobDomain,bobPort,None, \
  1032. httpPrefix,'Sent from my ActivityPub client',followersOnly, \
  1033. attachedImageFilename,mediaType, \
  1034. attachedImageDescription,useBlurhash, \
  1035. cachedWebfingers,personCache, \
  1036. True,None,None,None)
  1037. print('sendResult: '+str(sendResult))
  1038. for i in range(30):
  1039. if os.path.isdir(outboxPath):
  1040. if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1:
  1041. break
  1042. time.sleep(1)
  1043. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1
  1044. print(">>> c2s post arrived in Alice's outbox")
  1045. for i in range(30):
  1046. if os.path.isdir(inboxPath):
  1047. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1:
  1048. break
  1049. time.sleep(1)
  1050. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
  1051. print(">>> s2s post arrived in Bob's inbox")
  1052. print("c2s send success")
  1053. print('\n\nGetting message id for the post')
  1054. statusNumber=0
  1055. outboxPostFilename=None
  1056. outboxPostId=None
  1057. for name in os.listdir(outboxPath):
  1058. if '#statuses#' in name:
  1059. statusNumber=int(name.split('#statuses#')[1].replace('.json','').replace('#activity',''))
  1060. outboxPostFilename=outboxPath+'/'+name
  1061. with open(outboxPostFilename, 'r') as fp:
  1062. postJsonObject=commentjson.load(fp)
  1063. outboxPostId=postJsonObject['id'].replace('/activity','')
  1064. assert outboxPostId
  1065. print('message id obtained: '+outboxPostId)
  1066. assert validInbox(bobDir,'bob',bobDomain)
  1067. assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort)
  1068. print('\n\nAlice follows Bob')
  1069. sendFollowRequestViaServer(aliceDir,sessionAlice, \
  1070. 'alice',password, \
  1071. aliceDomain,alicePort, \
  1072. 'bob',bobDomain,bobPort, \
  1073. httpPrefix, \
  1074. cachedWebfingers,personCache, \
  1075. True,__version__)
  1076. for t in range(10):
  1077. if os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt'):
  1078. if 'alice@'+aliceDomain+':'+str(alicePort) in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read():
  1079. if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt'):
  1080. if 'bob@'+bobDomain+':'+str(bobPort) in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read():
  1081. break
  1082. time.sleep(1)
  1083. assert os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt')
  1084. assert os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt')
  1085. assert 'alice@'+aliceDomain+':'+str(alicePort) in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read()
  1086. assert 'bob@'+bobDomain+':'+str(bobPort) in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read()
  1087. assert validInbox(bobDir,'bob',bobDomain)
  1088. assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort)
  1089. print('\n\nBob follows Alice')
  1090. sendFollowRequestViaServer(aliceDir,sessionAlice, \
  1091. 'bob','bobpass', \
  1092. bobDomain,bobPort, \
  1093. 'alice',aliceDomain,alicePort, \
  1094. httpPrefix, \
  1095. cachedWebfingers,personCache, \
  1096. True,__version__)
  1097. for t in range(10):
  1098. if os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/followers.txt'):
  1099. if 'bob@'+bobDomain+':'+str(bobPort) in open(aliceDir+'/accounts/alice@'+aliceDomain+'/followers.txt').read():
  1100. if os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/following.txt'):
  1101. if 'alice@'+aliceDomain+':'+str(alicePort) in open(bobDir+'/accounts/bob@'+bobDomain+'/following.txt').read():
  1102. break
  1103. time.sleep(1)
  1104. assert os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/followers.txt')
  1105. assert os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/following.txt')
  1106. assert 'bob@'+bobDomain+':'+str(bobPort) in open(aliceDir+'/accounts/alice@'+aliceDomain+'/followers.txt').read()
  1107. assert 'alice@'+aliceDomain+':'+str(alicePort) in open(bobDir+'/accounts/bob@'+bobDomain+'/following.txt').read()
  1108. print('\n\nBob likes the post')
  1109. sessionBob = createSession(bobDomain,bobPort,useTor)
  1110. password='bobpass'
  1111. outboxPath=bobDir+'/accounts/bob@'+bobDomain+'/outbox'
  1112. inboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/inbox'
  1113. print(str(len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])))
  1114. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==1
  1115. print(str(len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])))
  1116. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
  1117. sendLikeViaServer(bobDir,sessionBob, \
  1118. 'bob','bobpass', \
  1119. bobDomain,bobPort, \
  1120. httpPrefix,outboxPostId, \
  1121. cachedWebfingers,personCache, \
  1122. True,__version__)
  1123. for i in range(20):
  1124. if os.path.isdir(outboxPath) and os.path.isdir(inboxPath):
  1125. if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==2:
  1126. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1:
  1127. break
  1128. time.sleep(1)
  1129. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==2
  1130. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
  1131. print('Post liked')
  1132. print('\n\nBob repeats the post')
  1133. print(str(len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])))
  1134. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==2
  1135. print(str(len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])))
  1136. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==1
  1137. sendAnnounceViaServer(bobDir,sessionBob,'bob',password, \
  1138. bobDomain,bobPort, \
  1139. httpPrefix,outboxPostId, \
  1140. cachedWebfingers, \
  1141. personCache,True,__version__)
  1142. for i in range(20):
  1143. if os.path.isdir(outboxPath) and os.path.isdir(inboxPath):
  1144. if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==3:
  1145. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==2:
  1146. break
  1147. time.sleep(1)
  1148. assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==3
  1149. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==2
  1150. print('Post repeated')
  1151. inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
  1152. outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
  1153. postsBefore = len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])
  1154. print('\n\nAlice deletes her post: '+outboxPostId+' '+str(postsBefore))
  1155. password='alicepass'
  1156. sendDeleteViaServer(aliceDir,sessionAlice,'alice',password,
  1157. aliceDomain,alicePort, \
  1158. httpPrefix,outboxPostId, \
  1159. cachedWebfingers,personCache, \
  1160. True,__version__)
  1161. for i in range(30):
  1162. if os.path.isdir(inboxPath):
  1163. if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==postsBefore-1:
  1164. break
  1165. time.sleep(1)
  1166. assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])==postsBefore-1
  1167. print(">>> post deleted from Alice's outbox and Bob's inbox")
  1168. assert validInbox(bobDir,'bob',bobDomain)
  1169. assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort)
  1170. print('\n\nAlice unfollows Bob')
  1171. password='alicepass'
  1172. sendUnfollowRequestViaServer(baseDir,sessionAlice, \
  1173. 'alice',password, \
  1174. aliceDomain,alicePort, \
  1175. 'bob',bobDomain,bobPort, \
  1176. httpPrefix, \
  1177. cachedWebfingers,personCache, \
  1178. True,__version__)
  1179. for t in range(10):
  1180. if 'alice@'+aliceDomain+':'+str(alicePort) not in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read():
  1181. if 'bob@'+bobDomain+':'+str(bobPort) not in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read():
  1182. break
  1183. time.sleep(1)
  1184. assert os.path.isfile(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt')
  1185. assert os.path.isfile(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt')
  1186. assert 'alice@'+aliceDomain+':'+str(alicePort) not in open(bobDir+'/accounts/bob@'+bobDomain+'/followers.txt').read()
  1187. assert 'bob@'+bobDomain+':'+str(bobPort) not in open(aliceDir+'/accounts/alice@'+aliceDomain+'/following.txt').read()
  1188. assert validInbox(bobDir,'bob',bobDomain)
  1189. assert validInboxFilenames(bobDir,'bob',bobDomain,aliceDomain,alicePort)
  1190. assert validInbox(aliceDir,'alice',aliceDomain)
  1191. assert validInboxFilenames(aliceDir,'alice',aliceDomain,bobDomain,bobPort)
  1192. # stop the servers
  1193. thrAlice.kill()
  1194. thrAlice.join()
  1195. assert thrAlice.isAlive()==False
  1196. thrBob.kill()
  1197. thrBob.join()
  1198. assert thrBob.isAlive()==False
  1199. os.chdir(baseDir)
  1200. #shutil.rmtree(aliceDir)
  1201. #shutil.rmtree(bobDir)
  1202. def testActorParsing():
  1203. print('testActorParsing')
  1204. actor='https://mydomain:72/users/mynick'
  1205. domain,port=getDomainFromActor(actor)
  1206. assert domain=='mydomain'
  1207. assert port==72
  1208. nickname=getNicknameFromActor(actor)
  1209. assert nickname=='mynick'
  1210. actor='https://randomain/users/rando'
  1211. domain,port=getDomainFromActor(actor)
  1212. assert domain=='randomain'
  1213. nickname=getNicknameFromActor(actor)
  1214. assert nickname=='rando'
  1215. actor='https://otherdomain:49/@othernick'
  1216. domain,port=getDomainFromActor(actor)
  1217. assert domain=='otherdomain'
  1218. assert port==49
  1219. nickname=getNicknameFromActor(actor)
  1220. assert nickname=='othernick'
  1221. def testWebLinks():
  1222. print('testWebLinks')
  1223. exampleText='This post has a web links https://somesite.net\n\nAnd some other text'
  1224. linkedText=addWebLinks(exampleText)
  1225. assert '<a href="https://somesite.net" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">somesite.net</span></a' in linkedText
  1226. def runAllTests():
  1227. print('Running tests...')
  1228. testWebLinks()
  1229. testActorParsing()
  1230. testHttpsig()
  1231. testCache()
  1232. testThreads()
  1233. testCreatePerson()
  1234. testAuthentication()
  1235. testFollowersOfPerson()
  1236. testNoOfFollowersOnDomain()
  1237. testFollows()
  1238. testGroupFollowers()
  1239. testDelegateRoles()
  1240. print('Tests succeeded\n')