delete.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. __filename__ = "delete.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. 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:
  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(session,fromNickname: str,password: str,
  78. fromDomain: str,fromPort: int, \
  79. httpPrefix: str,deleteObjectUrl: str, \
  80. cachedWebfingers: {},personCache: {}, \
  81. debug: bool,projectVersion: str) -> {}:
  82. """Creates a delete request message via c2s
  83. """
  84. if not session:
  85. print('WARN: No session for sendDeleteViaServer')
  86. return 6
  87. fromDomainFull=fromDomain
  88. if fromPort:
  89. if fromPort!=80 and fromPort!=443:
  90. if ':' not in fromDomain:
  91. fromDomainFull=fromDomain+':'+str(fromPort)
  92. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  93. ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  94. newDeleteJson = {
  95. "@context": "https://www.w3.org/ns/activitystreams",
  96. 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
  97. 'cc': [ccUrl],
  98. 'object': deleteObjectUrl,
  99. 'to': [toUrl],
  100. 'type': 'Delete'
  101. }
  102. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  103. # lookup the inbox for the To handle
  104. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  105. fromDomain,projectVersion)
  106. if not wfRequest:
  107. if debug:
  108. print('DEBUG: announce webfinger failed for '+handle)
  109. return 1
  110. postToBox='outbox'
  111. # get the actor inbox for the To handle
  112. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,preferredName = \
  113. getPersonBox(session,wfRequest,personCache, \
  114. projectVersion,httpPrefix,fromDomain,postToBox)
  115. if not inboxUrl:
  116. if debug:
  117. print('DEBUG: No '+postToBox+' was found for '+handle)
  118. return 3
  119. if not fromPersonId:
  120. if debug:
  121. print('DEBUG: No actor was found for '+handle)
  122. return 4
  123. authHeader=createBasicAuthHeader(fromNickname,password)
  124. headers = {'host': fromDomain, \
  125. 'Content-type': 'application/json', \
  126. 'Authorization': authHeader}
  127. postResult = \
  128. postJson(session,newDeleteJson,[],inboxUrl,headers,"inbox:write")
  129. #if not postResult:
  130. # if debug:
  131. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  132. # return 5
  133. if debug:
  134. print('DEBUG: c2s POST delete request success')
  135. return newDeleteJson
  136. def deletePublic(session,baseDir: str,federationList: [], \
  137. nickname: str, domain: str, port: int, httpPrefix: str, \
  138. objectUrl: str,clientToServer: bool, \
  139. sendThreads: [],postLog: [], \
  140. personCache: {},cachedWebfingers: {}, \
  141. debug: bool) -> {}:
  142. """Makes a public delete activity
  143. """
  144. fromDomain=domain
  145. if port:
  146. if port!=80 and port!=443:
  147. if ':' not in domain:
  148. fromDomain=domain+':'+str(port)
  149. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  150. ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers'
  151. return createDelete(session,baseDir,federationList, \
  152. nickname,domain,port, \
  153. toUrl,ccUrl,httpPrefix, \
  154. objectUrl,clientToServer, \
  155. sendThreads,postLog, \
  156. personCache,cachedWebfingers, \
  157. debug)
  158. def deletePostPub(session,baseDir: str,federationList: [], \
  159. nickname: str, domain: str, port: int, httpPrefix: str, \
  160. deleteNickname: str, deleteDomain: str, \
  161. deletePort: int, deleteHttpsPrefix: str, \
  162. deleteStatusNumber: int,clientToServer: bool, \
  163. sendThreads: [],postLog: [], \
  164. personCache: {},cachedWebfingers: {}, \
  165. debug: bool) -> {}:
  166. """Deletes a given status post
  167. """
  168. deletedDomain=deleteDomain
  169. if deletePort:
  170. if deletePort!=80 and deletePort!=443:
  171. if ':' not in deletedDomain:
  172. deletedDomain=deletedDomain+':'+str(deletePort)
  173. objectUrl = deleteHttpsPrefix + '://'+deletedDomain+'/users/'+ \
  174. deleteNickname+'/statuses/'+str(deleteStatusNumber)
  175. return deletePublic(session,baseDir,federationList, \
  176. nickname,domain,port,httpPrefix, \
  177. objectUrl,clientToServer, \
  178. sendThreads,postLog, \
  179. personCache,cachedWebfingers, \
  180. debug)
  181. def outboxDelete(baseDir: str,httpPrefix: str, \
  182. nickname: str,domain: str, \
  183. messageJson: {},debug: bool,
  184. allowDeletion: bool) -> None:
  185. """ When a delete request is received by the outbox from c2s
  186. """
  187. if not messageJson.get('type'):
  188. if debug:
  189. print('DEBUG: delete - no type')
  190. return
  191. if not messageJson['type']=='Delete':
  192. if debug:
  193. print('DEBUG: not a delete')
  194. return
  195. if not messageJson.get('object'):
  196. if debug:
  197. print('DEBUG: no object in delete')
  198. return
  199. if not isinstance(messageJson['object'], str):
  200. if debug:
  201. print('DEBUG: delete object is not string')
  202. return
  203. if debug:
  204. print('DEBUG: c2s delete request arrived in outbox')
  205. deletePrefix=httpPrefix+'://'+domain
  206. if not allowDeletion and \
  207. (not messageJson['object'].startswith(deletePrefix) or \
  208. not messageJson['actor'].startswith(deletePrefix)):
  209. if debug:
  210. print('DEBUG: delete not permitted from other instances')
  211. return
  212. messageId=messageJson['object'].replace('/activity','')
  213. if '/statuses/' not in messageId:
  214. if debug:
  215. print('DEBUG: c2s delete object is not a status')
  216. return
  217. if '/users/' not in messageId:
  218. if debug:
  219. print('DEBUG: c2s delete object has no nickname')
  220. return
  221. deleteNickname=getNicknameFromActor(messageId)
  222. if deleteNickname!=nickname:
  223. if debug:
  224. print("DEBUG: you can't delete a post which wasn't created by you (nickname does not match)")
  225. return
  226. deleteDomain,deletePort=getDomainFromActor(messageId)
  227. if ':' in domain:
  228. domain=domain.split(':')[0]
  229. if deleteDomain!=domain:
  230. if debug:
  231. print("DEBUG: you can't delete a post which wasn't created by you (domain does not match)")
  232. return
  233. removeModerationPostFromIndex(baseDir,messageId,debug)
  234. postFilename=locatePost(baseDir,deleteNickname,deleteDomain,messageId)
  235. if not postFilename:
  236. if debug:
  237. print('DEBUG: c2s delete post not found in inbox or outbox')
  238. print(messageId)
  239. return True
  240. deletePost(baseDir,httpPrefix,deleteNickname,deleteDomain,postFilename,debug)
  241. if debug:
  242. print('DEBUG: post deleted via c2s - '+postFilename)