epicyon/roles.py

358 lines
12 KiB
Python
Raw Normal View History

2020-04-04 10:28:58 +00:00
__filename__ = "roles.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2023-01-21 23:03:30 +00:00
__version__ = "1.4.0"
2020-04-04 10:28:58 +00:00
__maintainer__ = "Bob Mottram"
2021-09-10 16:14:50 +00:00
__email__ = "bob@libreserver.org"
2020-04-04 10:28:58 +00:00
__status__ = "Production"
2021-06-26 11:16:41 +00:00
__module_group__ = "Profile Metadata"
2020-04-04 10:28:58 +00:00
2019-07-18 15:09:23 +00:00
import os
2021-12-26 15:13:34 +00:00
from utils import load_json
2021-12-26 14:47:21 +00:00
from utils import save_json
2021-12-27 17:42:35 +00:00
from utils import get_status_number
2021-12-26 18:17:37 +00:00
from utils import remove_domain_port
2021-12-26 12:02:29 +00:00
from utils import acct_dir
2022-06-10 11:43:33 +00:00
from utils import text_in_file
2022-09-02 10:29:42 +00:00
from utils import get_config_param
2019-07-18 15:09:23 +00:00
2020-04-04 10:28:58 +00:00
2021-12-29 21:55:09 +00:00
def _clear_role_status(base_dir: str, role: str) -> None:
2021-03-08 21:07:28 +00:00
"""Removes role status from all accounts
2020-10-11 19:42:21 +00:00
This could be slow if there are many users, but only happens
2021-03-08 21:07:28 +00:00
rarely when roles are appointed or removed
2020-10-11 19:42:21 +00:00
"""
2021-12-25 16:17:53 +00:00
directory = os.fsencode(base_dir + '/accounts/')
2021-12-31 10:49:09 +00:00
for fname in os.scandir(directory):
filename = os.fsdecode(fname.name)
2020-11-24 14:06:42 +00:00
if '@' not in filename:
continue
if not filename.endswith(".json"):
continue
2021-12-25 16:17:53 +00:00
filename = os.path.join(base_dir + '/accounts/', filename)
2022-06-10 11:43:33 +00:00
if not text_in_file('"' + role + '"', filename):
2020-11-24 14:06:42 +00:00
continue
2021-12-26 15:13:34 +00:00
actor_json = load_json(filename)
2021-12-26 10:29:52 +00:00
if not actor_json:
2020-11-24 14:06:42 +00:00
continue
2021-12-31 10:49:09 +00:00
roles_list = get_actor_roles_list(actor_json)
if role in roles_list:
roles_list.remove(role)
2022-09-02 18:06:13 +00:00
actor_roles_from_list(actor_json, roles_list)
2021-12-26 14:47:21 +00:00
save_json(actor_json, filename)
2020-10-11 19:42:21 +00:00
2021-12-29 21:55:09 +00:00
def _add_role(base_dir: str, nickname: str, domain: str,
2021-12-31 10:49:09 +00:00
role_filename: str) -> None:
2021-05-16 15:10:39 +00:00
"""Adds a role nickname to the file.
This is a file containing the nicknames of accounts having this role
2019-08-11 11:25:27 +00:00
"""
2021-12-26 18:17:37 +00:00
domain = remove_domain_port(domain)
2021-12-31 10:49:09 +00:00
role_file = base_dir + '/accounts/' + role_filename
if os.path.isfile(role_file):
2019-08-11 11:25:27 +00:00
# is this nickname already in the file?
2021-12-31 10:49:09 +00:00
lines = []
try:
2022-06-09 14:46:30 +00:00
with open(role_file, 'r', encoding='utf-8') as fp_role:
2021-12-31 10:49:09 +00:00
lines = fp_role.readlines()
except OSError:
print('EX: _add_role, failed to read roles file ' + role_file)
for role_nickname in lines:
role_nickname = role_nickname.strip('\n').strip('\r')
if role_nickname == nickname:
2019-08-11 11:25:27 +00:00
return
lines.append(nickname)
2021-12-31 10:49:09 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(role_file, 'w+', encoding='utf-8') as fp_role:
2021-12-31 10:49:09 +00:00
for role_nickname in lines:
role_nickname = role_nickname.strip('\n').strip('\r')
if len(role_nickname) < 2:
continue
if os.path.isdir(base_dir + '/accounts/' +
role_nickname + '@' + domain):
fp_role.write(role_nickname + '\n')
except OSError:
print('EX: _add_role, failed to write roles file1 ' + role_file)
else:
2021-12-31 10:49:09 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(role_file, 'w+', encoding='utf-8') as fp_role:
2021-12-31 10:49:09 +00:00
account_dir = acct_dir(base_dir, nickname, domain)
if os.path.isdir(account_dir):
fp_role.write(nickname + '\n')
except OSError:
print('EX: _add_role, failed to write roles file2 ' + role_file)
2020-03-22 21:16:02 +00:00
2020-04-04 10:28:58 +00:00
2021-12-31 10:49:09 +00:00
def _remove_role(base_dir: str, nickname: str, role_filename: str) -> None:
2021-05-16 15:10:39 +00:00
"""Removes a role nickname from the file.
This is a file containing the nicknames of accounts having this role
2019-08-11 11:25:27 +00:00
"""
2021-12-31 10:49:09 +00:00
role_file = base_dir + '/accounts/' + role_filename
if not os.path.isfile(role_file):
2019-08-11 11:25:27 +00:00
return
2021-12-31 10:49:09 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(role_file, 'r', encoding='utf-8') as fp_role:
2021-12-31 10:49:09 +00:00
lines = fp_role.readlines()
except OSError:
print('EX: _remove_role, failed to read roles file ' + role_file)
try:
2022-06-09 14:46:30 +00:00
with open(role_file, 'w+', encoding='utf-8') as fp_role:
2021-12-31 10:49:09 +00:00
for role_nickname in lines:
role_nickname = role_nickname.strip('\n').strip('\r')
if len(role_nickname) > 1 and role_nickname != nickname:
fp_role.write(role_nickname + '\n')
except OSError:
print('EX: _remove_role, failed to regenerate roles file ' + role_file)
2021-03-08 21:07:28 +00:00
2021-12-31 10:49:09 +00:00
def _set_actor_role(actor_json: {}, role_name: str) -> bool:
2021-05-16 15:10:39 +00:00
"""Sets a role for an actor
"""
2021-12-26 10:29:52 +00:00
if not actor_json.get('hasOccupation'):
2021-05-16 15:10:39 +00:00
return False
2021-12-26 10:29:52 +00:00
if not isinstance(actor_json['hasOccupation'], list):
2021-05-16 15:10:39 +00:00
return False
2021-07-04 17:14:18 +00:00
# occupation category from www.onetonline.org
2021-05-16 15:10:39 +00:00
category = None
2021-12-31 10:49:09 +00:00
if 'admin' in role_name:
2021-05-16 15:10:39 +00:00
category = '15-1299.01'
2021-12-31 10:49:09 +00:00
elif 'moderator' in role_name:
2021-05-16 15:10:39 +00:00
category = '11-9199.02'
2021-12-31 10:49:09 +00:00
elif 'editor' in role_name:
2021-05-16 15:10:39 +00:00
category = '27-3041.00'
2021-12-31 10:49:09 +00:00
elif 'counselor' in role_name:
2021-05-16 15:10:39 +00:00
category = '23-1022.00'
2021-12-31 10:49:09 +00:00
elif 'artist' in role_name:
2021-05-17 16:13:56 +00:00
category = '27-1024.00'
2021-05-16 15:10:39 +00:00
if not category:
return False
2022-01-08 10:58:54 +00:00
for index, _ in enumerate(actor_json['hasOccupation']):
2021-12-31 10:49:09 +00:00
occupation_item = actor_json['hasOccupation'][index]
if not isinstance(occupation_item, dict):
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if not occupation_item.get('@type'):
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if occupation_item['@type'] != 'Role':
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if occupation_item['hasOccupation']['name'] == role_name:
2021-05-16 15:10:39 +00:00
return True
2021-12-31 10:49:09 +00:00
_, published = get_status_number()
new_role = {
2021-05-16 15:10:39 +00:00
"@type": "Role",
"hasOccupation": {
"@type": "Occupation",
2021-12-31 10:49:09 +00:00
"name": role_name,
2021-05-17 11:33:59 +00:00
"description": "Fediverse instance role",
2021-05-17 10:04:26 +00:00
"occupationLocation": {
2021-05-17 10:27:14 +00:00
"@type": "City",
"url": "Fediverse"
2021-05-17 09:28:15 +00:00
},
2021-05-16 15:10:39 +00:00
"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
}
2021-12-31 10:49:09 +00:00
actor_json['hasOccupation'].append(new_role)
2021-05-16 15:10:39 +00:00
return True
2022-09-02 18:06:13 +00:00
def actor_roles_from_list(actor_json: {}, roles_list: []) -> None:
"""Sets roles from a list
"""
2021-05-16 15:10:39 +00:00
# clear Roles from the occupation list
2021-12-31 10:49:09 +00:00
empty_roles_list = []
for occupation_item in actor_json['hasOccupation']:
if not isinstance(occupation_item, dict):
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if not occupation_item.get('@type'):
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if occupation_item['@type'] == 'Role':
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
empty_roles_list.append(occupation_item)
actor_json['hasOccupation'] = empty_roles_list
2021-05-16 15:10:39 +00:00
# create the new list
2021-12-31 10:49:09 +00:00
for role_name in roles_list:
_set_actor_role(actor_json, role_name)
2021-12-28 22:22:09 +00:00
def get_actor_roles_list(actor_json: {}) -> []:
2021-05-16 15:10:39 +00:00
"""Gets a list of role names from an actor
"""
2021-12-26 10:29:52 +00:00
if not actor_json.get('hasOccupation'):
2021-05-16 15:10:39 +00:00
return []
2021-12-26 10:29:52 +00:00
if not isinstance(actor_json['hasOccupation'], list):
2021-05-16 15:10:39 +00:00
return []
2021-12-31 10:49:09 +00:00
roles_list = []
for occupation_item in actor_json['hasOccupation']:
if not isinstance(occupation_item, dict):
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if not occupation_item.get('@type'):
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
if occupation_item['@type'] != 'Role':
2021-05-16 15:10:39 +00:00
continue
2021-12-31 10:49:09 +00:00
role_name = occupation_item['hasOccupation']['name']
if role_name not in roles_list:
roles_list.append(role_name)
return roles_list
2021-12-28 22:22:09 +00:00
def set_role(base_dir: str, nickname: str, domain: str,
role: str) -> bool:
"""Set a person's role
2019-07-18 15:09:23 +00:00
Setting the role to an empty string or None will remove it
"""
# avoid giant strings
if len(role) > 128:
2019-07-18 15:09:23 +00:00
return False
2021-12-31 10:49:09 +00:00
actor_filename = acct_dir(base_dir, nickname, domain) + '.json'
if not os.path.isfile(actor_filename):
2019-07-18 15:09:23 +00:00
return False
2019-09-30 22:39:02 +00:00
2021-12-31 10:49:09 +00:00
role_files = {
2021-03-08 21:18:20 +00:00
"moderator": "moderators.txt",
2021-03-08 23:03:02 +00:00
"editor": "editors.txt",
2021-05-17 16:13:56 +00:00
"counselor": "counselors.txt",
"artist": "artists.txt"
2021-03-08 21:18:20 +00:00
}
2021-12-31 10:49:09 +00:00
actor_json = load_json(actor_filename)
2021-12-26 10:29:52 +00:00
if actor_json:
if not actor_json.get('hasOccupation'):
return False
2021-12-31 10:49:09 +00:00
roles_list = get_actor_roles_list(actor_json)
actor_changed = False
2019-07-18 15:09:23 +00:00
if role:
2019-08-11 11:25:27 +00:00
# add the role
2021-12-31 10:49:09 +00:00
if role_files.get(role):
_add_role(base_dir, nickname, domain, role_files[role])
if role not in roles_list:
roles_list.append(role)
roles_list.sort()
2022-09-02 18:06:13 +00:00
actor_roles_from_list(actor_json, roles_list)
2021-12-31 10:49:09 +00:00
actor_changed = True
2019-07-18 15:09:23 +00:00
else:
2019-08-11 11:25:27 +00:00
# remove the role
2021-12-31 10:49:09 +00:00
if role_files.get(role):
_remove_role(base_dir, nickname, role_files[role])
if role in roles_list:
roles_list.remove(role)
2022-09-02 18:06:13 +00:00
actor_roles_from_list(actor_json, roles_list)
2021-12-31 10:49:09 +00:00
actor_changed = True
if actor_changed:
save_json(actor_json, actor_filename)
2019-07-18 16:21:26 +00:00
return True
2021-05-16 15:10:39 +00:00
2021-12-31 10:49:09 +00:00
def actor_has_role(actor_json: {}, role_name: str) -> bool:
2021-05-16 15:10:39 +00:00
"""Returns true if the given actor has the given role
"""
2021-12-31 10:49:09 +00:00
roles_list = get_actor_roles_list(actor_json)
return role_name in roles_list
2022-09-02 10:29:42 +00:00
def is_devops(base_dir: str, nickname: str) -> bool:
"""Returns true if the given nickname has the devops role
"""
devops_file = base_dir + '/accounts/devops.txt'
if not os.path.isfile(devops_file):
admin_name = get_config_param(base_dir, 'admin')
if not admin_name:
return False
if admin_name == nickname:
return True
return False
with open(devops_file, 'r', encoding='utf-8') as fp_mod:
lines = fp_mod.readlines()
if len(lines) == 0:
# if there is nothing in the file
admin_name = get_config_param(base_dir, 'admin')
if not admin_name:
return False
if admin_name == nickname:
return True
for devops in lines:
devops = devops.strip('\n').strip('\r')
if devops == nickname:
return True
return False
2022-09-02 17:52:09 +00:00
def set_roles_from_list(base_dir: str, domain: str, admin_nickname: str,
list_name: str, role_name: str, fields: [], path: str,
list_filename: str) -> None:
"""Sets the roles from a list returned from the edit profile screen under
role assignments
"""
# check for admin user
if not path.startswith('/users/' + admin_nickname + '/'):
return
roles_filename = base_dir + '/accounts/' + list_filename
2022-09-02 18:44:29 +00:00
if not fields.get(list_name):
if os.path.isfile(roles_filename):
_clear_role_status(base_dir, role_name)
try:
os.remove(roles_filename)
except OSError:
print('EX: failed to remove roles file ' + roles_filename)
return
2022-09-02 17:52:09 +00:00
_clear_role_status(base_dir, role_name)
if ',' in fields[list_name]:
# if the list was given as comma separated
roles_list = fields[list_name].split(',')
try:
with open(roles_filename, 'w+',
encoding='utf-8') as rolesfile:
for roles_nick in roles_list:
roles_nick = roles_nick.strip()
roles_dir = acct_dir(base_dir, roles_nick, domain)
if os.path.isdir(roles_dir):
rolesfile.write(roles_nick + '\n')
except OSError as ex:
print('EX: unable to write ' + list_name + ' ' +
roles_filename + ' ' + str(ex))
for roles_nick in roles_list:
roles_nick = roles_nick.strip()
roles_dir = acct_dir(base_dir, roles_nick, domain)
if os.path.isdir(roles_dir):
set_role(base_dir, roles_nick, domain, role_name)
else:
# nicknames on separate lines
roles_list = fields[list_name].split('\n')
try:
with open(roles_filename, 'w+',
encoding='utf-8') as rolesfile:
for roles_nick in roles_list:
roles_nick = roles_nick.strip()
roles_dir = acct_dir(base_dir, roles_nick, domain)
if os.path.isdir(roles_dir):
rolesfile.write(roles_nick + '\n')
except OSError as ex:
print('EX: unable to write ' + list_name + ' ' +
roles_filename + ' ' + str(ex))
for roles_nick in roles_list:
roles_nick = roles_nick.strip()
roles_dir = acct_dir(base_dir, roles_nick, domain)
if os.path.isdir(roles_dir):
set_role(base_dir, roles_nick, domain, role_name)