Remove role delegation

The keeps the handling of roles very simple
main
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.
## 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

View File

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

View File

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

View File

@ -74,7 +74,6 @@ from media import getAttachmentMediaType
from delete import sendDeleteViaServer
from like import sendLikeViaServer
from like import sendUndoLikeViaServer
from roles import sendRoleViaServer
from skills import sendSkillViaServer
from availability import setAvailability
from availability import sendAvailabilityViaServer
@ -495,9 +494,6 @@ parser.add_argument('--maxEmoji', '--maxemoji', dest='maxEmoji',
help='Maximum number of emoji within a post')
parser.add_argument('--role', dest='role', type=str, default=None,
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,
help='Set a skill for a person')
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')
parser.add_argument('--unmute', dest='unmute', type=str, default=None,
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,
help='Adds a word or phrase which if present will ' +
'cause a message to be ignored')
@ -1987,24 +1978,6 @@ if args.backgroundImage:
print('Background image was not added for ' + args.nickname)
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 not nickname:
print('Specify a nickname with the --nickname option')
@ -2218,86 +2191,6 @@ if args.unmute:
time.sleep(1)
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 not nickname:
print('Specify a nickname with the --nickname option')
@ -2388,9 +2281,7 @@ if args.testdata:
True, False, 'likewhateveryouwantscoob')
setSkillLevel(baseDir, nickname, domain, 'testing', 60)
setSkillLevel(baseDir, nickname, domain, 'typing', 50)
setRole(baseDir, nickname, domain, 'instance', 'admin')
setRole(baseDir, nickname, domain, 'epicyon', 'hacker')
setRole(baseDir, nickname, domain, 'someproject', 'assistant')
setRole(baseDir, nickname, domain, 'admin')
setAvailability(baseDir, nickname, domain, 'busy')
addShare(baseDir,

View File

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

View File

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

281
roles.py
View File

@ -37,10 +37,13 @@ def _clearRoleStatus(baseDir: str, role: str) -> None:
actorJson = loadJson(filename)
if not actorJson:
continue
if actorJson['roles'].get('instance'):
if role in actorJson['roles']['instance']:
actorJson['roles']['instance'].remove(role)
saveJson(actorJson, filename)
if not actorJson.get('affiliation'):
continue
rolesList = \
getRolesFromString(actorJson['affiliation']['roleName'])
if role in rolesList:
rolesList.remove(role)
saveJson(actorJson, filename)
def clearEditorStatus(baseDir: str) -> None:
@ -112,13 +115,35 @@ def _removeRole(baseDir: str, nickname: str, roleFilename: str) -> None:
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,
project: str, role: str) -> bool:
"""Set a person's role within a project
role: str) -> bool:
"""Set a person's role
Setting the role to an empty string or None will remove it
"""
# avoid giant strings
if len(role) > 128 or len(project) > 128:
if len(role) > 128:
return False
actorFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '.json'
@ -133,230 +158,28 @@ def setRole(baseDir: str, nickname: str, domain: str,
actorJson = loadJson(actorFilename)
if actorJson:
if not actorJson.get('affiliation'):
return False
rolesList = \
getRolesFromString(actorJson['affiliation']['roleName'])
actorChanged = False
if role:
# add the role
if project == 'instance':
if roleFiles.get(role):
_addRole(baseDir, nickname, domain, roleFiles[role])
if actorJson['roles'].get(project):
if role not in actorJson['roles'][project]:
actorJson['roles'][project].append(role)
else:
actorJson['roles'][project] = [role]
if roleFiles.get(role):
_addRole(baseDir, nickname, domain, roleFiles[role])
if role not in rolesList:
rolesList.append(role)
rolesList.sort()
setRolesFromList(actorJson, rolesList)
actorChanged = True
else:
# remove the role
if project == 'instance':
if roleFiles.get(role):
_removeRole(baseDir, nickname, roleFiles[role])
if actorJson['roles'].get(project):
actorJson['roles'][project].remove(role)
# if the project contains no roles then remove it
if len(actorJson['roles'][project]) == 0:
del actorJson['roles'][project]
saveJson(actorJson, actorFilename)
if roleFiles.get(role):
_removeRole(baseDir, nickname, roleFiles[role])
if role in rolesList:
rolesList.remove(role)
setRolesFromList(actorJson, rolesList)
actorChanged = True
if actorChanged:
saveJson(actorJson, actorFilename)
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 setSkillsFromDict
from skills import getSkillsFromString
from roles import setRolesFromList
from roles import getRolesFromString
from roles import setRole
from roles import outboxDelegate
from auth import constantTimeStringCheck
from auth import createBasicAuthHeader
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, 'outbox')
assert setSkillLevel(path, nickname, domain, 'hacking', 90)
assert setRole(path, nickname, domain, 'someproject', 'guru')
assert setRole(path, nickname, domain, 'guru')
if hasFollows:
followPerson(path, nickname, domain, 'bob', bobAddress,
federationList, False)
@ -558,8 +559,6 @@ def createServerBob(path: str, domain: str, port: int,
False, password)
deleteAllPosts(path, nickname, domain, 'inbox')
deleteAllPosts(path, nickname, domain, 'outbox')
assert setRole(path, nickname, domain, 'bandname', 'bass player')
assert setRole(path, nickname, domain, 'bandname', 'publicist')
if hasFollows:
followPerson(path, nickname, domain,
'alice', aliceAddress, federationList, False)
@ -1411,80 +1410,6 @@ def testCreatePerson():
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():
print('testAuthentication')
currDir = os.getcwd()
@ -3755,9 +3680,31 @@ def testSkills() -> None:
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():
print('Running tests...')
testFunctions()
testRoles()
testSkills()
testSpoofGeolocation()
testRemovePostInteractions()
@ -3812,5 +3759,4 @@ def runAllTests():
testNoOfFollowersOnDomain()
testFollows()
testGroupFollowers()
testDelegateRoles()
print('Tests succeeded\n')

View File

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