Remove role delegation

The keeps the handling of roles very simple
merge-requests/30/head
Bob Mottram 2021-05-13 20:58:16 +01:00
parent 57015d6000
commit 7e48beb0fe
9 changed files with 140 additions and 500 deletions

View File

@ -310,52 +310,6 @@ python3 epicyon.py --domainmax 1000 --accountmax 200
With these settings you're going to be receiving no more than 200 messages for any given account within a day. With these settings you're going to be receiving no more than 200 messages for any given account within a day.
## Delegated roles
Within an organization you may want to define different roles and for some projects to be delegated. By default the first account added to the system will be the admin, and be assigned *moderator* and *delegator* roles under a project called *instance*. The admin can then delegate a person to other projects with:
``` bash
python3 epicyon.py --nickname [admin nickname] --domain [mydomain] \
--delegate [person nickname] \
--project [project name] --role [title] \
--password [c2s password]
```
The other person could also be made a delegator, but they will only be able to delegate further within projects which they're assigned to. By design, this creates a restricted organizational hierarchy. For example:
``` bash
python3 epicyon.py --nickname [admin nickname] --domain [mydomain] \
--delegate [person nickname] \
--project [project name] --role delegator \
--password [c2s password]
```
A delegated role can also be removed.
``` bash
python3 epicyon.py --nickname [admin nickname] --domain [mydomain] \
--undelegate [person nickname] \
--project [project name] \
--password [c2s password]
```
This extends the ActivityPub client-to-server protocol to include activities called *Delegate* and *Role*. The JSON looks like:
``` json
{ 'type': 'Delegate',
'actor': https://somedomain/users/admin,
'object': {
'type': 'Role',
'actor': https://'+somedomain+'/users/'+other,
'object': 'otherproject;otherrole',
'to': [],
'cc': []
},
'to': [],
'cc': []}
```
Projects and roles are only scoped within a single instance. There presently are not enough security mechanisms to support multi-instance distributed organizations.
## Assigning skills ## Assigning skills

View File

