diff --git a/daemon.py b/daemon.py index b54fd2b07..888c363b0 100644 --- a/daemon.py +++ b/daemon.py @@ -123,6 +123,7 @@ from blocking import removeGlobalBlock from blocking import isBlockedHashtag from blocking import isBlockedDomain from blocking import getDomainBlocklist +from roles import getActorRolesList from roles import setRole from roles import clearModeratorStatus from roles import clearEditorStatus @@ -200,6 +201,8 @@ from shares import addShare from shares import removeShare from shares import expireShares from categories import setHashtagCategory +from utils import getOccupationName +from utils import setOccupationName from utils import loadTranslationsFromFile from utils import getLocalNetworkAddresses from utils import decodedHost @@ -4581,21 +4584,18 @@ class PubServer(BaseHTTPRequestHandler): actorChanged = True # Other accounts (alsoKnownAs) - occupationName = "" - if actorJson.get('hasOccupation'): - if actorJson['hasOccupation'].get('name'): - occupationName = actorJson['hasOccupation']['name'] + occupationName = getOccupationName(actorJson) if fields.get('occupationName'): fields['occupationName'] = \ removeHtml(fields['occupationName']) if occupationName != \ fields['occupationName']: - actorJson['hasOccupation']['name'] = \ - fields['occupationName'] + setOccupationName(actorJson, + fields['occupationName']) actorChanged = True else: if occupationName: - actorJson['hasOccupation']['name'] = '' + setOccupationName(actorJson, '') actorChanged = True # Other accounts (alsoKnownAs) @@ -7373,7 +7373,7 @@ class PubServer(BaseHTTPRequestHandler): if not actorJson: return False - if actorJson.get('affiliation'): + if actorJson.get('hasOccupation'): if self._requestHTTP(): getPerson = \ personLookup(domain, path.replace('/roles', ''), @@ -7394,11 +7394,7 @@ class PubServer(BaseHTTPRequestHandler): if self.server.keyShortcuts.get(nickname): accessKeys = self.server.keyShortcuts[nickname] - rolesList = [] - if actorJson.get('affiliation'): - if isinstance(actorJson['affiliation']['roleName'], - list): - rolesList = actorJson['affiliation']['roleName'] + rolesList = getActorRolesList(actorJson) city = self._getSpoofedCity(baseDir, nickname, domain) msg = \ htmlProfile(self.server.rssIconAtTop, @@ -7435,12 +7431,7 @@ class PubServer(BaseHTTPRequestHandler): 'show roles') else: if self._fetchAuthenticated(): - rolesList = [] - if actorJson.get('affiliation'): - if isinstance(actorJson['affiliation']['roleName'], - list): - rolesList = actorJson['affiliation']['roleName'] - + rolesList = getActorRolesList(actorJson) msg = json.dumps(rolesList, ensure_ascii=False) msg = msg.encode('utf-8') diff --git a/person.py b/person.py index 747847787..ea7b228b2 100644 --- a/person.py +++ b/person.py @@ -34,6 +34,8 @@ from posts import createModeration from auth import storeBasicCredentials from auth import removePassword from roles import setRole +from roles import setRolesFromList +from roles import getActorRolesList from media import processMetaData from utils import getStatusNumber from utils import getFullDomain @@ -201,10 +203,12 @@ def getDefaultPersonContext() -> str: 'toot': 'http://joinmastodon.org/ns#', 'value': 'schema:value', 'hasOccupation': 'schema:hasOccupation', - 'affiliation': 'schema:affiliation', 'Occupation': 'schema:Occupation', - 'OrganizationRole': 'schema:OrganizationRole', - 'WebSite': 'schema:Project' + 'occupationalCategory': 'schema:occupationalCategory', + 'Role': 'schema:Role', + 'WebSite': 'schema:Project', + 'CategoryCode': 'schema:CategoryCode', + 'CategoryCodeSet': 'schema:CategoryCodeSet' } @@ -280,20 +284,13 @@ def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int, 'following': personId + '/following', 'tts': personId + '/speaker', 'shares': personId + '/shares', - 'hasOccupation': { - '@type': 'Occupation', - 'name': "", - 'skills': [] - }, - "affiliation": { - "@type": "OrganizationRole", - "roleName": [], - "affiliation": { - "@type": "WebSite", - "url": httpPrefix + '://' + domain - }, - "startDate": published - }, + 'hasOccupation': [ + { + '@type': 'Occupation', + 'name': "", + 'skills': [] + } + ], 'availability': None, 'icon': { 'mediaType': 'image/png', @@ -587,16 +584,13 @@ def personUpgradeActor(baseDir: str, personJson: {}, # if the older skills format is being used then switch # to the new one if not personJson.get('hasOccupation'): - personJson['hasOccupation'] = { - '@type': 'Occupation', - 'name': occupationName, - 'skills': [] - } - updateActor = True - - if isinstance(personJson['hasOccupation']['skills'], str): - skillsList = personJson['hasOccupation']['skills'].split(', ') - personJson['hasOccupation']['skills'] = skillsList + personJson['hasOccupation'] = [ + { + '@type': 'Occupation', + 'name': occupationName, + 'skills': [] + } + ] updateActor = True # remove the old skills format @@ -606,36 +600,29 @@ def personUpgradeActor(baseDir: str, personJson: {}, # if the older roles format is being used then switch # to the new one - if not personJson.get('affiliation'): - rolesList = [] - adminName = getConfigParam(baseDir, 'admin') - if personJson['id'].endswith('/users/' + adminName): - rolesList = ["admin", "moderator", "editor"] - statusNumber, published = getStatusNumber() - personJson['affiliation'] = { - "@type": "OrganizationRole", - "roleName": rolesList, - "affiliation": { - "@type": "WebSite", - "url": personJson['id'].split('/users/')[0] - }, - "startDate": published - } + if personJson.get('affiliation'): + del personJson['affiliation'] updateActor = True - if isinstance(personJson['affiliation']['roleName'], str): - rolesList = personJson['affiliation']['roleName'].split(', ') - personJson['affiliation']['roleName'] = rolesList + if not isinstance(personJson['hasOccupation'], list): + personJson['hasOccupation'] = [ + { + '@type': 'Occupation', + 'name': occupationName, + 'skills': [] + } + ] updateActor = True # if no roles are defined then ensure that the admin # roles are configured - if not personJson['affiliation']['roleName']: + rolesList = getActorRolesList(personJson) + if not rolesList: adminName = getConfigParam(baseDir, 'admin') if personJson['id'].endswith('/users/' + adminName): - personJson['affiliation']['roleName'] = \ - ["admin", "moderator", "editor"] - updateActor = True + rolesList = ["admin", "moderator", "editor"] + setRolesFromList(personJson, rolesList) + updateActor = True # remove the old roles format if personJson.get('roles'): diff --git a/roles.py b/roles.py index 3a59d21c3..3655c99e9 100644 --- a/roles.py +++ b/roles.py @@ -9,6 +9,7 @@ __status__ = "Production" import os from utils import loadJson from utils import saveJson +from utils import getStatusNumber def _clearRoleStatus(baseDir: str, role: str) -> None: @@ -30,12 +31,10 @@ def _clearRoleStatus(baseDir: str, role: str) -> None: actorJson = loadJson(filename) if not actorJson: continue - if not actorJson.get('affiliation'): - continue - rolesList = \ - getRolesFromList(actorJson['affiliation']['roleName']) + rolesList = getActorRolesList(actorJson) if role in rolesList: rolesList.remove(role) + setRolesFromList(actorJson, rolesList) saveJson(actorJson, filename) @@ -65,7 +64,8 @@ def clearModeratorStatus(baseDir: str) -> None: def _addRole(baseDir: str, nickname: str, domain: str, roleFilename: str) -> None: - """Adds a role nickname to the file + """Adds a role nickname to the file. + This is a file containing the nicknames of accounts having this role """ if ':' in domain: domain = domain.split(':')[0] @@ -94,7 +94,8 @@ def _addRole(baseDir: str, nickname: str, domain: str, def _removeRole(baseDir: str, nickname: str, roleFilename: str) -> None: - """Removes a role nickname from the file + """Removes a role nickname from the file. + This is a file containing the nicknames of accounts having this role """ roleFile = baseDir + '/accounts/' + roleFilename if not os.path.isfile(roleFile): @@ -108,24 +109,99 @@ def _removeRole(baseDir: str, nickname: str, roleFilename: str) -> None: f.write(roleNickname + '\n') +def _setActorRole(actorJson: {}, roleName: str) -> bool: + """Sets a role for an actor + """ + if not actorJson.get('hasOccupation'): + return False + if not isinstance(actorJson['hasOccupation'], list): + return False + + category = None + if 'admin' in roleName: + category = '15-1299.01' + elif 'moderator' in roleName: + category = '11-9199.02' + elif 'editor' in roleName: + category = '27-3041.00' + elif 'counselor' in roleName: + category = '23-1022.00' + if not category: + return False + + for index in range(len(actorJson['hasOccupation'])): + occupationItem = actorJson['hasOccupation'][index] + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if occupationItem['@type'] != 'Role': + continue + if occupationItem['hasOccupation']['name'] == roleName: + return True + statusNumber, published = getStatusNumber() + newRole = { + "@type": "Role", + "hasOccupation": { + "@type": "Occupation", + "name": roleName, + "occupationalCategory": { + "@type": "CategoryCode", + "inCodeSet": { + "@type": "CategoryCodeSet", + "name": "O*Net-SOC", + "dateModified": "2019", + "url": "https://www.onetonline.org/" + }, + "codeValue": category, + "url": "https://www.onetonline.org/link/summary/" + category + } + }, + "startDate": published + } + actorJson['hasOccupation'].append(newRole) + return True + + def setRolesFromList(actorJson: {}, rolesList: []) -> None: """Sets roles from a list """ - if actorJson.get('affiliation'): - actorJson['affiliation']['roleName'] = rolesList.copy() + # clear Roles from the occupation list + emptyRolesList = [] + for occupationItem in actorJson['hasOccupation']: + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if occupationItem['@type'] == 'Role': + continue + emptyRolesList.append(occupationItem) + actorJson['hasOccupation'] = emptyRolesList + + # create the new list + for roleName in rolesList: + _setActorRole(actorJson, roleName) -def getRolesFromList(rolesList: []) -> []: - """Returns a list of roles from a list +def getActorRolesList(actorJson: {}) -> []: + """Gets a list of role names from an actor """ - if isinstance(rolesList, list): - rolesList2 = rolesList - else: - rolesList2 = rolesList.split(',') - rolesResult = [] - for roleName in rolesList2: - rolesResult.append(roleName.strip().lower()) - return rolesResult + if not actorJson.get('hasOccupation'): + return [] + if not isinstance(actorJson['hasOccupation'], list): + return [] + rolesList = [] + for occupationItem in actorJson['hasOccupation']: + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if occupationItem['@type'] != 'Role': + continue + roleName = occupationItem['hasOccupation']['name'] + if roleName not in rolesList: + rolesList.append(roleName) + return rolesList def setRole(baseDir: str, nickname: str, domain: str, @@ -149,10 +225,9 @@ def setRole(baseDir: str, nickname: str, domain: str, actorJson = loadJson(actorFilename) if actorJson: - if not actorJson.get('affiliation'): + if not actorJson.get('hasOccupation'): return False - rolesList = \ - getRolesFromList(actorJson['affiliation']['roleName']) + rolesList = getActorRolesList(actorJson) actorChanged = False if role: # add the role @@ -174,3 +249,10 @@ def setRole(baseDir: str, nickname: str, domain: str, if actorChanged: saveJson(actorJson, actorFilename) return True + + +def actorHasRole(actorJson: {}, roleName: str) -> bool: + """Returns true if the given actor has the given role + """ + rolesList = getActorRolesList(actorJson) + return roleName in rolesList diff --git a/skills.py b/skills.py index 8105cc5c0..c97c678fd 100644 --- a/skills.py +++ b/skills.py @@ -15,16 +15,18 @@ from utils import getFullDomain from utils import getNicknameFromActor from utils import getDomainFromActor from utils import loadJson +from utils import getOccupationSkills +from utils import setOccupationSkillsList def setSkillsFromDict(actorJson: {}, skillsDict: {}) -> []: - """Converts a dict containing skills to a string + """Converts a dict containing skills to a list Returns the string version of the dictionary """ skillsList = [] for name, value in skillsDict.items(): skillsList.append(name + ':' + str(value)) - actorJson['hasOccupation']['skills'] = skillsList + setOccupationSkillsList(actorJson, skillsList) return skillsList @@ -47,21 +49,11 @@ def getSkillsFromList(skillsList: []) -> {}: return skillsDict -def actorHasSkill(actorJson: {}, skillName: str) -> bool: - """Returns true if the actor has the given skill - """ - skillsDict = \ - getSkillsFromList(actorJson['hasOccupation']['skills']) - if not skillsDict: - return False - return skillsDict.get(skillName.lower()) - - def actorSkillValue(actorJson: {}, skillName: str) -> int: """Returns The skill level from an actor """ - skillsDict = \ - getSkillsFromList(actorJson['hasOccupation']['skills']) + ocSkillsList = getOccupationSkills(actorJson) + skillsDict = getSkillsFromList(ocSkillsList) if not skillsDict: return 0 skillName = skillName.lower() @@ -74,13 +66,8 @@ def noOfActorSkills(actorJson: {}) -> int: """Returns the number of skills that an actor has """ if actorJson.get('hasOccupation'): - skillsStr = actorJson['hasOccupation']['skills'] - if isinstance(skillsStr, list): - skillsList = skillsStr - else: - skillsList = skillsStr.split(',') - if skillsList: - return len(skillsList) + skillsList = getOccupationSkills(actorJson) + return len(skillsList) return 0 @@ -95,13 +82,15 @@ def setActorSkillLevel(actorJson: {}, if not actorJson: return True if not actorJson.get('hasOccupation'): - actorJson['hasOccupation'] = { - '@type': 'Occupation', - 'name': '', - 'skills': '' - } - skillsDict = \ - getSkillsFromList(actorJson['hasOccupation']['skills']) + actorJson['hasOccupation'] = [ + { + '@type': 'Occupation', + 'name': '', + 'skills': [] + } + ] + ocSkillsList = getOccupationSkills(actorJson) + skillsDict = getSkillsFromList(ocSkillsList) if skillLevelPercent > 0: skillsDict[skill] = skillLevelPercent else: @@ -138,7 +127,8 @@ def getSkills(baseDir: str, nickname: str, domain: str) -> []: if actorJson: if not actorJson.get('hasOccupation'): return None - return getSkillsFromList(actorJson['hasOccupation']['skills']) + ocSkillsList = getOccupationSkills(actorJson) + return getSkillsFromList(ocSkillsList) return None @@ -258,3 +248,13 @@ def sendSkillViaServer(baseDir: str, session, nickname: str, password: str, print('DEBUG: c2s POST skill success') return newSkillJson + + +def actorHasSkill(actorJson: {}, skillName: str) -> bool: + """Returns true if the given actor has the given skill + """ + ocSkillsList = getOccupationSkills(actorJson) + for skillStr in ocSkillsList: + if skillName + ':' in skillStr: + return True + return False diff --git a/tests.py b/tests.py index 4a3d84fe3..5b22e7860 100644 --- a/tests.py +++ b/tests.py @@ -67,11 +67,12 @@ from person import setDisplayNickname from person import setBio # from person import generateRSAKey from skills import setSkillLevel +from skills import actorSkillValue from skills import setSkillsFromDict -from skills import getSkillsFromList +from skills import actorHasSkill from roles import setRolesFromList -from roles import getRolesFromList from roles import setRole +from roles import actorHasRole from auth import constantTimeStringCheck from auth import createBasicAuthHeader from auth import authorizeBasic @@ -3661,44 +3662,42 @@ def testSpoofGeolocation() -> None: def testSkills() -> None: print('testSkills') actorJson = { - 'hasOccupation': { - '@type': 'Occupation', - 'name': "", - 'skills': [] - } + 'hasOccupation': [ + { + '@type': 'Occupation', + 'name': "Sysop", + 'skills': [] + } + ] } skillsDict = { 'bakery': 40, 'gardening': 70 } setSkillsFromDict(actorJson, skillsDict) - assert actorJson['hasOccupation']['skills'] - skillsDict = getSkillsFromList(actorJson['hasOccupation']['skills']) - assert skillsDict.get('bakery') - assert skillsDict.get('gardening') - assert skillsDict['bakery'] == 40 - assert skillsDict['gardening'] == 70 + assert actorHasSkill(actorJson, 'bakery') + assert actorHasSkill(actorJson, 'gardening') + assert actorSkillValue(actorJson, 'bakery') == 40 + assert actorSkillValue(actorJson, 'gardening') == 70 def testRoles() -> None: print('testRoles') actorJson = { - 'affiliation': { - "@type": "OrganizationRole", - "roleName": [], - "affiliation": { - "@type": "WebSite", - "url": "https://testinstance.org" - }, - "startDate": "date goes here" - } + 'hasOccupation': [ + { + '@type': 'Occupation', + 'name': "Sysop", + 'skills': [] + } + ] } testRolesList = ["admin", "moderator"] setRolesFromList(actorJson, testRolesList) - assert actorJson['affiliation']['roleName'] - rolesList = getRolesFromList(actorJson['affiliation']['roleName']) - assert 'admin' in rolesList - assert 'moderator' in rolesList + assert actorHasRole(actorJson, "admin") + assert actorHasRole(actorJson, "moderator") + assert not actorHasRole(actorJson, "editor") + assert not actorHasRole(actorJson, "counselor") def runAllTests(): diff --git a/utils.py b/utils.py index 5b4ef6bd9..c478158d7 100644 --- a/utils.py +++ b/utils.py @@ -2279,3 +2279,89 @@ def dmAllowedFromDomain(baseDir: str, if sendingActorDomain + '\n' in open(dmAllowedInstancesFilename).read(): return True return False + + +def getOccupationSkills(actorJson: {}) -> []: + """Returns the list of skills for an actor + """ + if 'hasOccupation' not in actorJson: + return [] + if not isinstance(actorJson['hasOccupation'], list): + return [] + for occupationItem in actorJson['hasOccupation']: + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if not occupationItem['@type'] == 'Occupation': + continue + if not occupationItem.get('skills'): + continue + if isinstance(occupationItem['skills'], list): + return occupationItem['skills'] + elif isinstance(occupationItem['skills'], str): + return [occupationItem['skills']] + break + return [] + + +def getOccupationName(actorJson: {}) -> str: + """Returns the occupation name an actor + """ + if not actorJson.get('hasOccupation'): + return "" + if not isinstance(actorJson['hasOccupation'], list): + return "" + for occupationItem in actorJson['hasOccupation']: + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if occupationItem['@type'] != 'Occupation': + continue + if not occupationItem.get('name'): + continue + if isinstance(occupationItem['name'], str): + return occupationItem['name'] + break + return "" + + +def setOccupationName(actorJson: {}, name: str) -> bool: + """Sets the occupation name of an actor + """ + if not actorJson.get('hasOccupation'): + return False + if not isinstance(actorJson['hasOccupation'], list): + return False + for index in range(len(actorJson['hasOccupation'])): + occupationItem = actorJson['hasOccupation'][index] + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if occupationItem['@type'] != 'Occupation': + continue + occupationItem['name'] = name + return True + return False + + +def setOccupationSkillsList(actorJson: {}, skillsList: []) -> bool: + """Sets the occupation skills for an actor + """ + if 'hasOccupation' not in actorJson: + return False + if not isinstance(actorJson['hasOccupation'], list): + return False + for index in range(len(actorJson['hasOccupation'])): + occupationItem = actorJson['hasOccupation'][index] + if not isinstance(occupationItem, dict): + continue + if not occupationItem.get('@type'): + continue + if occupationItem['@type'] != 'Occupation': + continue + occupationItem['skills'] = skillsList + return True + return False diff --git a/webapp_profile.py b/webapp_profile.py index 5a8baffa4..5ec33c1c9 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -8,6 +8,7 @@ __status__ = "Production" import os from pprint import pprint +from utils import getOccupationName from utils import getLockedAccount from utils import hasUsersPath from utils import getFullDomain @@ -741,8 +742,7 @@ def htmlProfile(rssIconAtTop: bool, joinedDate = profileJson['published'] occupationName = None if profileJson.get('hasOccupation'): - if profileJson['hasOccupation'].get('name'): - occupationName = profileJson['hasOccupation']['name'] + occupationName = getOccupationName(profileJson) avatarUrl = profileJson['icon']['url'] @@ -1602,8 +1602,7 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, occupationName = '' if actorJson.get('hasOccupation'): - if actorJson['hasOccupation'].get('name'): - occupationName = actorJson['hasOccupation']['name'] + occupationName = getOccupationName(actorJson) editProfileForm += '
\n'