delete.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. __filename__ = "delete.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 os
  9. import json
  10. import commentjson
  11. from utils import getStatusNumber
  12. from utils import createOutboxDir
  13. from utils import urlPermitted
  14. from utils import getNicknameFromActor
  15. from utils import getDomainFromActor
  16. from utils import locatePost
  17. from utils import deletePost
  18. from utils import removeModerationPostFromIndex
  19. from posts import sendSignedJson
  20. from session import postJson
  21. from webfinger import webfingerHandle
  22. from auth import createBasicAuthHeader
  23. from posts import getPersonBox
  24. def createDelete(session,baseDir: str,federationList: [], \
  25. nickname: str, domain: str, port: int, \
  26. toUrl: str, ccUrl: str, httpPrefix: str, \
  27. objectUrl: str,clientToServer: bool, \
  28. sendThreads: [],postLog: [], \
  29. personCache: {},cachedWebfingers: {}, \
  30. debug: bool) -> {}:
  31. """Creates a delete message
  32. Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
  33. and ccUrl might be a specific person whose post is to be deleted
  34. objectUrl is typically the url of the message, corresponding to url
  35. or atomUri in createPostBase
  36. """
  37. if not urlPermitted(objectUrl,federationList,"inbox:write"):
  38. return None
  39. if ':' in domain:
  40. domain=domain.split(':')[0]
  41. fullDomain=domain
  42. if port:
  43. if port!=80 and port!=443:
  44. if ':' not in domain:
  45. fullDomain=domain+':'+str(port)
  46. statusNumber,published = getStatusNumber()
  47. newDeleteId= \
  48. httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
  49. newDelete = {
  50. "@context": "https://www.w3.org/ns/activitystreams",
  51. 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
  52. 'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber,
  53. 'cc': [],
  54. 'id': newDeleteId+'/activity',
  55. 'object': objectUrl,
  56. 'published': published,
  57. 'to': [toUrl],
  58. 'type': 'Delete'
  59. }
  60. if ccUrl:
  61. if len(ccUrl)>0:
  62. newDelete['cc']=[ccUrl]
  63. deleteNickname=None
  64. deleteDomain=None
  65. deletePort=None
  66. if '/users/' in objectUrl or '/profile/' in objectUrl:
  67. deleteNickname=getNicknameFromActor(objectUrl)
  68. deleteDomain,deletePort=getDomainFromActor(objectUrl)
  69. if deleteNickname and deleteDomain:
  70. sendSignedJson(newDelete,session,baseDir, \
  71. nickname,domain,port, \
  72. deleteNickname,deleteDomain,deletePort, \
  73. 'https://www.w3.org/ns/activitystreams#Public', \
  74. httpPrefix,True,clientToServer,federationList, \
  75. sendThreads,postLog,cachedWebfingers,personCache,debug)
  76. return newDelete
  77. def sendDeleteViaServer(baseDir: str,session, \
  78. fromNickname: str,password: str, \
  79. fromDomain: str,fromPort: int, \
  80. httpPrefix: str,deleteObjectUrl: str, \
  81. cachedWebfingers: {},personCache: {}, \
  82. debug: bool,projectVersion: str) -> {}:
  83. """Creates a delete request message via c2s
  84. """
  85. if not session:
  86. print('WARN: No session for sendDeleteViaServer')
  87. return 6
  88. fromDomainFull=fromDomain
  89. if fromPort:
  90. if fromPort!=80 and fromPort!=443:
  91. if ':' not in fromDomain:
  92. fromDomainFull=fromDomain+':'+str(fromPort)
  93. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  94. ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  95. newDeleteJson = {
  96. "@context": "https://www.w3.org/ns/activitystreams",
  97. 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
  98. 'cc': [ccUrl],
  99. 'object': deleteObjectUrl,
  100. 'to': [toUrl],
  101. 'type': 'Delete'
  102. }
  103. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  104. # lookup the inbox for the To handle
  105. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  106. fromDomain,projectVersion)
  107. if not wfRequest:
  108. if debug:
  109. print('DEBUG: announce webfinger failed for '+handle)
  110. return 1
  111. postToBox='outbox'
  112. # get the actor inbox for the To handle
  113. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  114. getPersonBox(baseDir,session,wfRequest,personCache, \
  115. projectVersion,httpPrefix,fromDomain,postToBox)
  116. if not inboxUrl:
  117. if debug:
  118. print('DEBUG: No '+postToBox+' was found for '+handle)
  119. return 3
  120. if not fromPersonId:
  121. if debug:
  122. print('DEBUG: No actor was found for '+handle)
  123. return 4
  124. authHeader=createBasicAuthHeader(fromNickname,password)
  125. headers = {'host': fromDomain, \
  126. 'Content-type': 'application/json', \
  127. 'Authorization': authHeader}
  128. postResult = \
  129. postJson(session,newDeleteJson,[],inboxUrl,headers,"inbox:write")
  130. #if not postResult:
  131. # if debug:
  132. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  133. # return 5
  134. if debug:
  135. print('DEBUG: c2s POST delete request success')
  136. return newDeleteJson
  137. def deletePublic(session,baseDir: str,federationList: [], \
  138. nickname: str, domain: str, port: int, httpPrefix: str, \
  139. objectUrl: str,clientToServer: bool, \
  140. sendThreads: [],postLog: [], \
  141. personCache: {},cachedWebfingers: {}, \
  142. debug: bool) -> {}:
  143. """Makes a public delete activity
  144. """
  145. fromDomain=domain
  146. if port:
  147. if port!=80 and port!=443:
  148. if ':' not in domain:
  149. fromDomain=domain+':'+str(port)
  150. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  151. ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers'
  152. return createDelete(session,baseDir,federationList, \
  153. nickname,domain,port, \
  154. toUrl,ccUrl,httpPrefix, \
  155. objectUrl,clientToServer, \
  156. sendThreads,postLog, \
  157. personCache,cachedWebfingers, \
  158. debug)
  159. def deletePostPub(session,baseDir: str,federationList: [], \
  160. nickname: str, domain: str, port: int, httpPrefix: str, \
  161. deleteNickname: str, deleteDomain: str, \
  162. deletePort: int, deleteHttpsPrefix: str, \
  163. deleteStatusNumber: int,clientToServer: bool, \
  164. sendThreads: [],postLog: [], \
  165. personCache: {},cachedWebfingers: {}, \
  166. debug: bool) -> {}:
  167. """Deletes a given status post
  168. """
  169. deletedDomain=deleteDomain
  170. if deletePort:
  171. if deletePort!=80 and deletePort!=443:
  172. if ':' not in deletedDomain:
  173. deletedDomain=deletedDomain+':'+str(deletePort)
  174. objectUrl = deleteHttpsPrefix + '://'+deletedDomain+'/users/'+ \
  175. deleteNickname+'/statuses/'+str(deleteStatusNumber)
  176. return deletePublic(session,baseDir,federationList, \
  177. nickname,domain,port,httpPrefix, \
  178. objectUrl,clientToServer, \
  179. sendThreads,postLog, \
  180. personCache,cachedWebfingers, \
  181. debug)
  182. def outboxDelete(baseDir: str,httpPrefix: str, \
  183. nickname: str,domain: str, \
  184. messageJson: {},debug: bool,
  185. allowDeletion: bool) -> None:
  186. """ When a delete request is received by the outbox from c2s
  187. """
  188. if not messageJson.get('type'):
  189. if debug:
  190. print('DEBUG: delete - no type')
  191. return
  192. if not messageJson['type']=='Delete':
  193. if debug:
  194. print('DEBUG: not a delete')
  195. return
  196. if not messageJson.get('object'):
  197. if debug:
  198. print('DEBUG: no object in delete')
  199. return
  200. if not isinstance(messageJson['object'], str):
  201. if debug:
  202. print('DEBUG: delete object is not string')
  203. return
  204. if debug:
  205. print('DEBUG: c2s delete request arrived in outbox')
  206. deletePrefix=httpPrefix+'://'+domain
  207. if not allowDeletion and \
  208. (not messageJson['object'].startswith(deletePrefix) or \
  209. not messageJson['actor'].startswith(deletePrefix)):
  210. if debug:
  211. print('DEBUG: delete not permitted from other instances')
  212. return
  213. messageId=messageJson['object'].replace('/activity','')
  214. if '/statuses/' not in messageId:
  215. if debug:
  216. print('DEBUG: c2s delete object is not a status')
  217. return
  218. if '/users/' not in messageId and '/profile/' not in messageId:
  219. if debug:
  220. print('DEBUG: c2s delete object has no nickname')
  221. return
  222. deleteNickname=getNicknameFromActor(messageId)
  223. if deleteNickname!=nickname:
  224. if debug:
  225. print("DEBUG: you can't delete a post which wasn't created by you (nickname does not match)")
  226. return
  227. deleteDomain,deletePort=getDomainFromActor(messageId)
  228. if ':' in domain:
  229. domain=domain.split(':')[0]
  230. if deleteDomain!=domain:
  231. if debug:
  232. print("DEBUG: you can't delete a post which wasn't created by you (domain does not match)")
  233. return
  234. removeModerationPostFromIndex(baseDir,messageId,debug)
  235. postFilename=locatePost(baseDir,deleteNickname,deleteDomain,messageId)
  236. if not postFilename:
  237. if debug:
  238. print('DEBUG: c2s delete post not found in inbox or outbox')
  239. print(messageId)
  240. return True
  241. deletePost(baseDir,httpPrefix,deleteNickname,deleteDomain,postFilename,debug)
  242. if debug:
  243. print('DEBUG: post deleted via c2s - '+postFilename)