diff options
| author | Barry Warsaw | 2009-06-30 23:14:26 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2009-06-30 23:14:26 -0400 |
| commit | 80fa4158997a8d6aa02a281bb1732417bee1b8a9 (patch) | |
| tree | f58adb4d5003a04b10d6bf3a1aaa9081e18c10b1 /src | |
| parent | fc07eb2b464eaea1f3dcc9ce4d57343571e8527f (diff) | |
| download | mailman-80fa4158997a8d6aa02a281bb1732417bee1b8a9.tar.gz mailman-80fa4158997a8d6aa02a281bb1732417bee1b8a9.tar.zst mailman-80fa4158997a8d6aa02a281bb1732417bee1b8a9.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/Utils.py | 12 | ||||
| -rw-r--r-- | src/mailman/app/bounces.py | 11 | ||||
| -rw-r--r-- | src/mailman/app/commands.py | 2 | ||||
| -rw-r--r-- | src/mailman/app/finder.py | 3 | ||||
| -rw-r--r-- | src/mailman/app/lifecycle.py | 5 | ||||
| -rw-r--r-- | src/mailman/app/membership.py | 30 | ||||
| -rw-r--r-- | src/mailman/constants.py | 6 | ||||
| -rw-r--r-- | src/mailman/domain.py | 2 | ||||
| -rw-r--r-- | src/mailman/i18n.py | 38 | ||||
| -rw-r--r-- | src/mailman/inject.py | 4 | ||||
| -rw-r--r-- | src/mailman/interact.py | 33 | ||||
| -rw-r--r-- | src/mailman/options.py | 9 | ||||
| -rw-r--r-- | src/mailman/passwords.py | 80 | ||||
| -rw-r--r-- | src/mailman/version.py | 2 |
14 files changed, 198 insertions, 39 deletions
diff --git a/src/mailman/Utils.py b/src/mailman/Utils.py index 799703ba6..7f2fd2a8f 100644 --- a/src/mailman/Utils.py +++ b/src/mailman/Utils.py @@ -39,6 +39,7 @@ import random import logging import htmlentitydefs +# pylint: disable-msg=E0611,W0403 from email.errors import HeaderParseError from email.header import decode_header, make_header from lazr.config import as_boolean @@ -71,7 +72,8 @@ log = logging.getLogger('mailman.error') -# a much more naive implementation than say, Emacs's fill-paragraph! +# A much more naive implementation than say, Emacs's fill-paragraph! +# pylint: disable-msg=R0912 def wrap(text, column=70, honor_leading_ws=True): """Wrap and fill the text to the specified column. @@ -179,7 +181,7 @@ def Secure_MakeRandomPassword(length): try: fd = os.open('/dev/urandom', os.O_RDONLY) except OSError, e: - if e.errno <> errno.ENOENT: + if e.errno != errno.ENOENT: raise # We have no available source of cryptographically # secure random characters. Log an error and fallback @@ -246,7 +248,7 @@ def get_global_password(siteadmin=True): challenge = fp.read()[:-1] # strip off trailing nl fp.close() except IOError, e: - if e.errno <> errno.ENOENT: + if e.errno != errno.ENOENT: raise # It's okay not to have a site admin password return None @@ -378,7 +380,7 @@ def findtext(templatefile, raw_dict=None, raw=False, lang=None, mlist=None): fp = open(filename) raise OuterExit except IOError, e: - if e.errno <> errno.ENOENT: + if e.errno != errno.ENOENT: raise # Okay, it doesn't exist, keep looping fp = None @@ -391,7 +393,7 @@ def findtext(templatefile, raw_dict=None, raw=False, lang=None, mlist=None): filename = os.path.join(TEMPLATE_DIR, 'en', templatefile) fp = open(filename) except IOError, e: - if e.errno <> errno.ENOENT: + if e.errno != errno.ENOENT: raise # We never found the template. BAD! raise IOError(errno.ENOENT, 'No template file found', templatefile) diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py index cea91b2e2..3972e2e6d 100644 --- a/src/mailman/app/bounces.py +++ b/src/mailman/app/bounces.py @@ -30,7 +30,7 @@ from email.mime.message import MIMEMessage from email.mime.text import MIMEText from mailman.Utils import oneline -from mailman.email.message import Message, UserNotification +from mailman.email.message import UserNotification from mailman.i18n import _ log = logging.getLogger('mailman.config') @@ -38,6 +38,15 @@ log = logging.getLogger('mailman.config') def bounce_message(mlist, msg, e=None): + """Bounce the message back to the original author. + + :param mlist: The mailing list that the message was posted to. + :type mlist: `IMailingList` + :param msg: The original message. + :type msg: `email.message.Message` + :param e: Optional exception causing the bounce. + :type e: Exception + """ # Bounce a message back to the sender, with an error message if provided # in the exception argument. if msg.sender is None: diff --git a/src/mailman/app/commands.py b/src/mailman/app/commands.py index 7131defb4..a4867e7fc 100644 --- a/src/mailman/app/commands.py +++ b/src/mailman/app/commands.py @@ -40,5 +40,5 @@ def initialize(): verifyObject(IEmailCommand, command) assert command_class.name not in config.commands, ( 'Duplicate email command "{0}" found in {1}'.format( - command_class.name, module)) + command_class.name, command_class.__module__)) config.commands[command_class.name] = command_class() diff --git a/src/mailman/app/finder.py b/src/mailman/app/finder.py index 2632957b8..2190c761e 100644 --- a/src/mailman/app/finder.py +++ b/src/mailman/app/finder.py @@ -27,6 +27,7 @@ __all__ = [ import os import sys + from pkg_resources import resource_listdir @@ -45,7 +46,7 @@ def find_components(package, interface): # Find all rules found in all modules inside our package. for filename in resource_listdir(package, ''): basename, extension = os.path.splitext(filename) - if extension <> '.py': + if extension != '.py': continue module_name = '{0}.{1}'.format(package, basename) __import__(module_name, fromlist='*') diff --git a/src/mailman/app/lifecycle.py b/src/mailman/app/lifecycle.py index ff087a874..7fdbc9d89 100644 --- a/src/mailman/app/lifecycle.py +++ b/src/mailman/app/lifecycle.py @@ -17,7 +17,7 @@ """Application level list creation.""" -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ @@ -27,11 +27,9 @@ __all__ = [ import os -import sys import shutil import logging -from mailman import Utils from mailman.config import config from mailman.core import errors from mailman.email.validate import validate @@ -48,6 +46,7 @@ def create_list(fqdn_listname, owners=None): if owners is None: owners = [] validate(fqdn_listname) + # pylint: disable-msg=W0612 listname, domain = fqdn_listname.split('@', 1) if domain not in config.domains: raise errors.BadDomainSpecificationError(domain) diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py index 6681cdda3..4022d5727 100644 --- a/src/mailman/app/membership.py +++ b/src/mailman/app/membership.py @@ -17,7 +17,7 @@ """Application support for membership management.""" -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ @@ -33,7 +33,7 @@ from mailman import i18n from mailman.app.notifications import send_goodbye_message from mailman.config import config from mailman.core import errors -from mailman.email.message import Message, OwnerNotification +from mailman.email.message import OwnerNotification from mailman.email.validate import validate from mailman.interfaces.member import AlreadySubscribedError, MemberRole @@ -47,17 +47,17 @@ def add_member(mlist, address, realname, password, delivery_mode, language): The member's subscription must be approved by whatever policy the list enforces. - :param mlist: the mailing list to add the member to - :type mlist: IMailingList - :param address: the address to subscribe + :param mlist: The mailing list to add the member to. + :type mlist: `IMailingList` + :param address: The address to subscribe. :type address: string - :param realname: the subscriber's full name + :param realname: The subscriber's full name. :type realname: string - :param password: the subscriber's password + :param password: The subscriber's password. :type password: string - :param delivery_mode: the delivery mode the subscriber has chosen + :param delivery_mode: The delivery mode the subscriber has chosen. :type delivery_mode: DeliveryMode - :param language: the language that the subscriber is going to use + :param language: The language that the subscriber is going to use. :type language: string """ # Let's be extra cautious. @@ -104,6 +104,7 @@ def add_member(mlist, address, realname, password, delivery_mode, language): raise AssertionError( 'User should have had linked address: {0}'.format(address)) # Create the member and set the appropriate preferences. + # pylint: disable-msg=W0631 member = address_obj.subscribe(mlist, MemberRole.member) member.preferences.preferred_language = language member.preferences.delivery_mode = delivery_mode @@ -113,6 +114,17 @@ def add_member(mlist, address, realname, password, delivery_mode, language): def delete_member(mlist, address, admin_notif=None, userack=None): + """Delete a member right now. + + :param mlist: The mailing list to add the member to. + :type mlist: `IMailingList` + :param address: The address to subscribe. + :type address: string + :param admin_notif: Whether the list administrator should be notified that + this member was deleted. + :type admin_notif: bool, or None to let the mailing list's + `admin_notify_mchange` attribute decide. + """ if userack is None: userack = mlist.send_goodbye_msg if admin_notif is None: diff --git a/src/mailman/constants.py b/src/mailman/constants.py index 2cd48270c..73158d8c7 100644 --- a/src/mailman/constants.py +++ b/src/mailman/constants.py @@ -33,7 +33,13 @@ from mailman.interfaces.preferences import IPreferences +# pylint: disable-msg=W0232 +# no class __init__() +# pylint: disable-msg=R0903 +# too few public methods class SystemDefaultPreferences: + """The default system preferences.""" + implements(IPreferences) acknowledge_posts = False diff --git a/src/mailman/domain.py b/src/mailman/domain.py index 4a7bef755..39c27a029 100644 --- a/src/mailman/domain.py +++ b/src/mailman/domain.py @@ -59,6 +59,8 @@ class Domain: self.contact_address = (contact_address if contact_address is not None else 'postmaster@' + email_host) + # pylint: disable-msg=E1101 + # no netloc member; yes it does self.url_host = urlparse(self.base_url).netloc def confirm_address(self, token=''): diff --git a/src/mailman/i18n.py b/src/mailman/i18n.py index d06db4cb8..51330ef2e 100644 --- a/src/mailman/i18n.py +++ b/src/mailman/i18n.py @@ -46,10 +46,12 @@ MESSAGES_DIR = os.path.dirname(mailman.messages.__file__) class Template(string.Template): + """Match any attribute path.""" idpattern = r'[_a-z][_a-z0-9.]*' class attrdict(dict): + """Follow attribute paths.""" def __getitem__(self, key): parts = key.split('.') value = super(attrdict, self).__getitem__(parts.pop(0)) @@ -62,6 +64,12 @@ class attrdict(dict): def set_language(language_code=None): + """Set the global translation context from a language code. + + :param language_code: The two letter language code to set. + :type language_code: str + """ + # pylint: disable-msg=W0603 global _translation # gettext.translation() API requires None or a sequence. codes = (None if language_code is None else [language_code]) @@ -74,10 +82,21 @@ def set_language(language_code=None): def get_translation(): + """Return the global translation context. + + :return: The global translation context. + :rtype: `GNUTranslation` + """ return _translation def set_translation(translation): + """Set the global translation context. + + :param translation: The translation context. + :type translation: `GNUTranslation`. + """ + # pylint: disable-msg=W0603 global _translation _translation = translation @@ -92,7 +111,9 @@ class using_language: self._old_translation = _translation set_language(self._language_code) + # pylint: disable-msg=W0613 def __exit__(self, *exc_info): + # pylint: disable-msg=W0603 global _translation _translation = self._old_translation # Do not suppress exceptions. @@ -107,6 +128,13 @@ if _translation is None: def _(s): + """Translate the string. + + :param s: The string to transate + :type s: string + :return: The translated string + :rtype: string + """ if s == '': return '' assert s, 'Cannot translate: {0}'.format(s) @@ -122,6 +150,7 @@ def _(s): # original string is 1) locals dictionary, 2) globals dictionary. # # Get the frame of the caller. + # pylint: disable-msg=W0212 frame = sys._getframe(1) # A `safe' dictionary is used so we won't get an exception if there's a # missing key in the dictionary. @@ -144,6 +173,13 @@ def _(s): def ctime(date): + """Translate a ctime. + + :param date: The date to translate. + :type date: str or time float + :return: The translated date. + :rtype: string + """ # Don't make these module globals since we have to do runtime translation # of the strings anyway. daysofweek = [ @@ -156,6 +192,7 @@ def ctime(date): _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec') ] + # pylint: disable-msg=W0612 tzname = _('Server Local Time') if isinstance(date, str): try: @@ -189,6 +226,7 @@ def ctime(date): mon = i break else: + # pylint: disable-msg=W0612 year, mon, day, hh, mm, ss, wday, yday, dst = time.localtime(date) if dst in (0, 1): tzname = time.tzname[dst] diff --git a/src/mailman/inject.py b/src/mailman/inject.py index ac2072fa1..eec096066 100644 --- a/src/mailman/inject.py +++ b/src/mailman/inject.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. +"""Inject a message into a queue.""" + from __future__ import absolute_import, unicode_literals __metaclass__ = type @@ -24,6 +26,8 @@ __all__ = [ ] +# pylint doesn't understand absolute_import +# pylint: disable-msg=E0611,W0403 from email import message_from_string from email.utils import formatdate, make_msgid diff --git a/src/mailman/interact.py b/src/mailman/interact.py index a30f22cee..d3994eb14 100644 --- a/src/mailman/interact.py +++ b/src/mailman/interact.py @@ -33,19 +33,31 @@ DEFAULT_BANNER = object() def interact(upframe=True, banner=DEFAULT_BANNER, overrides=None): - # The interactive prompt's namespace - ns = dict() - # If uplocals is true, also populate the console's locals with the locals - # of the frame that called this function (i.e. one up from here). + """Start an interactive interpreter prompt. + + :param upframe: Whether or not to populate the interpreter's globals with + the locals from the frame that called this function. + :type upfframe: bool + :param banner: The banner to print before the interpreter starts. + :type banner: string + :param overrides: Additional interpreter globals to add. + :type overrides: dict + """ + # The interactive prompt's namespace. + namespace = dict() + # Populate the console's with the locals of the frame that called this + # function (i.e. one up from here). if upframe: + # pylint: disable-msg=W0212 frame = sys._getframe(1) - ns.update(frame.f_globals) - ns.update(frame.f_locals) + namespace.update(frame.f_globals) + namespace.update(frame.f_locals) if overrides is not None: - ns.update(overrides) - interp = code.InteractiveConsole(ns) - # Try to import the readline module, but don't worry if it's unavailable + namespace.update(overrides) + interp = code.InteractiveConsole(namespace) + # Try to import the readline module, but don't worry if it's unavailable. try: + # pylint: disable-msg=W0612 import readline except ImportError: pass @@ -54,8 +66,9 @@ def interact(upframe=True, banner=DEFAULT_BANNER, overrides=None): # than once, this could cause a problem. startup = os.environ.get('PYTHONSTARTUP') if startup: + # pylint: disable-msg=W0702 try: - execfile(startup, ns) + execfile(startup, namespace) except: pass # We don't want the funky console object in parentheses in the banner. diff --git a/src/mailman/options.py b/src/mailman/options.py index d18c78cea..41c3651e4 100644 --- a/src/mailman/options.py +++ b/src/mailman/options.py @@ -17,7 +17,7 @@ """Common argument parsing.""" -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ @@ -40,7 +40,9 @@ from mailman.version import MAILMAN_VERSION +# pylint: disable-msg=W0613 def check_unicode(option, opt, value): + """Check that the value is a unicode string.""" if isinstance(value, unicode): return value try: @@ -50,7 +52,9 @@ def check_unicode(option, opt, value): 'option {0}: Cannot decode: {1}'.format(opt, value)) +# pylint: disable-msg=W0613 def check_yesno(option, opt, value): + """Check that the value is 'yes' or 'no'.""" value = value.lower() if value not in ('yes', 'no', 'y', 'n'): raise OptionValueError('option {0}: invalid: {1}'.format(opt, value)) @@ -73,6 +77,7 @@ class SafeOptionParser(OptionParser): having to wrap the options in str() calls. """ def add_option(self, *args, **kwargs): + """See `OptionParser`.""" # Check to see if the first or first two options are unicodes and turn # them into 8-bit strings before calling the superclass's method. if len(args) == 0: @@ -150,6 +155,7 @@ class SingleMailingListOptions(Options): """A helper for specifying the mailing list on the command line.""" def add_options(self): + """See `Options`.""" self.parser.add_option( '-l', '--listname', type='unicode', help=_('The mailing list name')) @@ -160,6 +166,7 @@ class MultipleMailingListOptions(Options): """A helper for specifying multiple mailing lists on the command line.""" def add_options(self): + """See `Options`.""" self.parser.add_option( '-l', '--listname', default=[], action='append', dest='listnames', type='unicode', diff --git a/src/mailman/passwords.py b/src/mailman/passwords.py index a03ef8fdb..54482ca44 100644 --- a/src/mailman/passwords.py +++ b/src/mailman/passwords.py @@ -20,13 +20,13 @@ Represents passwords using RFC 2307 syntax (as best we can tell). """ -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ 'Schemes', - 'make_secret', 'check_response', + 'make_secret', ] @@ -47,71 +47,102 @@ ITERATIONS = 2000 +# pylint: disable-msg=W0232 class PasswordScheme: + """Password scheme base class.""" TAG = b'' @staticmethod def make_secret(password): - """Return the hashed password""" + """Return the hashed password. + + :param password: The clear text password. + :type password: string + :return: The encrypted password. + :rtype: string + """ raise NotImplementedError @staticmethod def check_response(challenge, response): - """Return True if response matches challenge. + """Check a response against a challenge. It is expected that the scheme specifier prefix is already stripped from the response string. + + :param challenge: The challenge. + :type challenge: string + :param response: The response. + :type response: string + :return: True if the response matches the challenge. + :rtype: bool """ raise NotImplementedError class NoPasswordScheme(PasswordScheme): + """A password scheme without passwords.""" + TAG = b'NONE' @staticmethod def make_secret(password): + """See `PasswordScheme`.""" return b'' + # pylint: disable-msg=W0613 @staticmethod def check_response(challenge, response): + """See `PasswordScheme`.""" return False class ClearTextPasswordScheme(PasswordScheme): + """A password scheme that stores clear text passwords.""" + TAG = b'CLEARTEXT' @staticmethod def make_secret(password): + """See `PasswordScheme`.""" return password @staticmethod def check_response(challenge, response): + """See `PasswordScheme`.""" return challenge == response class SHAPasswordScheme(PasswordScheme): + """A password scheme that encodes the password using SHA1.""" + TAG = b'SHA' @staticmethod def make_secret(password): + """See `PasswordScheme`.""" h = hashlib.sha1(password) return encode(h.digest()) @staticmethod def check_response(challenge, response): + """See `PasswordScheme`.""" h = hashlib.sha1(response) return challenge == encode(h.digest()) class SSHAPasswordScheme(PasswordScheme): + """A password scheme that encodes the password using salted SHA1.""" + TAG = b'SSHA' @staticmethod def make_secret(password): + """See `PasswordScheme`.""" salt = os.urandom(SALT_LENGTH) h = hashlib.sha1(password) h.update(salt) @@ -119,6 +150,7 @@ class SSHAPasswordScheme(PasswordScheme): @staticmethod def check_response(challenge, response): + """See `PasswordScheme`.""" # Get the salt from the challenge challenge_bytes = decode(challenge) digest = challenge_bytes[:20] @@ -131,6 +163,8 @@ class SSHAPasswordScheme(PasswordScheme): # Basic algorithm given by Bob Fleck class PBKDF2PasswordScheme(PasswordScheme): + """RFC 2989 password encoding scheme.""" + # This is a bit nasty if we wanted a different prf or iterations. OTOH, # we really have no clue what the standard LDAP-ish specification for # those options is. @@ -157,7 +191,9 @@ class PBKDF2PasswordScheme(PasswordScheme): @staticmethod def make_secret(password): - """From RFC2898 sec. 5.2. Simplified to handle only 20 byte output + """See `PasswordScheme`. + + From RFC2898 sec. 5.2. Simplified to handle only 20 byte output case. Output of 20 bytes means always exactly one block to handle, and a constant block counter appended to the salt in the initial hmac update. @@ -167,11 +203,13 @@ class PBKDF2PasswordScheme(PasswordScheme): derived_key = encode(digest + salt) return derived_key + # pylint: disable-msg=W0221 @staticmethod def check_response(challenge, response, prf, iterations): - # Decode the challenge to get the number of iterations and salt - # XXX we don't support anything but sha prf - if prf.lower() <> b'sha': + """See `PasswordScheme`.""" + # Decode the challenge to get the number of iterations and salt. We + # don't support anything but SHA PRF. + if prf.lower() != b'sha': return False try: iterations = int(iterations) @@ -186,6 +224,7 @@ class PBKDF2PasswordScheme(PasswordScheme): class Schemes(Enum): + """List of password schemes.""" # no_scheme is deliberately ugly because no one should be using it. Yes, # this makes cleartext inconsistent, but that's a common enough # terminology to justify the missing underscore. @@ -215,6 +254,15 @@ _DEFAULT_SCHEME = NoPasswordScheme def make_secret(password, scheme=None): + """Encrypt a password. + + :param password: The clear text password. + :type password: string + :param scheme: The password scheme name. + :type scheme: string + :return: The encrypted password. + :rtype: string + """ # The hash algorithms operate on bytes not strings. The password argument # as provided here by the client will be a string (in Python 2 either # unicode or 8-bit, in Python 3 always unicode). We need to encode this @@ -231,6 +279,15 @@ def make_secret(password, scheme=None): def check_response(challenge, response): + """Check a response against a challenge. + + :param challenge: The challenge. + :type challenge: string + :param response: The response. + :type response: string + :return: True if the response matches the challenge. + :rtype: bool + """ mo = re.match(r'{(?P<scheme>[^}]+?)}(?P<rest>.*)', challenge, re.IGNORECASE) if not mo: @@ -250,4 +307,11 @@ def check_response(challenge, response): def lookup_scheme(scheme_name): + """Look up a password scheme. + + :param scheme_name: The password scheme name. + :type scheme_name: string + :return: Password scheme class. + :rtype: `PasswordScheme` + """ return _SCHEMES_BY_TAG.get(scheme_name.lower()) diff --git a/src/mailman/version.py b/src/mailman/version.py index 68034239a..5c4a0d9c3 100644 --- a/src/mailman/version.py +++ b/src/mailman/version.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see <http://www.gnu.org/licenses/>. +"""Mailman version strings.""" + # Mailman version VERSION = "3.0.0a3" CODENAME = 'Working Man' |
