add MailService and MailmanService
This commit is contained in:
parent
de0f473881
commit
3b78b7ffb4
|
@ -1,4 +0,0 @@
|
|||
[DEFAULT]
|
||||
sign-tags = True
|
||||
posttag = git push /users/git/public/pyceo.git --tags
|
||||
debian-tag=v%(version)s
|
|
@ -1,5 +1,3 @@
|
|||
/build-stamp
|
||||
/build
|
||||
*.pyc
|
||||
/build-ceo
|
||||
/build-ceod
|
||||
__pycache__/
|
||||
/venv/
|
||||
.vscode/
|
||||
|
|
40
bin/ceo
40
bin/ceo
|
@ -1,40 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import sys, ldap
|
||||
from getpass import getpass
|
||||
import ceo.urwid.main
|
||||
import ceo.console.main
|
||||
from ceo import ldapi, members
|
||||
|
||||
def start():
|
||||
try:
|
||||
if len(sys.argv) == 1:
|
||||
print "Reading config file...",
|
||||
members.configure()
|
||||
|
||||
print "Connecting to LDAP..."
|
||||
members.connect(AuthCallback())
|
||||
|
||||
ceo.urwid.main.start()
|
||||
else:
|
||||
members.configure()
|
||||
members.connect(AuthCallback())
|
||||
ceo.console.main.start()
|
||||
except ldap.LOCAL_ERROR, e:
|
||||
print ldapi.format_ldaperror(e)
|
||||
except ldap.INSUFFICIENT_ACCESS, e:
|
||||
print ldapi.format_ldaperror(e)
|
||||
print "You probably aren't permitted to do whatever you just tried."
|
||||
print "Admittedly, ceo probably shouldn't have crashed either."
|
||||
|
||||
class AuthCallback:
|
||||
def callback(self, error):
|
||||
try:
|
||||
print "Password: ",
|
||||
return getpass("")
|
||||
except KeyboardInterrupt:
|
||||
print ""
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
5
build.sh
5
build.sh
|
@ -1,5 +0,0 @@
|
|||
if test -e .git; then
|
||||
git-buildpackage --git-ignore-new -us -uc
|
||||
else
|
||||
debuild -us -uc
|
||||
fi
|
|
@ -0,0 +1,24 @@
|
|||
from typing import List
|
||||
|
||||
from zope.interface import Interface
|
||||
|
||||
|
||||
class IFileService(Interface):
|
||||
"""
|
||||
A service which can access, create and modify files on the
|
||||
NFS users' directory.
|
||||
"""
|
||||
|
||||
def create_home_dir(username: str, is_club: bool = False):
|
||||
"""
|
||||
Create a new home dir for the given user or club.
|
||||
"""
|
||||
|
||||
def get_forwarding_addresses(username: str) -> List[str]:
|
||||
"""
|
||||
Get the contents of the user's ~/.forward file,
|
||||
one line at a time.
|
||||
"""
|
||||
|
||||
def set_forwarding_addresses(username: str, addresses: List[str]):
|
||||
"""Set the contents of the user's ~/.forward file."""
|
|
@ -25,14 +25,14 @@ class IGroup(Interface):
|
|||
def get_members() -> List[IUser]:
|
||||
"""Get a list of the members in this group."""
|
||||
|
||||
def serialize_for_modlist() -> Dict:
|
||||
def serialize_for_modlist() -> Dict[str, List[bytes]]:
|
||||
"""
|
||||
Serialize this group into a dict to be passed to
|
||||
ldap.modlist.addModlist().
|
||||
"""
|
||||
|
||||
# static method
|
||||
def deserialize_from_dict(data: Dict):
|
||||
def deserialize_from_dict(data: Dict[str, List[bytes]]):
|
||||
"""Deserialize this group from a dict returned by ldap.search_s().
|
||||
|
||||
:returns: IGroup
|
||||
|
|
|
@ -10,18 +10,18 @@ class ILDAPService(Interface):
|
|||
def get_user(username: str) -> IUser:
|
||||
"""Retrieve the user with the given username."""
|
||||
|
||||
def save_user(user: IUser) -> IUser:
|
||||
def add_user(user: IUser) -> IUser:
|
||||
"""
|
||||
Save the user in the database.
|
||||
Add the user to the database.
|
||||
A new UID and GID will be generated and returned in the new user.
|
||||
"""
|
||||
|
||||
def get_group(cn: str, is_club: bool = False) -> IGroup:
|
||||
"""Retrieve the group with the given cn (Unix group name)."""
|
||||
|
||||
def save_group(group: IGroup) -> IGroup:
|
||||
def add_group(group: IGroup) -> IGroup:
|
||||
"""
|
||||
Save the group in the database.
|
||||
Add the group to the database.
|
||||
The GID will not be changed and must be valid.
|
||||
"""
|
||||
|
||||
|
@ -30,3 +30,6 @@ class ILDAPService(Interface):
|
|||
|
||||
def modify_group(old_group: IGroup, new_group: IGroup):
|
||||
"""Replace old_group with new_group."""
|
||||
|
||||
def add_sudo_role(uid: str):
|
||||
"""Create a sudo role for the club with this UID."""
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from typing import Dict
|
||||
|
||||
from zope.interface import Interface
|
||||
|
||||
from .IUser import IUser
|
||||
|
||||
|
||||
class IMailService(Interface):
|
||||
"""An interface to send email messages."""
|
||||
|
||||
def send(_from: str, to: str, headers: Dict[str, str], content: str):
|
||||
"""Send a message with the given headers and content."""
|
||||
|
||||
def send_welcome_message_to(user: IUser):
|
||||
"""Send a welcome message to the new member."""
|
|
@ -0,0 +1,11 @@
|
|||
from zope.interface import Interface
|
||||
|
||||
|
||||
class IMailmanService(Interface):
|
||||
"""A service to susbcribe and unsubscribe people from mailing lists."""
|
||||
|
||||
def subscribe(address: str, mailing_list: str):
|
||||
"""Subscribe the email address to the mailing list."""
|
||||
|
||||
def unsubscribe(address: str, mailing_list: str):
|
||||
"""Unsubscribe the email address from the mailing list."""
|
|
@ -0,0 +1,15 @@
|
|||
from typing import Union
|
||||
|
||||
from zope.interface import Interface
|
||||
|
||||
|
||||
class IUWLDAPService(Interface):
|
||||
"""Represents the UW LDAP database."""
|
||||
|
||||
def get(username: str):
|
||||
"""
|
||||
Return the LDAP record for the given user, or
|
||||
None if no such record exists.
|
||||
|
||||
:rtype: Union[UWLDAPRecord, None]
|
||||
"""
|
|
@ -24,6 +24,12 @@ class IUser(Interface):
|
|||
# Non-LDAP attributes
|
||||
forwarding_addresses = Attribute('list of email forwarding addresses')
|
||||
|
||||
def get_forwarding_addresses(self) -> List[str]:
|
||||
"""Get the forwarding addresses for this user."""
|
||||
|
||||
def set_forwarding_addresses(self, addresses: List[str]):
|
||||
"""Set the forwarding addresses for this user."""
|
||||
|
||||
def is_club() -> bool:
|
||||
"""
|
||||
Returns True if this is the Unix user for a club.
|
||||
|
@ -31,7 +37,10 @@ class IUser(Interface):
|
|||
"""
|
||||
|
||||
def add_to_ldap():
|
||||
"""Add a new record to LDAP for this user."""
|
||||
"""
|
||||
Add a new record to LDAP for this user.
|
||||
A new UID number and GID number will be created.
|
||||
"""
|
||||
|
||||
def add_to_kerberos(password: str):
|
||||
"""Add a new Kerberos principal for this user."""
|
||||
|
@ -51,14 +60,17 @@ class IUser(Interface):
|
|||
def change_password(password: str):
|
||||
"""Replace the user's password."""
|
||||
|
||||
def serialize_for_modlist() -> Dict:
|
||||
def create_home_dir():
|
||||
"""Create a new home directory for this user."""
|
||||
|
||||
def serialize_for_modlist() -> Dict[str, List[bytes]]:
|
||||
"""
|
||||
Serialize this user into a dict to be passed to
|
||||
ldap.modlist.addModlist().
|
||||
"""
|
||||
|
||||
# static method
|
||||
def deserialize_from_dict(data: Dict):
|
||||
def deserialize_from_dict(data: Dict[str, List[bytes]]):
|
||||
"""Deserialize this user from a dict returned by ldap.search_s().
|
||||
|
||||
:returns: IUser
|
||||
|
|
|
@ -3,3 +3,7 @@ from .IConfig import IConfig
|
|||
from .IUser import IUser
|
||||
from .ILDAPService import ILDAPService
|
||||
from .IGroup import IGroup
|
||||
from .IFileService import IFileService
|
||||
from .IUWLDAPService import IUWLDAPService
|
||||
from .IMailService import IMailService
|
||||
from .IMailmanService import IMailmanService
|
||||
|
|
|
@ -9,15 +9,28 @@ class Config:
|
|||
_domain = 'csclub.internal'
|
||||
_ldap_base = ','.join(['dc=' + dc for dc in _domain.split('.')])
|
||||
_config = {
|
||||
'base_domain': _domain,
|
||||
'ldap_admin_principal': 'ceod/admin',
|
||||
'ldap_server_url': 'ldap://ldap-master.' + _domain,
|
||||
'ldap_users_base': 'ou=People,' + _ldap_base,
|
||||
'ldap_groups_base': 'ou=Group,' + _ldap_base,
|
||||
'ldap_sudo_base': 'ou=SUDOers,' + _ldap_base,
|
||||
'ldap_sasl_realm': _domain.upper(),
|
||||
'uwldap_server_url': 'ldap://uwldap.uwaterloo.ca',
|
||||
'uwldap_base': 'dc=uwaterloo,dc=ca',
|
||||
'member_min_id': 20001,
|
||||
'member_max_id': 29999,
|
||||
'club_min_id': 30001,
|
||||
'club_max_id': 39999,
|
||||
'member_home': '/users',
|
||||
'club_home': '/users',
|
||||
'member_home_skel': '/users/skel',
|
||||
'club_home_skel': '/users/skel',
|
||||
'smtp_url': 'smtp://mail.' + _domain,
|
||||
'smtp_starttls': False,
|
||||
'mailman3_api_base_url': 'http://localhost:8001/3.1',
|
||||
'mailman3_api_username': 'restadmin',
|
||||
'mailman3_api_password': 'mailman3',
|
||||
}
|
||||
|
||||
def get(self, key: str) -> str:
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import os
|
||||
import pwd
|
||||
import shutil
|
||||
from typing import List
|
||||
|
||||
from zope import component
|
||||
from zope.interface import implementer
|
||||
|
||||
from .validators import is_valid_forwarding_address, InvalidForwardingAddressException
|
||||
from ceo_common.interfaces import IFileService, IConfig
|
||||
|
||||
|
||||
@implementer(IFileService)
|
||||
class FileService:
|
||||
def __init__(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
self.member_home_skel = cfg.get('member_home_skel')
|
||||
self.club_home_skel = cfg.get('club_home_skel')
|
||||
|
||||
def create_home_dir(self, username: str, is_club: bool = False):
|
||||
if is_club:
|
||||
skel_dir = self.club_home_skel
|
||||
else:
|
||||
skel_dir = self.member_home_skel
|
||||
pwnam = pwd.getpwnam(username)
|
||||
home = pwnam.pw_dir
|
||||
uid = pwnam.pw_uid
|
||||
gid = pwnam.pw_gid
|
||||
# recursively copy skel dir to user's home
|
||||
shutil.copytree(skel_dir, home)
|
||||
# Set ownership and permissions on user's home.
|
||||
# The setgid bit ensures that all files created under that
|
||||
# directory belong to the owner (useful for clubs).
|
||||
os.chmod(home, mode=0o2751) # rwxr-s--x
|
||||
os.chown(home, uid=uid, gid=gid)
|
||||
# recursively set file ownership
|
||||
for root, dirs, files in os.walk(home):
|
||||
for dir in dirs:
|
||||
os.chown(os.path.join(root, dir), uid=uid, gid=gid)
|
||||
for file in files:
|
||||
os.chown(os.path.join(root, file), uid=uid, gid=gid)
|
||||
|
||||
def get_forwarding_addresses(self, username: str) -> List[str]:
|
||||
pwnam = pwd.getpwnam(username)
|
||||
home = pwnam.pw_dir
|
||||
forward_file = os.path.join(home, '.forward')
|
||||
if not os.path.isfile(forward_file):
|
||||
return []
|
||||
lines = [
|
||||
line.strip() for line in open(forward_file).readlines()
|
||||
]
|
||||
return [
|
||||
line for line in lines
|
||||
if line != '' and line[0] != '#'
|
||||
]
|
||||
|
||||
def set_forwarding_addresses(self, username: str, addresses: List[str]):
|
||||
for line in addresses:
|
||||
if not is_valid_forwarding_address(line):
|
||||
raise InvalidForwardingAddressException(line)
|
||||
pwnam = pwd.getpwnam(username)
|
||||
home = pwnam.pw_dir
|
||||
uid = pwnam.pw_uid
|
||||
gid = pwnam.pw_gid
|
||||
forward_file = os.path.join(home, '.forward')
|
||||
|
||||
if os.path.exists(forward_file):
|
||||
# create a backup
|
||||
backup_forward_file = forward_file + '.bak'
|
||||
shutil.copyfile(forward_file, backup_forward_file)
|
||||
os.chown(backup_forward_file, uid=uid, gid=gid)
|
||||
else:
|
||||
# create a new ~/.forward file
|
||||
open(forward_file, 'w')
|
||||
os.chown(forward_file, uid=uid, gid=gid)
|
||||
|
||||
with open(forward_file, 'w') as f:
|
||||
for line in addresses:
|
||||
f.write(line + '\n')
|
|
@ -40,7 +40,7 @@ class Group:
|
|||
def add_to_ldap(self):
|
||||
self.ldap_srv.add_group(self)
|
||||
|
||||
def serialize_for_modlist(self) -> Dict:
|
||||
def serialize_for_modlist(self) -> Dict[str, List[bytes]]:
|
||||
data = {
|
||||
'cn': [self.cn],
|
||||
'gidNumber': [str(self.gid_number)],
|
||||
|
@ -55,7 +55,7 @@ class Group:
|
|||
return strings_to_bytes(data)
|
||||
|
||||
@staticmethod
|
||||
def deserialize_from_dict(data: Dict) -> IGroup:
|
||||
def deserialize_from_dict(data: Dict[str, List[bytes]]) -> IGroup:
|
||||
data = bytes_to_strings(data)
|
||||
return Group(
|
||||
cn=data['cn'][0],
|
||||
|
|
|
@ -10,6 +10,7 @@ from zope.interface import implementer
|
|||
from ceo_common.interfaces import ILDAPService, IKerberosService, IConfig, IUser, IGroup
|
||||
from .User import User
|
||||
from .Group import Group
|
||||
from .SudoRole import SudoRole
|
||||
|
||||
|
||||
class UserNotFoundError:
|
||||
|
@ -34,6 +35,7 @@ class LDAPService:
|
|||
self.club_max_id = cfg.get('club_max_id')
|
||||
|
||||
def _get_ldap_conn(self, gssapi_bind: bool = True) -> ldap.ldapobject.LDAPObject:
|
||||
# TODO: cache the connection
|
||||
conn = ldap.initialize(self.ldap_server_url)
|
||||
if gssapi_bind:
|
||||
self._gssapi_bind(conn)
|
||||
|
@ -99,7 +101,13 @@ class LDAPService:
|
|||
return uid
|
||||
raise Exception('no UIDs remaining')
|
||||
|
||||
def save_user(self, user: IUser) -> IUser:
|
||||
def add_sudo_role(self, uid: str):
|
||||
conn = self._get_ldap_conn()
|
||||
sudo_role = SudoRole(uid)
|
||||
modlist = ldap.modlist.addModlist(sudo_role.serialize_for_modlist())
|
||||
conn.add_s(sudo_role.dn, modlist)
|
||||
|
||||
def add_user(self, user: IUser) -> IUser:
|
||||
if user.is_club():
|
||||
min_id, max_id = self.club_min_id, self.club_max_id
|
||||
else:
|
||||
|
@ -112,9 +120,10 @@ class LDAPService:
|
|||
|
||||
modlist = ldap.modlist.addModlist(new_user.serialize_for_modlist())
|
||||
conn.add_s(new_user.dn, modlist)
|
||||
|
||||
return new_user
|
||||
|
||||
def save_group(self, group: IGroup) -> IGroup:
|
||||
def add_group(self, group: IGroup) -> IGroup:
|
||||
conn = self._get_ldap_conn()
|
||||
# make sure that the caller initialized the GID number
|
||||
assert group.gid_number
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import datetime
|
||||
from email.message import EmailMessage
|
||||
import re
|
||||
import smtplib
|
||||
from typing import Dict
|
||||
|
||||
import jinja2
|
||||
from zope import component
|
||||
from zope.interface import implementer
|
||||
|
||||
from ceo_common.interfaces import IMailService, IConfig, IUser
|
||||
|
||||
smtp_url_re = re.compile(r'^(?P<scheme>smtps?)://(?P<host>[\w.-]+)(:(?P<port>\d+))?$')
|
||||
|
||||
|
||||
@implementer(IMailService)
|
||||
class MailService:
|
||||
def __init__(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
smtp_url = cfg.get('smtp_url')
|
||||
match = smtp_url_re.match(smtp_url)
|
||||
if match is None:
|
||||
raise Exception('Invalid SMTP URL: %s' % smtp_url)
|
||||
self.smtps = match.group('scheme') == 'smtps'
|
||||
self.host = match.group('host')
|
||||
self.port = int(match.group('port') or 25)
|
||||
self.starttls = cfg.get('smtp_starttls')
|
||||
assert not (self.smtps and self.starttls)
|
||||
self.base_domain = cfg.get('base_domain')
|
||||
self.jinja_env = jinja2.Environment(
|
||||
loader=jinja2.PackageLoader('ceod.model'),
|
||||
)
|
||||
|
||||
def send(self, _from: str, to: str, headers: Dict[str, str], content: str):
|
||||
msg = EmailMessage()
|
||||
msg.set_content(content)
|
||||
msg['From'] = _from
|
||||
msg['To'] = to
|
||||
msg['Date'] = datetime.datetime.now().astimezone().strftime('%a, %d %b %Y %H:%M:%S %z')
|
||||
for key, val in headers.items():
|
||||
msg[key] = val
|
||||
|
||||
if self.smtps:
|
||||
client = smtplib.SMTP_SSL(self.host, self.port)
|
||||
else:
|
||||
client = smtplib.SMTP(self.host, self.port)
|
||||
client.ehlo()
|
||||
if self.starttls:
|
||||
client.starttls()
|
||||
client.send_message(msg)
|
||||
client.quit()
|
||||
|
||||
def send_welcome_message_to(self, user: IUser):
|
||||
template = self.jinja_env.get_template('welcome_message.j2')
|
||||
# TODO: store surname and givenName in LDAP
|
||||
first_name = user.cn.split(' ', 1)[0]
|
||||
body = template.render(name=first_name, user=user.uid)
|
||||
self.send(
|
||||
f'Computer Science Club <exec@{self.base_domain}>',
|
||||
f'{user.cn} <{user.uid}@{self.base_domain}>',
|
||||
{'Subject': 'Welcome to the Computer Science Club'},
|
||||
body,
|
||||
)
|
|
@ -0,0 +1,51 @@
|
|||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from zope import component
|
||||
from zope.interface import implementer
|
||||
|
||||
from ceo_common.interfaces import IMailmanService, IConfig
|
||||
|
||||
|
||||
@implementer(IMailmanService)
|
||||
class MailmanService:
|
||||
def __init__(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
self.base_domain = cfg.get('base_domain')
|
||||
self.api_base_url = cfg.get('mailman3_api_base_url')
|
||||
self.api_username = cfg.get('mailman3_api_username')
|
||||
self.api_password = cfg.get('mailman3_api_password')
|
||||
|
||||
def subscribe(self, address: str, mailing_list: str):
|
||||
if '@' in mailing_list:
|
||||
mailing_list = mailing_list[:mailing_list.index('@')]
|
||||
if '@' not in address:
|
||||
address = f'{address}@{self.base_domain}'
|
||||
url = f'{self.api_base_url}/members'
|
||||
resp = requests.post(
|
||||
url,
|
||||
data={
|
||||
'list_id': f'{mailing_list}.{self.base_domain}',
|
||||
'subscriber': address,
|
||||
'pre_verified': 'True',
|
||||
'pre_confirmed': 'True',
|
||||
'pre_approved': 'True',
|
||||
},
|
||||
auth=HTTPBasicAuth(self.api_username, self.api_password),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
def unsubscribe(self, address: str, mailing_list: str):
|
||||
if '@' not in mailing_list:
|
||||
mailing_list = f'{mailing_list}@{self.base_domain}'
|
||||
if '@' not in address:
|
||||
address = f'{address}@{self.base_domain}'
|
||||
url = f'{self.api_base_url}/lists/{mailing_list}/member/{address}'
|
||||
resp = requests.delete(
|
||||
url,
|
||||
data={
|
||||
'pre_approved': 'True',
|
||||
'pre_confirmed': 'True',
|
||||
},
|
||||
auth=HTTPBasicAuth(self.api_username, self.api_password),
|
||||
)
|
||||
resp.raise_for_status()
|
|
@ -0,0 +1,31 @@
|
|||
from zope import component
|
||||
|
||||
from .utils import strings_to_bytes
|
||||
from ceo_common.interfaces import IConfig
|
||||
|
||||
|
||||
class SudoRole:
|
||||
"""Represents a sudoRole record in LDAP."""
|
||||
|
||||
def __init__(self, uid: str):
|
||||
cfg = component.getUtility(IConfig)
|
||||
ldap_sudo_base = cfg.get('ldap_sudo_base')
|
||||
|
||||
self.uid = uid
|
||||
self.dn = f'cn=%{uid},{ldap_sudo_base}'
|
||||
|
||||
def serialize_for_modlist(self):
|
||||
# TODO: use sudoOrder
|
||||
data = {
|
||||
'objectClass': [
|
||||
'top',
|
||||
'sudoRole',
|
||||
],
|
||||
'cn': '%' + self.uid,
|
||||
'sudoUser': '%' + self.uid,
|
||||
'sudoHost': 'ALL',
|
||||
'sudoCommand': 'ALL',
|
||||
'sudoOption': '!authenticate',
|
||||
'sudoRunAsUser': self.uid,
|
||||
}
|
||||
return strings_to_bytes(data)
|
|
@ -0,0 +1,30 @@
|
|||
from typing import List, Dict, Union
|
||||
|
||||
from .utils import bytes_to_strings
|
||||
|
||||
|
||||
class UWLDAPRecord:
|
||||
"""Represents a record from the UW LDAP."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
uid: str,
|
||||
program: Union[str, None],
|
||||
mail_local_addresses: List[str],
|
||||
):
|
||||
self.uid = uid
|
||||
self.program = program
|
||||
self.mail_local_addresses = mail_local_addresses
|
||||
|
||||
@staticmethod
|
||||
def deserialize_from_dict(self, data: Dict[str, List[bytes]]):
|
||||
"""
|
||||
Deserializes a dict returned from ldap.search_s() into a
|
||||
UWLDAPRecord.
|
||||
"""
|
||||
data = bytes_to_strings(data)
|
||||
return UWLDAPRecord(
|
||||
uid=data['uid'][0],
|
||||
program=data.get('ou', [None])[0],
|
||||
mail_local_addresses=data['mailLocalAddress'],
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
from typing import Union
|
||||
|
||||
import ldap
|
||||
from zope import component
|
||||
from zope.interface import implementer
|
||||
|
||||
from .UWLDAPRecord import UWLDAPRecord
|
||||
from ceo_common.interfaces import IUWLDAPService, IConfig
|
||||
|
||||
|
||||
@implementer(IUWLDAPService)
|
||||
class UWLDAPService:
|
||||
def __init__(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
self.uwldap_server_url = cfg.get('uwldap_server_url')
|
||||
self.uwldap_base = cfg.get('uwldap_base')
|
||||
|
||||
def get(self, username: str) -> Union[UWLDAPRecord, None]:
|
||||
conn = ldap.initialize(self.uwldap_server_url)
|
||||
results = conn.search_s(self.uwldap_base, ldap.SCOPE_SUBTREE, f'uid={username}')
|
||||
if not results:
|
||||
return None
|
||||
return UWLDAPRecord.deserialize_from_dict(results[0])
|
|
@ -6,7 +6,8 @@ from zope import component
|
|||
from zope.interface import implementer
|
||||
|
||||
from .utils import strings_to_bytes, bytes_to_strings
|
||||
from ceo_common.interfaces import ILDAPService, IKerberosService, IUser, IConfig
|
||||
from ceo_common.interfaces import ILDAPService, IKerberosService, IFileService, \
|
||||
IUser, IConfig
|
||||
|
||||
|
||||
@implementer(IUser)
|
||||
|
@ -26,6 +27,8 @@ class User:
|
|||
):
|
||||
if not is_club and not terms and not non_member_terms:
|
||||
raise Exception('terms and non_member_terms cannot both be empty')
|
||||
cfg = component.getUtility(IConfig)
|
||||
|
||||
self.uid = uid
|
||||
self.cn = cn
|
||||
self.program = program
|
||||
|
@ -34,20 +37,23 @@ class User:
|
|||
self.login_shell = login_shell
|
||||
self.uid_number = uid_number
|
||||
self.gid_number = gid_number
|
||||
self.home_directory = home_directory or os.path.join('/users', uid)
|
||||
if home_directory is None:
|
||||
if is_club:
|
||||
home_parent = cfg.get('member_home')
|
||||
else:
|
||||
home_parent = cfg.get('club_home')
|
||||
self.home_directory = os.path.join(home_parent, uid)
|
||||
else:
|
||||
self.home_directory = home_directory
|
||||
self.positions = positions or []
|
||||
self.mail_local_addresses = mail_local_addresses or []
|
||||
self._is_club = is_club
|
||||
|
||||
cfg = component.getUtility(IConfig)
|
||||
self.ldap_sasl_realm = cfg.get('ldap_sasl_realm')
|
||||
self.dn = f'uid={uid},{cfg.get("ldap_users_base")}'
|
||||
self.ldap_srv = component.getUtility(ILDAPService)
|
||||
self.krb_srv = component.getUtility(IKerberosService)
|
||||
|
||||
@property
|
||||
def forwarding_addresses(self):
|
||||
raise NotImplementedError()
|
||||
self.file_srv = component.getUtility(IFileService)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
lines = [
|
||||
|
@ -80,7 +86,7 @@ class User:
|
|||
return self._is_club
|
||||
|
||||
def add_to_ldap(self):
|
||||
new_member = self.ldap_srv.save_user(self)
|
||||
new_member = self.ldap_srv.add_user(self)
|
||||
self.uid_number = new_member.uid_number
|
||||
self.gid_number = new_member.gid_number
|
||||
|
||||
|
@ -90,6 +96,9 @@ class User:
|
|||
def change_password(self, password: str):
|
||||
self.krb_srv.change_password(self.uid, password)
|
||||
|
||||
def create_home_dir(self):
|
||||
self.file_srv.create_home_dir(self.uid, self._is_club)
|
||||
|
||||
def serialize_for_modlist(self) -> Dict:
|
||||
data = {
|
||||
'cn': [self.cn],
|
||||
|
@ -124,7 +133,7 @@ class User:
|
|||
return strings_to_bytes(data)
|
||||
|
||||
@staticmethod
|
||||
def deserialize_from_dict(data: Dict) -> IUser:
|
||||
def deserialize_from_dict(data: Dict[str, List[bytes]]) -> IUser:
|
||||
data = bytes_to_strings(data)
|
||||
return User(
|
||||
uid=data['uid'][0],
|
||||
|
@ -167,3 +176,11 @@ class User:
|
|||
new_user.positions.remove(position)
|
||||
self.ldap_srv.modify_user(self, new_user)
|
||||
self.positions = new_user.positions
|
||||
|
||||
def get_forwarding_addresses(self) -> List[str]:
|
||||
return self.file_srv.get_forwarding_addresses(self.uid)
|
||||
|
||||
def set_forwarding_addresses(self, addresses: List[str]):
|
||||
self.file_srv.set_forwarding_addresses(self.uid, addresses)
|
||||
|
||||
forwarding_addresses = property(get_forwarding_addresses, set_forwarding_addresses)
|
||||
|
|
|
@ -2,3 +2,9 @@ from .KerberosService import KerberosService
|
|||
from .LDAPService import LDAPService, UserNotFoundError, GroupNotFoundError
|
||||
from .User import User
|
||||
from .Group import Group
|
||||
from .UWLDAPService import UWLDAPService
|
||||
from .UWLDAPRecord import UWLDAPRecord
|
||||
from .FileService import FileService
|
||||
from .SudoRole import SudoRole
|
||||
from .MailService import MailService
|
||||
from .MailmanService import MailmanService
|
||||
|
|
44
etc/spam/new-member.d/welcome → ceod/model/templates/welcome_message.j2
Executable file → Normal file
44
etc/spam/new-member.d/welcome → ceod/model/templates/welcome_message.j2
Executable file → Normal file
|
@ -1,25 +1,4 @@
|
|||
#!/bin/bash -p
|
||||
|
||||
# This is a privileged script.
|
||||
IFS=$' \t\n'
|
||||
PATH=/usr/bin:/bin
|
||||
unset ENV BASH_ENV CDPATH
|
||||
umask 077
|
||||
|
||||
prog=$CEO_PROG
|
||||
auth=$CEO_AUTH
|
||||
|
||||
tmp="$(tempfile)"
|
||||
trap "rm $tmp" 0
|
||||
exec >"$tmp"
|
||||
|
||||
h_from="Computer Science Club <exec@csclub.uwaterloo.ca>"
|
||||
h_to="$CEO_NAME <$CEO_USER@csclub.uwaterloo.ca>"
|
||||
subj="Welcome to the Computer Science Club"
|
||||
|
||||
if test "$prog" = addmember; then
|
||||
user="$CEO_USER" name="$CEO_NAME"
|
||||
body="Hello $name:
|
||||
Hello {{ name }}:
|
||||
|
||||
Welcome to the Computer Science Club! We are pleased that you have chosen to join us. We welcome you to come out to our events, or just hang out in our office (MC 3036/3037). You have been automatically subscribed to our mailing list, csc-general, which we use to keep you informed of upcoming events.
|
||||
|
||||
|
@ -40,17 +19,17 @@ You can hear about upcoming events in a number of ways:
|
|||
Even when events aren't being held, you are welcome to hang out in the club office (MC 3036/3037, across the hall from MathSoc). It's often open late into the evening, and sells pop and snacks at reasonable prices. If you're so inclined, you are also welcome in our IRC channel, #csc on FreeNode.
|
||||
|
||||
|
||||
You now have a CSC user account with username \"$user\" and the password you supplied when you joined. You can use this account to log into almost any CSC system, including our office terminals and servers. A complete list is available at:
|
||||
You now have a CSC user account with username "{{ user }}" and the password you supplied when you joined. You can use this account to log into almost any CSC system, including our office terminals and servers. A complete list is available at:
|
||||
|
||||
http://wiki.csclub.uwaterloo.ca/Machine_List
|
||||
|
||||
You can connect remotely using SSH. On Windows, PuTTY is a popular SSH client; on Unix-like operating systems, you can connect with the 'ssh' command, like this:
|
||||
|
||||
ssh $user@corn-syrup.csclub.uwaterloo.ca
|
||||
ssh {{ user }}@corn-syrup.csclub.uwaterloo.ca
|
||||
|
||||
To use CSC web hosting, simply place files in the 'www' directory in your home directory. Files placed there will be available at:
|
||||
|
||||
http://csclub.uwaterloo.ca/~$user/
|
||||
http://csclub.uwaterloo.ca/~{{ user }}/
|
||||
|
||||
We support many server-side technologies, including PHP, Perl and Python. If you need a MySQL database, you can create one for yourself using the 'ceo' command-line tool.
|
||||
|
||||
|
@ -70,18 +49,3 @@ To contact the executive, email:
|
|||
Regards,
|
||||
|
||||
Computer Science Club Executive
|
||||
"
|
||||
elif [[ "$prog" = addclubrep || "$prog" = addclub ]]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "From: $h_from"
|
||||
echo "To: $h_to"
|
||||
echo "Subject: $subj"
|
||||
echo
|
||||
echo "$body" | fmt -s
|
||||
|
||||
exec >&2
|
||||
env - /usr/sbin/sendmail -t -f "exec@csclub.uwaterloo.ca" < "$tmp"
|
|
@ -0,0 +1,52 @@
|
|||
import inspect
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class InvalidUsernameException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidForwardingAddressException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
valid_username_re = re.compile(r'^[a-z][\w-]+$')
|
||||
|
||||
# Only allow usernames and email addresses to be set in ~/.forward
|
||||
valid_forwarding_address_re = re.compile(r'^[\w-]+|[\w.+-]+@[\w-]+(\.[\w-]+)*$')
|
||||
|
||||
|
||||
def is_valid_username(username: str) -> bool:
|
||||
"""Returns True if the username has a valid format."""
|
||||
return valid_username_re.match(username) is not None
|
||||
|
||||
|
||||
def check_valid_username(f: Callable) -> Callable:
|
||||
"""
|
||||
A decorator which raises an Exception if the username passed
|
||||
to f is invalid.
|
||||
f must accept `username` as a regular argument.
|
||||
"""
|
||||
argspec = inspect.getfullargspec(f)
|
||||
try:
|
||||
arg_idx = argspec.args.index('username')
|
||||
except ValueError:
|
||||
return f
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
username = None
|
||||
if arg_idx < len(args):
|
||||
username = args[arg_idx]
|
||||
elif 'username' in kwargs:
|
||||
username = kwargs['username']
|
||||
if username is not None and not is_valid_username(username):
|
||||
raise InvalidUsernameException(username)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def is_valid_forwarding_address(address: str) -> bool:
|
||||
"""Returns True if the address is a valid forwarding address."""
|
||||
return valid_forwarding_address_re.match(address) is not None
|
|
@ -1,9 +0,0 @@
|
|||
/ceo.substvars
|
||||
/ceo-common
|
||||
/ceo-clients
|
||||
/ceo-daemon
|
||||
/ceo-python
|
||||
/files
|
||||
/*.debhelper
|
||||
/*.debhelper.log
|
||||
/*.substvars
|
|
@ -1,2 +0,0 @@
|
|||
docs/addclub.1
|
||||
docs/addmember.1
|
|
@ -1 +0,0 @@
|
|||
etc/csc
|
|
@ -1 +0,0 @@
|
|||
etc/*.cf etc/ops etc/spam etc/csc
|
|
@ -1,55 +0,0 @@
|
|||
#! /bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: ceod
|
||||
# Required-Start: $remote_fs $syslog $network
|
||||
# Required-Stop: $remote_fs $syslog $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: CEO Daemon
|
||||
### END INIT INFO
|
||||
|
||||
set -e
|
||||
|
||||
test -x /usr/sbin/ceod || exit 0
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
log_daemon_msg "Starting CEO Daemon" "ceod"
|
||||
if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/ceod.pid --exec /usr/sbin/ceod -- -dq; then
|
||||
log_end_msg 0
|
||||
else
|
||||
log_end_msg 1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping CEO Daemon" "ceod"
|
||||
if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/ceod.pid; then
|
||||
log_end_msg 0
|
||||
else
|
||||
log_end_msg 1
|
||||
fi
|
||||
;;
|
||||
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting CEO Daemon" "ceod"
|
||||
start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/ceod.pid
|
||||
if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/ceod.pid --exec /usr/sbin/ceod -- -dq; then
|
||||
log_end_msg 0
|
||||
else
|
||||
log_end_msg 1
|
||||
fi
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p /var/run/ceod.pid /usr/sbin/ceod ceod && exit 0 || exit $?
|
||||
;;
|
||||
|
||||
*)
|
||||
log_action_msg "Usage: /etc/init.d/ceod {start|stop|force-reload|restart|status}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -1 +0,0 @@
|
|||
etc/ldap/schema
|
|
@ -1 +0,0 @@
|
|||
etc/csc.schema etc/ldap/schema
|
|
@ -1 +0,0 @@
|
|||
docs/ceod.8
|
|
@ -1 +0,0 @@
|
|||
docs/ceo.1
|
|
@ -1,739 +0,0 @@
|
|||
ceo (0.7.1-buster1) buster; urgency=medium
|
||||
|
||||
* Update mailman path to use virtualenv
|
||||
|
||||
-- Max Erenberg <merenber@csclub.uwaterloo.ca> Tue, 18 May 2021 01:45:49 -0400
|
||||
|
||||
ceo (0.7.0-buster1) buster; urgency=medium
|
||||
|
||||
* Set userPassword field in LDAP for SASL authentication
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Fri, 07 May 2021 21:44:02 -0400
|
||||
|
||||
ceo (0.6.0-buster1.2) buster; urgency=medium
|
||||
|
||||
* Decrease minimum username length from 3 to 2
|
||||
|
||||
-- Max Erenberg <merenber@csclub.uwaterloo.ca> Sun, 02 May 2021 18:02:31 -0400
|
||||
|
||||
ceo (0.6.0-buster1.1) buster; urgency=medium
|
||||
|
||||
* Use Mailman 3 instead of Mailman 2
|
||||
|
||||
-- Max Erenberg <merenber@csclub.uwaterloo.ca> Sun, 11 Apr 2021 21:54:06 -0400
|
||||
|
||||
ceo (0.6.0-stretch1) stretch; urgency=high
|
||||
|
||||
* Move adduser and mail operations to phosphoric-acid due to decommissioning
|
||||
of aspartame
|
||||
|
||||
* Packaging for stretch
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sun, 21 Mar 2021 23:04:05 -0400
|
||||
|
||||
ceo (0.6.0-buster1) buster; urgency=high
|
||||
|
||||
* Move adduser and mail operations to phosphoric-acid due to decommissioning
|
||||
of aspartame
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sun, 21 Mar 2021 22:39:05 -0400
|
||||
|
||||
ceo (0.5.28-bionic1.1) bionic; urgency=medium
|
||||
|
||||
* Packaging for bionic
|
||||
|
||||
-- Jennifer Zhou <c7zou@csclub.uwaterloo.ca> Sun, 21 Oct 2018 21:38:57 -0400
|
||||
|
||||
ceo (0.5.28-buster1) buster; urgency=medium
|
||||
|
||||
* Package for buster
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sun, 15 Apr 2018 14:31:08 -0400
|
||||
|
||||
ceo (0.5.28-xenial1) xenial; urgency=medium
|
||||
|
||||
* Build for xenial
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Tue, 02 May 2017 00:24:45 -0400
|
||||
|
||||
ceo (0.5.28-jessie1) jessie; urgency=medium
|
||||
|
||||
* Build for jessie
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Tue, 02 May 2017 00:16:31 -0400
|
||||
|
||||
ceo (0.5.28-stretch1) stretch; urgency=medium
|
||||
|
||||
* Check for host (IPv4 or IPV6) or MX record when verying valid email
|
||||
addresses
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Wed, 01 May 2017 13:07:21 -0500
|
||||
|
||||
ceo (0.5.27-stretch1) stretch; urgency=medium
|
||||
|
||||
* Build for stretch
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Wed, 11 Jan 2017 16:07:21 -0500
|
||||
|
||||
ceo (0.5.27jessie2) jessie; urgency=low
|
||||
|
||||
* Include library as a dependency
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sat, 20 Feb 2016 15:54:29 -0500
|
||||
|
||||
ceo (0.5.27trusty2) trusty; urgency=medium
|
||||
|
||||
* Include library as a dependency
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Sat, 20 Feb 2016 15:57:18 -0500
|
||||
|
||||
ceo (0.5.27trusty1) trusty; urgency=high
|
||||
|
||||
* Resolved issue from previous release which resulted in CEO not launching
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Fri, 19 Feb 2016 23:38:41 -0500
|
||||
|
||||
ceo (0.5.27jessie1) jessie; urgency=high
|
||||
|
||||
* Resolved issue from previous release which resulted in CEO not launching
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Fri, 19 Feb 2016 23:38:41 -0500
|
||||
|
||||
ceo (0.5.27jessie) jessie; urgency=medium
|
||||
|
||||
* "Library" now launches "librarian"
|
||||
|
||||
-- Felix Bauckholt <fbauckho@csclub.uwaterloo.ca> Fri, 19 Feb 2016 22:12:25 -0500
|
||||
|
||||
ceo (0.5.26trusty) trusty; urgency=medium
|
||||
|
||||
* "Library" now launches "librarian"
|
||||
|
||||
-- Felix Bauckholt <fbauckho@csclub.uwaterloo.ca> Fri, 19 Feb 2016 22:07:37 -0500
|
||||
|
||||
ceo (0.5.26) jessie; urgency=medium
|
||||
|
||||
* Repackage for jessie
|
||||
* Fix build for latest package versions
|
||||
|
||||
-- Zachary Seguin <ztseguin@csclub.uwaterloo.ca> Wed, 11 Nov 2015 22:39:49 -0500
|
||||
|
||||
ceo (0.5.25jessie0) jessie; urgency=low
|
||||
|
||||
* Replace mention of the safe with the cup.
|
||||
* Remind users that club accounts are free.
|
||||
|
||||
-- Sean Hunt <scshunt@csclub.uwaterloo.ca> Tue, 22 Jul 2014 14:20:16 -0400
|
||||
|
||||
ceo (0.5.24ubuntu5) saucy; urgency=low
|
||||
|
||||
* Packaging for saucy.
|
||||
|
||||
-- Sean Hunt <scshunt@csclub.uwaterloo.ca> Thu, 05 Dec 2013 15:59:17 -0500
|
||||
|
||||
ceo (0.5.24jessie0) jessie; urgency=low
|
||||
|
||||
* Packaging for jessie.
|
||||
|
||||
-- Luqman Aden <laden@csclub.uwaterloo.ca> Thu, 10 Oct 2013 21:51:26 -0400
|
||||
|
||||
ceo (0.5.24squeeze0) oldstable; urgency=low
|
||||
|
||||
* Rebuild for squeeze, since a wheezy package was accepted there by accident.
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Mon, 16 Sep 2013 08:33:58 -0400
|
||||
|
||||
ceo (0.5.24) stable; urgency=low
|
||||
|
||||
* Fix bug introduced in Kerberos change.
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Mon, 16 Sep 2013 08:28:51 -0400
|
||||
|
||||
ceo (0.5.23) stable; urgency=low
|
||||
|
||||
* Stable is now wheezy; rebuild.
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sat, 07 Sep 2013 11:59:24 -0400
|
||||
|
||||
ceo (0.5.22) stable; urgency=low
|
||||
|
||||
* Drop support for Kerberos LDAP backend; this is not the current CSC setup.
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sat, 07 Sep 2013 11:45:33 -0400
|
||||
|
||||
ceo (0.5.21) testing; urgency=low
|
||||
|
||||
* Build with older protoc-c for compatibility with squeeze.
|
||||
|
||||
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Tue, 28 May 2013 11:14:36 -0400
|
||||
|
||||
ceo (0.5.20) testing; urgency=low
|
||||
|
||||
* Work around bug in libgssapi 2.0.25 present in wheezy.
|
||||
|
||||
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Tue, 28 May 2013 10:45:09 -0400
|
||||
|
||||
ceo (0.5.19ubuntu2) quantal; urgency=low
|
||||
|
||||
* Packaging for quantal.
|
||||
|
||||
-- Owen Michael Smith <omsmith@gwem.csclub.uwaterloo.ca> Sat, 25 May 2013 19:46:52 -0400
|
||||
|
||||
ceo (0.5.19ubuntu1) precise; urgency=low
|
||||
|
||||
* Added precise package with changes
|
||||
|
||||
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Wed, 06 Feb 2013 23:44:18 -0500
|
||||
|
||||
ceo (0.5.19) stable; urgency=low
|
||||
|
||||
* Updated mail, adduser host to be aspartame, not ginseng (following filesystem migration)
|
||||
|
||||
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Wed, 06 Feb 2013 23:36:46 -0500
|
||||
|
||||
ceo (0.5.18ubuntu1) precise; urgency=low
|
||||
|
||||
* Added precise package with changes.
|
||||
|
||||
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Wed, 12 Sep 2012 08:42:02 -0400
|
||||
|
||||
ceo (0.5.18) stable; urgency=low
|
||||
|
||||
* Updated mailman host to be mail, not caffeine (following mail container migration)
|
||||
|
||||
-- Sarah Harvey <sharvey@csclub.uwaterloo.ca> Mon, 10 Sep 2012 19:06:16 -0400
|
||||
|
||||
ceo (0.5.17ubuntu2) precise; urgency=low
|
||||
|
||||
* Accidentally merged in broken changes. Fixing.
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Thu, 26 Apr 2012 15:19:03 -0400
|
||||
|
||||
ceo (0.5.17) stable; urgency=low
|
||||
|
||||
* Change behavior of ceod to add Kerberos principal,
|
||||
* as opposed to changing principal password.
|
||||
|
||||
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Fri, 16 Mar 2012 15:27:35 -0400
|
||||
|
||||
ceo (0.5.16) stable; urgency=low
|
||||
|
||||
* Fix CEO for CMC by allow mailman to be disabled.
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sat, 17 Sep 2011 16:36:01 -0400
|
||||
|
||||
ceo (0.5.14) stable; urgency=low
|
||||
|
||||
* Add support for sending a welcome message.
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Fri, 26 Aug 2011 00:59:08 -0400
|
||||
|
||||
ceo (0.5.13) stable; urgency=low
|
||||
|
||||
* Fix Mailman path
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Mon, 09 May 2011 19:12:09 -0400
|
||||
|
||||
ceo (0.5.12) stable; urgency=low
|
||||
|
||||
* Change sudoRunAs to sudoRunAsUser.
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sun, 13 Mar 2011 03:24:30 -0400
|
||||
|
||||
ceo (0.5.11) stable; urgency=low
|
||||
|
||||
* Fix library check in and search bug introduced in 0.5.9+nmu1.
|
||||
|
||||
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Fri, 04 Mar 2011 16:52:32 -0500
|
||||
|
||||
ceo (0.5.10) stable; urgency=low
|
||||
|
||||
* Fix squeeze build warnings
|
||||
* Add m4burns to debian/control
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Fri, 04 Mar 2011 00:47:09 -0500
|
||||
|
||||
ceo (0.5.9+nmu1) stable; urgency=low
|
||||
|
||||
* Non-maintainer upload.
|
||||
* Fix library book search page to display message when no books are found.
|
||||
|
||||
-- Marc Burns <m4burns@csclub.uwaterloo.ca> Mon, 28 Feb 2011 13:00:24 -0500
|
||||
|
||||
ceo (0.5.9) stable; urgency=low
|
||||
|
||||
* Fix build for squeeze.
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Thu, 14 Oct 2010 14:22:04 -0400
|
||||
|
||||
ceo (0.5.8+nmu1) stable; urgency=low
|
||||
|
||||
* fixed bug reported by jdonland
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sun, 26 Sep 2010 22:32:50 -0400
|
||||
|
||||
ceo (0.5.8) stable; urgency=low
|
||||
|
||||
* tab support in most forms (note that the tab key is already bound for the LDAP lookup fields)
|
||||
* new members can be added for multiple terms without going through renewal
|
||||
* fix for the squeeze version of urwid
|
||||
* new members are automatically added to csc-general
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Sat, 25 Sep 2010 01:04:02 -0400
|
||||
|
||||
ceo (0.5.7+nmu4) stable; urgency=low
|
||||
|
||||
* Non-maintainer upload.
|
||||
* add Office Manager position to positions list
|
||||
|
||||
-- Jeremy Roman <jbroman@csclub.uwaterloo.ca> Tue, 14 Sep 2010 18:19:50 -0400
|
||||
|
||||
ceo (0.5.7+nmu3) stable; urgency=low
|
||||
|
||||
* Added phpmyadmin to mysql info file generated by CEO
|
||||
|
||||
-- Michael Ellis <me@michaelellis.ca> Thu, 19 Aug 2010 14:06:16 -0400
|
||||
|
||||
ceo (0.5.7+nmu2) stable; urgency=low
|
||||
|
||||
* Removed the need for separate entries to manage office and syscom
|
||||
* Added check to ensure group is valid
|
||||
|
||||
-- Michael Ellis <me@michaelellis.ca> Fri, 18 Jun 2010 21:29:48 -0400
|
||||
|
||||
ceo (0.5.7+nmu1) stable; urgency=low
|
||||
|
||||
* Non-maintainer upload.
|
||||
* Removed uwdir lookup for expired accounts emailing
|
||||
|
||||
-- Michael Ellis <m2ellis@caffeine.csclub.uwaterloo.ca> Tue, 18 May 2010 18:18:02 -0400
|
||||
|
||||
ceo (0.5.7) stable; urgency=low
|
||||
|
||||
[ Michael Spang ]
|
||||
* Fix expiredaccounts
|
||||
|
||||
[ Michael Ellis ]
|
||||
* Reworded expired account email. Club rep accounts can be renewed for
|
||||
free (as usual).
|
||||
|
||||
[ Michael Spang ]
|
||||
* Readd quota support
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sun, 09 May 2010 02:10:48 -0400
|
||||
|
||||
ceo (0.5.6) stable; urgency=low
|
||||
|
||||
[ Michael Spang ]
|
||||
* Fix use of freopen
|
||||
* Fix auth for mysql database creation
|
||||
|
||||
[ Jeremy Brandon Roman ]
|
||||
* added ability to use first letter of menu items
|
||||
|
||||
[ Michael Spang ]
|
||||
* Remove ternary operators
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sun, 20 Dec 2009 13:45:48 -0500
|
||||
|
||||
ceo (0.5.5) stable; urgency=low
|
||||
|
||||
* Add missing dependency on python-mysql
|
||||
* Add CLI version of mysql thing
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Mon, 02 Nov 2009 20:34:52 +0000
|
||||
|
||||
ceo (0.5.4) stable; urgency=low
|
||||
|
||||
* Switch from SCTP to TCP
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Mon, 02 Nov 2009 03:04:52 +0000
|
||||
|
||||
ceo (0.5.3) stable; urgency=low
|
||||
|
||||
* Fix gss error reporting bug
|
||||
* Clarify email forwarding upon renewal
|
||||
* Fail fast if not authenticated
|
||||
* Encrypt all post-auth ceoc<->ceod communication
|
||||
* Improve error handling when writing
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Sat, 24 Oct 2009 14:49:51 -0400
|
||||
|
||||
ceo (0.5.2) stable; urgency=low
|
||||
|
||||
* Clarify search operation in menu
|
||||
* Move some code
|
||||
* Fix segfault
|
||||
* Write mysql file to ~club
|
||||
* Kill mathsoclist
|
||||
* Blacklist orphaned/expired from updateprograms
|
||||
* Add status thing
|
||||
* Force redraw after status thing
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Wed, 16 Sep 2009 18:32:56 -0400
|
||||
|
||||
ceo (0.5.1) stable; urgency=low
|
||||
|
||||
* Add mysql magic.
|
||||
* Add email forwarding magic.
|
||||
* Labels on the menu.
|
||||
|
||||
-- Michael Spang <mspang@csclub.uwaterloo.ca> Wed, 09 Sep 2009 17:54:49 -0400
|
||||
|
||||
ceo (0.5.0) stable; urgency=low
|
||||
|
||||
* Add ceo daemon.
|
||||
|
||||
-- Michael Spang <mspang@uwaterloo.ca> Thu, 30 Jul 2009 00:19:42 -0400
|
||||
|
||||
ceo (0.4.24) stable; urgency=low
|
||||
|
||||
* Bump standards version.
|
||||
|
||||
-- Michael Spang <mspang@uwaterloo.ca> Wed, 29 Jul 2009 07:31:24 -0400
|
||||
|
||||
ceo (0.4.23) stable; urgency=low
|
||||
|
||||
* CEO library now only finds books that are signed out as being overdue.
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 11 Mar 2009 03:30:01 -0500
|
||||
|
||||
ceo (0.4.22) stable; urgency=low
|
||||
|
||||
* CEO now closes window when it should. (Sorry)
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 11 Mar 2009 02:25:01 -0500
|
||||
|
||||
ceo (0.4.21) stable; urgency=low
|
||||
|
||||
* CEO Library can now add boox.
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 11 Mar 2009 02:09:01 -0500
|
||||
|
||||
ceo (0.4.20) stable; urgency=low
|
||||
|
||||
* Update kadmin headers
|
||||
|
||||
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Tue, 24 Feb 2009 16:08:12 -0500
|
||||
|
||||
ceo (0.4.19) stable; urgency=low
|
||||
|
||||
* Rebuild for lenny.
|
||||
|
||||
-- Michael Spang <mspang@uwaterloo.ca> Tue, 17 Feb 2009 22:23:30 -0500
|
||||
|
||||
ceo (0.4.18) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Added new search function, and books now display due dates.
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 29 Jan 2009 01:04:00 -0500
|
||||
|
||||
ceo (0.4.17) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Books can now be returned!!! Yay!
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 23:42:00 -0500
|
||||
|
||||
ceo (0.4.16) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Fixed error in calling of members.current
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 22:40:00 -0500
|
||||
|
||||
ceo (0.4.15) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Fixed incorrect usage of members.registered in library
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 19:10:00 -0500
|
||||
|
||||
ceo (0.4.14) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Corrected members.registered() to account for
|
||||
non-existent members.
|
||||
* Corrected overdue search.
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 18:40:00 -0500
|
||||
|
||||
ceo (0.4.13) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Add user validation to library system
|
||||
* Add search function to library
|
||||
* Can search for overdue books.
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Thu, 15 Jan 2009 17:00:00 -0500
|
||||
|
||||
ceo (0.4.12) stable; urgency=low
|
||||
|
||||
[ Michael Gregson ]
|
||||
* Rewrite library system.
|
||||
* Support for book checkout and return on sqlobject backends
|
||||
* We dont die when not having LDAP to connect to.
|
||||
|
||||
-- Michael Gregson <mgregson@csclub.uwaterloo.ca> Wed, 14 Jan 2009 19:38:00 -0400
|
||||
|
||||
ceo (0.4.11) stable; urgency=low
|
||||
|
||||
[ David Bartley ]
|
||||
* Add library path to config
|
||||
|
||||
[ Nick Guenther ]
|
||||
* library backend, initial version
|
||||
* Library GUI is coming, but awkwardsadface
|
||||
* CEO notifies of it's connect attempt (since if LDAP is being sad
|
||||
then CEO hangs without any indication of why)
|
||||
* Search works whoooo
|
||||
* We've gone from not having a library, to having a basic library that
|
||||
almost works! There's kinks and the code could be cleaner in places,
|
||||
but it's a really decent start for only a day's work. yayyyy python
|
||||
|
||||
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Mon, 02 Jun 2008 23:49:09 -0400
|
||||
|
||||
ceo (0.4.10) stable; urgency=low
|
||||
|
||||
[ David Bartley ]
|
||||
* Always call deauth
|
||||
* Add configurable refquota support
|
||||
|
||||
[ Michael Spang ]
|
||||
* Auth as ceo/admin for zfsaddhomedir
|
||||
|
||||
-- David Bartley <dtbartle@csclub.uwaterloo.ca> Wed, 28 May 2008 02:01:53 -0400
|
||||