2020-04-05 09:47:42 +00:00
|
|
|
__filename__ = "capabilities.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
|
|
|
__version__ = "1.1.0"
|
|
|
|
__maintainer__ = "Bob Mottram"
|
|
|
|
__email__ = "bob@freedombone.net"
|
|
|
|
__status__ = "Production"
|
2019-07-05 21:24:16 +00:00
|
|
|
|
2019-07-06 09:07:24 +00:00
|
|
|
import os
|
2019-07-05 21:24:16 +00:00
|
|
|
from auth import createPassword
|
2019-07-09 08:39:50 +00:00
|
|
|
from utils import getNicknameFromActor
|
|
|
|
from utils import getDomainFromActor
|
2019-10-22 11:55:06 +00:00
|
|
|
from utils import loadJson
|
|
|
|
from utils import saveJson
|
2019-07-05 21:24:16 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
|
|
|
|
def getOcapFilename(baseDir: str,
|
|
|
|
nickname: str, domain: str,
|
|
|
|
actor: str, subdir: str) -> str:
|
2019-07-09 14:20:23 +00:00
|
|
|
"""Returns the filename for a particular capability accepted or granted
|
|
|
|
Also creates directories as needed
|
|
|
|
"""
|
2019-08-18 20:43:10 +00:00
|
|
|
if not actor:
|
|
|
|
return None
|
|
|
|
|
2019-07-07 19:25:38 +00:00
|
|
|
if ':' in domain:
|
2020-04-05 09:47:42 +00:00
|
|
|
domain = domain.split(':')[0]
|
2019-07-07 19:25:38 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
if not os.path.isdir(baseDir + '/accounts'):
|
|
|
|
os.mkdir(baseDir + '/accounts')
|
2019-07-07 15:51:04 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
ocDir = baseDir + '/accounts/' + nickname + '@' + domain
|
2019-07-07 16:33:59 +00:00
|
|
|
if not os.path.isdir(ocDir):
|
|
|
|
os.mkdir(ocDir)
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
ocDir = baseDir + '/accounts/' + nickname + '@' + domain + '/ocap'
|
2019-07-07 16:33:59 +00:00
|
|
|
if not os.path.isdir(ocDir):
|
|
|
|
os.mkdir(ocDir)
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
ocDir = baseDir + '/accounts/' + \
|
|
|
|
nickname + '@' + domain + '/ocap/' + subdir
|
2019-07-07 16:33:59 +00:00
|
|
|
if not os.path.isdir(ocDir):
|
|
|
|
os.mkdir(ocDir)
|
2019-08-11 18:32:29 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
return baseDir + '/accounts/' + \
|
|
|
|
nickname + '@' + domain + '/ocap/' + \
|
|
|
|
subdir + '/' + actor.replace('/', '#') + '.json'
|
|
|
|
|
2019-07-07 11:53:32 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
def CapablePost(postJson: {}, capabilityList: [], debug: bool) -> bool:
|
2019-07-07 22:06:46 +00:00
|
|
|
"""Determines whether a post arriving in the inbox
|
|
|
|
should be accepted accoring to the list of capabilities
|
|
|
|
"""
|
|
|
|
if postJson.get('type'):
|
|
|
|
# No announces/repeats
|
2020-04-05 09:47:42 +00:00
|
|
|
if postJson['type'] == 'Announce':
|
2019-07-07 22:06:46 +00:00
|
|
|
if 'inbox:noannounce' in capabilityList:
|
|
|
|
if debug:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('DEBUG: ' +
|
|
|
|
'inbox post rejected because inbox:noannounce')
|
2019-07-07 22:06:46 +00:00
|
|
|
return False
|
|
|
|
# No likes
|
2020-04-05 09:47:42 +00:00
|
|
|
if postJson['type'] == 'Like':
|
2019-07-07 22:06:46 +00:00
|
|
|
if 'inbox:nolike' in capabilityList:
|
|
|
|
if debug:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('DEBUG: ' +
|
|
|
|
'inbox post rejected because inbox:nolike')
|
2019-07-07 22:06:46 +00:00
|
|
|
return False
|
2020-04-05 09:47:42 +00:00
|
|
|
if postJson['type'] == 'Create':
|
2019-07-07 22:06:46 +00:00
|
|
|
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:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('DEBUG: ' +
|
|
|
|
'inbox post rejected because ' +
|
|
|
|
'inbox:noreply')
|
2019-07-07 22:06:46 +00:00
|
|
|
return False
|
|
|
|
# are content warnings enforced?
|
|
|
|
if postJson['object'].get('sensitive'):
|
|
|
|
if not postJson['object']['sensitive']:
|
|
|
|
if 'inbox:cw' in capabilityList:
|
|
|
|
if debug:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('DEBUG: ' +
|
|
|
|
'inbox post rejected because inbox:cw')
|
2019-07-07 22:06:46 +00:00
|
|
|
return False
|
|
|
|
# content warning must have non-zero summary
|
|
|
|
if postJson['object'].get('summary'):
|
2020-04-05 09:47:42 +00:00
|
|
|
if len(postJson['object']['summary']) < 2:
|
2019-07-07 22:06:46 +00:00
|
|
|
if 'inbox:cw' in capabilityList:
|
|
|
|
if debug:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('DEBUG: ' +
|
|
|
|
'inbox post rejected because ' +
|
|
|
|
'inbox:cw, summary missing')
|
2020-03-22 21:16:02 +00:00
|
|
|
return False
|
2019-07-07 22:06:46 +00:00
|
|
|
if 'inbox:write' in capabilityList:
|
|
|
|
return True
|
|
|
|
return True
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
|
|
|
|
def capabilitiesRequest(baseDir: str, httpPrefix: str, domain: str,
|
|
|
|
requestedActor: str, requestedDomain: str,
|
|
|
|
requestedCaps=["inbox:write", "objects:read"]) -> {}:
|
2019-07-06 09:07:24 +00:00
|
|
|
# This is sent to the capabilities endpoint /caps/new
|
|
|
|
# which could be instance wide or for a particular person
|
2019-07-07 11:53:32 +00:00
|
|
|
# This could also be added to a follow activity
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapId = createPassword(32)
|
|
|
|
ocapRequest = {
|
2019-08-18 11:23:04 +00:00
|
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
2020-04-05 09:47:42 +00:00
|
|
|
"id": httpPrefix + "://" + requestedDomain + "/caps/request/" + ocapId,
|
2019-07-05 21:24:16 +00:00
|
|
|
"type": "Request",
|
2019-07-06 10:38:48 +00:00
|
|
|
"capability": requestedCaps,
|
2019-07-06 09:15:40 +00:00
|
|
|
"actor": requestedActor
|
2019-07-06 09:07:24 +00:00
|
|
|
}
|
2019-07-07 11:53:32 +00:00
|
|
|
return ocapRequest
|
2020-03-22 21:16:02 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
|
|
|
|
def capabilitiesAccept(baseDir: str, httpPrefix: str,
|
|
|
|
nickname: str, domain: str, port: int,
|
|
|
|
acceptedActor: str, saveToFile: bool,
|
|
|
|
acceptedCaps=["inbox:write", "objects:read"]) -> {}:
|
2019-07-06 09:07:24 +00:00
|
|
|
# This gets returned to capabilities requester
|
2019-07-07 11:53:32 +00:00
|
|
|
# This could also be added to a follow Accept activity
|
|
|
|
|
|
|
|
# reject excessively long actors
|
2020-04-05 09:47:42 +00:00
|
|
|
if len(acceptedActor) > 256:
|
2019-07-07 11:53:32 +00:00
|
|
|
return None
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
fullDomain = domain
|
2019-08-16 20:35:11 +00:00
|
|
|
if port:
|
2020-04-05 09:47:42 +00:00
|
|
|
if port != 80 and port != 443:
|
2019-08-16 20:35:11 +00:00
|
|
|
if ':' not in domain:
|
2020-04-05 09:47:42 +00:00
|
|
|
fullDomain = domain + ':' + str(port)
|
2020-03-22 21:16:02 +00:00
|
|
|
|
2019-07-07 11:53:32 +00:00
|
|
|
# make directories to store capabilities
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapFilename = \
|
|
|
|
getOcapFilename(baseDir, nickname, fullDomain, acceptedActor, 'accept')
|
2019-08-18 20:43:10 +00:00
|
|
|
if not ocapFilename:
|
|
|
|
return None
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapAccept = None
|
2019-07-07 11:53:32 +00:00
|
|
|
|
|
|
|
# if the capability already exists then load it from file
|
2019-07-07 15:51:04 +00:00
|
|
|
if os.path.isfile(ocapFilename):
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapAccept = loadJson(ocapFilename)
|
2020-03-22 21:16:02 +00:00
|
|
|
# otherwise create a new capability
|
2019-07-07 11:53:32 +00:00
|
|
|
if not ocapAccept:
|
2020-04-05 09:47:42 +00:00
|
|
|
acceptedActorNickname = getNicknameFromActor(acceptedActor)
|
2019-09-02 09:43:43 +00:00
|
|
|
if not acceptedActorNickname:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('WARN: unable to find nickname in ' + acceptedActor)
|
2019-09-02 09:43:43 +00:00
|
|
|
return None
|
2020-04-05 09:47:42 +00:00
|
|
|
acceptedActorDomain, acceptedActorPort = \
|
|
|
|
getDomainFromActor(acceptedActor)
|
2020-03-22 21:16:02 +00:00
|
|
|
if acceptedActorPort:
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapId = acceptedActorNickname + '@' + acceptedActorDomain + \
|
|
|
|
':' + str(acceptedActorPort) + '#'+createPassword(32)
|
2019-07-09 08:39:50 +00:00
|
|
|
else:
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapId = acceptedActorNickname + '@' + acceptedActorDomain + \
|
|
|
|
'#' + createPassword(32)
|
|
|
|
ocapAccept = {
|
2019-08-18 11:23:04 +00:00
|
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
2020-04-05 09:47:42 +00:00
|
|
|
"id": httpPrefix + "://" + fullDomain + "/caps/" + ocapId,
|
2019-07-07 11:53:32 +00:00
|
|
|
"type": "Capability",
|
|
|
|
"capability": acceptedCaps,
|
|
|
|
"scope": acceptedActor,
|
2020-04-05 09:47:42 +00:00
|
|
|
"actor": httpPrefix + "://" + fullDomain
|
2019-07-07 11:53:32 +00:00
|
|
|
}
|
|
|
|
if nickname:
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapAccept['actor'] = \
|
|
|
|
httpPrefix + "://" + fullDomain + '/users/' + nickname
|
2019-07-06 09:07:24 +00:00
|
|
|
|
2019-07-07 11:53:32 +00:00
|
|
|
if saveToFile:
|
2020-04-05 09:47:42 +00:00
|
|
|
saveJson(ocapAccept, ocapFilename)
|
2019-07-07 11:53:32 +00:00
|
|
|
return ocapAccept
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
|
|
|
|
def capabilitiesGrantedSave(baseDir: str,
|
|
|
|
nickname: str, domain: str, ocap: {}) -> bool:
|
2019-07-07 11:53:32 +00:00
|
|
|
"""A capabilities accept is received, so stor it for
|
|
|
|
reference when sending to the actor
|
|
|
|
"""
|
|
|
|
if not ocap.get('actor'):
|
|
|
|
return False
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapFilename = \
|
|
|
|
getOcapFilename(baseDir, nickname, domain, ocap['actor'], 'granted')
|
2019-08-18 20:43:10 +00:00
|
|
|
if not ocapFilename:
|
|
|
|
return False
|
2020-04-05 09:47:42 +00:00
|
|
|
saveJson(ocap, ocapFilename)
|
2019-07-07 11:53:32 +00:00
|
|
|
return True
|
2019-07-09 14:20:23 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
|
|
|
|
def capabilitiesUpdate(baseDir: str, httpPrefix: str,
|
|
|
|
nickname: str, domain: str, port: int,
|
|
|
|
updateActor: str,
|
2019-07-09 14:20:23 +00:00
|
|
|
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
|
2020-04-05 09:47:42 +00:00
|
|
|
if len(updateActor) > 256:
|
2019-07-09 14:20:23 +00:00
|
|
|
return None
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
fullDomain = domain
|
2019-08-16 20:35:11 +00:00
|
|
|
if port:
|
2020-04-05 09:47:42 +00:00
|
|
|
if port != 80 and port != 443:
|
2019-08-16 20:35:11 +00:00
|
|
|
if ':' not in domain:
|
2020-04-05 09:47:42 +00:00
|
|
|
fullDomain = domain + ':' + str(port)
|
2020-03-22 21:16:02 +00:00
|
|
|
|
2019-07-09 14:20:23 +00:00
|
|
|
# Get the filename of the capability
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapFilename = \
|
|
|
|
getOcapFilename(baseDir, nickname, fullDomain, updateActor, 'accept')
|
2019-08-18 20:43:10 +00:00
|
|
|
if not ocapFilename:
|
|
|
|
return None
|
2019-07-09 14:20:23 +00:00
|
|
|
|
|
|
|
# The capability should already exist for it to be updated
|
|
|
|
if not os.path.isfile(ocapFilename):
|
|
|
|
return None
|
|
|
|
|
|
|
|
# create an update activity
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapUpdate = {
|
2019-08-18 11:23:04 +00:00
|
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
2019-07-09 14:20:23 +00:00
|
|
|
'type': 'Update',
|
2020-04-05 09:47:42 +00:00
|
|
|
'actor': httpPrefix + '://' + fullDomain + '/users/' + nickname,
|
2019-07-09 14:20:23 +00:00
|
|
|
'to': [updateActor],
|
|
|
|
'cc': [],
|
|
|
|
'object': {}
|
|
|
|
}
|
|
|
|
|
|
|
|
# read the existing capability
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapJson = loadJson(ocapFilename)
|
2019-07-09 14:20:23 +00:00
|
|
|
|
|
|
|
# set the new capabilities list. eg. ["inbox:write","objects:read"]
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapJson['capability'] = updateCaps
|
2019-07-09 14:20:23 +00:00
|
|
|
|
|
|
|
# change the id, so that the old capabilities can't continue to be used
|
2020-04-05 09:47:42 +00:00
|
|
|
updateActorNickname = getNicknameFromActor(updateActor)
|
2019-09-02 09:43:43 +00:00
|
|
|
if not updateActorNickname:
|
2020-04-05 09:47:42 +00:00
|
|
|
print('WARN: unable to find nickname in ' + updateActor)
|
2019-09-02 09:43:43 +00:00
|
|
|
return None
|
2020-04-05 09:47:42 +00:00
|
|
|
updateActorDomain, updateActorPort = getDomainFromActor(updateActor)
|
2019-07-09 14:20:23 +00:00
|
|
|
if updateActorPort:
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapId = updateActorNickname + '@' + updateActorDomain + \
|
|
|
|
':' + str(updateActorPort) + '#' + createPassword(32)
|
2019-07-09 14:20:23 +00:00
|
|
|
else:
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapId = updateActorNickname + '@' + updateActorDomain + \
|
|
|
|
'#' + createPassword(32)
|
|
|
|
ocapJson['id'] = httpPrefix + "://" + fullDomain + "/caps/" + ocapId
|
|
|
|
ocapUpdate['object'] = ocapJson
|
2019-07-09 14:20:23 +00:00
|
|
|
|
|
|
|
# save it again
|
2020-04-05 09:47:42 +00:00
|
|
|
saveJson(ocapJson, ocapFilename)
|
2019-09-30 22:39:02 +00:00
|
|
|
|
2019-07-09 14:20:23 +00:00
|
|
|
return ocapUpdate
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
|
|
|
|
def capabilitiesReceiveUpdate(baseDir: str,
|
|
|
|
nickname: str, domain: str, port: int,
|
|
|
|
actor: str,
|
|
|
|
newCapabilitiesId: str,
|
|
|
|
capabilityList: [], debug: bool) -> bool:
|
2019-07-09 14:20:23 +00:00
|
|
|
"""An update for a capability or the given actor has arrived
|
|
|
|
"""
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapFilename = \
|
|
|
|
getOcapFilename(baseDir, nickname, domain, actor, 'granted')
|
2019-08-18 20:43:10 +00:00
|
|
|
if not ocapFilename:
|
|
|
|
return False
|
|
|
|
|
2019-07-09 14:20:23 +00:00
|
|
|
if not os.path.isfile(ocapFilename):
|
|
|
|
if debug:
|
|
|
|
print('DEBUG: capabilities file not found during update')
|
|
|
|
print(ocapFilename)
|
|
|
|
return False
|
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapJson = loadJson(ocapFilename)
|
2019-09-30 22:39:02 +00:00
|
|
|
|
|
|
|
if ocapJson:
|
2020-04-05 09:47:42 +00:00
|
|
|
ocapJson['id'] = newCapabilitiesId
|
|
|
|
ocapJson['capability'] = capabilityList
|
2019-09-30 22:39:02 +00:00
|
|
|
|
2020-04-05 09:47:42 +00:00
|
|
|
return saveJson(ocapJson, ocapFilename)
|
2019-07-09 14:20:23 +00:00
|
|
|
return False
|