Deprecate capabilities module

main
Bob Mottram 2020-09-27 19:35:35 +01:00
parent 38302fd686
commit b064d6f4d6
10 changed files with 54 additions and 656 deletions

View File

@ -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'

View File

@ -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

View File

@ -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()

View File

@ -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,

View File

@ -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
View File

@ -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):

View File

@ -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
View File

@ -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:

View File

@ -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)

View File

@ -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',