diff options
| author | Barry Warsaw | 2012-06-27 23:03:04 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2012-06-27 23:03:04 -0400 |
| commit | 955d267e5f4479cea6adb93871af1eb80aa492b6 (patch) | |
| tree | c3e412bcf0251ed6a53694d781d757ddd21b5ac7 /src | |
| parent | 3c8a07fc76176a8ea89ee6b73aef571d0b2c81ed (diff) | |
| download | mailman-955d267e5f4479cea6adb93871af1eb80aa492b6.tar.gz mailman-955d267e5f4479cea6adb93871af1eb80aa492b6.tar.zst mailman-955d267e5f4479cea6adb93871af1eb80aa492b6.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/membership.py | 9 | ||||
| -rw-r--r-- | src/mailman/app/subscriptions.py | 2 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_membership.py | 7 | ||||
| -rw-r--r-- | src/mailman/commands/cli_members.py | 2 | ||||
| -rw-r--r-- | src/mailman/config/schema.cfg | 5 | ||||
| -rw-r--r-- | src/mailman/model/docs/requests.rst | 2 | ||||
| -rw-r--r-- | src/mailman/rest/docs/users.rst | 14 | ||||
| -rw-r--r-- | src/mailman/rest/users.py | 6 | ||||
| -rw-r--r-- | src/mailman/rules/approved.py | 2 | ||||
| -rw-r--r-- | src/mailman/rules/docs/approved.rst | 5 | ||||
| -rw-r--r-- | src/mailman/rules/tests/test_approved.py | 13 | ||||
| -rw-r--r-- | src/mailman/testing/layers.py | 2 | ||||
| -rw-r--r-- | src/mailman/utilities/passwords.py | 57 |
13 files changed, 88 insertions, 38 deletions
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py index e31a1695c..e11cef44a 100644 --- a/src/mailman/app/membership.py +++ b/src/mailman/app/membership.py @@ -27,11 +27,9 @@ __all__ = [ from email.utils import formataddr -from flufl.password import lookup, make_secret from zope.component import getUtility from mailman.app.notifications import send_goodbye_message -from mailman.config import config from mailman.core.i18n import _ from mailman.email.message import OwnerNotification from mailman.interfaces.address import IEmailValidator @@ -40,6 +38,7 @@ from mailman.interfaces.member import ( MemberRole, MembershipIsBannedError, NotAMemberError) from mailman.interfaces.usermanager import IUserManager from mailman.utilities.i18n import make +from mailman.utilities.passwords import encrypt @@ -96,10 +95,8 @@ def add_member(mlist, email, display_name, password, delivery_mode, language, user.display_name = ( display_name if display_name else address.display_name) user.link(address) - # Encrypt the password using the currently selected scheme. The - # scheme is recorded in the hashed password string. - scheme = lookup(config.passwords.password_scheme.upper()) - user.password = make_secret(password, scheme) + # Encrypt the password using the currently selected hash scheme. + user.password = encrypt(password) user.preferences.preferred_language = language member = mlist.subscribe(address, role) member.preferences.delivery_mode = delivery_mode diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py index ebbe14492..7949f83ee 100644 --- a/src/mailman/app/subscriptions.py +++ b/src/mailman/app/subscriptions.py @@ -26,8 +26,8 @@ __all__ = [ ] -from flufl.password import generate from operator import attrgetter +from passlib.utils import generate_password as generate from storm.expr import And, Or from uuid import UUID from zope.component import getUtility diff --git a/src/mailman/app/tests/test_membership.py b/src/mailman/app/tests/test_membership.py index 74e15d0e2..c3c310b6c 100644 --- a/src/mailman/app/tests/test_membership.py +++ b/src/mailman/app/tests/test_membership.py @@ -134,7 +134,7 @@ class AddMemberTest(unittest.TestCase): self.assertEqual(member.address.email, 'aperson@example.com') self.assertEqual(member.mailing_list, 'test@example.com') self.assertEqual(member.role, MemberRole.moderator) - + def test_add_member_twice(self): # Adding a member with the same role twice causes an # AlreadySubscribedError to be raised. @@ -182,7 +182,7 @@ class AddMemberPasswordTest(unittest.TestCase): # inappropriate for unit tests. config.push('password scheme', """ [passwords] - password_scheme: sha + password_scheme: passlib.hash.sha1_crypt """) def tearDown(self): @@ -195,4 +195,5 @@ class AddMemberPasswordTest(unittest.TestCase): 'Anne Person', 'abc', DeliveryMode.regular, system_preferences.preferred_language) self.assertEqual( - member.user.password, '{SHA}qZk-NkcGgWq6PiVxeFDCbJzQ2J0=') + member.user.password, + '{sha1_crypt}$sha1$40000$$nY5NBnPWWAD5KI4X8Jjzp7.1YhV6') diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py index aef3991d8..7c5d3b8f3 100644 --- a/src/mailman/commands/cli_members.py +++ b/src/mailman/commands/cli_members.py @@ -29,8 +29,8 @@ import sys import codecs from email.utils import formataddr, parseaddr -from flufl.password import generate from operator import attrgetter +from passlib.utils import generate_password as generate from zope.component import getUtility from zope.interface import implementer diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index 00b8d9325..8d17d806c 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -156,8 +156,9 @@ wait: 10s [passwords] # The default scheme to use to encrypt new passwords. Existing passwords # include the scheme that was used to encrypt them, so it's okay to change -# this after users have been added. -password_scheme: ssha +# this after users have been added. This is the path to a passlib hash +# algorithm. See http://packages.python.org/passlib/lib/passlib.hash.html +password_scheme: passlib.hash.pbkdf2_sha512 # When Mailman generates them, this is the default length of passwords. password_length: 8 diff --git a/src/mailman/model/docs/requests.rst b/src/mailman/model/docs/requests.rst index a20823a91..a51cbc099 100644 --- a/src/mailman/model/docs/requests.rst +++ b/src/mailman/model/docs/requests.rst @@ -696,7 +696,7 @@ Frank Person is now a member of the mailing list. >>> print member.user.display_name Frank Person >>> print member.user.password - {CLEARTEXT}abcxyz + {plaintext}abcxyz Holding unsubscription requests diff --git a/src/mailman/rest/docs/users.rst b/src/mailman/rest/docs/users.rst index a20306e17..cdede10ee 100644 --- a/src/mailman/rest/docs/users.rst +++ b/src/mailman/rest/docs/users.rst @@ -94,7 +94,7 @@ It is also available via the location given in the response. created_on: 2005-08-01T07:49:23 display_name: Bart Person http_etag: "..." - password: {CLEARTEXT}bbb + password: {plaintext}bbb self_link: http://localhost:9001/3.0/users/3 user_id: 3 @@ -105,7 +105,7 @@ them with user ids. Thus, a user can be retrieved via its email address. created_on: 2005-08-01T07:49:23 display_name: Bart Person http_etag: "..." - password: {CLEARTEXT}bbb + password: {plaintext}bbb self_link: http://localhost:9001/3.0/users/3 user_id: 3 @@ -129,7 +129,7 @@ therefore cannot be retrieved. It can be reset though. created_on: 2005-08-01T07:49:23 display_name: Cris Person http_etag: "..." - password: {CLEARTEXT}... + password: {plaintext}... self_link: http://localhost:9001/3.0/users/4 user_id: 4 @@ -227,7 +227,7 @@ In fact, any of these addresses can be used to look up Bart's user record. created_on: 2005-08-01T07:49:23 display_name: Bart Person http_etag: "..." - password: {CLEARTEXT}bbb + password: {plaintext}bbb self_link: http://localhost:9001/3.0/users/3 user_id: 3 @@ -235,7 +235,7 @@ In fact, any of these addresses can be used to look up Bart's user record. created_on: 2005-08-01T07:49:23 display_name: Bart Person http_etag: "..." - password: {CLEARTEXT}bbb + password: {plaintext}bbb self_link: http://localhost:9001/3.0/users/3 user_id: 3 @@ -243,7 +243,7 @@ In fact, any of these addresses can be used to look up Bart's user record. created_on: 2005-08-01T07:49:23 display_name: Bart Person http_etag: "..." - password: {CLEARTEXT}bbb + password: {plaintext}bbb self_link: http://localhost:9001/3.0/users/3 user_id: 3 @@ -251,6 +251,6 @@ In fact, any of these addresses can be used to look up Bart's user record. created_on: 2005-08-01T07:49:23 display_name: Bart Person http_etag: "..." - password: {CLEARTEXT}bbb + password: {plaintext}bbb self_link: http://localhost:9001/3.0/users/3 user_id: 3 diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py index 4e1362120..6bab7c789 100644 --- a/src/mailman/rest/users.py +++ b/src/mailman/rest/users.py @@ -26,7 +26,7 @@ __all__ = [ ] -from flufl.password import lookup, make_secret, generate +from passlib.utils import generate_password as generate from restish import http, resource from uuid import UUID from zope.component import getUtility @@ -38,6 +38,7 @@ from mailman.rest.addresses import UserAddresses from mailman.rest.helpers import CollectionMixin, etag, no_content, path_to from mailman.rest.preferences import Preferences from mailman.rest.validator import Validator +from mailman.utilities.passwords import encrypt @@ -102,8 +103,7 @@ class AllUsers(_UserBase): if password is None: # This will have to be reset since it cannot be retrieved. password = generate(int(config.passwords.password_length)) - scheme = lookup(config.passwords.password_scheme.upper()) - user.password = make_secret(password, scheme) + user.password = encrypt(password) location = path_to('users/{0}'.format(user.user_id.int)) return http.created(location, [], None) diff --git a/src/mailman/rules/approved.py b/src/mailman/rules/approved.py index 1f4fc1369..dadc25322 100644 --- a/src/mailman/rules/approved.py +++ b/src/mailman/rules/approved.py @@ -28,11 +28,11 @@ __all__ = [ import re from email.iterators import typed_subpart_iterator -from flufl.password import verify from zope.interface import implementer from mailman.core.i18n import _ from mailman.interfaces.rules import IRule +from mailman.utilities.passwords import verify EMPTYSTRING = '' diff --git a/src/mailman/rules/docs/approved.rst b/src/mailman/rules/docs/approved.rst index 9c61a7419..7f0714e17 100644 --- a/src/mailman/rules/docs/approved.rst +++ b/src/mailman/rules/docs/approved.rst @@ -20,9 +20,8 @@ which is shared among all the administrators. This password will not be stored in clear text, so it must be hashed using the configured hash protocol. - >>> from flufl.password import lookup, make_secret - >>> scheme = lookup(config.passwords.password_scheme.upper()) - >>> mlist.moderator_password = make_secret('super secret', scheme) + >>> from mailman.utilities.passwords import encrypt + >>> mlist.moderator_password = encrypt('super secret') The ``approved`` rule determines whether the message contains the proper approval or not. diff --git a/src/mailman/rules/tests/test_approved.py b/src/mailman/rules/tests/test_approved.py index d078556ba..de409f654 100644 --- a/src/mailman/rules/tests/test_approved.py +++ b/src/mailman/rules/tests/test_approved.py @@ -30,14 +30,12 @@ __all__ = [ import unittest -from flufl.password import lookup, make_secret - from mailman.app.lifecycle import create_list -from mailman.config import config from mailman.rules import approved from mailman.testing.helpers import ( specialized_message_from_string as mfs) from mailman.testing.layers import ConfigLayer +from mailman.utilities.passwords import encrypt @@ -48,8 +46,7 @@ class TestApproved(unittest.TestCase): def setUp(self): self._mlist = create_list('test@example.com') - scheme = lookup(config.passwords.password_scheme.upper()) - self._mlist.moderator_password = make_secret('super secret', scheme) + self._mlist.moderator_password = encrypt('super secret') self._rule = approved.Approved() self._msg = mfs("""\ From: anne@example.com @@ -150,8 +147,7 @@ class TestApprovedPseudoHeader(unittest.TestCase): def setUp(self): self._mlist = create_list('test@example.com') - scheme = lookup(config.passwords.password_scheme.upper()) - self._mlist.moderator_password = make_secret('super secret', scheme) + self._mlist.moderator_password = encrypt('super secret') self._rule = approved.Approved() self._msg = mfs("""\ From: anne@example.com @@ -283,8 +279,7 @@ class TestApprovedPseudoHeaderMIME(unittest.TestCase): def setUp(self): self._mlist = create_list('test@example.com') - scheme = lookup(config.passwords.password_scheme.upper()) - self._mlist.moderator_password = make_secret('super secret', scheme) + self._mlist.moderator_password = encrypt('super secret') self._rule = approved.Approved() self._msg_text_template = """\ From: anne@example.com diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 0faa1c8e4..7a019f53a 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -118,7 +118,7 @@ class ConfigLayer(MockAndMonkeyLayer): [mailman] layout: testing [passwords] - password_scheme: cleartext + password_scheme: passlib.hash.plaintext [paths.testing] var_dir: %s [devmode] diff --git a/src/mailman/utilities/passwords.py b/src/mailman/utilities/passwords.py new file mode 100644 index 000000000..b9981f057 --- /dev/null +++ b/src/mailman/utilities/passwords.py @@ -0,0 +1,57 @@ +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""A wrapper around passlib.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'encrypt', + 'verify', + ] + + +import re + +from passlib.registry import get_crypt_handler + +from mailman.config import config +from mailman.testing import layers +from mailman.utilities.modules import find_name + +SCHEME_RE = r'{(?P<scheme>[^}]+?)}(?P<rest>.*)'.encode() + + + +def encrypt(secret): + hasher = find_name(config.passwords.password_scheme) + # For reproducibility, don't use any salt in the test suite. + kws = {} + if layers.is_testing and 'salt' in hasher.setting_kwds: + kws['salt'] = b'' + hashed = hasher.encrypt(secret, **kws) + return b'{{{0}}}{1}'.format(hasher.name, hashed) + + +def verify(hashed, password): + mo = re.match(SCHEME_RE, hashed, re.IGNORECASE) + if not mo: + return False + scheme, secret = mo.groups(('scheme', 'rest')) + hasher = get_crypt_handler(scheme) + return hasher.verify(password, secret) |
