Commit 955d267e authored by Barry Warsaw's avatar Barry Warsaw

Replace flufl.password with passlib, albeit with a wrapper.

parent 3c8a07fc
......@@ -98,11 +98,11 @@ case second `m'. Any other spelling is incorrect.""",
'flufl.enum',
'flufl.i18n',
'flufl.lock',
'flufl.password',
'httplib2',
'lazr.config',
'lazr.smtptest',
'mock',
'passlib',
'restish',
'storm',
'zc.buildout',
......
......@@ -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
......
......@@ -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
......
......@@ -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')
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -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)
......
......@@ -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 = ''
......
......@@ -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.
......
......@@ -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
......
......@@ -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]
......
# 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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment