forked from indymedia/epicyon
Deprecate capabilities module
parent
38302fd686
commit
b064d6f4d6
|
@ -7,8 +7,6 @@ __email__ = "bob@freedombone.net"
|
|||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from capabilities import capabilitiesAccept
|
||||
from capabilities import capabilitiesGrantedSave
|
||||
from utils import urlPermitted
|
||||
from utils import getDomainFromActor
|
||||
from utils import getNicknameFromActor
|
||||
|
@ -19,7 +17,7 @@ from utils import followPerson
|
|||
def createAcceptReject(baseDir: str, federationList: [],
|
||||
nickname: str, domain: str, port: int,
|
||||
toUrl: str, ccUrl: str, httpPrefix: str,
|
||||
objectJson: {}, ocapJson, acceptType: str) -> {}:
|
||||
objectJson: {}, acceptType: str) -> {}:
|
||||
"""Accepts or rejects something (eg. a follow request or offer)
|
||||
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
|
||||
and ccUrl might be a specific person favorited or repeated and
|
||||
|
@ -48,9 +46,6 @@ def createAcceptReject(baseDir: str, federationList: [],
|
|||
if ccUrl:
|
||||
if len(ccUrl) > 0:
|
||||
newAccept['cc'] = [ccUrl]
|
||||
# attach capabilities for follow accept
|
||||
if ocapJson:
|
||||
newAccept['capabilities'] = ocapJson
|
||||
return newAccept
|
||||
|
||||
|
||||
|
@ -59,14 +54,10 @@ def createAccept(baseDir: str, federationList: [],
|
|||
toUrl: str, ccUrl: str, httpPrefix: str,
|
||||
objectJson: {},
|
||||
acceptedCaps=["inbox:write", "objects:read"]) -> {}:
|
||||
# create capabilities accept
|
||||
ocapNew = capabilitiesAccept(baseDir, httpPrefix,
|
||||
nickname, domain, port,
|
||||
toUrl, True, acceptedCaps)
|
||||
return createAcceptReject(baseDir, federationList,
|
||||
nickname, domain, port,
|
||||
toUrl, ccUrl, httpPrefix,
|
||||
objectJson, ocapNew, 'Accept')
|
||||
objectJson, 'Accept')
|
||||
|
||||
|
||||
def createReject(baseDir: str, federationList: [],
|
||||
|
@ -154,13 +145,6 @@ def acceptFollow(baseDir: str, domain: str, messageJson: {},
|
|||
if acceptedPort:
|
||||
acceptedDomainFull = acceptedDomain + ':' + str(acceptedPort)
|
||||
|
||||
# are capabilities attached? If so then store them
|
||||
if messageJson.get('capabilities'):
|
||||
if isinstance(messageJson['capabilities'], dict):
|
||||
capabilitiesGrantedSave(baseDir,
|
||||
nickname, acceptedDomainFull,
|
||||
messageJson['capabilities'])
|
||||
|
||||
# has this person already been unfollowed?
|
||||
unfollowedFilename = baseDir + '/accounts/' + \
|
||||
nickname + '@' + acceptedDomainFull + '/unfollowed.txt'
|
||||
|
|
283
capabilities.py
283
capabilities.py
|
@ -1,283 +0,0 @@
|
|||
__filename__ = "capabilities.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from auth import createPassword
|
||||
from utils import getNicknameFromActor
|
||||
from utils import getDomainFromActor
|
||||
from utils import loadJson
|
||||
from utils import saveJson
|
||||
|
||||
|
||||
def getOcapFilename(baseDir: str,
|
||||
nickname: str, domain: str,
|
||||
actor: str, subdir: str) -> str:
|
||||
"""Returns the filename for a particular capability accepted or granted
|
||||
Also creates directories as needed
|
||||
"""
|
||||
if not actor:
|
||||
return None
|
||||
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')[0]
|
||||
|
||||
if not os.path.isdir(baseDir + '/accounts'):
|
||||
os.mkdir(baseDir + '/accounts')
|
||||
|
||||
ocDir = baseDir + '/accounts/' + nickname + '@' + domain
|
||||
if not os.path.isdir(ocDir):
|
||||
os.mkdir(ocDir)
|
||||
|
||||
ocDir = baseDir + '/accounts/' + nickname + '@' + domain + '/ocap'
|
||||
if not os.path.isdir(ocDir):
|
||||
os.mkdir(ocDir)
|
||||
|
||||
ocDir = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/ocap/' + subdir
|
||||
if not os.path.isdir(ocDir):
|
||||
os.mkdir(ocDir)
|
||||
|
||||
return baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/ocap/' + \
|
||||
subdir + '/' + actor.replace('/', '#') + '.json'
|
||||
|
||||
|
||||
def CapablePost(postJson: {}, capabilityList: [], debug: bool) -> bool:
|
||||
"""Determines whether a post arriving in the inbox
|
||||
should be accepted accoring to the list of capabilities
|
||||
"""
|
||||
if postJson.get('type'):
|
||||
# No announces/repeats
|
||||
if postJson['type'] == 'Announce':
|
||||
if 'inbox:noannounce' in capabilityList:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'inbox post rejected because inbox:noannounce')
|
||||
return False
|
||||
# No likes
|
||||
if postJson['type'] == 'Like':
|
||||
if 'inbox:nolike' in capabilityList:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'inbox post rejected because inbox:nolike')
|
||||
return False
|
||||
if postJson['type'] == 'Create':
|
||||
if postJson.get('object'):
|
||||
# Does this have a reply?
|
||||
if postJson['object'].get('inReplyTo'):
|
||||
if postJson['object']['inReplyTo']:
|
||||
if 'inbox:noreply' in capabilityList:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'inbox post rejected because ' +
|
||||
'inbox:noreply')
|
||||
return False
|
||||
# are content warnings enforced?
|
||||
if postJson['object'].get('sensitive'):
|
||||
if not postJson['object']['sensitive']:
|
||||
if 'inbox:cw' in capabilityList:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'inbox post rejected because inbox:cw')
|
||||
return False
|
||||
# content warning must have non-zero summary
|
||||
if postJson['object'].get('summary'):
|
||||
if len(postJson['object']['summary']) < 2:
|
||||
if 'inbox:cw' in capabilityList:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'inbox post rejected because ' +
|
||||
'inbox:cw, summary missing')
|
||||
return False
|
||||
if 'inbox:write' in capabilityList:
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def capabilitiesRequest(baseDir: str, httpPrefix: str, domain: str,
|
||||
requestedActor: str, requestedDomain: str,
|
||||
requestedCaps=["inbox:write", "objects:read"]) -> {}:
|
||||
# This is sent to the capabilities endpoint /caps/new
|
||||
# which could be instance wide or for a particular person
|
||||
# This could also be added to a follow activity
|
||||
ocapId = createPassword(32)
|
||||
ocapRequest = {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": httpPrefix + "://" + requestedDomain + "/caps/request/" + ocapId,
|
||||
"type": "Request",
|
||||
"capability": requestedCaps,
|
||||
"actor": requestedActor
|
||||
}
|
||||
return ocapRequest
|
||||
|
||||
|
||||
def capabilitiesAccept(baseDir: str, httpPrefix: str,
|
||||
nickname: str, domain: str, port: int,
|
||||
acceptedActor: str, saveToFile: bool,
|
||||
acceptedCaps=["inbox:write", "objects:read"]) -> {}:
|
||||
# This gets returned to capabilities requester
|
||||
# This could also be added to a follow Accept activity
|
||||
|
||||
# reject excessively long actors
|
||||
if len(acceptedActor) > 256:
|
||||
return None
|
||||
|
||||
fullDomain = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
if ':' not in domain:
|
||||
fullDomain = domain + ':' + str(port)
|
||||
|
||||
# make directories to store capabilities
|
||||
ocapFilename = \
|
||||
getOcapFilename(baseDir, nickname, fullDomain, acceptedActor, 'accept')
|
||||
if not ocapFilename:
|
||||
return None
|
||||
ocapAccept = None
|
||||
|
||||
# if the capability already exists then load it from file
|
||||
if os.path.isfile(ocapFilename):
|
||||
ocapAccept = loadJson(ocapFilename)
|
||||
# otherwise create a new capability
|
||||
if not ocapAccept:
|
||||
acceptedActorNickname = getNicknameFromActor(acceptedActor)
|
||||
if not acceptedActorNickname:
|
||||
print('WARN: unable to find nickname in ' + acceptedActor)
|
||||
return None
|
||||
acceptedActorDomain, acceptedActorPort = \
|
||||
getDomainFromActor(acceptedActor)
|
||||
if acceptedActorPort:
|
||||
ocapId = acceptedActorNickname + '@' + acceptedActorDomain + \
|
||||
':' + str(acceptedActorPort) + '#'+createPassword(32)
|
||||
else:
|
||||
ocapId = acceptedActorNickname + '@' + acceptedActorDomain + \
|
||||
'#' + createPassword(32)
|
||||
ocapAccept = {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": httpPrefix + "://" + fullDomain + "/caps/" + ocapId,
|
||||
"type": "Capability",
|
||||
"capability": acceptedCaps,
|
||||
"scope": acceptedActor,
|
||||
"actor": httpPrefix + "://" + fullDomain
|
||||
}
|
||||
if nickname:
|
||||
ocapAccept['actor'] = \
|
||||
httpPrefix + "://" + fullDomain + '/users/' + nickname
|
||||
|
||||
if saveToFile:
|
||||
saveJson(ocapAccept, ocapFilename)
|
||||
return ocapAccept
|
||||
|
||||
|
||||
def capabilitiesGrantedSave(baseDir: str,
|
||||
nickname: str, domain: str, ocap: {}) -> bool:
|
||||
"""A capabilities accept is received, so stor it for
|
||||
reference when sending to the actor
|
||||
"""
|
||||
if not ocap.get('actor'):
|
||||
return False
|
||||
ocapFilename = \
|
||||
getOcapFilename(baseDir, nickname, domain, ocap['actor'], 'granted')
|
||||
if not ocapFilename:
|
||||
return False
|
||||
saveJson(ocap, ocapFilename)
|
||||
return True
|
||||
|
||||
|
||||
def capabilitiesUpdate(baseDir: str, httpPrefix: str,
|
||||
nickname: str, domain: str, port: int,
|
||||
updateActor: str,
|
||||
updateCaps: []) -> {}:
|
||||
"""Used to sends an update for a change of object capabilities
|
||||
Note that the capability id gets changed with a new random token
|
||||
so that the old capabilities can't continue to be used
|
||||
"""
|
||||
|
||||
# reject excessively long actors
|
||||
if len(updateActor) > 256:
|
||||
return None
|
||||
|
||||
fullDomain = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
if ':' not in domain:
|
||||
fullDomain = domain + ':' + str(port)
|
||||
|
||||
# Get the filename of the capability
|
||||
ocapFilename = \
|
||||
getOcapFilename(baseDir, nickname, fullDomain, updateActor, 'accept')
|
||||
if not ocapFilename:
|
||||
return None
|
||||
|
||||
# The capability should already exist for it to be updated
|
||||
if not os.path.isfile(ocapFilename):
|
||||
return None
|
||||
|
||||
# create an update activity
|
||||
ocapUpdate = {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Update',
|
||||
'actor': httpPrefix + '://' + fullDomain + '/users/' + nickname,
|
||||
'to': [updateActor],
|
||||
'cc': [],
|
||||
'object': {}
|
||||
}
|
||||
|
||||
# read the existing capability
|
||||
ocapJson = loadJson(ocapFilename)
|
||||
|
||||
# set the new capabilities list. eg. ["inbox:write","objects:read"]
|
||||
ocapJson['capability'] = updateCaps
|
||||
|
||||
# change the id, so that the old capabilities can't continue to be used
|
||||
updateActorNickname = getNicknameFromActor(updateActor)
|
||||
if not updateActorNickname:
|
||||
print('WARN: unable to find nickname in ' + updateActor)
|
||||
return None
|
||||
updateActorDomain, updateActorPort = getDomainFromActor(updateActor)
|
||||
if updateActorPort:
|
||||
ocapId = updateActorNickname + '@' + updateActorDomain + \
|
||||
':' + str(updateActorPort) + '#' + createPassword(32)
|
||||
else:
|
||||
ocapId = updateActorNickname + '@' + updateActorDomain + \
|
||||
'#' + createPassword(32)
|
||||
ocapJson['id'] = httpPrefix + "://" + fullDomain + "/caps/" + ocapId
|
||||
ocapUpdate['object'] = ocapJson
|
||||
|
||||
# save it again
|
||||
saveJson(ocapJson, ocapFilename)
|
||||
|
||||
return ocapUpdate
|
||||
|
||||
|
||||
def capabilitiesReceiveUpdate(baseDir: str,
|
||||
nickname: str, domain: str, port: int,
|
||||
actor: str,
|
||||
newCapabilitiesId: str,
|
||||
capabilityList: [], debug: bool) -> bool:
|
||||
"""An update for a capability or the given actor has arrived
|
||||
"""
|
||||
ocapFilename = \
|
||||
getOcapFilename(baseDir, nickname, domain, actor, 'granted')
|
||||
if not ocapFilename:
|
||||
return False
|
||||
|
||||
if not os.path.isfile(ocapFilename):
|
||||
if debug:
|
||||
print('DEBUG: capabilities file not found during update')
|
||||
print(ocapFilename)
|
||||
return False
|
||||
|
||||
ocapJson = loadJson(ocapFilename)
|
||||
|
||||
if ocapJson:
|
||||
ocapJson['id'] = newCapabilitiesId
|
||||
ocapJson['capability'] = capabilityList
|
||||
|
||||
return saveJson(ocapJson, ocapFilename)
|
||||
return False
|
|
@ -10299,7 +10299,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.path.endswith('/inbox') or
|
||||
self.path.endswith('/shares') or
|
||||
self.path.endswith('/moderationaction') or
|
||||
self.path.endswith('/caps/new') or
|
||||
self.path == '/sharedInbox'):
|
||||
print('Attempt to POST to invalid path ' + self.path)
|
||||
self._400()
|
||||
|
|
24
epicyon.py
24
epicyon.py
|
@ -230,26 +230,6 @@ parser.add_argument("--testsnetwork", type=str2bool, nargs='?',
|
|||
parser.add_argument("--testdata", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Generate some data for testing purposes")
|
||||
parser.add_argument("--ocap", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Always strictly enforce object capabilities")
|
||||
parser.add_argument("--noreply", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Default capabilities don't allow replies on posts")
|
||||
parser.add_argument("--nolike", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Default capabilities don't allow " +
|
||||
"likes/favourites on posts")
|
||||
parser.add_argument("--nopics", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Default capabilities don't allow attached pictures")
|
||||
parser.add_argument("--noannounce", "--norepeat", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Default capabilities don't allow announce/repeat")
|
||||
parser.add_argument("--cw", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Default capabilities don't allow posts " +
|
||||
"without content warnings")
|
||||
parser.add_argument('--icon', '--avatar', dest='avatar', type=str,
|
||||
default=None,
|
||||
help='Set the avatar filename for an account')
|
||||
|
@ -1920,8 +1900,8 @@ if __name__ == "__main__":
|
|||
port, proxyPort, httpPrefix,
|
||||
federationList, args.maxMentions,
|
||||
args.maxEmoji, args.authenticatedFetch,
|
||||
args.noreply, args.nolike, args.nopics,
|
||||
args.noannounce, args.cw, ocapAlways,
|
||||
False, False, False,
|
||||
False, False, ocapAlways,
|
||||
proxyType, args.maxReplies,
|
||||
args.domainMaxPostsPerDay,
|
||||
args.accountMaxPostsPerDay,
|
||||
|
|
32
follow.py
32
follow.py
|
@ -1045,14 +1045,12 @@ def getFollowersOfActor(baseDir: str, actor: str, debug: bool) -> {}:
|
|||
"""In a shared inbox if we receive a post we know who it's from
|
||||
and if it's addressed to followers then we need to get a list of those.
|
||||
This returns a list of account handles which follow the given actor
|
||||
and also the corresponding capability id if it exists
|
||||
"""
|
||||
if debug:
|
||||
print('DEBUG: getting followers of ' + actor)
|
||||
recipientsDict = {}
|
||||
if ':' not in actor:
|
||||
return recipientsDict
|
||||
httpPrefix = actor.split(':')[0]
|
||||
nickname = getNicknameFromActor(actor)
|
||||
if not nickname:
|
||||
if debug:
|
||||
|
@ -1084,35 +1082,7 @@ def getFollowersOfActor(baseDir: str, actor: str, debug: bool) -> {}:
|
|||
if debug:
|
||||
print('DEBUG: ' + account +
|
||||
' follows ' + actorHandle)
|
||||
ocapFilename = baseDir + '/accounts/' + \
|
||||
account + '/ocap/accept/' + httpPrefix + \
|
||||
':##' + domain + ':' + str(port) + \
|
||||
'#users#' + nickname + '.json'
|
||||
if debug:
|
||||
print('DEBUG: checking capabilities of' + account)
|
||||
if os.path.isfile(ocapFilename):
|
||||
ocapJson = loadJson(ocapFilename)
|
||||
if ocapJson:
|
||||
if ocapJson.get('id'):
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'capabilities id found for ' +
|
||||
account)
|
||||
|
||||
recipientsDict[account] = ocapJson['id']
|
||||
else:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'capabilities has no ' +
|
||||
'id attribute')
|
||||
recipientsDict[account] = None
|
||||
else:
|
||||
if debug:
|
||||
print('DEBUG: ' +
|
||||
'No capabilities file found for ' +
|
||||
account + ' granted by ' + actorHandle)
|
||||
print(ocapFilename)
|
||||
recipientsDict[account] = None
|
||||
recipientsDict[account] = None
|
||||
return recipientsDict
|
||||
|
||||
|
||||
|
|
220
inbox.py
220
inbox.py
|
@ -40,9 +40,6 @@ from pprint import pprint
|
|||
from cache import getPersonFromCache
|
||||
from cache import storePersonInCache
|
||||
from acceptreject import receiveAcceptReject
|
||||
from capabilities import getOcapFilename
|
||||
from capabilities import CapablePost
|
||||
from capabilities import capabilitiesReceiveUpdate
|
||||
from bookmarks import updateBookmarksCollection
|
||||
from bookmarks import undoBookmarksCollectionEntry
|
||||
from blocking import isBlocked
|
||||
|
@ -437,81 +434,12 @@ def savePostToInboxQueue(baseDir: str, httpPrefix: str,
|
|||
return filename
|
||||
|
||||
|
||||
def inboxCheckCapabilities(baseDir: str, nickname: str, domain: str,
|
||||
actor: str, queueFilename: str, queue: [],
|
||||
queueJson: {}, capabilityId: str,
|
||||
debug: bool) -> bool:
|
||||
if nickname == 'inbox':
|
||||
return True
|
||||
|
||||
ocapFilename = \
|
||||
getOcapFilename(baseDir,
|
||||
queueJson['nickname'], queueJson['domain'],
|
||||
actor, 'accept')
|
||||
if not ocapFilename:
|
||||
return False
|
||||
if not os.path.isfile(ocapFilename):
|
||||
if debug:
|
||||
print('DEBUG: capabilities for ' +
|
||||
actor + ' do not exist')
|
||||
if os.path.isfile(queueFilename):
|
||||
os.remove(queueFilename)
|
||||
if len(queue) > 0:
|
||||
queue.pop(0)
|
||||
return False
|
||||
|
||||
oc = loadJson(ocapFilename)
|
||||
if not oc:
|
||||
return False
|
||||
|
||||
if not oc.get('id'):
|
||||
if debug:
|
||||
print('DEBUG: capabilities for ' + actor + ' do not contain an id')
|
||||
if os.path.isfile(queueFilename):
|
||||
os.remove(queueFilename)
|
||||
if len(queue) > 0:
|
||||
queue.pop(0)
|
||||
return False
|
||||
|
||||
if oc['id'] != capabilityId:
|
||||
if debug:
|
||||
print('DEBUG: capability id mismatch')
|
||||
if os.path.isfile(queueFilename):
|
||||
os.remove(queueFilename)
|
||||
if len(queue) > 0:
|
||||
queue.pop(0)
|
||||
return False
|
||||
|
||||
if not oc.get('capability'):
|
||||
if debug:
|
||||
print('DEBUG: missing capability list')
|
||||
if os.path.isfile(queueFilename):
|
||||
os.remove(queueFilename)
|
||||
if len(queue) > 0:
|
||||
queue.pop(0)
|
||||
return False
|
||||
|
||||
if not CapablePost(queueJson['post'], oc['capability'], debug):
|
||||
if debug:
|
||||
print('DEBUG: insufficient capabilities to write to inbox from ' +
|
||||
actor)
|
||||
if os.path.isfile(queueFilename):
|
||||
os.remove(queueFilename)
|
||||
if len(queue) > 0:
|
||||
queue.pop(0)
|
||||
return False
|
||||
|
||||
if debug:
|
||||
print('DEBUG: object capabilities check success')
|
||||
return True
|
||||
|
||||
|
||||
def inboxPostRecipientsAdd(baseDir: str, httpPrefix: str, toList: [],
|
||||
recipientsDict: {},
|
||||
domainMatch: str, domain: str,
|
||||
actor: str, debug: bool) -> bool:
|
||||
"""Given a list of post recipients (toList) from 'to' or 'cc' parameters
|
||||
populate a recipientsDict with the handle and capabilities id for each
|
||||
populate a recipientsDict with the handle for each
|
||||
"""
|
||||
followerRecipients = False
|
||||
for recipient in toList:
|
||||
|
@ -523,24 +451,7 @@ def inboxPostRecipientsAdd(baseDir: str, httpPrefix: str, toList: [],
|
|||
nickname = recipient.split(domainMatch)[1]
|
||||
handle = nickname+'@'+domain
|
||||
if os.path.isdir(baseDir + '/accounts/' + handle):
|
||||
# are capabilities granted for this account to the
|
||||
# sender (actor) of the post?
|
||||
ocapFilename = \
|
||||
baseDir + '/accounts/' + handle + \
|
||||
'/ocap/accept/' + actor.replace('/', '#') + '.json'
|
||||
if os.path.isfile(ocapFilename):
|
||||
# read the granted capabilities and obtain the id
|
||||
ocapJson = loadJson(ocapFilename)
|
||||
if ocapJson:
|
||||
if ocapJson.get('id'):
|
||||
# append with the capabilities id
|
||||
recipientsDict[handle] = ocapJson['id']
|
||||
else:
|
||||
recipientsDict[handle] = None
|
||||
else:
|
||||
if debug:
|
||||
print('DEBUG: ' + ocapFilename + ' not found')
|
||||
recipientsDict[handle] = None
|
||||
recipientsDict[handle] = None
|
||||
else:
|
||||
if debug:
|
||||
print('DEBUG: ' + baseDir + '/accounts/' +
|
||||
|
@ -1005,24 +916,6 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
|||
print('DEBUG: Profile update was received for ' +
|
||||
messageJson['object']['url'])
|
||||
return True
|
||||
|
||||
if messageJson['object'].get('capability') and \
|
||||
messageJson['object'].get('scope'):
|
||||
nickname = getNicknameFromActor(messageJson['object']['scope'])
|
||||
if nickname:
|
||||
domain, tempPort = \
|
||||
getDomainFromActor(messageJson['object']['scope'])
|
||||
|
||||
if messageJson['object']['type'] == 'Capability':
|
||||
capability = messageJson['object']['capability']
|
||||
if capabilitiesReceiveUpdate(baseDir, nickname, domain, port,
|
||||
messageJson['actor'],
|
||||
messageJson['object']['id'],
|
||||
capability,
|
||||
debug):
|
||||
if debug:
|
||||
print('DEBUG: An update was received')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
@ -2124,20 +2017,20 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,
|
|||
return False
|
||||
|
||||
|
||||
def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int,
|
||||
session, keyId: str, handle: str, messageJson: {},
|
||||
baseDir: str, httpPrefix: str, sendThreads: [],
|
||||
postLog: [], cachedWebfingers: {}, personCache: {},
|
||||
queue: [], domain: str,
|
||||
onionDomain: str, i2pDomain: str,
|
||||
port: int, proxyType: str,
|
||||
federationList: [], ocapAlways: bool, debug: bool,
|
||||
acceptedCaps: [],
|
||||
queueFilename: str, destinationFilename: str,
|
||||
maxReplies: int, allowDeletion: bool,
|
||||
maxMentions: int, maxEmoji: int, translate: {},
|
||||
unitTest: bool, YTReplacementDomain: str) -> bool:
|
||||
""" Anything which needs to be done after capabilities checks have passed
|
||||
def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||
session, keyId: str, handle: str, messageJson: {},
|
||||
baseDir: str, httpPrefix: str, sendThreads: [],
|
||||
postLog: [], cachedWebfingers: {}, personCache: {},
|
||||
queue: [], domain: str,
|
||||
onionDomain: str, i2pDomain: str,
|
||||
port: int, proxyType: str,
|
||||
federationList: [], ocapAlways: bool, debug: bool,
|
||||
acceptedCaps: [],
|
||||
queueFilename: str, destinationFilename: str,
|
||||
maxReplies: int, allowDeletion: bool,
|
||||
maxMentions: int, maxEmoji: int, translate: {},
|
||||
unitTest: bool, YTReplacementDomain: str) -> bool:
|
||||
""" Anything which needs to be done after initial checks have passed
|
||||
"""
|
||||
actor = keyId
|
||||
if '#' in actor:
|
||||
|
@ -2247,7 +2140,7 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int,
|
|||
return False
|
||||
|
||||
if debug:
|
||||
print('DEBUG: object capabilities passed')
|
||||
print('DEBUG: initial checks passed')
|
||||
print('copy queue file from ' + queueFilename +
|
||||
' to ' + destinationFilename)
|
||||
|
||||
|
@ -2929,10 +2822,6 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
|||
# Copy any posts addressed to followers into the shared inbox
|
||||
# this avoid copying file multiple times to potentially many
|
||||
# individual inboxes
|
||||
# This obviously bypasses object capabilities and so
|
||||
# any checking will needs to be handled at the time when inbox
|
||||
# GET happens on individual accounts.
|
||||
# See posts.py/createBoxBase
|
||||
if len(recipientsDictFollowers) > 0:
|
||||
sharedInboxPostFilename = \
|
||||
queueJson['destination'].replace(inboxHandle, inboxHandle)
|
||||
|
@ -2943,61 +2832,26 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
|||
for handle, capsId in recipientsDict.items():
|
||||
destination = \
|
||||
queueJson['destination'].replace(inboxHandle, handle)
|
||||
# check that capabilities are accepted
|
||||
if queueJson['post'].get('capability'):
|
||||
capabilityIdList = queueJson['post']['capability']
|
||||
# does the capability id list within the post
|
||||
# contain the id of the recipient with this handle?
|
||||
# Here the capability id begins with the handle,
|
||||
# so this could also be matched separately, but it's
|
||||
# probably not necessary
|
||||
if capsId in capabilityIdList:
|
||||
inboxAfterCapabilities(recentPostsCache,
|
||||
maxRecentPosts,
|
||||
session, keyId, handle,
|
||||
queueJson['post'],
|
||||
baseDir, httpPrefix,
|
||||
sendThreads, postLog,
|
||||
cachedWebfingers,
|
||||
personCache, queue,
|
||||
domain,
|
||||
onionDomain, i2pDomain,
|
||||
port, proxyType,
|
||||
federationList, ocapAlways,
|
||||
debug, acceptedCaps,
|
||||
queueFilename, destination,
|
||||
maxReplies, allowDeletion,
|
||||
maxMentions, maxEmoji,
|
||||
translate, unitTest,
|
||||
YTReplacementDomain)
|
||||
else:
|
||||
print('Queue: object capabilities check has failed')
|
||||
if debug:
|
||||
pprint(queueJson['post'])
|
||||
else:
|
||||
if not ocapAlways:
|
||||
inboxAfterCapabilities(recentPostsCache,
|
||||
maxRecentPosts,
|
||||
session, keyId, handle,
|
||||
queueJson['post'],
|
||||
baseDir, httpPrefix,
|
||||
sendThreads, postLog,
|
||||
cachedWebfingers,
|
||||
personCache, queue,
|
||||
domain,
|
||||
onionDomain, i2pDomain,
|
||||
port, proxyType,
|
||||
federationList, ocapAlways,
|
||||
debug, acceptedCaps,
|
||||
queueFilename, destination,
|
||||
maxReplies, allowDeletion,
|
||||
maxMentions, maxEmoji,
|
||||
translate, unitTest,
|
||||
YTReplacementDomain)
|
||||
if debug:
|
||||
pprint(queueJson['post'])
|
||||
print('No capability list within post')
|
||||
print('ocapAlways: ' + str(ocapAlways))
|
||||
inboxAfterInitial(recentPostsCache,
|
||||
maxRecentPosts,
|
||||
session, keyId, handle,
|
||||
queueJson['post'],
|
||||
baseDir, httpPrefix,
|
||||
sendThreads, postLog,
|
||||
cachedWebfingers,
|
||||
personCache, queue,
|
||||
domain,
|
||||
onionDomain, i2pDomain,
|
||||
port, proxyType,
|
||||
federationList, ocapAlways,
|
||||
debug, acceptedCaps,
|
||||
queueFilename, destination,
|
||||
maxReplies, allowDeletion,
|
||||
maxMentions, maxEmoji,
|
||||
translate, unitTest,
|
||||
YTReplacementDomain)
|
||||
if debug:
|
||||
pprint(queueJson['post'])
|
||||
|
||||
print('Queue: Queue post accepted')
|
||||
if os.path.isfile(queueFilename):
|
||||
|
|
10
person.py
10
person.py
|
@ -259,7 +259,6 @@ def createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
|
|||
'id': personId+'/endpoints',
|
||||
'sharedInbox': httpPrefix+'://'+domain+'/inbox',
|
||||
},
|
||||
'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new',
|
||||
'followers': personId+'/followers',
|
||||
'following': personId+'/following',
|
||||
'shares': personId+'/shares',
|
||||
|
@ -506,15 +505,6 @@ def createSharedInbox(baseDir: str, nickname: str, domain: str, port: int,
|
|||
True, True, None)
|
||||
|
||||
|
||||
def createCapabilitiesInbox(baseDir: str, nickname: str,
|
||||
domain: str, port: int,
|
||||
httpPrefix: str) -> (str, str, {}, {}):
|
||||
"""Generates the capabilities inbox to sign requests
|
||||
"""
|
||||
return createPersonBase(baseDir, nickname, domain, port,
|
||||
httpPrefix, True, True, None)
|
||||
|
||||
|
||||
def personUpgradeActor(baseDir: str, personJson: {},
|
||||
handle: str, filename: str) -> None:
|
||||
"""Alter the actor to add any new properties
|
||||
|
|
111
posts.py
111
posts.py
|
@ -45,8 +45,6 @@ from utils import validNickname
|
|||
from utils import locatePost
|
||||
from utils import loadJson
|
||||
from utils import saveJson
|
||||
from capabilities import getOcapFilename
|
||||
from capabilities import capabilitiesUpdate
|
||||
from media import attachMedia
|
||||
from media import replaceYouTube
|
||||
from content import removeHtml
|
||||
|
@ -893,24 +891,12 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
|||
if not clientToServer:
|
||||
actorUrl = httpPrefix + '://' + domain + '/users/' + nickname
|
||||
|
||||
# if capabilities have been granted for this actor
|
||||
# then get the corresponding id
|
||||
capabilityIdList = []
|
||||
ocapFilename = getOcapFilename(baseDir, nickname, domain,
|
||||
toUrl, 'granted')
|
||||
if ocapFilename:
|
||||
if os.path.isfile(ocapFilename):
|
||||
oc = loadJson(ocapFilename)
|
||||
if oc:
|
||||
if oc.get('id'):
|
||||
capabilityIdList = [oc['id']]
|
||||
idStr = \
|
||||
httpPrefix + '://' + domain + '/users/' + nickname + \
|
||||
'/statuses/' + statusNumber + '/replies'
|
||||
newPost = {
|
||||
'@context': postContext,
|
||||
'id': newPostId + '/activity',
|
||||
'capability': capabilityIdList,
|
||||
'type': 'Create',
|
||||
'actor': actorUrl,
|
||||
'published': published,
|
||||
|
@ -1674,20 +1660,13 @@ def sendPost(projectVersion: str,
|
|||
projectVersion, httpPrefix,
|
||||
nickname, domain, postToBox)
|
||||
|
||||
# If there are more than one followers on the target domain
|
||||
# then send to the shared inbox indead of the individual inbox
|
||||
if nickname == 'capabilities':
|
||||
inboxUrl = capabilityAcquisition
|
||||
if not capabilityAcquisition:
|
||||
return 2
|
||||
|
||||
if not inboxUrl:
|
||||
return 3
|
||||
if not pubKey:
|
||||
return 4
|
||||
if not toPersonId:
|
||||
return 5
|
||||
# sharedInbox and capabilities are optional
|
||||
# sharedInbox is optional
|
||||
|
||||
postJsonObject = \
|
||||
createPostBase(baseDir, nickname, domain, port,
|
||||
|
@ -2003,7 +1982,7 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
|
|||
else:
|
||||
postToBox = 'outbox'
|
||||
|
||||
# get the actor inbox/outbox/capabilities for the To handle
|
||||
# get the actor inbox/outbox for the To handle
|
||||
(inboxUrl, pubKeyId, pubKey, toPersonId, sharedInboxUrl,
|
||||
capabilityAcquisition, avatarUrl,
|
||||
displayName) = getPersonBox(baseDir, session, wfRequest,
|
||||
|
@ -2011,17 +1990,12 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
|
|||
projectVersion, httpPrefix,
|
||||
nickname, domain, postToBox)
|
||||
|
||||
if nickname == 'capabilities':
|
||||
inboxUrl = capabilityAcquisition
|
||||
if not capabilityAcquisition:
|
||||
return 2
|
||||
else:
|
||||
print("inboxUrl: " + str(inboxUrl))
|
||||
print("toPersonId: " + str(toPersonId))
|
||||
print("sharedInboxUrl: " + str(sharedInboxUrl))
|
||||
if inboxUrl:
|
||||
if inboxUrl.endswith('/actor/inbox'):
|
||||
inboxUrl = sharedInboxUrl
|
||||
print("inboxUrl: " + str(inboxUrl))
|
||||
print("toPersonId: " + str(toPersonId))
|
||||
print("sharedInboxUrl: " + str(sharedInboxUrl))
|
||||
if inboxUrl:
|
||||
if inboxUrl.endswith('/actor/inbox'):
|
||||
inboxUrl = sharedInboxUrl
|
||||
|
||||
if not inboxUrl:
|
||||
if debug:
|
||||
|
@ -2039,7 +2013,7 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
|
|||
if debug:
|
||||
print('DEBUG: missing personId')
|
||||
return 5
|
||||
# sharedInbox and capabilities are optional
|
||||
# sharedInbox is optional
|
||||
|
||||
# get the senders private key
|
||||
privateKeyPem = getPersonKey(nickname, domain, baseDir, 'private', debug)
|
||||
|
@ -2791,32 +2765,8 @@ def createSharedInboxIndex(baseDir: str, sharedBoxDir: str,
|
|||
if actorNickname + '@' + actorDomain not in followingHandles:
|
||||
continue
|
||||
|
||||
if ocapAlways:
|
||||
capsList = None
|
||||
# Note: should this be in the Create or the object of a post?
|
||||
if postJsonObject.get('capability'):
|
||||
if isinstance(postJsonObject['capability'], list):
|
||||
capsList = postJsonObject['capability']
|
||||
|
||||
# Have capabilities been granted for the sender?
|
||||
ocapFilename = \
|
||||
baseDir + '/accounts/' + handle + '/ocap/granted/' + \
|
||||
postJsonObject['actor'].replace('/', '#') + '.json'
|
||||
if not os.path.isfile(ocapFilename):
|
||||
continue
|
||||
|
||||
# read the capabilities id
|
||||
ocapJson = loadJson(ocapFilename, 0)
|
||||
if not ocapJson:
|
||||
print('WARN: json load exception createSharedInboxIndex')
|
||||
else:
|
||||
if ocapJson.get('id'):
|
||||
if ocapJson['id'] in capsList:
|
||||
postsInBoxDict[statusNumber] = sharedInboxFilename
|
||||
postsCtr += 1
|
||||
else:
|
||||
postsInBoxDict[statusNumber] = sharedInboxFilename
|
||||
postsCtr += 1
|
||||
postsInBoxDict[statusNumber] = sharedInboxFilename
|
||||
postsCtr += 1
|
||||
return postsCtr
|
||||
|
||||
|
||||
|
@ -3413,45 +3363,6 @@ def checkDomains(session, baseDir: str,
|
|||
print(followerWarningStr)
|
||||
|
||||
|
||||
def sendCapabilitiesUpdate(session, baseDir: str, httpPrefix: str,
|
||||
nickname: str, domain: str, port: int,
|
||||
followerUrl, updateCaps: [],
|
||||
sendThreads: [], postLog: [],
|
||||
cachedWebfingers: {}, personCache: {},
|
||||
federationList: [], debug: bool,
|
||||
projectVersion: str) -> int:
|
||||
"""When the capabilities for a follower are changed this
|
||||
sends out an update. followerUrl is the actor of the follower.
|
||||
"""
|
||||
updateJson = \
|
||||
capabilitiesUpdate(baseDir, httpPrefix,
|
||||
nickname, domain, port,
|
||||
followerUrl, updateCaps)
|
||||
|
||||
if not updateJson:
|
||||
return 1
|
||||
|
||||
if debug:
|
||||
pprint(updateJson)
|
||||
print('DEBUG: sending capabilities update from ' +
|
||||
nickname + '@' + domain + ' port ' + str(port) +
|
||||
' to ' + followerUrl)
|
||||
|
||||
clientToServer = False
|
||||
followerNickname = getNicknameFromActor(followerUrl)
|
||||
if not followerNickname:
|
||||
print('WARN: unable to find nickname in ' + followerUrl)
|
||||
return 1
|
||||
followerDomain, followerPort = getDomainFromActor(followerUrl)
|
||||
return sendSignedJson(updateJson, session, baseDir,
|
||||
nickname, domain, port,
|
||||
followerNickname, followerDomain, followerPort, '',
|
||||
httpPrefix, True, clientToServer,
|
||||
federationList,
|
||||
sendThreads, postLog, cachedWebfingers,
|
||||
personCache, debug, projectVersion)
|
||||
|
||||
|
||||
def populateRepliesJson(baseDir: str, nickname: str, domain: str,
|
||||
postRepliesFilename: str, authorized: bool,
|
||||
repliesJson: {}) -> None:
|
||||
|
|
|
@ -141,13 +141,6 @@ def postJsonString(session, postJsonStr: str,
|
|||
conversions between string and json format don't invalidate
|
||||
the message body digest of http signatures
|
||||
"""
|
||||
# always allow capability requests
|
||||
if not capability.startswith('cap'):
|
||||
# check that we are posting to a permitted domain
|
||||
if not urlPermitted(inboxUrl, federationList, capability):
|
||||
print('postJson: ' + inboxUrl + ' not permitted by capabilities')
|
||||
return None, None
|
||||
|
||||
try:
|
||||
postResult = \
|
||||
session.post(url=inboxUrl, data=postJsonStr, headers=headers)
|
||||
|
|
2
utils.py
2
utils.py
|
@ -669,7 +669,7 @@ def validNickname(domain: str, nickname: str) -> bool:
|
|||
return False
|
||||
reservedNames = ('inbox', 'dm', 'outbox', 'following',
|
||||
'public', 'followers',
|
||||
'channel', 'capabilities', 'calendar',
|
||||
'channel', 'calendar',
|
||||
'tlreplies', 'tlmedia', 'tlblogs',
|
||||
'tlevents',
|
||||
'moderation', 'activity', 'undo',
|
||||
|
|
Loading…
Reference in New Issue