From 55c4b2151d1c217e7c7f246dbcd4770a05ec4b94 Mon Sep 17 00:00:00 2001 From: Rio Date: Thu, 2 Jun 2022 02:06:49 -0400 Subject: [PATCH] Unsubscribe/resubscribe members when they're expired and renewed (#53) Co-authored-by: Rio Liu Co-authored-by: Rio6 Co-authored-by: Max Erenberg Reviewed-on: https://git.csclub.uwaterloo.ca/public/pyceo/pulls/53 Co-authored-by: Rio Co-committed-by: Rio --- ceod/api/members.py | 25 +++++++++-- .../unsubscribe_expired_members.py | 43 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100755 one_time_scripts/unsubscribe_expired_members.py diff --git a/ceod/api/members.py b/ceod/api/members.py index 7013782..ee34bb2 100644 --- a/ceod/api/members.py +++ b/ceod/api/members.py @@ -4,8 +4,8 @@ from zope import component from .utils import authz_restrict_to_staff, authz_restrict_to_syscom, \ user_is_in_group, requires_authentication_no_realm, \ create_streaming_response, development_only, is_truthy -from ceo_common.errors import BadRequest -from ceo_common.interfaces import ILDAPService +from ceo_common.errors import BadRequest, UserAlreadySubscribedError, UserNotSubscribedError +from ceo_common.interfaces import ILDAPService, IConfig from ceod.transactions.members import ( AddMemberTransaction, ModifyMemberTransaction, @@ -92,14 +92,25 @@ def renew_user(username: str): g.need_admin_creds = True ldap_srv = component.getUtility(ILDAPService) + cfg = component.getUtility(IConfig) user = ldap_srv.get_user(username) + member_list = cfg.get('mailman3_new_member_list') + + def unexpire(user): + if user.shadowExpire: + user.set_expired(False) + try: + user.subscribe_to_mailing_list(member_list) + except UserAlreadySubscribedError: + pass + if body.get('terms'): user.add_terms(body['terms']) - user.set_expired(False) + unexpire(user) return {'terms_added': body['terms']} elif body.get('non_member_terms'): user.add_non_member_terms(body['non_member_terms']) - user.set_expired(False) + unexpire(user) return {'non_member_terms_added': body['non_member_terms']} else: raise BadRequest('Must specify either terms or non-member terms') @@ -129,10 +140,16 @@ def expire_users(): dry_run = is_truthy(request.args.get('dry_run', 'false')) ldap_srv = component.getUtility(ILDAPService) + cfg = component.getUtility(IConfig) members = ldap_srv.get_expiring_users() + member_list = cfg.get('mailman3_new_member_list') if not dry_run: for member in members: member.set_expired(True) + try: + member.unsubscribe_from_mailing_list(member_list) + except UserNotSubscribedError: + pass return json.jsonify([member.uid for member in members]) diff --git a/one_time_scripts/unsubscribe_expired_members.py b/one_time_scripts/unsubscribe_expired_members.py new file mode 100755 index 0000000..ccfc491 --- /dev/null +++ b/one_time_scripts/unsubscribe_expired_members.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" +This is a script which unsubscribes expired members from csc-general. + +GSSAPI is used for SPNEGO authentication, so make sure to run `kinit` first. +Also, make sure to run this script from the top-level of the git directory +(see the sys.path hack below). +""" +import os +import sys + +import ldap3 +from zope import component + +sys.path.append(os.getcwd()) +from ceo_common.errors import UserNotSubscribedError +from ceo_common.interfaces import IConfig, IHTTPClient +from ceo_common.model import Config, HTTPClient, RemoteMailmanService + +# modify as necessary +CONFIG_FILE = '/etc/csc/ceod.ini' +NEW_MEMBER_LIST = 'csc-general' + +cfg = Config(CONFIG_FILE) +component.provideUtility(cfg, IConfig) +http_client = HTTPClient() +component.provideUtility(http_client, IHTTPClient) +mailman_srv = RemoteMailmanService() +LDAP_URI = cfg.get('ldap_server_url') +LDAP_MEMBERS_BASE = cfg.get('ldap_users_base') + +conn = ldap3.Connection(LDAP_URI, auto_bind=True, raise_exceptions=True) +conn.search(LDAP_MEMBERS_BASE, '(shadowExpire=1)', attributes=['uid']) +total_unsubscribed = 0 +for entry in conn.entries: + uid = entry.uid.value + try: + mailman_srv.unsubscribe(uid, NEW_MEMBER_LIST) + print(f'Unsubscribed {uid}') + total_unsubscribed += 1 + except UserNotSubscribedError: + print(f'{uid} is already unsubscribed') +print(f'Total unsubscribed: {total_unsubscribed}')