Last active
September 26, 2020 03:47
-
-
Save jdiverp/148e8745ff5fb0fd8edc81007b0e3248 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python | |
| """Set moderator settings for UW Medicine HR Lists | |
| This script takes a predefined csv file of mail lists and | |
| a seperate config file of important people and adjusts | |
| settings accordingy | |
| Usage: %(PROGRAM)s [options] | |
| Options: | |
| -h / --help | |
| Print this message and exit. | |
| -l listname | |
| --listname=listname | |
| Process only the given list, otherwise do all lists. | |
| """ | |
| import sys | |
| import time | |
| import getopt | |
| import os | |
| import csv | |
| import re | |
| import pprint | |
| import httplib | |
| import json | |
| from collections import defaultdict | |
| sys.path | |
| sys.path.append('/usr/lusers/mailman') | |
| #from dateutil.parser import parse | |
| import paths | |
| # mm_cfg must be imported before the other modules, due to the side-effect of | |
| # it hacking sys.paths to include site-packages. Without this, running this | |
| # script from cron with python -S will fail. | |
| from Mailman import mm_cfg | |
| from Mailman import Utils | |
| from Mailman import MailList | |
| from Mailman import Pending | |
| from Mailman import MemberAdaptor | |
| from Mailman import Errors | |
| from Mailman.Bouncer import _BounceInfo | |
| from Mailman.Logging.Syslog import syslog | |
| from Mailman.i18n import _ | |
| from Mailman.Errors import MMUnknownListError | |
| # Work around known problems with some RedHat cron daemons | |
| import signal | |
| signal.signal(signal.SIGCHLD, signal.SIG_DFL) | |
| GWS_HOST = 'groups.uw.edu' | |
| GWS_PORT = '443' | |
| GWS_BASE = '/group_sws/v3' | |
| USE_PROD = 1 | |
| EXTRAS = '?synchronized=true' | |
| if USE_PROD != 1: | |
| GWS_HOST = 'eval.groups.uw.edu' | |
| GWS_PORT = '443' | |
| GWS_BASE = '/group_sws/v3' | |
| GWS_KEY_FILE = '/usr/lusers/mailman/uw_medicine/certs/uwmlists.key' | |
| GWS_CERT_FILE = '/usr/lusers/mailman/uw_medicine/certs/uwmlists.cert' | |
| # set if wanting to not update anything | |
| #DEVMODE = True | |
| DEVMODE = False | |
| uwm_lists_file = "uwmc_check2" | |
| uwm_lists_perms_file = "uw_medicine_lists_permissions_v6.csv" | |
| email_regex = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)") | |
| email_sub = re.compile(r"(@uw.edu)") | |
| list_regex = re.compile(r"(^(uwm|wwami_faculty|alliedhealth)_[a-z_-]+$)", re.I) | |
| all_lists_regex = re.compile(r"(^all lists$)", re.I) | |
| pp=pprint.PrettyPrinter( indent = 2 ) | |
| PROGRAM = sys.argv[0] | |
| LISTPERMS = {} | |
| ALLPERMS = [] | |
| _gws_connection = None | |
| GWS_ADMINS = [ {"type": "group", "id": "u_uwmlists_group-admins"} ] | |
| GWS_HEAD = { "Content-type": "application/json", "Accept": "application/json" } | |
| GWS_UPDATE_HEAD = { "Content-type": "application/json", "Accept": "application/json", "If-Match": "*" } | |
| def is_grouplist(listname): | |
| enrolled = ''.join(["/mm/lists/", listname, "/enrolled"]) | |
| try: | |
| os.stat(enrolled) | |
| except: | |
| return False | |
| return True | |
| def is_mailman_folder(listname): | |
| config = ''.join(["/mm/lists/", listname, "/config.pck"]) | |
| try: | |
| os.stat(config) | |
| except: | |
| return False | |
| return True | |
| def usage(code, msg=''): | |
| if code: | |
| fd = sys.stderr | |
| else: | |
| fd = sys.stdout | |
| print >> fd, _(__doc__) | |
| if msg: | |
| print >> fd, msg | |
| sys.exit(code) | |
| def get_unmoderated_members(mlist): | |
| # Get the lowercased member addresses | |
| rmembers = mlist.getRegularMemberKeys() | |
| dmembers = mlist.getDigestMemberKeys() | |
| unmod_members = [] | |
| mod_members = [] | |
| all = rmembers + dmembers | |
| all.sort() | |
| for addr in all: | |
| if not mlist.getMemberOption(addr, mm_cfg.Moderate): | |
| unmod_members.append(addr) | |
| else: | |
| mod_members.append(addr) | |
| #print "get_unmoderated_members() has %s" % unmod_members | |
| return list(unmod_members), list(mod_members) | |
| def unmoderate_user(addr, mlist): | |
| try: | |
| if DEVMODE: | |
| print "Would have called mlist.setMemberOption(%s,mm_cfg.Moderate, False)" % addr | |
| else: | |
| mlist.setMemberOption(addr,mm_cfg.Moderate, False) | |
| except NotAMemberError: | |
| return False | |
| return True | |
| def moderate_user(addr, mlist): | |
| try: | |
| if DEVMODE: | |
| print "Would have called mlist.setMemberOption(%s,mm_cfg.Moderate, True)" % addr | |
| else: | |
| mlist.setMemberOption(addr,mm_cfg.Moderate, True) | |
| except NotAMemberError: | |
| return False | |
| return True | |
| def read_listnames_file(): | |
| listnames = [] | |
| with open(uwm_lists_file, 'r') as source_list_file: | |
| reader = csv.reader(source_list_file) | |
| for row in reader: | |
| listnames.append(row[0]) | |
| return listnames | |
| #Noting this hhhis highly dependant on the patterns used by | |
| # UW Medicine Communications in their DraftApprovedSenders_v2.xlsx file | |
| def load_permissions_file(): | |
| with open(uwm_lists_perms_file, 'r') as source_list_file: | |
| reader = csv.reader(source_list_file) | |
| # processes each row in the csv file separately | |
| for row in reader: | |
| emails = [] | |
| lists = [] | |
| all_lists = False | |
| if DEVMODE: | |
| print row | |
| # process each field on this row | |
| for field in row: | |
| field = field.strip() | |
| field = field.lower() | |
| # if this is an email address add it to the list of emails found on that row | |
| if email_regex.match( field ): | |
| emails.append( email_sub.sub('@uw.edu', field ) ) | |
| # if this looks like uwm_ then add it as a list | |
| if list_regex.match( field ): | |
| #print "matched list_regex for "+field | |
| lists.append( re.sub(r'-',r'_',field) ) | |
| # if we found "All Lists" in a field that row is a full permissions row | |
| if all_lists_regex.match( field ): | |
| all_lists = True | |
| # start updating the list of permissions | |
| # if row is flagged as "All Lists" then emails go there | |
| if all_lists == True: | |
| if DEVMODE: | |
| print " Matched All-Lists:",str(row) | |
| for addr in emails: | |
| try: | |
| ALLPERMS.index(addr) | |
| except ValueError: | |
| ALLPERMS.append(addr) | |
| # if row doesn'thave "All Lists" then put emsils on the list of lists that "follow" | |
| else: | |
| if DEVMODE: | |
| print " emails: " + ','.join(emails) | |
| print " lists: " + ','.join(lists) | |
| for uwm in lists: | |
| if DEVMODE: | |
| print " CHECKING LIST %s with addresses: %s" % (uwm, str(emails)) | |
| if uwm in LISTPERMS: | |
| if DEVMODE: | |
| print " found %s in LISTPERMS for %s\n adding %s" % (str(LISTPERMS[uwm]), uwm, str(emails)) | |
| for addr in emails: | |
| if addr not in LISTPERMS[uwm]: | |
| LISTPERMS[ uwm ].append(addr) | |
| # if the uwm dict object doesn't exist yet add the list to it. This is where I made may error.. | |
| else: | |
| LISTPERMS[uwm] = list(emails) | |
| return True | |
| def unmod_list(permissioned, mlist): | |
| list_real_name = mlist.real_name | |
| for person in permissioned: | |
| if mlist.isMember(person): | |
| if DEVMODE: | |
| print "would have unset moderation flag for %s on %s" % (person, list_real_name) | |
| else: | |
| unmoderate_user(person,mlist) | |
| #else: | |
| # if DEVMODE: | |
| # print "%s not a member of list %s" % (person, list_real_name) | |
| return True | |
| def remod_list(unauthorized, mlist): | |
| list_real_name = mlist.real_name | |
| for person in unauthorized: | |
| if mlist.isMember(person): | |
| if DEVMODE: | |
| print "would have set moderation flag for %s on %s" % (person, list_real_name) | |
| else: | |
| moderate_user(person,mlist) | |
| #else: | |
| # if DEVMODE: | |
| # print "%s not a member of list %s" % (person, list_real_name) | |
| return True | |
| def intersection(list1, list2): | |
| return list(set(list1) & set(list2)) | |
| #remove list2 items from list1 | |
| def filter_set (list1, list2): | |
| list_to_filter = list1[:] | |
| items_to_filter = list2[:] | |
| for item in items_to_filter: | |
| if item in list_to_filter: | |
| list_to_filter.remove(item) | |
| return list(list_to_filter) | |
| def create_reader_group(_gws_connection, listname): | |
| groupname = 'u_uwmlists_' + re.sub(r'_', r'-',listname) | |
| readergroupname = 'u_uwmlists_' + re.sub(r'_', r'-',listname) + '_approvedsenders' | |
| request = GWS_BASE + '/group/'+readergroupname | |
| METHOD = 'PUT' | |
| #create group or try to | |
| DATA = {"data": { | |
| "id": readergroupname, | |
| "displayName": "Readers of "+groupname, | |
| "description": "Individuals who can read group "+groupname +" and send email to "+listname+"@uw.edu", | |
| "contact": "uwmlists", | |
| "readers": [ | |
| { "type": "group", "id": "u_uwmlists_group-readers"}, | |
| { "type": "group", "id": "u_uwmlists_approved-senders-all-lists"}, | |
| { "type": "group", "id": readergroupname } | |
| ], | |
| "admins": GWS_ADMINS } } | |
| _gws_connection.request(METHOD, request, json.dumps(DATA), headers=GWS_HEAD) | |
| response1 = _gws_connection.getresponse() | |
| code = response1.status | |
| body = response1.read() | |
| print "PUT #1 group code status: "+str(code) | |
| #update parent group with this new group name | |
| DATAPARENT = { "data": { | |
| "id": groupname, | |
| "admins": GWS_ADMINS, | |
| "readers": [ | |
| { "type": "group", "id": "u_uwmlists_group-readers"}, | |
| { "type": "group", "id": readergroupname } | |
| ] | |
| } } | |
| _gws_connection.request(METHOD, request, json.dumps(DATAPARENT), headers=GWS_UPDATE_HEAD) | |
| response2 = _gws_connection.getresponse() | |
| code = response2.status | |
| body = response2.read() | |
| #pp.pprint(body) | |
| print "PUT #2 parent group code status: "+str(code) | |
| #update reader group so this credential isn't explicitly added | |
| _gws_connection.request(METHOD, request, json.dumps(DATA), headers=GWS_UPDATE_HEAD) | |
| response3 = _gws_connection.getresponse() | |
| code = response3.status | |
| body = response3.read() | |
| print "PUT #3 group code status: "+str(code) | |
| def update_reader_group(_gws_connection, listname, specific_approved_senders): | |
| REQUEST = GWS_BASE + '/group/u_uwmlists_' + re.sub(r'_', r'-',listname) + '_approvedsenders' | |
| METHOD = 'GET' | |
| print "Request: " +REQUEST | |
| _gws_connection.request(METHOD, REQUEST) | |
| response1 = _gws_connection.getresponse() | |
| code = response1.status | |
| body = response1.read() | |
| print 'GWS member response 1: ' + str(code) | |
| if(code == 404): | |
| create_reader_group(_gws_connection, listname) | |
| #elif DEVMODE: | |
| # create_reader_group(_gws_connection, listname) | |
| METHOD = 'PUT' | |
| members = [] | |
| if specific_approved_senders: | |
| for person in specific_approved_senders: | |
| if re.search('@uw.edu',person): | |
| netid = person.split('@')[0] | |
| members.append( { "type": "uwnetid", "id": netid } ) | |
| else: | |
| continue | |
| members.append( { "type": "group", "id": "u_uwmlists_approved-senders-all-lists" } ) | |
| DATA = { "data": members } | |
| _gws_connection.request(METHOD, REQUEST + "/member", json.dumps(DATA), headers=GWS_HEAD) | |
| response2 = _gws_connection.getresponse() | |
| code = response2.status | |
| body = response2.read() | |
| print 'GWS member response 2: ' + str(code) | |
| def update_all_lists_group(_gws_connection, people_permitted_for_all): | |
| REQUEST = GWS_BASE + '/group/u_uwmlists_approved-senders-all-lists' | |
| METHOD = 'PUT' | |
| members = [] | |
| if people_permitted_for_all: | |
| for person in people_permitted_for_all: | |
| if re.search('@uw.edu',person): | |
| netid = person.split('@')[0] | |
| members.append( { "type": "uwnetid", "id": netid } ) | |
| else: | |
| print "Permissions list for u_uwmlists_approved-senders-all-lists is empty" | |
| return False | |
| DATA = { "data": members } | |
| _gws_connection.request(METHOD, REQUEST + "/member", json.dumps(DATA), headers=GWS_HEAD) | |
| response2 = _gws_connection.getresponse() | |
| code = response2.status | |
| body = response2.read() | |
| print 'GWS all list response: ' + str(code) +'\n' | |
| def main(): | |
| #process arguments | |
| try: | |
| opts, args = getopt.getopt( | |
| sys.argv[1:], 'hl:omubaf', | |
| ['listname=', 'help']) | |
| except getopt.error, msg: | |
| usage(1, msg) | |
| try: | |
| _gws_connection = httplib.HTTPSConnection(GWS_HOST, GWS_PORT, GWS_KEY_FILE, GWS_CERT_FILE) | |
| except: | |
| print "unable to connect to groups service" | |
| if args: | |
| usage(1) | |
| listnames = [] | |
| for opt, arg in opts: | |
| if opt in ('-h', '--help'): | |
| usage(0) | |
| elif opt in ('-l', '--list'): | |
| listnames.append(arg) | |
| if not listnames: | |
| listnames = read_listnames_file() | |
| #load permissions file | |
| load_permissions_file() | |
| if DEVMODE: | |
| print"\nLIST PERMISSONS" | |
| pp.pprint( LISTPERMS ) | |
| print "\nALL PERMISSONS" | |
| print str(ALLPERMS) | |
| #update all lists group | |
| update_all_lists_group(_gws_connection, ALLPERMS) | |
| #begin processing mail lists | |
| for listname in listnames: | |
| permissions = [] | |
| unmod_members = [] | |
| unmod_notmembers = [] | |
| if(is_mailman_folder(listname) == False): | |
| print "%s Not a list on this mailman server" % listname | |
| continue | |
| try: | |
| mlist = MailList.MailList(listname) | |
| except MMUnknownListError: | |
| print "Couldn't find list: "+listname | |
| continue | |
| #set permissions | |
| try: | |
| print "%s list:" % listname | |
| # set permissions afresh | |
| permissions = list(ALLPERMS) | |
| # overlay list specific people | |
| if listname in LISTPERMS: | |
| permissions = permissions + LISTPERMS.get(listname) | |
| #update UW Group if list has people in permissions file | |
| update_reader_group(_gws_connection, listname, LISTPERMS.get(listname)) | |
| #get list of people who are unmoderated and unmoderated | |
| unmod_members, mod_members = get_unmoderated_members(mlist) | |
| if DEVMODE: | |
| print "unmod members\n" + str(unmod_members) | |
| #print "mod members\n" + str(mod_members) #some lists are very large | |
| # get list of people who need to be updated, then set those | |
| should_be_authorized = intersection(permissions, mod_members) | |
| if should_be_authorized: | |
| print " moderated members who are approved:\n " + '\n '.join(should_be_authorized) | |
| unmod_list(should_be_authorized, mlist) | |
| # get list of people who have access but shouldn't | |
| unauthorized = filter_set(unmod_members, permissions) | |
| if unauthorized: | |
| print " unmoderated members who are not approved:\n " + '\n '.join(unauthorized) | |
| remod_list(unauthorized, mlist) | |
| # we can set accept these non-members in one call so here it is | |
| if DEVMODE: | |
| print "would have set accept_these_nonmembers on %s\n %s" % (listname, ', '.join(permissions)) | |
| else: | |
| mlist.accept_these_nonmembers = permissions | |
| #cleanup step | |
| finally: | |
| # if you don't do mlist.Save() the mlist.accept_these_nonmembers | |
| # setting doesn't get saved | |
| if not DEVMODE: | |
| mlist.Save() | |
| mlist.Unlock() | |
| _gws_connection.close() | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment