summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2012-06-27 23:03:04 -0400
committerBarry Warsaw2012-06-27 23:03:04 -0400
commit955d267e5f4479cea6adb93871af1eb80aa492b6 (patch)
treec3e412bcf0251ed6a53694d781d757ddd21b5ac7 /src
parent3c8a07fc76176a8ea89ee6b73aef571d0b2c81ed (diff)
downloadmailman-955d267e5f4479cea6adb93871af1eb80aa492b6.tar.gz
mailman-955d267e5f4479cea6adb93871af1eb80aa492b6.tar.zst
mailman-955d267e5f4479cea6adb93871af1eb80aa492b6.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/membership.py9
-rw-r--r--src/mailman/app/subscriptions.py2
-rw-r--r--src/mailman/app/tests/test_membership.py7
-rw-r--r--src/mailman/commands/cli_members.py2
-rw-r--r--src/mailman/config/schema.cfg5
-rw-r--r--src/mailman/model/docs/requests.rst2
-rw-r--r--src/mailman/rest/docs/users.rst14
-rw-r--r--src/mailman/rest/users.py6
-rw-r--r--src/mailman/rules/approved.py2
-rw-r--r--src/mailman/rules/docs/approved.rst5
-rw-r--r--src/mailman/rules/tests/test_approved.py13
-rw-r--r--src/mailman/testing/layers.py2
-rw-r--r--src/mailman/utilities/passwords.py57
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)