diff --git a/ceo_common/interfaces/IGroup.py b/ceo_common/interfaces/IGroup.py index 7049f14..09857cc 100644 --- a/ceo_common/interfaces/IGroup.py +++ b/ceo_common/interfaces/IGroup.py @@ -1,3 +1,5 @@ +from typing import List + from zope.interface import Interface, Attribute @@ -21,5 +23,8 @@ class IGroup(Interface): def remove_member(username: str): """Remove the member from this group in LDAP.""" + def set_members(usernames: List[str]): + """Set all of the members of this group in LDAP.""" + def to_dict(): """Serialize this group as JSON.""" diff --git a/ceo_common/interfaces/ILDAPService.py b/ceo_common/interfaces/ILDAPService.py index ee8e795..89424f4 100644 --- a/ceo_common/interfaces/ILDAPService.py +++ b/ceo_common/interfaces/ILDAPService.py @@ -25,7 +25,7 @@ class ILDAPService(Interface): """ def get_users_with_positions(self) -> List[IUser]: - """Retrieve users who has their position set""" + """Retrieve users who have a non-empty position attribute.""" def add_user(user: IUser): """ diff --git a/ceo_common/model/Config.py b/ceo_common/model/Config.py index f2ff94e..b78b58f 100644 --- a/ceo_common/model/Config.py +++ b/ceo_common/model/Config.py @@ -27,6 +27,6 @@ class Config: return True if val.lower() in ['false', 'no']: return False - if section.startswith('auxiliary '): + if section.startswith('auxiliary ') or section == 'positions': return val.split(',') return val diff --git a/ceod/api/app_factory.py b/ceod/api/app_factory.py index 8530e5c..f033a2b 100644 --- a/ceod/api/app_factory.py +++ b/ceod/api/app_factory.py @@ -29,9 +29,8 @@ def create_app(flask_config={}): # Only ceod_admin_host should serve the /api/members endpoints because # it needs to run kadmin if hostname == cfg.get('ceod_admin_host'): - from ceod.api import members, positions + from ceod.api import members app.register_blueprint(members.bp, url_prefix='/api/members') - app.register_blueprint(positions.bp, url_prefix='/api/positions') # Only offer mailman API if this host is running Mailman if hostname == cfg.get('ceod_mailman_host'): @@ -41,6 +40,9 @@ def create_app(flask_config={}): from ceod.api import groups app.register_blueprint(groups.bp, url_prefix='/api/groups') + from ceod.api import positions + app.register_blueprint(positions.bp, url_prefix='/api/positions') + from ceod.api import uwldap app.register_blueprint(uwldap.bp, url_prefix='/api/uwldap') diff --git a/ceod/model/Group.py b/ceod/model/Group.py index cf99e57..695a8f0 100644 --- a/ceod/model/Group.py +++ b/ceod/model/Group.py @@ -105,3 +105,10 @@ class Group: logger.warning(err) raise UserNotInGroupError() self.members.remove(username) + + def set_members(self, usernames: List[str]): + DNs = [ + self.ldap_srv.uid_to_dn(username) for username in usernames + ] + with self.ldap_srv.entry_ctx_for_group(self) as entry: + entry.uniqueMember = DNs diff --git a/ceod/model/LDAPService.py b/ceod/model/LDAPService.py index 9ddd1e6..218f971 100644 --- a/ceod/model/LDAPService.py +++ b/ceod/model/LDAPService.py @@ -96,7 +96,7 @@ class LDAPService: { 'uid': entry.uid.value, 'cn': entry.cn.value, - 'program': entry.program.value, + 'program': entry.program.value or 'Unknown', } for entry in conn.entries ] diff --git a/ceod/transactions/members/UpdateMemberPositionsTransaction.py b/ceod/transactions/members/UpdateMemberPositionsTransaction.py index 9b61dcf..6cbd1b4 100644 --- a/ceod/transactions/members/UpdateMemberPositionsTransaction.py +++ b/ceod/transactions/members/UpdateMemberPositionsTransaction.py @@ -16,14 +16,13 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): operations = [ 'update_positions_ldap', + 'update_exec_group_ldap', 'subscribe_to_mailing_lists', ] def __init__(self, positions_reversed: Dict[str, str]): # positions_reversed is position -> username super().__init__() - # self.old_positions is username -> positions - self.old_positions = {} # For rollback self.ldap_srv = component.getUtility(ILDAPService) # Reverse the dict so it's easier to use (username -> positions) @@ -34,11 +33,14 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): # a cached Dict of the Users who need to be modified (username -> User) self.users: Dict[str, IUser] = {} + # for rollback purposes + self.old_positions = {} # username -> positions + self.old_execs = [] + def child_execute_iter(self): cfg = component.getUtility(IConfig) mailing_lists = cfg.get('auxiliary mailing lists_exec') - subscribe_status: Dict[str, bool] = {} # position -> username new_positions_reversed = {} # For returning result @@ -53,9 +55,9 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): self.positions[user.uid] = [] self.users[user.uid] = user - # Update positions via ldap - for username, user in self.users.items(): - new_positions = self.positions[username] + # Update positions in LDAP + for username, new_positions in self.positions.items(): + user = self.users[username] old_positions = user.positions[:] user.set_positions(new_positions) @@ -63,14 +65,24 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): self.old_positions[username] = old_positions for position in new_positions: new_positions_reversed[position] = username - subscribe_status[username] = len(new_positions) > 0 yield 'update_positions_ldap' - # Update mailing list subscription - for username, subscribe in subscribe_status.items(): + # update exec group in LDAP + exec_group = self.ldap_srv.get_group('exec') + self.old_execs = exec_group.members[:] + new_execs = [ + username for username, new_positions in self.positions.items() + if len(new_positions) > 0 + ] + exec_group.set_members(new_execs) + yield 'update_exec_group_ldap' + + # Update mailing list subscriptions + for username, new_positions in self.positions.items(): + user = self.users[username] for mailing_list in mailing_lists: try: - if subscribe: + if len(new_positions) > 0: user.subscribe_to_mailing_list(mailing_list) else: user.unsubscribe_from_mailing_list(mailing_list) @@ -83,6 +95,10 @@ class UpdateMemberPositionsTransaction(AbstractTransaction): self.finish(new_positions_reversed) def rollback(self): + if 'update_exec_group_ldap' in self.finished_operations: + exec_group = self.ldap_srv.get_group('exec') + exec_group.set_members(self.old_execs) + for username, positions in self.old_positions.items(): user = self.users[username] user.set_positions(positions)