@ -128,7 +128,7 @@ def _getCityPulse(currTimeOfDay, decoySeed: int) -> (float, float):
def spoofGeolocation(baseDir: str, def spoofGeolocation(baseDir: str,
city: str, currTime, decoySeed: int, city: str, currTime, decoySeed: int,
citiesList: []) -> (float, float, str, str, citiesList: []) -> (float, float, str, str, \
str, str, int): str, str, int):
"""Given a city and the current time spoofs the location """Given a city and the current time spoofs the location
for an image for an image
@ -150,7 +150,8 @@ def spoofGeolocation(baseDir: str,
else: else:
if not os.path.isfile(locationsFilename): if not os.path.isfile(locationsFilename):
return (default_latitude, default_longitude, return (default_latitude, default_longitude,
default_latdirection, default_longdirection) default_latdirection, default_longdirection,
"", "", 0)
cities = [] cities = []
with open(locationsFilename, "r") as f: with open(locationsFilename, "r") as f:
cities = f.readlines() cities = f.readlines()

View File

@ -124,6 +124,7 @@ from blocking import isBlockedHashtag
from blocking import isBlockedDomain from blocking import isBlockedDomain
from blocking import getDomainBlocklist from blocking import getDomainBlocklist
from roles import setRole from roles import setRole
from roles import getRolesFromString
from roles import clearModeratorStatus from roles import clearModeratorStatus
from roles import clearEditorStatus from roles import clearEditorStatus
from roles import clearCounselorStatus from roles import clearCounselorStatus
@ -4732,7 +4733,7 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isdir(modDir): if os.path.isdir(modDir):
setRole(baseDir, setRole(baseDir,
modNick, domain, modNick, domain,
'instance', 'moderator') 'moderator')
else: else:
# nicknames on separate lines # nicknames on separate lines
modFile = open(moderatorsFile, "w+") modFile = open(moderatorsFile, "w+")
@ -4757,7 +4758,6 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isdir(modDir): if os.path.isdir(modDir):
setRole(baseDir, setRole(baseDir,
modNick, domain, modNick, domain,
'instance',
'moderator') 'moderator')
# change site editors list # change site editors list
@ -4789,7 +4789,7 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isdir(edDir): if os.path.isdir(edDir):
setRole(baseDir, setRole(baseDir,
edNick, domain, edNick, domain,
'instance', 'editor') 'editor')
else: else:
# nicknames on separate lines # nicknames on separate lines
edFile = open(editorsFile, "w+") edFile = open(editorsFile, "w+")
@ -4814,7 +4814,6 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isdir(edDir): if os.path.isdir(edDir):
setRole(baseDir, setRole(baseDir,
edNick, domain, edNick, domain,
'instance',
'editor') 'editor')
# change site counselors list # change site counselors list
@ -4846,7 +4845,7 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isdir(edDir): if os.path.isdir(edDir):
setRole(baseDir, setRole(baseDir,
edNick, domain, edNick, domain,
'instance', 'counselor') 'counselor')
else: else:
# nicknames on separate lines # nicknames on separate lines
edFile = open(counselorsFile, "w+") edFile = open(counselorsFile, "w+")
@ -4871,7 +4870,6 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isdir(edDir): if os.path.isdir(edDir):
setRole(baseDir, setRole(baseDir,
edNick, domain, edNick, domain,
'instance',
'counselor') 'counselor')
# remove scheduled posts # remove scheduled posts
@ -7376,7 +7374,7 @@ class PubServer(BaseHTTPRequestHandler):
if not actorJson: if not actorJson:
return False return False
if actorJson.get('roles'): if actorJson.get('affiliation'):
if self._requestHTTP(): if self._requestHTTP():
getPerson = \ getPerson = \
personLookup(domain, path.replace('/roles', ''), personLookup(domain, path.replace('/roles', ''),
@ -7397,6 +7395,10 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.keyShortcuts.get(nickname): if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname] accessKeys = self.server.keyShortcuts[nickname]
rolesList = []
if actorJson.get('affiliation'):
actorRolesStr = actorJson['affiliation']['roleName']
rolesList = getRolesFromString(actorRolesStr)
msg = \ msg = \
htmlProfile(self.server.rssIconAtTop, htmlProfile(self.server.rssIconAtTop,
self.server.cssCache, self.server.cssCache,
@ -7420,8 +7422,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess, self.server.allowLocalNetworkAccess,
self.server.textModeBanner, self.server.textModeBanner,
self.server.debug, self.server.debug,
accessKeys, accessKeys, rolesList,
actorJson['roles'],
None, None) None, None)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -7433,7 +7434,12 @@ class PubServer(BaseHTTPRequestHandler):
'show roles') 'show roles')
else: else:
if self._fetchAuthenticated(): if self._fetchAuthenticated():
msg = json.dumps(actorJson['roles'], rolesList = []
if actorJson.get('affiliation'):
actorRolesStr = actorJson['affiliation']['roleName']
rolesList = getRolesFromString(actorRolesStr)
msg = json.dumps(rolesList,
ensure_ascii=False) ensure_ascii=False)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)

View File

