2021-08-23 09:59:01 -04:00
|
|
|
import sys
|
|
|
|
from typing import Dict
|
|
|
|
|
|
|
|
import click
|
|
|
|
from zope import component
|
|
|
|
|
2021-09-06 12:40:05 -04:00
|
|
|
from ..term_utils import get_terms_for_new_user, get_terms_for_renewal
|
2021-08-28 23:09:02 -04:00
|
|
|
from ..utils import http_post, http_get, http_patch, http_delete, \
|
2021-09-06 12:40:05 -04:00
|
|
|
get_failed_operations, user_dict_lines, get_adduser_operations
|
2021-08-28 23:09:02 -04:00
|
|
|
from .utils import handle_stream_response, handle_sync_response, print_lines, \
|
2021-08-24 01:48:55 -04:00
|
|
|
check_if_in_development
|
2021-08-23 09:59:01 -04:00
|
|
|
from ceo_common.interfaces import IConfig
|
2021-08-28 23:09:02 -04:00
|
|
|
from ceod.transactions.members import DeleteMemberTransaction
|
2021-08-23 09:59:01 -04:00
|
|
|
|
|
|
|
|
2021-08-24 15:37:05 -04:00
|
|
|
@click.group(short_help='Perform operations on CSC members and club reps')
|
2021-08-23 09:59:01 -04:00
|
|
|
def members():
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help='Add a new member or club rep')
|
|
|
|
@click.argument('username')
|
2021-10-23 23:21:09 -04:00
|
|
|
@click.option('--cn', help='Full name', required=False)
|
|
|
|
@click.option('--given-name', help='First name', required=False)
|
|
|
|
@click.option('--sn', help='Last name', required=False)
|
2021-08-23 09:59:01 -04:00
|
|
|
@click.option('--program', required=False, help='Academic program')
|
|
|
|
@click.option('--terms', 'num_terms', type=click.IntRange(1, 100),
|
2021-09-06 12:40:05 -04:00
|
|
|
help='Number of terms to add', default=1)
|
2021-08-23 09:59:01 -04:00
|
|
|
@click.option('--clubrep', is_flag=True, default=False,
|
|
|
|
help='Add non-member terms instead of member terms')
|
|
|
|
@click.option('--forwarding-address', required=False,
|
|
|
|
help=('Forwarding address to set in ~/.forward. '
|
|
|
|
'Default is UW address. '
|
|
|
|
'Set to the empty string to disable forwarding.'))
|
2021-10-23 23:21:09 -04:00
|
|
|
def add(username, cn, given_name, sn, program, num_terms, clubrep, forwarding_address):
|
2021-08-23 09:59:01 -04:00
|
|
|
cfg = component.getUtility(IConfig)
|
|
|
|
uw_domain = cfg.get('uw_domain')
|
|
|
|
|
2021-10-23 23:21:09 -04:00
|
|
|
# Try to get info from UWLDAP
|
|
|
|
resp = http_get('/api/uwldap/' + username)
|
|
|
|
if resp.ok:
|
|
|
|
result = handle_sync_response(resp)
|
|
|
|
if cn is None and result.get('cn'):
|
|
|
|
cn = result['cn']
|
|
|
|
if given_name is None and result.get('given_name'):
|
|
|
|
given_name = result['given_name']
|
|
|
|
if sn is None and result.get('sn'):
|
|
|
|
sn = result['sn']
|
|
|
|
if program is None and result.get('program'):
|
|
|
|
program = result['program']
|
|
|
|
if forwarding_address is None:
|
|
|
|
forwarding_address = result['mail_local_addresses'][0]
|
|
|
|
if cn is None:
|
|
|
|
cn = click.prompt('Full name')
|
|
|
|
if given_name is None:
|
|
|
|
given_name = click.prompt('First name')
|
|
|
|
if sn is None:
|
|
|
|
sn = click.prompt('Last name')
|
2021-08-23 09:59:01 -04:00
|
|
|
if forwarding_address is None:
|
|
|
|
forwarding_address = username + '@' + uw_domain
|
|
|
|
|
2021-10-23 23:21:09 -04:00
|
|
|
terms = get_terms_for_new_user(num_terms)
|
|
|
|
|
2021-08-23 09:59:01 -04:00
|
|
|
body = {
|
|
|
|
'uid': username,
|
|
|
|
'cn': cn,
|
2021-10-23 23:21:09 -04:00
|
|
|
'given_name': given_name,
|
|
|
|
'sn': sn,
|
2021-08-23 09:59:01 -04:00
|
|
|
}
|
|
|
|
if program is not None:
|
|
|
|
body['program'] = program
|
|
|
|
if clubrep:
|
|
|
|
body['non_member_terms'] = terms
|
2021-08-23 19:01:24 -04:00
|
|
|
else:
|
|
|
|
body['terms'] = terms
|
2021-08-23 09:59:01 -04:00
|
|
|
if forwarding_address != '':
|
|
|
|
body['forwarding_addresses'] = [forwarding_address]
|
2021-08-28 23:09:02 -04:00
|
|
|
else:
|
|
|
|
body['forwarding_addresses'] = []
|
|
|
|
|
|
|
|
click.echo("The following user will be created:")
|
|
|
|
print_user_lines(body)
|
|
|
|
click.confirm('Do you want to continue?', abort=True)
|
|
|
|
|
|
|
|
operations = get_adduser_operations(body)
|
2021-08-23 19:01:24 -04:00
|
|
|
|
2021-08-23 09:59:01 -04:00
|
|
|
resp = http_post('/api/members', json=body)
|
2021-08-23 19:01:24 -04:00
|
|
|
data = handle_stream_response(resp, operations)
|
2021-08-23 09:59:01 -04:00
|
|
|
result = data[-1]['result']
|
|
|
|
print_user_lines(result)
|
|
|
|
|
|
|
|
failed_operations = get_failed_operations(data)
|
|
|
|
if 'send_welcome_message' in failed_operations:
|
|
|
|
click.echo(click.style(
|
|
|
|
'Warning: welcome message was not sent. You now need to manually '
|
|
|
|
'send the user their password.', fg='yellow'))
|
|
|
|
|
|
|
|
|
2021-08-28 23:09:02 -04:00
|
|
|
def print_user_lines(d: Dict):
|
|
|
|
"""Pretty-print a serialized User."""
|
|
|
|
print_lines(user_dict_lines(d))
|
2021-08-23 09:59:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help='Get info about a user')
|
|
|
|
@click.argument('username')
|
|
|
|
def get(username):
|
|
|
|
resp = http_get('/api/members/' + username)
|
|
|
|
result = handle_sync_response(resp)
|
|
|
|
print_user_lines(result)
|
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help="Replace a user's login shell or forwarding addresses")
|
|
|
|
@click.argument('username')
|
|
|
|
@click.option('--login-shell', required=False, help='Login shell')
|
|
|
|
@click.option('--forwarding-addresses', required=False,
|
2021-08-23 19:01:24 -04:00
|
|
|
help=(
|
|
|
|
'Comma-separated list of forwarding addresses. '
|
|
|
|
'Set to the empty string to disable forwarding.'
|
|
|
|
))
|
2021-08-23 09:59:01 -04:00
|
|
|
def modify(username, login_shell, forwarding_addresses):
|
|
|
|
if login_shell is None and forwarding_addresses is None:
|
|
|
|
click.echo('Nothing to do.')
|
|
|
|
sys.exit()
|
|
|
|
operations = []
|
|
|
|
body = {}
|
|
|
|
if login_shell is not None:
|
|
|
|
body['login_shell'] = login_shell
|
|
|
|
operations.append('replace_login_shell')
|
|
|
|
click.echo('Login shell will be set to: ' + login_shell)
|
|
|
|
if forwarding_addresses is not None:
|
2021-08-23 19:01:24 -04:00
|
|
|
if forwarding_addresses == '':
|
|
|
|
forwarding_addresses = []
|
|
|
|
else:
|
|
|
|
forwarding_addresses = forwarding_addresses.split(',')
|
2021-08-23 09:59:01 -04:00
|
|
|
body['forwarding_addresses'] = forwarding_addresses
|
|
|
|
operations.append('replace_forwarding_addresses')
|
|
|
|
prefix = '~/.forward will be set to: '
|
2021-08-23 19:01:24 -04:00
|
|
|
if len(forwarding_addresses) > 0:
|
|
|
|
click.echo(prefix + forwarding_addresses[0])
|
|
|
|
for address in forwarding_addresses[1:]:
|
|
|
|
click.echo((' ' * len(prefix)) + address)
|
|
|
|
else:
|
|
|
|
click.echo(prefix)
|
2021-08-23 09:59:01 -04:00
|
|
|
|
|
|
|
click.confirm('Do you want to continue?', abort=True)
|
|
|
|
|
|
|
|
resp = http_patch('/api/members/' + username, json=body)
|
|
|
|
handle_stream_response(resp, operations)
|
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help="Renew a member or club rep's membership")
|
|
|
|
@click.argument('username')
|
|
|
|
@click.option('--terms', 'num_terms', type=click.IntRange(1, 100),
|
|
|
|
help='Number of terms to add', prompt='Number of terms')
|
|
|
|
@click.option('--clubrep', is_flag=True, default=False,
|
|
|
|
help='Add non-member terms instead of member terms')
|
|
|
|
def renew(username, num_terms, clubrep):
|
2021-09-06 12:40:05 -04:00
|
|
|
terms = get_terms_for_renewal(username, num_terms, clubrep)
|
2021-08-23 09:59:01 -04:00
|
|
|
|
|
|
|
if clubrep:
|
|
|
|
body = {'non_member_terms': terms}
|
|
|
|
click.echo('The following non-member terms will be added: ' + ','.join(terms))
|
|
|
|
else:
|
|
|
|
body = {'terms': terms}
|
|
|
|
click.echo('The following member terms will be added: ' + ','.join(terms))
|
|
|
|
|
|
|
|
click.confirm('Do you want to continue?', abort=True)
|
|
|
|
|
|
|
|
resp = http_post(f'/api/members/{username}/renew', json=body)
|
|
|
|
handle_sync_response(resp)
|
|
|
|
click.echo('Done.')
|
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help="Reset a user's password")
|
|
|
|
@click.argument('username')
|
|
|
|
def pwreset(username):
|
|
|
|
click.confirm(f"Are you sure you want to reset {username}'s password?", abort=True)
|
|
|
|
resp = http_post(f'/api/members/{username}/pwreset')
|
|
|
|
result = handle_sync_response(resp)
|
|
|
|
click.echo('New password: ' + result['password'])
|
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help="Delete a user")
|
|
|
|
@click.argument('username')
|
|
|
|
def delete(username):
|
2021-08-24 01:48:55 -04:00
|
|
|
check_if_in_development()
|
2021-08-23 09:59:01 -04:00
|
|
|
click.confirm(f"Are you sure you want to delete {username}?", abort=True)
|
|
|
|
resp = http_delete(f'/api/members/{username}')
|
|
|
|
handle_stream_response(resp, DeleteMemberTransaction.operations)
|
2021-12-11 16:30:18 -05:00
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help="Check for and mark expired members")
|
|
|
|
@click.option('--dry-run', is_flag=True, default=False)
|
|
|
|
def expire(dry_run):
|
|
|
|
resp = http_post(f'/api/members/expire?dry_run={dry_run and "yes" or "no"}')
|
|
|
|
result = handle_sync_response(resp)
|
|
|
|
|
|
|
|
if len(result) > 0:
|
|
|
|
if dry_run:
|
|
|
|
click.echo("The following members will be marked as expired:")
|
|
|
|
else:
|
|
|
|
click.echo("The following members has been marked as expired:")
|
|
|
|
for username in result:
|
|
|
|
click.echo(username)
|
2022-06-30 20:02:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
@members.command(short_help="Send renewal reminder emails to expiring members")
|
|
|
|
@click.option('--dry-run', is_flag=True, default=False)
|
|
|
|
def remindexpire(dry_run):
|
|
|
|
url = '/api/members/remindexpire'
|
|
|
|
if dry_run:
|
|
|
|
url += '?dry_run=true'
|
|
|
|
resp = http_post(url)
|
|
|
|
result = handle_sync_response(resp)
|
|
|
|
|
|
|
|
if len(result) > 0:
|
|
|
|
if dry_run:
|
|
|
|
click.echo("The following members will be sent membership renewal reminders:")
|
|
|
|
else:
|
|
|
|
click.echo("The following members were sent membership renewal reminders:")
|
|
|
|
for username in result:
|
|
|
|
click.echo(username)
|
|
|
|
else:
|
|
|
|
click.echo("No members are pending expiration.")
|