WIP: #65 isClubRep fix #93

Draft
j23leung wants to merge 2 commits from 65-isClubRep-fix into master
5 changed files with 60 additions and 10 deletions

View File

@ -5,6 +5,7 @@ from zope import component
from .utils import authz_restrict_to_staff, authz_restrict_to_syscom, \
user_is_in_group, requires_authentication_no_realm, requires_admin_creds, \
create_streaming_response, development_only, is_truthy
from .model.utils import should_be_club_rep
from ceo_common.errors import BadRequest, UserAlreadySubscribedError, UserNotSubscribedError
from ceo_common.interfaces import ILDAPService, IConfig, IMailService
from ceo_common.logger_factory import logger_factory
@ -194,3 +195,28 @@ def remind_users_of_expiration():
mail_srv.send_membership_renewal_reminder(member)
return jsonify([member.uid for member in members])
@bp.route('/updateisclubrep', methods=['POST'])
@authz_restrict_to_syscom
def update_is_club_rep():
dry_run = is_truthy(request.args.get('dry_run', 'false'))
ldap_srv = component.getUtility(ILDAPService)
members = ldap_srv.get_members()
modified_uids = []
for member in members:
current_club_rep_status = member.is_club_rep
new_club_rep_status = should_be_club_rep(member.non_member_terms)
if current_club_rep_status == new_club_rep_status:
continue
modified_uids.append(member.uid)
if not dry_run:
member.set_club_rep(new_club_rep_status)
if new_club_rep_status:
logger.info(f"Updated {member.uid} to be club rep")
else:
logger.info(f"Updated {member.uid} to be non club rep")
return jsonify(modified_uids)

View File

@ -359,6 +359,27 @@ class LDAPService:
return users_to_change
def _get_member_uids(self, conn: ldap3.Connection) -> List[str]:
conn.extend.standard.paged_search(self.ldap_users_base,
'(objectClass=member)',
attributes=['uid'],
paged_size=50,
generator=False)
return [entry.uid.value for entry in conn.entries]
def get_members(self) -> List[IUser]:
batch_size = 100
conn = self._get_ldap_conn()
member_uids = self._get_member_uids(conn)
members = []
for i in range(0, len(member_uids), batch_size):
member_uids_slice = member_uids[i:i + batch_size]
filter = '(|' + ''.join([f'(uid={uid})' for uid in member_uids_slice]) + ')'
conn.search(self.ldap_users_base, filter, attributes=ldap3.ALL_ATTRIBUTES)
for entry in conn.entries:
members.append(User.deserialize_from_ldap(entry))
return members
def _get_club_uids(self, conn: ldap3.Connection) -> List[str]:
conn.extend.standard.paged_search(self.ldap_users_base,
'(objectClass=club)',

View File

@ -64,7 +64,7 @@ class User:
# not a real user
is_club_rep = False
else:
is_club_rep = should_be_club_rep(terms, non_member_terms)
is_club_rep = should_be_club_rep(non_member_terms)
self.is_club_rep = is_club_rep
if is_member_or_club_rep is None:
is_member_or_club_rep = terms is not None or non_member_terms is not None
@ -230,3 +230,8 @@ class User:
else:
entry.shadowExpire.remove()
self.shadowExpire = None
def set_club_rep(self, is_club_rep: bool):
with self.ldap_srv.entry_ctx_for_user(self) as entry:
entry.isClubRep = is_club_rep
self.is_club_rep = is_club_rep

View File

@ -34,17 +34,15 @@ def dn_to_uid(dn: str) -> str:
return dn.split(',', 1)[0].split('=')[1]
def should_be_club_rep(terms: Union[None, List[str]],
non_member_terms: Union[None, List[str]]) -> bool:
"""Returns True iff a user's most recent term was a non-member term."""
def should_be_club_rep(non_member_terms: Union[None, List[str]]) -> bool:
"""Returns True iff a user should be a club rep in the current term."""
if not non_member_terms:
# no non-member terms => was only ever a member
return False
if not terms:
# no member terms => was only ever a club rep
return True
# decide using the most recent term (member or non-member)
return max(map(Term, non_member_terms)) > max(map(Term, terms))
current_term = Term.current()
most_recent_non_member_term = max(map(Term, non_member_terms))
return most_recent_non_member_term == current_term
def rate_limit(api_name: str, limit_secs: int):

View File

@ -26,7 +26,7 @@ conn.search(LDAP_MEMBERS_BASE, '(objectClass=member)',
attributes=['uid', 'isClubRep', 'term', 'nonMemberTerm'])
total_records_updated = 0
for entry in conn.entries:
if not should_be_club_rep(entry.term.values, entry.nonMemberTerm.values):
if not should_be_club_rep(entry.nonMemberTerm.values):
continue
if entry.isClubRep.value:
continue