@ -74,7 +74,6 @@ from media import getAttachmentMediaType
from delete import sendDeleteViaServer from delete import sendDeleteViaServer
from like import sendLikeViaServer from like import sendLikeViaServer
from like import sendUndoLikeViaServer from like import sendUndoLikeViaServer
from roles import sendRoleViaServer
from skills import sendSkillViaServer from skills import sendSkillViaServer
from availability import setAvailability from availability import setAvailability
from availability import sendAvailabilityViaServer from availability import sendAvailabilityViaServer
@ -495,9 +494,6 @@ parser.add_argument('--maxEmoji', '--maxemoji', dest='maxEmoji',
help='Maximum number of emoji within a post') help='Maximum number of emoji within a post')
parser.add_argument('--role', dest='role', type=str, default=None, parser.add_argument('--role', dest='role', type=str, default=None,
help='Set a role for a person') help='Set a role for a person')
parser.add_argument('--organization', '--project', dest='project',
type=str, default=None,
help='Set a project for a person')
parser.add_argument('--skill', dest='skill', type=str, default=None, parser.add_argument('--skill', dest='skill', type=str, default=None,
help='Set a skill for a person') help='Set a skill for a person')
parser.add_argument('--level', dest='skillLevelPercent', type=int, parser.add_argument('--level', dest='skillLevelPercent', type=int,
@ -518,11 +514,6 @@ parser.add_argument('--mute', dest='mute', type=str, default=None,
help='Mute a particular post URL') help='Mute a particular post URL')
parser.add_argument('--unmute', dest='unmute', type=str, default=None, parser.add_argument('--unmute', dest='unmute', type=str, default=None,
help='Unmute a particular post URL') help='Unmute a particular post URL')
parser.add_argument('--delegate', dest='delegate', type=str, default=None,
help='Address of an account to delegate a role to')
parser.add_argument('--undodelegate', '--undelegate', dest='undelegate',
type=str, default=None,
help='Removes a delegated role for the given address')
parser.add_argument('--filter', dest='filterStr', type=str, default=None, parser.add_argument('--filter', dest='filterStr', type=str, default=None,
help='Adds a word or phrase which if present will ' + help='Adds a word or phrase which if present will ' +
'cause a message to be ignored') 'cause a message to be ignored')
@ -1987,24 +1978,6 @@ if args.backgroundImage:
print('Background image was not added for ' + args.nickname) print('Background image was not added for ' + args.nickname)
sys.exit() sys.exit()
if args.project:
if not args.delegate and not args.undelegate:
if not nickname:
print('No nickname given')
sys.exit()
if args.role.lower() == 'none' or \
args.role.lower() == 'remove' or \
args.role.lower() == 'delete':
args.role = None
if args.role:
if setRole(baseDir, nickname, domain, args.project, args.role):
print('Role within ' + args.project + ' set to ' + args.role)
else:
if setRole(baseDir, nickname, domain, args.project, None):
print('Left ' + args.project)
sys.exit()
if args.skill: if args.skill:
if not nickname: if not nickname:
print('Specify a nickname with the --nickname option') print('Specify a nickname with the --nickname option')
@ -2218,86 +2191,6 @@ if args.unmute:
time.sleep(1) time.sleep(1)
sys.exit() sys.exit()
if args.delegate:
if not nickname:
print('Specify a nickname with the --nickname option')
sys.exit()
if not args.password:
args.password = getpass.getpass('Password: ')
if not args.password:
print('Specify a password with the --password option')
sys.exit()
args.password = args.password.replace('\n', '')
if not args.project:
print('Specify a project with the --project option')
sys.exit()
if not args.role:
print('Specify a role with the --role option')
sys.exit()
if '@' in args.delegate:
delegatedNickname = args.delegate.split('@')[0]
args.delegate = blockedActor
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
print('Sending delegation for ' + args.delegate +
' with role ' + args.role + ' in project ' + args.project)
sendRoleViaServer(baseDir, session,
nickname, args.password,
domain, port,
httpPrefix, args.delegate,
args.project, args.role,
cachedWebfingers, personCache,
True, __version__)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
sys.exit()
if args.undelegate:
if not nickname:
print('Specify a nickname with the --nickname option')
sys.exit()
if not args.password:
args.password = getpass.getpass('Password: ')
if not args.password:
print('Specify a password with the --password option')
sys.exit()
args.password = args.password.replace('\n', '')
if not args.project:
print('Specify a project with the --project option')
sys.exit()
if '@' in args.undelegate:
delegatedNickname = args.undelegate.split('@')[0]
args.undelegate = blockedActor
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
print('Sending delegation removal for ' + args.undelegate +
' from role ' + args.role + ' in project ' + args.project)
sendRoleViaServer(baseDir, session,
nickname, args.password,
domain, port,
httpPrefix, args.delegate,
args.project, None,
cachedWebfingers, personCache,
True, __version__)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
sys.exit()
if args.unblock: if args.unblock:
if not nickname: if not nickname:
print('Specify a nickname with the --nickname option') print('Specify a nickname with the --nickname option')
@ -2388,9 +2281,7 @@ if args.testdata:
True, False, 'likewhateveryouwantscoob') True, False, 'likewhateveryouwantscoob')
setSkillLevel(baseDir, nickname, domain, 'testing', 60) setSkillLevel(baseDir, nickname, domain, 'testing', 60)
setSkillLevel(baseDir, nickname, domain, 'typing', 50) setSkillLevel(baseDir, nickname, domain, 'typing', 50)
setRole(baseDir, nickname, domain, 'instance', 'admin') setRole(baseDir, nickname, domain, 'admin')
setRole(baseDir, nickname, domain, 'epicyon', 'hacker')
setRole(baseDir, nickname, domain, 'someproject', 'assistant')
setAvailability(baseDir, nickname, domain, 'busy') setAvailability(baseDir, nickname, domain, 'busy')
addShare(baseDir, addShare(baseDir,

View File

@ -35,7 +35,6 @@ from inbox import inboxUpdateIndex
from announce import outboxAnnounce from announce import outboxAnnounce
from announce import outboxUndoAnnounce from announce import outboxUndoAnnounce
from follow import outboxUndoFollow from follow import outboxUndoFollow
from roles import outboxDelegate
from skills import outboxSkills from skills import outboxSkills
from availability import outboxAvailability from availability import outboxAvailability
from like import outboxLike from like import outboxLike
@ -313,7 +312,7 @@ def postMessageToOutbox(session, translate: {},
permittedOutboxTypes = ('Create', 'Announce', 'Like', 'Follow', 'Undo', permittedOutboxTypes = ('Create', 'Announce', 'Like', 'Follow', 'Undo',
'Update', 'Add', 'Remove', 'Block', 'Delete', 'Update', 'Add', 'Remove', 'Block', 'Delete',
'Delegate', 'Skill', 'Add', 'Remove', 'Event', 'Skill', 'Add', 'Remove', 'Event',
'Ignore') 'Ignore')
if messageJson['type'] not in permittedOutboxTypes: if messageJson['type'] not in permittedOutboxTypes:
if debug: if debug:
@ -461,10 +460,6 @@ def postMessageToOutbox(session, translate: {},
print('DEBUG: handle any unfollow requests') print('DEBUG: handle any unfollow requests')
outboxUndoFollow(baseDir, messageJson, debug) outboxUndoFollow(baseDir, messageJson, debug)
if debug:
print('DEBUG: handle delegation requests')
outboxDelegate(baseDir, postToNickname, messageJson, debug)
if debug: if debug:
print('DEBUG: handle skills changes requests') print('DEBUG: handle skills changes requests')
outboxSkills(baseDir, postToNickname, messageJson, debug) outboxSkills(baseDir, postToNickname, messageJson, debug)

View File

@ -200,7 +200,9 @@ def getDefaultPersonContext() -> str:
'suspended': 'toot:suspended', 'suspended': 'toot:suspended',
'toot': 'http://joinmastodon.org/ns#', 'toot': 'http://joinmastodon.org/ns#',
'value': 'schema:value', 'value': 'schema:value',
'Occupation': 'schema:Occupation' 'Occupation': 'schema:Occupation',
'OrganizationRole': 'schema:OrganizationRole',
'WebSite': 'schema:Project'
} }
@ -281,7 +283,15 @@ def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
'name': "", 'name': "",
'skills': "" 'skills': ""
}, },
'roles': {}, "affiliation": {
"@type": "OrganizationRole",
"roleName": "",
"affiliation": {
"@type": "WebSite",
"url": httpPrefix + '://' + domain
},
"startDate": published
},
'availability': None, 'availability': None,
'icon': { 'icon': {
'mediaType': 'image/png', 'mediaType': 'image/png',
@ -319,6 +329,7 @@ def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
if newPerson.get('skills'): if newPerson.get('skills'):
del newPerson['skills'] del newPerson['skills']
del newPerson['shares'] del newPerson['shares']
if newPerson.get('roles'):
del newPerson['roles'] del newPerson['roles']
del newPerson['tag'] del newPerson['tag']
del newPerson['availability'] del newPerson['availability']
@ -463,10 +474,9 @@ def createPerson(baseDir: str, nickname: str, domain: str, port: int,
if nickname != 'news': if nickname != 'news':
# print(nickname+' becomes the instance admin and a moderator') # print(nickname+' becomes the instance admin and a moderator')
setConfigParam(baseDir, 'admin', nickname) setConfigParam(baseDir, 'admin', nickname)
setRole(baseDir, nickname, domain, 'instance', 'admin') setRole(baseDir, nickname, domain, 'admin')
setRole(baseDir, nickname, domain, 'instance', 'moderator') setRole(baseDir, nickname, domain, 'moderator')
setRole(baseDir, nickname, domain, 'instance', 'editor') setRole(baseDir, nickname, domain, 'editor')
setRole(baseDir, nickname, domain, 'instance', 'delegator')
if not os.path.isdir(baseDir + '/accounts'): if not os.path.isdir(baseDir + '/accounts'):
os.mkdir(baseDir + '/accounts') os.mkdir(baseDir + '/accounts')
@ -582,6 +592,22 @@ def personUpgradeActor(baseDir: str, personJson: {},
del personJson['skills'] del personJson['skills']
updateActor = True updateActor = True
if not personJson.get('affiliation'):
personJson['affiliation'] = {
"@type": "OrganizationRole",
"roleName": "",
"affiliation": {
"@type": "WebSite",
"url": personJson['id'].split('/users/')[0]
},
"startDate": published
}
updateActor = True
if personJson.get('roles'):
del personJson['roles']
updateActor = True
if updateActor: if updateActor:
personJson['@context'] = [ personJson['@context'] = [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',

269
roles.py
View File

@ -37,9 +37,12 @@ def _clearRoleStatus(baseDir: str, role: str) -> None:
actorJson = loadJson(filename) actorJson = loadJson(filename)
if not actorJson: if not actorJson:
continue continue
if actorJson['roles'].get('instance'): if not actorJson.get('affiliation'):
if role in actorJson['roles']['instance']: continue
actorJson['roles']['instance'].remove(role) rolesList = \
getRolesFromString(actorJson['affiliation']['roleName'])
if role in rolesList:
rolesList.remove(role)
saveJson(actorJson, filename) saveJson(actorJson, filename)
@ -112,13 +115,35 @@ def _removeRole(baseDir: str, nickname: str, roleFilename: str) -> None:
f.write(roleNickname + '\n') f.write(roleNickname + '\n')
def setRolesFromList(actorJson: {}, rolesList: []) -> None:
"""Sets roles from a list
"""
rolesStr = ''
for roleName in rolesList:
if rolesStr:
rolesStr += ', '
rolesStr += roleName.lower()
if actorJson.get('affiliation'):
actorJson['affiliation']['roleName'] = rolesStr
def getRolesFromString(rolesStr: str) -> []:
"""Returns a list of roles from a string
"""
rolesList = rolesStr.split(',')
rolesResult = []
for roleName in rolesList:
rolesResult.append(roleName.strip().lower())
return rolesResult
def setRole(baseDir: str, nickname: str, domain: str, def setRole(baseDir: str, nickname: str, domain: str,
project: str, role: str) -> bool: role: str) -> bool:
"""Set a person's role within a project """Set a person's role
Setting the role to an empty string or None will remove it Setting the role to an empty string or None will remove it
""" """
# avoid giant strings # avoid giant strings
if len(role) > 128 or len(project) > 128: if len(role) > 128:
return False return False
actorFilename = baseDir + '/accounts/' + \ actorFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '.json' nickname + '@' + domain + '.json'
@ -133,230 +158,28 @@ def setRole(baseDir: str, nickname: str, domain: str,
actorJson = loadJson(actorFilename) actorJson = loadJson(actorFilename)
if actorJson: if actorJson:
if not actorJson.get('affiliation'):
return False
rolesList = \
getRolesFromString(actorJson['affiliation']['roleName'])
actorChanged = False
if role: if role:
# add the role # add the role
if project == 'instance':
if roleFiles.get(role): if roleFiles.get(role):
_addRole(baseDir, nickname, domain, roleFiles[role]) _addRole(baseDir, nickname, domain, roleFiles[role])
if actorJson['roles'].get(project): if role not in rolesList:
if role not in actorJson['roles'][project]: rolesList.append(role)
actorJson['roles'][project].append(role) rolesList.sort()
else: setRolesFromList(actorJson, rolesList)
actorJson['roles'][project] = [role] actorChanged = True
else: else:
# remove the role # remove the role
if project == 'instance':
if roleFiles.get(role): if roleFiles.get(role):
_removeRole(baseDir, nickname, roleFiles[role]) _removeRole(baseDir, nickname, roleFiles[role])
if actorJson['roles'].get(project): if role in rolesList:
actorJson['roles'][project].remove(role) rolesList.remove(role)
# if the project contains no roles then remove it setRolesFromList(actorJson, rolesList)
if len(actorJson['roles'][project]) == 0: actorChanged = True
del actorJson['roles'][project] if actorChanged:
saveJson(actorJson, actorFilename) saveJson(actorJson, actorFilename)
return True return True
def _getRoles(baseDir: str, nickname: str, domain: str,
project: str) -> []:
"""Returns the roles for a given person on a given project
"""
actorFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '.json'
if not os.path.isfile(actorFilename):
return False
actorJson = loadJson(actorFilename)
if actorJson:
if not actorJson.get('roles'):
return None
if not actorJson['roles'].get(project):
return None
return actorJson['roles'][project]
return None
def outboxDelegate(baseDir: str, authenticatedNickname: str,
messageJson: {}, debug: bool) -> bool:
"""Handles receiving a delegation request
"""
if not messageJson.get('type'):
return False
if not messageJson['type'] == 'Delegate':
return False
if not messageJson.get('object'):
return False
if not isinstance(messageJson['object'], dict):
return False
if not messageJson['object'].get('type'):
return False
if not messageJson['object']['type'] == 'Role':
return False
if not messageJson['object'].get('object'):
return False
if not messageJson['object'].get('actor'):
return False
if not isinstance(messageJson['object']['object'], str):
return False
if ';' not in messageJson['object']['object']:
print('WARN: No ; separator between project and role')
return False
delegatorNickname = getNicknameFromActor(messageJson['actor'])
if delegatorNickname != authenticatedNickname:
return
domain, port = getDomainFromActor(messageJson['actor'])
project = messageJson['object']['object'].split(';')[0].strip()
# instance delegators can delagate to other projects
# than their own
canDelegate = False
delegatorRoles = _getRoles(baseDir, delegatorNickname,
domain, 'instance')
if delegatorRoles:
if 'delegator' in delegatorRoles:
canDelegate = True
if not canDelegate:
canDelegate = True
# non-instance delegators can only delegate within their project
delegatorRoles = _getRoles(baseDir, delegatorNickname,
domain, project)
if delegatorRoles:
if 'delegator' not in delegatorRoles:
return False
else:
return False
if not canDelegate:
return False
nickname = getNicknameFromActor(messageJson['object']['actor'])
if not nickname:
print('WARN: unable to find nickname in ' +
messageJson['object']['actor'])
return False
role = \
messageJson['object']['object'].split(';')[1].strip().lower()
if not role:
setRole(baseDir, nickname, domain, project, None)
return True
# what roles is this person already assigned to?
existingRoles = _getRoles(baseDir, nickname, domain, project)
if existingRoles:
if role in existingRoles:
if debug:
print(nickname + '@' + domain +
' is already assigned to the role ' +
role + ' within the project ' + project)
return False
setRole(baseDir, nickname, domain, project, role)
if debug:
print(nickname + '@' + domain +
' assigned to the role ' + role +
' within the project ' + project)
return True
def sendRoleViaServer(baseDir: str, session,
delegatorNickname: str, password: str,
delegatorDomain: str, delegatorPort: int,
httpPrefix: str, nickname: str,
project: str, role: str,
cachedWebfingers: {}, personCache: {},
debug: bool, projectVersion: str) -> {}:
"""A delegator creates a role for a person via c2s
Setting role to an empty string or None removes the role
"""
if not session:
print('WARN: No session for sendRoleViaServer')
return 6
delegatorDomainFull = getFullDomain(delegatorDomain, delegatorPort)
toUrl = \
httpPrefix + '://' + delegatorDomainFull + '/users/' + nickname
ccUrl = \
httpPrefix + '://' + delegatorDomainFull + '/users/' + \
delegatorNickname + '/followers'
if role:
roleStr = project.lower() + ';' + role.lower()
else:
roleStr = project.lower() + ';'
actor = \
httpPrefix + '://' + delegatorDomainFull + \
'/users/' + delegatorNickname
delegateActor = \
httpPrefix + '://' + delegatorDomainFull + '/users/' + nickname
newRoleJson = {
'type': 'Delegate',
'actor': actor,
'object': {
'type': 'Role',
'actor': delegateActor,
'object': roleStr,
'to': [toUrl],
'cc': [ccUrl]
},
'to': [toUrl],
'cc': [ccUrl]
}
handle = \
httpPrefix + '://' + delegatorDomainFull + '/@' + delegatorNickname
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
delegatorDomain, projectVersion, debug)
if not wfRequest:
if debug:
print('DEBUG: role webfinger failed for ' + handle)
return 1
if not isinstance(wfRequest, dict):
print('WARN: role 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,
avatarUrl, displayName) = getPersonBox(baseDir, session,
wfRequest, personCache,
projectVersion, httpPrefix,
delegatorNickname,
delegatorDomain, postToBox,
765672)
if not inboxUrl:
if debug:
print('DEBUG: role no ' + postToBox +
' was found for ' + handle)
return 3
if not fromPersonId:
if debug:
print('DEBUG: role no actor was found for ' + handle)
return 4
authHeader = createBasicAuthHeader(delegatorNickname, password)
headers = {
'host': delegatorDomain,
'Content-type': 'application/json',
'Authorization': authHeader
}
postResult = \
postJson(session, newRoleJson, [], inboxUrl, headers, 30, True)
if not postResult:
if debug:
print('DEBUG: POST role failed for c2s to ' + inboxUrl)
# return 5
if debug:
print('DEBUG: c2s POST role success')
return newRoleJson

104
tests.py
View File

@ -69,8 +69,9 @@ from person import setBio
from skills import setSkillLevel from skills import setSkillLevel
from skills import setSkillsFromDict from skills import setSkillsFromDict
from skills import getSkillsFromString from skills import getSkillsFromString
from roles import setRolesFromList
from roles import getRolesFromString
from roles import setRole from roles import setRole
from roles import outboxDelegate
from auth import constantTimeStringCheck from auth import constantTimeStringCheck
from auth import createBasicAuthHeader from auth import createBasicAuthHeader
from auth import authorizeBasic from auth import authorizeBasic
@ -454,7 +455,7 @@ def createServerAlice(path: str, domain: str, port: int,
deleteAllPosts(path, nickname, domain, 'inbox') deleteAllPosts(path, nickname, domain, 'inbox')
deleteAllPosts(path, nickname, domain, 'outbox') deleteAllPosts(path, nickname, domain, 'outbox')
assert setSkillLevel(path, nickname, domain, 'hacking', 90) assert setSkillLevel(path, nickname, domain, 'hacking', 90)
assert setRole(path, nickname, domain, 'someproject', 'guru') assert setRole(path, nickname, domain, 'guru')
if hasFollows: if hasFollows:
followPerson(path, nickname, domain, 'bob', bobAddress, followPerson(path, nickname, domain, 'bob', bobAddress,
federationList, False) federationList, False)
@ -558,8 +559,6 @@ def createServerBob(path: str, domain: str, port: int,
False, password) False, password)
deleteAllPosts(path, nickname, domain, 'inbox') deleteAllPosts(path, nickname, domain, 'inbox')
deleteAllPosts(path, nickname, domain, 'outbox') deleteAllPosts(path, nickname, domain, 'outbox')
assert setRole(path, nickname, domain, 'bandname', 'bass player')
assert setRole(path, nickname, domain, 'bandname', 'publicist')
if hasFollows: if hasFollows:
followPerson(path, nickname, domain, followPerson(path, nickname, domain,
'alice', aliceAddress, federationList, False) 'alice', aliceAddress, federationList, False)
@ -1411,80 +1410,6 @@ def testCreatePerson():
shutil.rmtree(baseDir) shutil.rmtree(baseDir)
def testDelegateRoles():
print('testDelegateRoles')
currDir = os.getcwd()
nickname = 'test382'
nicknameDelegated = 'test383'
domain = 'badgerdomain.com'
password = 'mypass'
port = 80
httpPrefix = 'https'
baseDir = currDir + '/.tests_delegaterole'
if os.path.isdir(baseDir):
shutil.rmtree(baseDir)
os.mkdir(baseDir)
os.chdir(baseDir)
privateKeyPem, publicKeyPem, person, wfEndpoint = \
createPerson(baseDir, nickname, domain, port,
httpPrefix, True, False, password)
privateKeyPem, publicKeyPem, person, wfEndpoint = \
createPerson(baseDir, nicknameDelegated, domain, port,
httpPrefix, True, False, 'insecure')
httpPrefix = 'http'
project = 'artechoke'
role = 'delegator'
actorDelegated = \
httpPrefix + '://' + domain + '/users/' + nicknameDelegated
newRoleJson = {
'type': 'Delegate',
'actor': httpPrefix + '://' + domain + '/users/' + nickname,
'object': {
'type': 'Role',
'actor': actorDelegated,
'object': project + ';' + role,
'to': [],
'cc': []
},
'to': [],
'cc': []
}
assert outboxDelegate(baseDir, nickname, newRoleJson, False)
# second time delegation has already happened so should return false
assert outboxDelegate(baseDir, nickname, newRoleJson, False) is False
assert '"delegator"' in open(baseDir + '/accounts/' + nickname +
'@' + domain + '.json').read()
assert '"delegator"' in open(baseDir + '/accounts/' + nicknameDelegated +
'@' + domain + '.json').read()
newRoleJson = {
'type': 'Delegate',
'actor': httpPrefix + '://' + domain + '/users/' + nicknameDelegated,
'object': {
'type': 'Role',
'actor': httpPrefix + '://' + domain + '/users/' + nickname,
'object': 'otherproject;otherrole',
'to': [],
'cc': []
},
'to': [],
'cc': []
}
# non-delegators cannot assign roles
assert outboxDelegate(baseDir, nicknameDelegated,
newRoleJson, False) is False
assert '"otherrole"' not in open(baseDir + '/accounts/' +
nickname + '@' + domain + '.json').read()
os.chdir(currDir)
shutil.rmtree(baseDir)
def testAuthentication(): def testAuthentication():
print('testAuthentication') print('testAuthentication')
currDir = os.getcwd() currDir = os.getcwd()
@ -3755,9 +3680,31 @@ def testSkills() -> None:
assert skillsDict['gardening'] == 70 assert skillsDict['gardening'] == 70
def testRoles() -> None:
print('testRoles')
actorJson = {
'affiliation': {
"@type": "OrganizationRole",
"roleName": "",
"affiliation": {
"@type": "WebSite",
"url": "https://testinstance.org"
},
"startDate": "date goes here"
}
}
testRolesList = ["admin", "moderator"]
setRolesFromList(actorJson, testRolesList)
assert actorJson['affiliation']['roleName']
rolesList = getRolesFromString(actorJson['affiliation']['roleName'])
assert 'admin' in rolesList
assert 'moderator' in rolesList
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testFunctions() testFunctions()
testRoles()
testSkills() testSkills()
testSpoofGeolocation() testSpoofGeolocation()
testRemovePostInteractions() testRemovePostInteractions()
@ -3812,5 +3759,4 @@ def runAllTests():
testNoOfFollowersOnDomain() testNoOfFollowersOnDomain()
testFollows() testFollows()
testGroupFollowers() testGroupFollowers()
testDelegateRoles()
print('Tests succeeded\n') print('Tests succeeded\n')

View File

@ -1027,14 +1027,12 @@ def _htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
def _htmlProfileRoles(translate: {}, nickname: str, domain: str, def _htmlProfileRoles(translate: {}, nickname: str, domain: str,
rolesJson: {}) -> str: rolesList: []) -> str:
"""Shows roles on the profile screen """Shows roles on the profile screen
""" """
profileStr = '' profileStr = ''
for project, rolesList in rolesJson.items():
profileStr += \ profileStr += \
'<div class="roles">\n<h2>' + project + \ '<div class="roles">\n<div class="roles-inner">\n'
'</h2>\n<div class="roles-inner">\n'
for role in rolesList: for role in rolesList:
if translate.get(role): if translate.get(role):
profileStr += '<h3>' + translate[role] + '</h3>\n' profileStr += '<h3>' + translate[role] + '</h3>\n'