blocking.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. __filename__ = "blocking.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "0.0.1"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. import os
  9. def addGlobalBlock(baseDir: str, \
  10. blockNickname: str,blockDomain: str) -> bool:
  11. """Global block which applies to all accounts
  12. """
  13. blockingFilename=baseDir+'/accounts/blocking.txt'
  14. if not blockNickname.startswith('#'):
  15. blockHandle=blockNickname+'@'+blockDomain
  16. if os.path.isfile(blockingFilename):
  17. if blockHandle in open(blockingFilename).read():
  18. return False
  19. blockFile=open(blockingFilename, "a+")
  20. blockFile.write(blockHandle+'\n')
  21. blockFile.close()
  22. else:
  23. blockHashtag=blockNickname
  24. if os.path.isfile(blockingFilename):
  25. if blockHashtag+'\n' in open(blockingFilename).read():
  26. return False
  27. blockFile=open(blockingFilename, "a+")
  28. blockFile.write(blockHashtag+'\n')
  29. blockFile.close()
  30. return True
  31. def addBlock(baseDir: str,nickname: str,domain: str, \
  32. blockNickname: str,blockDomain: str) -> bool:
  33. """Block the given account
  34. """
  35. if ':' in domain:
  36. domain=domain.split(':')[0]
  37. blockingFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  38. blockHandle=blockNickname+'@'+blockDomain
  39. if os.path.isfile(blockingFilename):
  40. if blockHandle in open(blockingFilename).read():
  41. return False
  42. blockFile=open(blockingFilename, "a+")
  43. blockFile.write(blockHandle+'\n')
  44. blockFile.close()
  45. return True
  46. def removeGlobalBlock(baseDir: str, \
  47. unblockNickname: str, \
  48. unblockDomain: str) -> bool:
  49. """Unblock the given global block
  50. """
  51. unblockingFilename=baseDir+'/accounts/blocking.txt'
  52. if not unblockNickname.startswith('#'):
  53. unblockHandle=unblockNickname+'@'+unblockDomain
  54. if os.path.isfile(unblockingFilename):
  55. if unblockHandle in open(unblockingFilename).read():
  56. with open(unblockingFilename, 'r') as fp:
  57. with open(unblockingFilename+'.new', 'w') as fpnew:
  58. for line in fp:
  59. handle=line.replace('\n','')
  60. if unblockHandle not in line:
  61. fpnew.write(handle+'\n')
  62. if os.path.isfile(unblockingFilename+'.new'):
  63. os.rename(unblockingFilename+'.new',unblockingFilename)
  64. return True
  65. else:
  66. unblockHashtag=unblockNickname
  67. if os.path.isfile(unblockingFilename):
  68. if unblockHashtag+'\n' in open(unblockingFilename).read():
  69. with open(unblockingFilename, 'r') as fp:
  70. with open(unblockingFilename+'.new', 'w') as fpnew:
  71. for line in fp:
  72. blockLine=line.replace('\n','')
  73. if unblockHashtag not in line:
  74. fpnew.write(blockLine+'\n')
  75. if os.path.isfile(unblockingFilename+'.new'):
  76. os.rename(unblockingFilename+'.new',unblockingFilename)
  77. return True
  78. return False
  79. def removeBlock(baseDir: str,nickname: str,domain: str, \
  80. unblockNickname: str,unblockDomain: str) -> bool:
  81. """Unblock the given account
  82. """
  83. if ':' in domain:
  84. domain=domain.split(':')[0]
  85. unblockingFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  86. unblockHandle=unblockNickname+'@'+unblockDomain
  87. if os.path.isfile(unblockingFilename):
  88. if unblockHandle in open(unblockingFilename).read():
  89. with open(unblockingFilename, 'r') as fp:
  90. with open(unblockingFilename+'.new', 'w') as fpnew:
  91. for line in fp:
  92. handle=line.replace('\n','')
  93. if unblockHandle not in line:
  94. fpnew.write(handle+'\n')
  95. if os.path.isfile(unblockingFilename+'.new'):
  96. os.rename(unblockingFilename+'.new',unblockingFilename)
  97. return True
  98. return False
  99. def isBlockedHashtag(baseDir: str,hashtag: str) -> bool:
  100. """Is the given hashtag blocked?
  101. """
  102. globalBlockingFilename=baseDir+'/accounts/blocking.txt'
  103. if os.path.isfile(globalBlockingFilename):
  104. hashtag=hashtag.strip('\n')
  105. if hashtag+'\n' in open(globalBlockingFilename).read():
  106. return True
  107. return False
  108. def isBlocked(baseDir: str,nickname: str,domain: str, \
  109. blockNickname: str,blockDomain: str) -> bool:
  110. """Is the given nickname blocked?
  111. """
  112. globalBlockingFilename=baseDir+'/accounts/blocking.txt'
  113. if os.path.isfile(globalBlockingFilename):
  114. if '*@'+blockDomain in open(globalBlockingFilename).read():
  115. return True
  116. blockHandle=blockNickname+'@'+blockDomain
  117. if blockHandle in open(globalBlockingFilename).read():
  118. return True
  119. allowFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt'
  120. if os.path.isfile(allowFilename):
  121. if blockDomain not in open(allowFilename).read():
  122. return True
  123. blockingFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  124. if os.path.isfile(blockingFilename):
  125. if '*@'+blockDomain in open(blockingFilename).read():
  126. return True
  127. blockHandle=blockNickname+'@'+blockDomain
  128. if blockHandle in open(blockingFilename).read():
  129. return True
  130. return False
  131. def sendBlockViaServer(baseDir: str,session, \
  132. fromNickname: str,password: str, \
  133. fromDomain: str,fromPort: int, \
  134. httpPrefix: str,blockedUrl: str, \
  135. cachedWebfingers: {},personCache: {}, \
  136. debug: bool,projectVersion: str) -> {}:
  137. """Creates a block via c2s
  138. """
  139. if not session:
  140. print('WARN: No session for sendBlockViaServer')
  141. return 6
  142. fromDomainFull=fromDomain
  143. if fromPort:
  144. if fromPort!=80 and fromPort!=443:
  145. if ':' not in fromDomain:
  146. fromDomainFull=fromDomain+':'+str(fromPort)
  147. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  148. ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  149. blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
  150. newBlockJson = {
  151. "@context": "https://www.w3.org/ns/activitystreams",
  152. 'type': 'Block',
  153. 'actor': blockActor,
  154. 'object': blockedUrl,
  155. 'to': [toUrl],
  156. 'cc': [ccUrl]
  157. }
  158. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  159. # lookup the inbox for the To handle
  160. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  161. fromDomain,projectVersion)
  162. if not wfRequest:
  163. if debug:
  164. print('DEBUG: announce webfinger failed for '+handle)
  165. return 1
  166. postToBox='outbox'
  167. # get the actor inbox for the To handle
  168. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  169. getPersonBox(baseDir,session,wfRequest,personCache, \
  170. projectVersion,httpPrefix,fromDomain,postToBox)
  171. if not inboxUrl:
  172. if debug:
  173. print('DEBUG: No '+postToBox+' was found for '+handle)
  174. return 3
  175. if not fromPersonId:
  176. if debug:
  177. print('DEBUG: No actor was found for '+handle)
  178. return 4
  179. authHeader=createBasicAuthHeader(fromNickname,password)
  180. headers = {'host': fromDomain, \
  181. 'Content-type': 'application/json', \
  182. 'Authorization': authHeader}
  183. postResult = \
  184. postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
  185. #if not postResult:
  186. # if debug:
  187. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  188. # return 5
  189. if debug:
  190. print('DEBUG: c2s POST block success')
  191. return newBlockJson
  192. def sendUndoBlockViaServer(baseDir: str,session, \
  193. fromNickname: str,password: str, \
  194. fromDomain: str,fromPort: int, \
  195. httpPrefix: str,blockedUrl: str, \
  196. cachedWebfingers: {},personCache: {}, \
  197. debug: bool,projectVersion: str) -> {}:
  198. """Creates a block via c2s
  199. """
  200. if not session:
  201. print('WARN: No session for sendBlockViaServer')
  202. return 6
  203. fromDomainFull=fromDomain
  204. if fromPort:
  205. if fromPort!=80 and fromPort!=443:
  206. if ':' not in fromDomain:
  207. fromDomainFull=fromDomain+':'+str(fromPort)
  208. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  209. ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  210. blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
  211. newBlockJson = {
  212. "@context": "https://www.w3.org/ns/activitystreams",
  213. 'type': 'Undo',
  214. 'actor': blockActor,
  215. 'object': {
  216. 'type': 'Block',
  217. 'actor': blockActor,
  218. 'object': blockedUrl,
  219. 'to': [toUrl],
  220. 'cc': [ccUrl]
  221. }
  222. }
  223. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  224. # lookup the inbox for the To handle
  225. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  226. fromDomain,projectVersion)
  227. if not wfRequest:
  228. if debug:
  229. print('DEBUG: announce webfinger failed for '+handle)
  230. return 1
  231. postToBox='outbox'
  232. # get the actor inbox for the To handle
  233. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  234. getPersonBox(baseDir,session,wfRequest,personCache, \
  235. projectVersion,httpPrefix,fromDomain,postToBox)
  236. if not inboxUrl:
  237. if debug:
  238. print('DEBUG: No '+postToBox+' was found for '+handle)
  239. return 3
  240. if not fromPersonId:
  241. if debug:
  242. print('DEBUG: No actor was found for '+handle)
  243. return 4
  244. authHeader=createBasicAuthHeader(fromNickname,password)
  245. headers = {'host': fromDomain, \
  246. 'Content-type': 'application/json', \
  247. 'Authorization': authHeader}
  248. postResult = \
  249. postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
  250. #if not postResult:
  251. # if debug:
  252. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  253. # return 5
  254. if debug:
  255. print('DEBUG: c2s POST block success')
  256. return newBlockJson
  257. def outboxBlock(baseDir: str,httpPrefix: str, \
  258. nickname: str,domain: str,port: int, \
  259. messageJson: {},debug: bool) -> None:
  260. """ When a block request is received by the outbox from c2s
  261. """
  262. if not messageJson.get('type'):
  263. if debug:
  264. print('DEBUG: block - no type')
  265. return
  266. if not messageJson['type']=='Block':
  267. if debug:
  268. print('DEBUG: not a block')
  269. return
  270. if not messageJson.get('object'):
  271. if debug:
  272. print('DEBUG: no object in block')
  273. return
  274. if not isinstance(messageJson['object'], str):
  275. if debug:
  276. print('DEBUG: block object is not string')
  277. return
  278. if debug:
  279. print('DEBUG: c2s block request arrived in outbox')
  280. messageId=messageJson['object'].replace('/activity','')
  281. if '/statuses/' not in messageId:
  282. if debug:
  283. print('DEBUG: c2s block object is not a status')
  284. return
  285. if '/users/' not in messageId:
  286. if debug:
  287. print('DEBUG: c2s block object has no nickname')
  288. return
  289. if ':' in domain:
  290. domain=domain.split(':')[0]
  291. postFilename=locatePost(baseDir,nickname,domain,messageId)
  292. if not postFilename:
  293. if debug:
  294. print('DEBUG: c2s block post not found in inbox or outbox')
  295. print(messageId)
  296. return True
  297. nicknameBlocked=getNicknameFromActor(messageJson['object'])
  298. domainBlocked,portBlocked=getDomainFromActor(messageJson['object'])
  299. domainBlockedFull=domainBlocked
  300. if portBlocked:
  301. if portBlocked!=80 and portBlocked!=443:
  302. if ':' not in domainBlocked:
  303. domainBlockedFull=domainBlocked+':'+str(portBlocked)
  304. addBlock(baseDir,nickname,domain, \
  305. nicknameBlocked,domainBlockedFull)
  306. if debug:
  307. print('DEBUG: post blocked via c2s - '+postFilename)
  308. def outboxUndoBlock(baseDir: str,httpPrefix: str, \
  309. nickname: str,domain: str,port: int, \
  310. messageJson: {},debug: bool) -> None:
  311. """ When an undo block request is received by the outbox from c2s
  312. """
  313. if not messageJson.get('type'):
  314. if debug:
  315. print('DEBUG: undo block - no type')
  316. return
  317. if not messageJson['type']=='Undo':
  318. if debug:
  319. print('DEBUG: not an undo block')
  320. return
  321. if not messageJson.get('object'):
  322. if debug:
  323. print('DEBUG: no object in undo block')
  324. return
  325. if not isinstance(messageJson['object'], dict):
  326. if debug:
  327. print('DEBUG: undo block object is not string')
  328. return
  329. if not messageJson['object'].get('type'):
  330. if debug:
  331. print('DEBUG: undo block - no type')
  332. return
  333. if not messageJson['object']['type']=='Block':
  334. if debug:
  335. print('DEBUG: not an undo block')
  336. return
  337. if not messageJson['object'].get('object'):
  338. if debug:
  339. print('DEBUG: no object in undo block')
  340. return
  341. if not isinstance(messageJson['object']['object'], str):
  342. if debug:
  343. print('DEBUG: undo block object is not string')
  344. return
  345. if debug:
  346. print('DEBUG: c2s undo block request arrived in outbox')
  347. messageId=messageJson['object']['object'].replace('/activity','')
  348. if '/statuses/' not in messageId:
  349. if debug:
  350. print('DEBUG: c2s undo block object is not a status')
  351. return
  352. if '/users/' not in messageId:
  353. if debug:
  354. print('DEBUG: c2s undo block object has no nickname')
  355. return
  356. if ':' in domain:
  357. domain=domain.split(':')[0]
  358. postFilename=locatePost(baseDir,nickname,domain,messageId)
  359. if not postFilename:
  360. if debug:
  361. print('DEBUG: c2s undo block post not found in inbox or outbox')
  362. print(messageId)
  363. return True
  364. nicknameBlocked=getNicknameFromActor(messageJson['object']['object'])
  365. domainBlocked,portBlocked=getDomainFromActor(messageJson['object']['object'])
  366. domainBlockedFull=domainBlocked
  367. if portBlocked:
  368. if portBlocked!=80 and portBlocked!=443:
  369. if ':' not in domainBlocked:
  370. domainBlockedFull=domainBlocked+':'+str(portBlocked)
  371. removeBlock(baseDir,nickname,domain, \
  372. nicknameBlocked,domainBlockedFull)
  373. if debug:
  374. print('DEBUG: post undo blocked via c2s - '+postFilename)