implement api for expiring members
This commit is contained in:
parent
f1c0ce3dd6
commit
67a7cc2d05
|
@ -87,3 +87,9 @@ class ILDAPService(Interface):
|
|||
be returned along with their new programs, in the same format
|
||||
described above.
|
||||
"""
|
||||
|
||||
def get_expiring_users(self) -> List[IUser]:
|
||||
"""
|
||||
Retrieves members whose term or nonMemberTerm does not contain the
|
||||
current or the last term.
|
||||
"""
|
||||
|
|
|
@ -28,6 +28,9 @@ class Term:
|
|||
s_term = c + str(dt.year)
|
||||
return Term(s_term)
|
||||
|
||||
def start_month(self):
|
||||
return self.seasons.index(self.s_term[0])
|
||||
|
||||
def __add__(self, other):
|
||||
assert type(other) is int
|
||||
c = self.s_term[0]
|
||||
|
@ -40,6 +43,7 @@ class Term:
|
|||
return Term(s_term)
|
||||
|
||||
def __sub__(self, other):
|
||||
assert type(other) is int
|
||||
return self.__add__(-other)
|
||||
|
||||
def __eq__(self, other):
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from flask import Blueprint, request
|
||||
from flask import Blueprint, request, json
|
||||
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
|
||||
create_streaming_response, development_only, is_truthy
|
||||
from ceo_common.errors import BadRequest
|
||||
from ceo_common.interfaces import ILDAPService
|
||||
from ceod.transactions.members import (
|
||||
|
@ -86,9 +86,11 @@ def renew_user(username: str):
|
|||
user = ldap_srv.get_user(username)
|
||||
if body.get('terms'):
|
||||
user.add_terms(body['terms'])
|
||||
user.set_expired(False)
|
||||
return {'terms_added': body['terms']}
|
||||
elif body.get('non_member_terms'):
|
||||
user.add_non_member_terms(body['non_member_terms'])
|
||||
user.set_expired(False)
|
||||
return {'non_member_terms_added': body['non_member_terms']}
|
||||
else:
|
||||
raise BadRequest('Must specify either terms or non-member terms')
|
||||
|
@ -110,3 +112,20 @@ def reset_user_password(username: str):
|
|||
def delete_user(username: str):
|
||||
txn = DeleteMemberTransaction(username)
|
||||
return create_streaming_response(txn)
|
||||
|
||||
|
||||
@bp.route('/expire', methods=['POST'])
|
||||
@authz_restrict_to_syscom
|
||||
def expire_users():
|
||||
dry_run = False
|
||||
if is_truthy(request.args.get('dry_run', 'false')):
|
||||
dry_run = True
|
||||
|
||||
ldap_srv = component.getUtility(ILDAPService)
|
||||
members = ldap_srv.get_expiring_users()
|
||||
|
||||
if not dry_run:
|
||||
for member in members:
|
||||
member.set_expired(True)
|
||||
|
||||
return json.jsonify([member.uid for member in members])
|
||||
|
|
|
@ -140,4 +140,4 @@ def development_only(f: Callable) -> Callable:
|
|||
|
||||
|
||||
def is_truthy(s: str) -> bool:
|
||||
return s.lower() in ['yes', 'true']
|
||||
return s.lower() in ['yes', 'true', '1']
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import contextlib
|
||||
import grp
|
||||
import pwd
|
||||
import datetime
|
||||
from typing import Union, Dict, List
|
||||
|
||||
from flask import g
|
||||
|
@ -13,6 +14,7 @@ from ceo_common.errors import UserNotFoundError, GroupNotFoundError, \
|
|||
UserAlreadyExistsError, GroupAlreadyExistsError
|
||||
from ceo_common.interfaces import ILDAPService, IConfig, \
|
||||
IUser, IGroup, IUWLDAPService
|
||||
from ceo_common.model import Term
|
||||
from .User import User
|
||||
from .Group import Group
|
||||
|
||||
|
@ -243,6 +245,30 @@ class LDAPService:
|
|||
except ldap3.core.exceptions.LDAPEntryAlreadyExistsResult:
|
||||
raise GroupAlreadyExistsError()
|
||||
|
||||
def get_expiring_users(self) -> List[IUser]:
|
||||
query = []
|
||||
|
||||
term = Term.current()
|
||||
query.append(f'term={term}')
|
||||
query.append(f'nonMemberTerm={term}')
|
||||
|
||||
# Include last term too if the new term just started
|
||||
dt = datetime.datetime.now()
|
||||
if dt.month == term.start_month():
|
||||
last_term = term - 1
|
||||
query.append(f'term={last_term}')
|
||||
query.append(f'nonMemberTerm={last_term}')
|
||||
|
||||
query = '(!(|(' + ')('.join(query) + ')))'
|
||||
|
||||
conn = self._get_ldap_conn()
|
||||
conn.search(
|
||||
self.ldap_users_base,
|
||||
query,
|
||||
attributes=ldap3.ALL_ATTRIBUTES,
|
||||
search_scope=ldap3.LEVEL)
|
||||
return [User.deserialize_from_ldap(entry) for entry in conn.entries]
|
||||
|
||||
@contextlib.contextmanager
|
||||
def entry_ctx_for_group(self, group: IGroup):
|
||||
entry = self._get_writable_entry_for_group(group)
|
||||
|
|
|
@ -33,6 +33,7 @@ class User:
|
|||
is_club_rep: Union[bool, None] = None,
|
||||
is_club: bool = False,
|
||||
ldap3_entry: Union[ldap3.Entry, None] = None,
|
||||
expired: bool = False,
|
||||
):
|
||||
cfg = component.getUtility(IConfig)
|
||||
|
||||
|
@ -66,6 +67,7 @@ class User:
|
|||
else:
|
||||
self.is_club_rep = is_club_rep
|
||||
self.ldap3_entry = ldap3_entry
|
||||
self.expired = expired
|
||||
|
||||
self.ldap_srv = component.getUtility(ILDAPService)
|
||||
self.krb_srv = component.getUtility(IKerberosService)
|
||||
|
@ -82,6 +84,7 @@ class User:
|
|||
'is_club': self.is_club(),
|
||||
'is_club_rep': self.is_club_rep,
|
||||
'program': self.program or 'Unknown',
|
||||
'expired': self.expired,
|
||||
}
|
||||
if self.sn and self.given_name:
|
||||
data['sn'] = self.sn
|
||||
|
@ -155,6 +158,7 @@ class User:
|
|||
mail_local_addresses=attrs.get('mailLocalAddress'),
|
||||
is_club_rep=attrs.get('isClubRep', [False])[0],
|
||||
is_club=('club' in attrs['objectClass']),
|
||||
expired=attrs.get('shadowExpire', 0) != 0,
|
||||
ldap3_entry=entry,
|
||||
)
|
||||
|
||||
|
@ -205,3 +209,11 @@ class User:
|
|||
current_term = Term.current()
|
||||
most_recent_term = max(map(Term, self.terms))
|
||||
return most_recent_term >= current_term
|
||||
|
||||
def set_expired(self, expired: bool):
|
||||
with self.ldap_srv.entry_ctx_for_user(self) as entry:
|
||||
if expired:
|
||||
entry.shadowExpire = 1
|
||||
else:
|
||||
entry.shadowExpire.remove()
|
||||
self.expired = expired
|
||||
|
|
Loading…
Reference in New Issue