__filename__ = "delete.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.1.0" __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" from utils import getStatusNumber from utils import urlPermitted from utils import getNicknameFromActor from utils import getDomainFromActor from utils import locatePost from utils import deletePost from utils import removeModerationPostFromIndex from posts import sendSignedJson from session import postJson from webfinger import webfingerHandle from auth import createBasicAuthHeader from posts import getPersonBox def createDelete(session, baseDir: str, federationList: [], nickname: str, domain: str, port: int, toUrl: str, ccUrl: str, httpPrefix: str, objectUrl: str, clientToServer: bool, sendThreads: [], postLog: [], personCache: {}, cachedWebfingers: {}, debug: bool) -> {}: """Creates a delete message Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person whose post is to be deleted objectUrl is typically the url of the message, corresponding to url or atomUri in createPostBase """ if not urlPermitted(objectUrl, federationList, "inbox:write"): return None if ':' in domain: domain = domain.split(':')[0] fullDomain = domain if port: if port != 80 and port != 443: if ':' not in domain: fullDomain = domain + ':' + str(port) statusNumber, published = getStatusNumber() newDeleteId = \ httpPrefix + '://' + fullDomain + '/users/' + \ nickname + '/statuses/' + statusNumber newDelete = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'atomUri': newDeleteId, 'cc': [], 'id': newDeleteId + '/activity', 'object': objectUrl, 'published': published, 'to': [toUrl], 'type': 'Delete' } if ccUrl: if len(ccUrl) > 0: newDelete['cc'] = [ccUrl] deleteNickname = None deleteDomain = None deletePort = None if '/users/' in objectUrl or \ '/channel/' in objectUrl or \ '/profile/' in objectUrl: deleteNickname = getNicknameFromActor(objectUrl) deleteDomain, deletePort = getDomainFromActor(objectUrl) if deleteNickname and deleteDomain: sendSignedJson(newDelete, session, baseDir, nickname, domain, port, deleteNickname, deleteDomain, deletePort, 'https://www.w3.org/ns/activitystreams#Public', httpPrefix, True, clientToServer, federationList, sendThreads, postLog, cachedWebfingers, personCache, debug) return newDelete def sendDeleteViaServer(baseDir: str, session, fromNickname: str, password: str, fromDomain: str, fromPort: int, httpPrefix: str, deleteObjectUrl: str, cachedWebfingers: {}, personCache: {}, debug: bool, projectVersion: str) -> {}: """Creates a delete request message via c2s """ if not session: print('WARN: No session for sendDeleteViaServer') return 6 fromDomainFull = fromDomain if fromPort: if fromPort != 80 and fromPort != 443: if ':' not in fromDomain: fromDomainFull = fromDomain + ':' + str(fromPort) actor = httpPrefix + '://' + fromDomainFull + \ '/users/' + fromNickname toUrl = 'https://www.w3.org/ns/activitystreams#Public' ccUrl = actor + '/followers' newDeleteJson = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': actor, 'cc': [ccUrl], 'object': deleteObjectUrl, 'to': [toUrl], 'type': 'Delete' } handle = httpPrefix + '://' + fromDomainFull + '/@' + fromNickname # lookup the inbox for the To handle wfRequest = \ webfingerHandle(session, handle, httpPrefix, cachedWebfingers, fromDomain, projectVersion) if not wfRequest: if debug: print('DEBUG: announce webfinger failed for ' + handle) return 1 if not isinstance(wfRequest, dict): print('WARN: Webfinger for ' + handle + ' did not return a dict. ' + str(wfRequest)) return 1 postToBox = 'outbox' # get the actor inbox for the To handle (inboxUrl, pubKeyId, pubKey, fromPersonId, sharedInbox, capabilityAcquisition, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, personCache, projectVersion, httpPrefix, fromNickname, fromDomain, postToBox) if not inboxUrl: if debug: print('DEBUG: No ' + postToBox + ' was found for ' + handle) return 3 if not fromPersonId: if debug: print('DEBUG: No actor was found for ' + handle) return 4 authHeader = createBasicAuthHeader(fromNickname, password) headers = { 'host': fromDomain, 'Content-type': 'application/json', 'Authorization': authHeader } postResult = \ postJson(session, newDeleteJson, [], inboxUrl, headers, "inbox:write") if not postResult: if debug: print('DEBUG: POST announce failed for c2s to ' + inboxUrl) return 5 if debug: print('DEBUG: c2s POST delete request success') return newDeleteJson def deletePublic(session, baseDir: str, federationList: [], nickname: str, domain: str, port: int, httpPrefix: str, objectUrl: str, clientToServer: bool, sendThreads: [], postLog: [], personCache: {}, cachedWebfingers: {}, debug: bool) -> {}: """Makes a public delete activity """ fromDomain = domain if port: if port != 80 and port != 443: if ':' not in domain: fromDomain = domain + ':' + str(port) toUrl = 'https://www.w3.org/ns/activitystreams#Public' ccUrl = httpPrefix + '://' + fromDomain + \ '/users/' + nickname + '/followers' return createDelete(session, baseDir, federationList, nickname, domain, port, toUrl, ccUrl, httpPrefix, objectUrl, clientToServer, sendThreads, postLog, personCache, cachedWebfingers, debug) def deletePostPub(session, baseDir: str, federationList: [], nickname: str, domain: str, port: int, httpPrefix: str, deleteNickname: str, deleteDomain: str, deletePort: int, deleteHttpsPrefix: str, deleteStatusNumber: int, clientToServer: bool, sendThreads: [], postLog: [], personCache: {}, cachedWebfingers: {}, debug: bool) -> {}: """Deletes a given status post """ deletedDomain = deleteDomain if deletePort: if deletePort != 80 and deletePort != 443: if ':' not in deletedDomain: deletedDomain = deletedDomain + ':' + str(deletePort) objectUrl = \ deleteHttpsPrefix + '://' + deletedDomain + '/users/' + \ deleteNickname + '/statuses/' + str(deleteStatusNumber) return deletePublic(session, baseDir, federationList, nickname, domain, port, httpPrefix, objectUrl, clientToServer, sendThreads, postLog, personCache, cachedWebfingers, debug) def outboxDelete(baseDir: str, httpPrefix: str, nickname: str, domain: str, messageJson: {}, debug: bool, allowDeletion: bool) -> None: """ When a delete request is received by the outbox from c2s """ if not messageJson.get('type'): if debug: print('DEBUG: delete - no type') return if not messageJson['type'] == 'Delete': if debug: print('DEBUG: not a delete') return if not messageJson.get('object'): if debug: print('DEBUG: no object in delete') return if not isinstance(messageJson['object'], str): if debug: print('DEBUG: delete object is not string') return if debug: print('DEBUG: c2s delete request arrived in outbox') deletePrefix = httpPrefix + '://' + domain if (not allowDeletion and (not messageJson['object'].startswith(deletePrefix) or not messageJson['actor'].startswith(deletePrefix))): if debug: print('DEBUG: delete not permitted from other instances') return messageId = messageJson['object'].replace('/activity', '') if '/statuses/' not in messageId: if debug: print('DEBUG: c2s delete object is not a status') return if '/users/' not in messageId and \ '/channel/' not in messageId and \ '/profile/' not in messageId: if debug: print('DEBUG: c2s delete object has no nickname') return deleteNickname = getNicknameFromActor(messageId) if deleteNickname != nickname: if debug: print("DEBUG: you can't delete a post which " + "wasn't created by you (nickname does not match)") return deleteDomain, deletePort = getDomainFromActor(messageId) if ':' in domain: domain = domain.split(':')[0] if deleteDomain != domain: if debug: print("DEBUG: you can't delete a post which " + "wasn't created by you (domain does not match)") return removeModerationPostFromIndex(baseDir, messageId, debug) postFilename = locatePost(baseDir, deleteNickname, deleteDomain, messageId) if not postFilename: if debug: print('DEBUG: c2s delete post not found in inbox or outbox') print(messageId) return True deletePost(baseDir, httpPrefix, deleteNickname, deleteDomain, postFilename, debug) if debug: print('DEBUG: post deleted via c2s - ' + postFilename)