matrix-bot/src/main.py

284 lines
10 KiB
Python
Raw Normal View History

2022-09-29 18:19:35 +00:00
from email.policy import default
import sys
import coloredlogs
import logging
import os
import time
import asyncio
from tokenize import group
2022-09-28 18:04:21 +00:00
from urllib import response
from ldapHelper import LdapHelper
from matrixHelper import MatrixHelper
2022-09-29 18:19:35 +00:00
coloredlogs.install(
level='INFO', fmt='%(asctime)s - [%(levelname)s] %(message)s')
2022-09-28 18:04:21 +00:00
class MlmMatrixBot:
def __init__(self) -> None:
self._config = self._readConfig()
2022-09-29 18:19:35 +00:00
self._matrixUsername = f"@{self._config['MATRIX_USERNAME']}:{self._config['MATRIX_DOMAIN']}"
2022-09-28 18:04:21 +00:00
2022-09-29 18:19:35 +00:00
self._ldapHelper = LdapHelper(self._config['LDAP_URI'], self._config['LDAP_BIND_DN'],
self._config['LDAP_BIND_DN_PASSWORD'], self._config['LDAP_BASE_DN'])
self._matrixHelper = MatrixHelper(
self._config['MATRIX_SERVER'], self._matrixUsername, self._config['MATRIX_SPACE_ID'], self._matrixUsername)
2022-09-28 18:04:21 +00:00
2022-09-29 18:19:35 +00:00
async def init(self):
2023-09-10 10:17:13 +00:00
logging.info("==> Initializing <==")
logging.info(" \t* Connecting to ldap")
2022-09-28 18:04:21 +00:00
if not self._ldapHelper.bind():
return False
2023-09-10 10:17:13 +00:00
logging.info(" \t* Connecting to matrix")
2022-09-28 18:04:21 +00:00
if not await self._matrixHelper.login(self._config['MATRIX_PASSWORD']):
return False
2023-09-10 10:17:13 +00:00
logging.info("==> Initialized <==")
2022-09-29 18:19:35 +00:00
return True
async def run(self):
2022-09-28 18:04:21 +00:00
2022-09-29 20:13:09 +00:00
self._current_log_step = 0
2022-09-29 18:19:35 +00:00
logging.info("==> Sync started <==")
2022-09-29 19:31:07 +00:00
2022-09-29 18:19:35 +00:00
await self._create_default_rooms()
current_users, all_projects = await self._load_users_and_groups()
2022-09-28 18:04:21 +00:00
2022-09-29 18:19:35 +00:00
await self._create_project_rooms(all_projects)
2022-09-29 20:18:59 +00:00
lab_rooms = await self._create_lab_rooms()
2022-09-29 20:13:09 +00:00
self._log_step("Loading current rooms")
2022-09-29 18:19:35 +00:00
matrix_rooms = await self._get_managed_rooms(with_power_levels=True)
matrix_room_ids = list(map(lambda room: room['id'], matrix_rooms))
room_name_id_map = {}
2022-09-29 19:31:07 +00:00
room_id_map = {}
2022-09-29 18:19:35 +00:00
for room in matrix_rooms:
room_name_id_map[room['name']] = room['id']
room_id_map[room['id']] = room
2022-09-29 20:13:09 +00:00
self._log_step("Syncing user memberships")
2022-09-29 18:19:35 +00:00
for user in current_users:
if not "mlm" in user['groups']:
continue
# default groups and space
2022-09-29 19:31:07 +00:00
rooms_to_join = self._get_default_rooms(
) + [self._config["MATRIX_SPACE_ID"]]
2022-09-29 18:19:35 +00:00
# projects
2022-09-29 19:31:07 +00:00
rooms_to_join += list(map(lambda group: group.replace('p_', ''),
filter(lambda project: project.startswith("p_"), user['groups'])))
2022-09-29 18:19:35 +00:00
# resolve names to ids
rooms_to_join = list(map(
2022-09-29 19:31:07 +00:00
lambda room: room if room.startswith(
"!") else room_name_id_map[room],
2022-09-29 18:19:35 +00:00
rooms_to_join))
2022-09-29 19:31:07 +00:00
2022-09-29 18:19:35 +00:00
logging.info(f" \t* Syncing user {user['username']}")
for room_id in rooms_to_join:
# set power level
if room_id in user['rooms']:
continue
2022-09-29 20:13:09 +00:00
logging.info(f"\t\t* joining {room_id_map[room_id]['name']}")
2022-09-29 18:19:35 +00:00
await self._matrixHelper.add_user_to_room(user['matrix_username'], room_id)
# remove from rooms that are not in the groups
2022-09-29 19:31:07 +00:00
rooms_to_be_left = list(filter(
2022-09-29 20:18:59 +00:00
lambda room: room in matrix_room_ids and not room in rooms_to_join and not room_id_map[room]['name'] in lab_rooms, user['rooms']))
2022-09-29 18:19:35 +00:00
for room_id in rooms_to_be_left:
2022-09-29 20:13:09 +00:00
logging.info(f"\t\t* leaving {room_id_map[room_id]['name']}")
2022-09-29 18:19:35 +00:00
await self._matrixHelper.remove_user_from_room(
user['matrix_username'], room_id)
2022-09-29 19:31:07 +00:00
# update power levels
for room in matrix_rooms:
2022-09-29 20:18:59 +00:00
if (not room['id'] in rooms_to_join and not room['id'] in user['rooms']) or room['id'] in rooms_to_be_left:
2022-09-29 19:31:07 +00:00
continue
if (user['matrix_username'] in room['power_levels'] and room['power_levels'][user['matrix_username']] != user['power_level']) or (user['matrix_username'] not in room['power_levels'] and user['power_level'] != 0):
logging.info(
f"\t\t* Setting power level in {room['name']} to {user['power_level']}")
await self._matrixHelper.set_user_power_level_in_room(user['matrix_username'], room['id'], user['power_level'])
2022-09-29 18:19:35 +00:00
logging.info("==> Sync finished <==")
async def _create_default_rooms(self):
# create default rooms
2022-09-29 20:13:09 +00:00
self._log_step("Creating default rooms")
2022-09-29 18:19:35 +00:00
await self._create_rooms(self._get_default_rooms())
async def _load_users_and_groups(self):
"""
loads users and their groups from ldap and matrix and merges them
returns:
([
{
'username': 'dozedler',
'groups': ['mlm', 'p_3d-printing'],
'rooms': ['!room1:matrix.org', '!room2:matrix.org'],
'matrix_username': '@dozedler:matrix.org',
'is_admin': True
}
],
[
'mlm',
'p_3d-printing'
])
"""
2022-09-29 20:13:09 +00:00
self._log_step("Loading users and their groups")
2022-09-29 18:19:35 +00:00
matrix_users = await self._matrixHelper.get_users()
current_users = []
all_projects = []
for user in matrix_users:
username = user['name'].split(':')[0].replace('@', '')
rc, ldap_user = self._ldapHelper.search(
f"(sAMAccountName={username})", ['distinguishedName'])
if not rc:
continue
rc, raw_groups = self._ldapHelper.search(f"(&(member:1.2.840.113556.1.4.1941:={ldap_user[0]['distinguishedName']})(|(sophomorixType=adminclass)(sophomorixType=project)))", [
2022-09-29 19:31:07 +00:00
'sAMAccountName', 'sophomorixType', 'sophomorixMailList'])
2022-09-29 18:19:35 +00:00
if not rc:
continue
2022-09-29 19:31:07 +00:00
all_groups = list(
map(lambda group: group['sAMAccountName'], raw_groups))
2022-09-29 18:19:35 +00:00
power_level = 100 if self._config['ADMIN_GROUP'] in all_groups else 50 if self._config['MODERATOR_GROUP'] in all_groups else 0
2022-09-29 19:31:07 +00:00
groups = list(filter(lambda group: group['sophomorixMailList'] ==
'TRUE' or group['sophomorixType'] == "adminclass", raw_groups))
2022-09-29 18:19:35 +00:00
all_projects.extend(list(map(lambda group: group['sAMAccountName'], filter(
lambda group: 'sophomorixType' in group and group['sophomorixType'] == 'project', groups))))
rooms = await self._matrixHelper.get_rooms_of_user(user['name'])
groups = list(map(lambda group: group['sAMAccountName'], groups))
current_users.append({
'username': username,
'matrix_username': user['name'],
'groups': groups,
'rooms': rooms,
'power_level': power_level
})
all_projects = list(set(all_projects))
return current_users, all_projects
async def _create_project_rooms(self, projects):
2022-09-29 20:13:09 +00:00
self._log_step("Creating project rooms")
2022-09-29 19:31:07 +00:00
projects = list(
map(lambda project: project.replace('p_', ''), projects))
2022-09-29 18:19:35 +00:00
await self._create_rooms(projects)
2022-09-29 20:13:09 +00:00
async def _create_lab_rooms(self):
self._log_step("Creating lab rooms")
rc, labs = self._ldapHelper.search(
f"(&(sophomorixType=project)(sAMAccountName=*lab))", ['sAMAccountName'])
if not rc:
return
2023-09-10 10:17:13 +00:00
labs = list(map(lambda lab: lab['sAMAccountName'].replace(
"p_", "").replace("lab", "-lab"), labs))
2022-09-29 20:13:09 +00:00
await self._create_rooms(labs, joinable_for_space_members=True)
2022-09-29 20:18:59 +00:00
return labs
2022-09-29 20:13:09 +00:00
async def _create_rooms(self, rooms, joinable_for_space_members=False, suggested=False):
2022-09-29 18:19:35 +00:00
matrix_room_names = list(map(lambda room: room['name'], await self._get_managed_rooms()))
for room in rooms:
if room not in matrix_room_names:
logging.info(f"* Creating room {room}")
2022-09-29 20:13:09 +00:00
await self._matrixHelper.create_room(room, room, joinable_for_space_members=joinable_for_space_members, suggested=True)
2022-09-29 18:19:35 +00:00
async def _get_managed_rooms(self, with_power_levels=False):
matrix_rooms = await self._matrixHelper.get_rooms()
matrix_rooms = filter(
lambda room: room['name'] != None and room['creator'] == self._matrixUsername, matrix_rooms)
matrix_rooms = map(
lambda room: {'id': room['room_id'], 'name': room['name']}, matrix_rooms)
matrix_rooms = list(matrix_rooms)
if with_power_levels:
for i in range(len(matrix_rooms)):
2022-09-29 19:31:07 +00:00
logging.info(
f"\t* Getting power levels of room {matrix_rooms[i]['name']}")
2022-09-29 18:19:35 +00:00
matrix_rooms[i]['power_levels'] = await self._matrixHelper.get_room_power_levels(matrix_rooms[i]['id'])
return matrix_rooms
def _get_default_rooms(self):
return self._config['DEFAULT_ROOMS'].split(',')
2022-09-28 18:04:21 +00:00
2022-09-29 20:13:09 +00:00
def _log_step(self, message):
logging.info(f"= ({self._current_log_step}/6) {message} =")
self._current_log_step += 1
2022-09-28 18:04:21 +00:00
def _readConfig(self):
requiredConfigKeys = [
2022-09-29 18:19:35 +00:00
'MATRIX_BOT_LDAP_URI',
2022-09-28 18:04:21 +00:00
'MATRIX_BOT_LDAP_BASE_DN',
2022-09-29 18:19:35 +00:00
'MATRIX_BOT_LDAP_BIND_DN',
2022-09-28 18:04:21 +00:00
'MATRIX_BOT_LDAP_BIND_DN_PASSWORD',
'MATRIX_BOT_MATRIX_DOMAIN',
'MATRIX_BOT_MATRIX_SPACE_ID',
'MATRIX_BOT_MATRIX_SERVER',
'MATRIX_BOT_MATRIX_USERNAME',
'MATRIX_BOT_MATRIX_PASSWORD',
2022-09-29 18:19:35 +00:00
'MATRIX_BOT_DEFAULT_ROOMS',
'MATRIX_BOT_ADMIN_GROUP',
'MATRIX_BOT_MODERATOR_GROUP'
2022-09-28 18:04:21 +00:00
]
allowedConfigKeys = [
]
config = {
}
for configKey in requiredConfigKeys:
if configKey not in os.environ:
2022-09-29 19:31:07 +00:00
logging.error(
f"Required environment value {configKey} is not set")
2022-09-29 18:19:35 +00:00
sys.exit()
config[configKey.replace('MATRIX_BOT_', '')
] = os.environ[configKey]
2022-09-28 18:04:21 +00:00
for configKey in allowedConfigKeys:
if configKey in os.environ:
2022-09-29 18:19:35 +00:00
config[configKey.replace(
'MATRIX_BOT_', '')] = os.environ[configKey]
2022-09-28 18:04:21 +00:00
logging.info("CONFIG:")
for key, value in config.items():
logging.info(" * {:25}: {}".format(key, value))
return config
2022-09-29 18:19:35 +00:00
async def main():
2022-09-28 18:04:21 +00:00
bot = MlmMatrixBot()
2022-09-29 21:14:48 +00:00
if not await bot.init():
logging.critical("Bot initialization failed")
sys.exit(1)
2023-09-10 10:17:13 +00:00
2022-09-29 18:19:35 +00:00
while True:
await bot.run()
time.sleep(60)
if __name__ == "__main__":
2022-09-28 18:04:21 +00:00
try:
2022-09-29 18:19:35 +00:00
asyncio.run(main())
2022-09-28 18:04:21 +00:00
except KeyboardInterrupt:
2022-09-29 18:19:35 +00:00
logging.info("Bye")