diff --git a/README.md b/README.md index 82dfa05..3bf0f4d 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ A Matrix-Bot which is able to: 1. create default rooms 2. load users and their groups from matrix and ldap -3. create rooms for all projects which have `sophomorixMailList=TRUE` -4. load all rooms (created by the bot) and their power levels from matrix -5. sync user memberships and power levels +3. create rooms for all projects which end in `lab` +4. create rooms for all projects which have `sophomorixMailList=TRUE` +5. load all rooms (created by the bot) and their power levels from matrix +6. sync user memberships and power levels # How to use diff --git a/src/main.py b/src/main.py index 0b7022a..b44fc6c 100644 --- a/src/main.py +++ b/src/main.py @@ -35,72 +35,76 @@ class MlmMatrixBot: async def run(self): + self._current_log_step = 0 logging.info("==> Sync started <==") - + await self._create_default_rooms() current_users, all_projects = await self._load_users_and_groups() await self._create_project_rooms(all_projects) + await self._create_lab_rooms() - logging.info("= (4/5) Loading current rooms =") + self._log_step("Loading current rooms") 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 = {} - room_id_map = { - self._config['MATRIX_SPACE_ID']: { - 'name': "default space", - } - } + room_id_map = {} for room in matrix_rooms: room_name_id_map[room['name']] = room['id'] room_id_map[room['id']] = room - logging.info("= (5/5) Syncing user memberships =") + self._log_step("Syncing user memberships") for user in current_users: if not "mlm" in user['groups']: continue # default groups and space - rooms_to_join = self._get_default_rooms() + [self._config["MATRIX_SPACE_ID"]] + rooms_to_join = self._get_default_rooms( + ) + [self._config["MATRIX_SPACE_ID"]] # projects - rooms_to_join += list(map(lambda group: group.replace('p_', ''), filter(lambda project: project.startswith("p_"), user['groups']))) + rooms_to_join += list(map(lambda group: group.replace('p_', ''), + filter(lambda project: project.startswith("p_"), user['groups']))) # resolve names to ids rooms_to_join = list(map( - lambda room: room if room.startswith("!") else room_name_id_map[room], + lambda room: room if room.startswith( + "!") else room_name_id_map[room], rooms_to_join)) - + logging.info(f" \t* Syncing user {user['username']}") for room_id in rooms_to_join: # set power level if room_id in user['rooms']: - if ( user['matrix_username'] in room_id_map[room_id]['power_levels'] and room_id_map[room_id]['power_levels'][user['matrix_username']] != user['power_level']) or (user['matrix_username'] not in room_id_map[room_id]['power_levels'] and user['power_level'] != 0): - logging.info(f"\t\t* Setting power level in {room_id_map[room_id]['name']} to {user['power_level']}") - await self._matrixHelper.set_user_power_level_in_room(user['matrix_username'], room_id, user['power_level']) continue - logging.info(f"\t* joining {room_id_map[room_id]['name']}") + logging.info(f"\t\t* joining {room_id_map[room_id]['name']}") await self._matrixHelper.add_user_to_room(user['matrix_username'], room_id) - if user['power_level'] != 0: - logging.info(f"\t\t* Setting power level in {room_id_map[room_id]['name']} to {user['power_level']}") - await self._matrixHelper.set_user_power_level_in_room(user['matrix_username'], room_id, user['power_level']) - # remove from rooms that are not in the groups - rooms_to_be_left = list(filter(lambda room: room in matrix_room_ids and not room in rooms_to_join, user['rooms'])) + rooms_to_be_left = list(filter( + lambda room: room in matrix_room_ids and not room in rooms_to_join, user['rooms'])) for room_id in rooms_to_be_left: - logging.info(f"\t* leaving {room_id_map[room_id]['name']}") + logging.info(f"\t\t* leaving {room_id_map[room_id]['name']}") await self._matrixHelper.remove_user_from_room( user['matrix_username'], room_id) + # update power levels + for room in matrix_rooms: + if not room['id'] in rooms_to_join and not room['id'] in user['rooms']: + 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']) logging.info("==> Sync finished <==") async def _create_default_rooms(self): # create default rooms - logging.info("= (1/5) Creating default rooms =") + self._log_step("Creating default rooms") await self._create_rooms(self._get_default_rooms()) async def _load_users_and_groups(self): @@ -121,7 +125,7 @@ class MlmMatrixBot: 'p_3d-printing' ]) """ - logging.info("= (2/5) Loading users and their groups =") + self._log_step("Loading users and their groups") matrix_users = await self._matrixHelper.get_users() current_users = [] all_projects = [] @@ -134,15 +138,17 @@ class MlmMatrixBot: continue rc, raw_groups = self._ldapHelper.search(f"(&(member:1.2.840.113556.1.4.1941:={ldap_user[0]['distinguishedName']})(|(sophomorixType=adminclass)(sophomorixType=project)))", [ - 'sAMAccountName', 'sophomorixType', 'sophomorixMailList']) - + 'sAMAccountName', 'sophomorixType', 'sophomorixMailList']) + if not rc: continue - all_groups = list(map(lambda group: group['sAMAccountName'], raw_groups)) + all_groups = list( + map(lambda group: group['sAMAccountName'], raw_groups)) power_level = 100 if self._config['ADMIN_GROUP'] in all_groups else 50 if self._config['MODERATOR_GROUP'] in all_groups else 0 - groups = list(filter(lambda group: group['sophomorixMailList'] == 'TRUE' or group['sophomorixType'] == "adminclass", raw_groups)) + groups = list(filter(lambda group: group['sophomorixMailList'] == + 'TRUE' or group['sophomorixType'] == "adminclass", raw_groups)) all_projects.extend(list(map(lambda group: group['sAMAccountName'], filter( lambda group: 'sophomorixType' in group and group['sophomorixType'] == 'project', groups)))) @@ -163,16 +169,28 @@ class MlmMatrixBot: return current_users, all_projects async def _create_project_rooms(self, projects): - logging.info("= (3/5) Creating project rooms =") - projects = list(map(lambda project: project.replace('p_', ''), projects)) + self._log_step("Creating project rooms") + projects = list( + map(lambda project: project.replace('p_', ''), projects)) await self._create_rooms(projects) - async def _create_rooms(self, rooms): + 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 + + labs = list(map(lambda lab: lab['sAMAccountName'].replace("p_", "").replace("lab", "-lab"), labs)) + await self._create_rooms(labs, joinable_for_space_members=True) + + async def _create_rooms(self, rooms, joinable_for_space_members=False, suggested=False): 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}") - await self._matrixHelper.create_room(room, room) + await self._matrixHelper.create_room(room, room, joinable_for_space_members=joinable_for_space_members, suggested=True) async def _get_managed_rooms(self, with_power_levels=False): matrix_rooms = await self._matrixHelper.get_rooms() @@ -184,7 +202,8 @@ class MlmMatrixBot: if with_power_levels: for i in range(len(matrix_rooms)): - logging.info(f"\t* Getting power levels of room {matrix_rooms[i]['name']}") + logging.info( + f"\t* Getting power levels of room {matrix_rooms[i]['name']}") matrix_rooms[i]['power_levels'] = await self._matrixHelper.get_room_power_levels(matrix_rooms[i]['id']) return matrix_rooms @@ -192,6 +211,10 @@ class MlmMatrixBot: def _get_default_rooms(self): return self._config['DEFAULT_ROOMS'].split(',') + def _log_step(self, message): + logging.info(f"= ({self._current_log_step}/6) {message} =") + self._current_log_step += 1 + def _readConfig(self): requiredConfigKeys = [ 'MATRIX_BOT_LDAP_URI', @@ -216,7 +239,8 @@ class MlmMatrixBot: for configKey in requiredConfigKeys: if configKey not in os.environ: - logging.error(f"Required environment value {configKey} is not set") + logging.error( + f"Required environment value {configKey} is not set") sys.exit() config[configKey.replace('MATRIX_BOT_', '') ] = os.environ[configKey] diff --git a/src/matrixHelper.py b/src/matrixHelper.py index 0bf6a13..121d5f1 100644 --- a/src/matrixHelper.py +++ b/src/matrixHelper.py @@ -57,27 +57,16 @@ class MatrixHelper: async def logout(self): await self._client.close() - async def create_room(self, name, alias, joinable_for_space_members=False): + async def create_room(self, name, alias, joinable_for_space_members=False, suggested=False): # we not only need to create the room but also have to add it to the space + via_domain = self._space_id.split(":")[1] initial_state = [ { "type": "m.space.parent", "state_key": self._space_id, "content": { "canonical": True, - "via": [self._space_id.split(":")[1]], - } - }, - { - "type": "m.room.join_rules", - "content": { - "join_rule": "restricted", - "allow": [ - { - "type": "m.room_membership", - "room_id": self._space_id - } - ] + "via": [via_domain], } }, { @@ -95,16 +84,34 @@ class MatrixHelper: ] if joinable_for_space_members: - initial_state.append({ + initial_state.insert(0, { "type": "m.room.guest_access", "state_key": "", "content": {"guest_access": "can_join"} }) + initial_state.insert(2, { + "type": "m.room.join_rules", + "content": { + "join_rule": "restricted", + "allow": [ + { + "type": "m.room_membership", + "room_id": self._space_id + } + ] + } + }) room = await self._client.room_create(visibility=nio.RoomVisibility.private, name=name, alias=alias, federate=False, initial_state=initial_state) - assert room.room_id is not None + # add as child room + state_update = await self._client.room_put_state(self._space_id, "m.space.child", { + "suggested": suggested, + "via": [via_domain], + }, state_key=room.room_id) + assert state_update.event_id is not None + return room async def get_rooms(self):