1.1 --- a/MoinMoin/_tests/test_user.py Sat Jan 19 04:23:42 2013 +0100
1.2 +++ b/MoinMoin/_tests/test_user.py Sun Jan 20 16:12:13 2013 +0100
1.3 @@ -1,6 +1,7 @@
1.4 # -*- coding: utf-8 -*-
1.5 # Copyright: 2003-2004 by Juergen Hermann <jh@web.de>
1.6 # Copyright: 2009 by ReimarBauer
1.7 +# Copyright: 2013 by ThomasWaldmann
1.8 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
1.9
1.10 """
1.11 @@ -38,9 +39,7 @@
1.12 assert u.exists()
1.13
1.14
1.15 -class TestLoginWithPassword(object):
1.16 - """user: login tests"""
1.17 -
1.18 +class TestUser(object):
1.19 def setup_method(self, method):
1.20 # Save original user
1.21 self.saved_user = flaskg.user
1.22 @@ -56,6 +55,8 @@
1.23 # Restore original user
1.24 flaskg.user = self.saved_user
1.25
1.26 + # Passwords / Login -----------------------------------------------
1.27 +
1.28 def testAsciiPassword(self):
1.29 """ user: login with ascii password """
1.30 # Create test user
1.31 @@ -78,21 +79,27 @@
1.32 theUser = user.User(name=name, password=password)
1.33 assert theUser.valid
1.34
1.35 - def test_login(self):
1.36 + def testPasswordHash(self):
1.37 """
1.38 - Create user with some password and check that user can login.
1.39 + Create user, set a specific pw hash and check that user can login
1.40 + with the correct password and can not log in with a wrong password.
1.41 """
1.42 # Create test user
1.43 name = u'Test User'
1.44 - password = '12345'
1.45 - salt = 'salt'
1.46 - pw_hash = ''
1.47 + # sha512_crypt passlib hash for '12345':
1.48 + pw_hash = '$6$rounds=1001$y9ObPHKb8cvRCs5G$39IW1i5w6LqXPRi4xqAu3OKv1UOpVKNkwk7zPnidsKZWqi1CrQBpl2wuq36J/s6yTxjCnmaGzv/2.dAmM8fDY/'
1.49 self.createUser(name, pw_hash, True)
1.50
1.51 - # Try to "login"
1.52 - theuser = user.User(name=name, password=password)
1.53 + # Try to "login" with correct password
1.54 + theuser = user.User(name=name, password='12345')
1.55 assert theuser.valid
1.56
1.57 + # Try to "login" with a wrong password
1.58 + theuser = user.User(name=name, password='wrong')
1.59 + assert not theuser.valid
1.60 +
1.61 + # Subscriptions ---------------------------------------------------
1.62 +
1.63 def testSubscriptionSubscribedPage(self):
1.64 """ user: tests is_subscribed_to """
1.65 pagename = u'HelpMiscellaneous'
2.1 --- a/MoinMoin/_tests/wikiconfig.py Sat Jan 19 04:23:42 2013 +0100
2.2 +++ b/MoinMoin/_tests/wikiconfig.py Sun Jan 20 16:12:13 2013 +0100
2.3 @@ -25,3 +25,12 @@
2.4 interwikiname = u'MoinTest'
2.5 interwiki_map = dict(Self='http://localhost:8080/', MoinMoin='http://moinmo.in/')
2.6 interwiki_map[interwikiname] = 'http://localhost:8080/'
2.7 +
2.8 + passlib_crypt_context = dict(
2.9 + schemes=["sha512_crypt", ],
2.10 + # for the tests, we don't want to have varying rounds
2.11 + sha512_crypt__vary_rounds=0,
2.12 + # for the tests, we want to have a rather low rounds count,
2.13 + # so the tests run quickly (do NOT use low counts in production!)
2.14 + sha512_crypt__default_rounds=1001,
2.15 + )
3.1 --- a/MoinMoin/apps/frontend/views.py Sat Jan 19 04:23:42 2013 +0100
3.2 +++ b/MoinMoin/apps/frontend/views.py Sun Jan 20 16:12:13 2013 +0100
3.3 @@ -1,5 +1,5 @@
3.4 # Copyright: 2012 MoinMoin:CheerXiao
3.5 -# Copyright: 2003-2010 MoinMoin:ThomasWaldmann
3.6 +# Copyright: 2003-2013 MoinMoin:ThomasWaldmann
3.7 # Copyright: 2011 MoinMoin:AkashSinha
3.8 # Copyright: 2011 MoinMoin:ReimarBauer
3.9 # Copyright: 2008 MoinMoin:FlorianKrupicka
3.10 @@ -1188,16 +1188,17 @@
3.11 """Validator for a valid password recovery form
3.12 """
3.13 passwords_mismatch_msg = L_('The passwords do not match.')
3.14 - password_encoding_problem_msg = L_('New password is unacceptable, encoding trouble.')
3.15 + password_problem_msg = L_('New password is unacceptable, could not get processed.')
3.16
3.17 def validate(self, element, state):
3.18 if element['password1'].value != element['password2'].value:
3.19 return self.note_error(element, state, 'passwords_mismatch_msg')
3.20
3.21 + password = element['password1'].value
3.22 try:
3.23 - crypto.crypt_password(element['password1'].value)
3.24 - except UnicodeError:
3.25 - return self.note_error(element, state, 'password_encoding_problem_msg')
3.26 + app.cfg.cache.pwd_context.encrypt(password)
3.27 + except (ValueError, TypeError) as err:
3.28 + return self.note_error(element, state, 'password_problem_msg')
3.29
3.30 return True
3.31
3.32 @@ -1322,7 +1323,7 @@
3.33 """
3.34 passwords_mismatch_msg = L_('The passwords do not match.')
3.35 current_password_wrong_msg = L_('The current password was wrong.')
3.36 - password_encoding_problem_msg = L_('New password is unacceptable, encoding trouble.')
3.37 + password_problem_msg = L_('New password is unacceptable, could not get processed.')
3.38
3.39 def validate(self, element, state):
3.40 if not (element['password_current'].valid and element['password1'].valid and element['password2'].valid):
3.41 @@ -1334,10 +1335,11 @@
3.42 if element['password1'].value != element['password2'].value:
3.43 return self.note_error(element, state, 'passwords_mismatch_msg')
3.44
3.45 + password = element['password1'].value
3.46 try:
3.47 - crypto.crypt_password(element['password1'].value)
3.48 - except UnicodeError:
3.49 - return self.note_error(element, state, 'password_encoding_problem_msg')
3.50 + app.cfg.cache.pwd_context.encrypt(password)
3.51 + except (ValueError, TypeError) as err:
3.52 + return self.note_error(element, state, 'password_problem_msg')
3.53 return True
3.54
3.55
4.1 --- a/MoinMoin/config/default.py Sat Jan 19 04:23:42 2013 +0100
4.2 +++ b/MoinMoin/config/default.py Sun Jan 20 16:12:13 2013 +0100
4.3 @@ -1,6 +1,6 @@
4.4 # -*- coding: utf-8 -*-
4.5 # Copyright: 2000-2004 Juergen Hermann <jh@web.de>
4.6 -# Copyright: 2005-2011 MoinMoin:ThomasWaldmann
4.7 +# Copyright: 2005-2013 MoinMoin:ThomasWaldmann
4.8 # Copyright: 2008 MoinMoin:JohannesBerg
4.9 # Copyright: 2010 MoinMoin:DiogenesAugusto
4.10 # Copyright: 2011 MoinMoin:AkashSinha
4.11 @@ -151,6 +151,12 @@
4.12 raise error.ConfigurationError("You must set a (at least {0} chars long) secret string for secrets['{1}']!".format(
4.13 secret_min_length, secret_key_name))
4.14
4.15 + from passlib.context import CryptContext
4.16 + try:
4.17 + self.cache.pwd_context = CryptContext(**self.passlib_crypt_context)
4.18 + except ValueError as err:
4.19 + raise error.ConfigurationError("passlib_crypt_context configuration is invalid [{0}].".format(err))
4.20 +
4.21 def _config_check(self):
4.22 """ Check namespace and warn about unknown names
4.23
4.24 @@ -311,6 +317,22 @@
4.25
4.26 ('password_checker', DefaultExpression('_default_password_checker'),
4.27 'checks whether a password is acceptable (default check is length >= 6, at least 4 different chars, no keyboard sequence, not username used somehow (you can switch this off by using `None`)'),
4.28 +
4.29 + ('passlib_crypt_context', dict(
4.30 + # schemes we want to support (or deprecated schemes for which we still have
4.31 + # hashes in our storage).
4.32 + # note about bcrypt: it needs additional code (that is not pure python and
4.33 + # thus either needs compiling or installing platform-specific binaries)
4.34 + schemes=["sha512_crypt", ],
4.35 + # default scheme for creating new pw hashes (if not given, passlib uses first from schemes)
4.36 + #default="sha512_crypt",
4.37 + # deprecated schemes get auto-upgraded to the default scheme at login
4.38 + # time or when setting a password (including doing a moin account pwreset).
4.39 + #deprecated=["auto"],
4.40 + # vary rounds parameter randomly when creating new hashes...
4.41 + #all__vary_rounds=0.1,
4.42 + ),
4.43 + "passlib CryptContext arguments, see passlib docs"),
4.44 )),
4.45 # ==========================================================================
4.46 'spam_leech_dos': ('Anti-Spam / Leech / DOS',
4.47 @@ -448,6 +470,7 @@
4.48 scroll_page_after_edit=True,
4.49 show_comments=False,
4.50 want_trivial=False,
4.51 + enc_password=u'', # empty value == invalid hash
4.52 disabled=False,
4.53 bookmarks={},
4.54 quicklinks=[],
5.1 --- a/MoinMoin/script/account/resetpw.py Sat Jan 19 04:23:42 2013 +0100
5.2 +++ b/MoinMoin/script/account/resetpw.py Sun Jan 20 16:12:13 2013 +0100
5.3 @@ -1,4 +1,4 @@
5.4 -# Copyright: 2006 MoinMoin:ThomasWaldmann
5.5 +# Copyright: 2006-2013 MoinMoin:ThomasWaldmann
5.6 # Copyright: 2008 MoinMoin:JohannesBerg
5.7 # Copyright: 2011 MoinMoin:ReimarBauer
5.8 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
5.9 @@ -14,7 +14,6 @@
5.10
5.11 from MoinMoin import user
5.12 from MoinMoin.app import before_wiki
5.13 -from MoinMoin.util import crypto
5.14
5.15
5.16 class Set_Password(Command):
5.17 @@ -45,6 +44,10 @@
5.18 print 'This user "{0!r}" does not exists!'.format(u.name)
5.19 return
5.20
5.21 - u.enc_password = crypto.crypt_password(password)
5.22 - u.save()
5.23 - print 'Password set.'
5.24 + try:
5.25 + u.enc_password = app.cfg.cache.pwd_context.encrypt(password)
5.26 + except (TypeError, ValueError) as err:
5.27 + print "Error: Password could not get processed, aborting."
5.28 + else:
5.29 + u.save()
5.30 + print 'Password set.'
6.1 --- a/MoinMoin/script/migration/moin19/import19.py Sat Jan 19 04:23:42 2013 +0100
6.2 +++ b/MoinMoin/script/migration/moin19/import19.py Sun Jan 20 16:12:13 2013 +0100
6.3 @@ -536,7 +536,7 @@
6.4 'editor_default', # not used any more
6.5 'editor_ui', # not used any more
6.6 'external_target', # ancient, not used any more
6.7 - 'passwd', # ancient, not used any more (use enc_passwd)
6.8 + 'passwd', # ancient, not used any more (use enc_password)
6.9 'show_emoticons', # ancient, not used any more
6.10 'show_fancy_diff', # kind of diff display now depends on mimetype
6.11 'show_fancy_links', # not used any more (now link rendering depends on theme)
6.12 @@ -568,6 +568,17 @@
6.13 if key in metadata and metadata[key] in [u'', tuple(), {}, [], ]:
6.14 del metadata[key]
6.15
6.16 + # moin2 only supports passlib generated hashes, drop everything else
6.17 + # (users need to do pw recovery in case they are affected)
6.18 + pw = metadata.get('enc_password')
6.19 + if pw is not None:
6.20 + if pw.startswith('{PASSLIB}'):
6.21 + # take it, but strip the prefix as moin2 does not use that any more
6.22 + metadata['enc_password'] = pw[len('{PASSLIB}'):]
6.23 + else:
6.24 + # drop old, unsupported (and also more or less unsafe) hashing scheme
6.25 + del metadata['enc_password']
6.26 +
6.27 # TODO quicklinks and subscribed_items - check for non-interwiki elements and convert them to interwiki
6.28
6.29 return metadata
7.1 --- a/MoinMoin/user.py Sat Jan 19 04:23:42 2013 +0100
7.2 +++ b/MoinMoin/user.py Sun Jan 20 16:12:13 2013 +0100
7.3 @@ -1,5 +1,5 @@
7.4 # Copyright: 2000-2004 Juergen Hermann <jh@web.de>
7.5 -# Copyright: 2003-2012 MoinMoin:ThomasWaldmann
7.6 +# Copyright: 2003-2013 MoinMoin:ThomasWaldmann
7.7 # Copyright: 2007 MoinMoin:JohannesBerg
7.8 # Copyright: 2007 MoinMoin:HeinrichWendel
7.9 # Copyright: 2008 MoinMoin:ChristopherDenter
7.10 @@ -33,14 +33,16 @@
7.11
7.12 from whoosh.query import Term, And, Or
7.13
7.14 +from MoinMoin import log
7.15 +logging = log.getLogger(__name__)
7.16 +
7.17 from MoinMoin import wikiutil
7.18 from MoinMoin.config import CONTENTTYPE_USER
7.19 from MoinMoin.constants.keys import *
7.20 from MoinMoin.i18n import _, L_, N_
7.21 from MoinMoin.mail import sendmail
7.22 from MoinMoin.util.interwiki import getInterwikiHome, getInterwikiName, is_local_wiki
7.23 -from MoinMoin.util.crypto import crypt_password, upgrade_password, valid_password, \
7.24 - generate_token, valid_token, make_uuid
7.25 +from MoinMoin.util.crypto import generate_token, valid_token, make_uuid
7.26 from MoinMoin.storage.error import NoSuchItemError, ItemAlreadyExistsError, NoSuchRevisionError
7.27
7.28
7.29 @@ -67,11 +69,7 @@
7.30 if pw_error:
7.31 return _("Password not acceptable: %(msg)s", msg=pw_error)
7.32
7.33 - try:
7.34 - theuser.set_password(password, is_encrypted)
7.35 - except UnicodeError as err:
7.36 - # Should never happen
7.37 - return "Can't encode password: %(msg)s" % dict(msg=str(err))
7.38 + theuser.set_password(password, is_encrypted)
7.39
7.40 # try to get the email, for new users it is required
7.41 if validate and not email:
7.42 @@ -422,20 +420,21 @@
7.43 if not pw_hash or not password:
7.44 return False, False
7.45
7.46 - # check the password against the password hash
7.47 - if not valid_password(password, pw_hash):
7.48 - return False, False
7.49 + pwd_context = self._cfg.cache.pwd_context
7.50 + password_correct = False
7.51 + recomputed_hash = None
7.52 + try:
7.53 + password_correct, recomputed_hash = pwd_context.verify_and_update(password, pw_hash)
7.54 + except (ValueError, TypeError) as err:
7.55 + logging.error('in user profile %r, verifying the passlib pw hash raised an Exception [%s]' % (self.id, str(err)))
7.56 + else:
7.57 + if recomputed_hash is not None:
7.58 + data[ENC_PASSWORD] = recomputed_hash
7.59 + return password_correct, bool(recomputed_hash)
7.60
7.61 - new_pw_hash = upgrade_password(password, pw_hash)
7.62 - if not new_pw_hash:
7.63 - return True, False
7.64 -
7.65 - data[ENC_PASSWORD] = new_pw_hash
7.66 - return True, True
7.67 -
7.68 - def set_password(self, password, is_encrypted=False):
7.69 + def set_password(self, password, is_encrypted=False, salt=None):
7.70 if not is_encrypted:
7.71 - password = crypt_password(password)
7.72 + password = self._cfg.cache.pwd_context.encrypt(password, salt=salt)
7.73 self.profile[ENC_PASSWORD] = password
7.74 # Invalidate all other browser sessions except this one.
7.75 session['user.session_token'] = self.generate_session_token(False)
8.1 --- a/MoinMoin/util/_tests/test_crypto.py Sat Jan 19 04:23:42 2013 +0100
8.2 +++ b/MoinMoin/util/_tests/test_crypto.py Sun Jan 20 16:12:13 2013 +0100
8.3 @@ -1,5 +1,5 @@
8.4 # -*- coding: utf-8 -*-
8.5 -# Copyright: 2011 by MoinMoin:ThomasWaldmann
8.6 +# Copyright: 2011-2013 by MoinMoin:ThomasWaldmann
8.7 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
8.8
8.9 """
8.10 @@ -53,6 +53,10 @@
8.11 result = crypto.valid_token(test_key, test_token)
8.12 assert not result
8.13
8.14 +
8.15 +class TestCacheKey(object):
8.16 + """ tests for cache key generation """
8.17 +
8.18 def test_cache_key(self):
8.19 """ The key must be different for different <kw> """
8.20 test_kw1 = {'MoinMoin': 'value1'}
8.21 @@ -61,4 +65,5 @@
8.22 result2 = crypto.cache_key(**test_kw2)
8.23 assert result1 != result2, ("Expected different keys for different <kw> but got the same")
8.24
8.25 +
8.26 coverage_modules = ['MoinMoin.util.crypto']
9.1 --- a/MoinMoin/util/crypto.py Sat Jan 19 04:23:42 2013 +0100
9.2 +++ b/MoinMoin/util/crypto.py Sun Jan 20 16:12:13 2013 +0100
9.3 @@ -1,9 +1,4 @@
9.4 -# Copyright: 2000-2004 Juergen Hermann <jh@web.de>
9.5 -# Copyright: 2003-2011 MoinMoin:ThomasWaldmann
9.6 -# Copyright: 2007 MoinMoin:JohannesBerg
9.7 -# Copyright: 2007 MoinMoin:HeinrichWendel
9.8 -# Copyright: 2008 MoinMoin:ChristopherDenter
9.9 -# Copyright: 2010 MoinMoin:DiogenesAugusto
9.10 +# Copyright: 2012-2013 MoinMoin:ThomasWaldmann
9.11 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
9.12
9.13 """
9.14 @@ -11,10 +6,6 @@
9.15
9.16 Features:
9.17
9.18 -- generate strong, salted cryptographic password hashes for safe pw storage
9.19 -- verify cleartext password against any supported crypto (see METHODS)
9.20 -- supports password hash upgrades to stronger methods if the cleartext
9.21 - password is available (usually at login time)
9.22 - generate password recovery tokens
9.23 - verify password recovery tokens
9.24 - generate random strings of given length (for salting)
9.25 @@ -26,7 +17,6 @@
9.26
9.27 import hashlib
9.28 import hmac
9.29 -import random
9.30 import time
9.31
9.32 from uuid import uuid4
9.33 @@ -34,61 +24,27 @@
9.34 make_uuid = lambda: unicode(uuid4().hex)
9.35 UUID_LEN = len(make_uuid())
9.36
9.37 -# random stuff
9.38 +from passlib.utils import rng, getrandstr, getrandbytes, consteq, generate_password
9.39 +
9.40
9.41 def random_string(length, allowed_chars=None):
9.42 """
9.43 Generate a random string with given length consisting of the given characters.
9.44
9.45 + Note: this is now just a little wrapper around passlib's randomness code.
9.46 +
9.47 :param length: length of the string
9.48 :param allowed_chars: string with allowed characters or None
9.49 to indicate all 256 byte values should be used
9.50 :returns: random string
9.51 """
9.52 if allowed_chars is None:
9.53 - s = ''.join([chr(random.randint(0, 255)) for dummy in xrange(length)])
9.54 + s = getrandbytes(rng, length)
9.55 else:
9.56 - s = ''.join([random.choice(allowed_chars) for dummy in xrange(length)])
9.57 + s = getrandstr(rng, allowed_chars, length)
9.58 return s
9.59
9.60
9.61 -# password stuff
9.62 -
9.63 -def crypt_password(password, salt=None):
9.64 - """
9.65 - Crypt/Hash a cleartext password
9.66 -
9.67 - :param password: cleartext password [unicode]
9.68 - :param salt: salt for the password [str] or None to generate a random salt
9.69 - :rtype: str
9.70 - :returns: the password hash
9.71 - """
9.72 - return 'foobar' # TODO
9.73 -
9.74 -
9.75 -def upgrade_password(password, pw_hash):
9.76 - """
9.77 - Upgrade a password to a better hash, if needed
9.78 -
9.79 - :param password: cleartext password [unicode]
9.80 - :param pw_hash: password hash (with hash type prefix)
9.81 - :rtype: str
9.82 - :returns: new password hash (or None, if unchanged)
9.83 - """
9.84 - # TODO
9.85 -
9.86 -def valid_password(password, pw_hash):
9.87 - """
9.88 - Validate a user password.
9.89 -
9.90 - :param password: cleartext password to verify [unicode]
9.91 - :param pw_hash: password hash (with hash type prefix)
9.92 - :rtype: bool
9.93 - :returns: password is valid
9.94 - """
9.95 - return True # TODO
9.96 -
9.97 -
9.98 # password recovery token
9.99
9.100 def generate_token(key=None, stamp=None):
9.101 @@ -109,18 +65,18 @@
9.102 :param key: give it to recompute some specific token for verification
9.103 :param stamp: give it to recompute some specific token for verification
9.104 :rtype: 2-tuple
9.105 - :returns: key, token
9.106 + :returns: key, token (both unicode)
9.107 """
9.108 if key is None:
9.109 - key = random_string(64, "abcdefghijklmnopqrstuvwxyz0123456789")
9.110 + key = generate_password(size=32)
9.111 if stamp is None:
9.112 stamp = int(time.time())
9.113 - h = hmac.new(str(key), str(stamp), digestmod=hashlib.sha1).hexdigest()
9.114 - token = str(stamp) + '-' + h
9.115 - return key, token
9.116 + h = hmac.new(str(key), str(stamp), digestmod=hashlib.sha256).hexdigest()
9.117 + token = u"{0}-{1}".format(stamp, h)
9.118 + return unicode(key), token
9.119
9.120
9.121 -def valid_token(key, token, timeout=12*60*60):
9.122 +def valid_token(key, token, timeout=2*60*60):
9.123 """
9.124 check if token is valid with respect to the secret key,
9.125 the token must not be older than timeout seconds.
9.126 @@ -141,7 +97,7 @@
9.127 if timeout and stamp + timeout < time.time():
9.128 return False
9.129 expected_token = generate_token(key, stamp)[1]
9.130 - return token == expected_token
9.131 + return consteq(token, expected_token)
9.132
9.133
9.134 # miscellaneous
10.1 --- a/docs/admin/configure.rst Sat Jan 19 04:23:42 2013 +0100
10.2 +++ b/docs/admin/configure.rst Sun Jan 20 16:12:13 2013 +0100
10.3 @@ -630,7 +630,28 @@
10.4
10.5 Password storage
10.6 ----------------
10.7 -Moin never stores passwords in clear text.
10.8 +Moin never stores wiki user passwords in clear text, but uses strong
10.9 +cryptographic hashes provided by the "passlib" library, see there for details:
10.10 +
10.11 + http://packages.python.org/passlib/.
10.12 +
10.13 +The passlib docs recommend 3 hashing schemes that have good security:
10.14 +sha512_crypt, pbkdf2_sha512 and bcrypt (bcrypt has additional binary/compiled
10.15 +package requirements, please refer to the passlib docs in case you want to use
10.16 +it).
10.17 +
10.18 +By default, we use sha512_crypt hashes with default parameters as provided
10.19 +by passlib (this is same algorithm as moin >= 1.9.7 used by default).
10.20 +
10.21 +In case you experience slow logins or feel that you might need to tweak the
10.22 +hash generation for other reasons, please read the passlib docs. moin allows
10.23 +you to configure passlib's CryptContext params within the wiki config, the
10.24 +default is this:
10.25 +
10.26 +::
10.27 + passlib_crypt_context = dict(
10.28 + schemes=["sha512_crypt", ],
10.29 + )
10.30
10.31
10.32 Authorization
11.1 --- a/docs/admin/upgrade.rst Sat Jan 19 04:23:42 2013 +0100
11.2 +++ b/docs/admin/upgrade.rst Sun Jan 20 16:12:13 2013 +0100
11.3 @@ -19,9 +19,9 @@
11.4
11.5 From moin < 1.9
11.6 ===============
11.7 -If you run an older moin version than 1.9, please first upgrade to moin 1.9.x
11.8 -before upgrading to moin2.
11.9 -You may want to run 1.9.x for a while to be sure everything is working as expected.
11.10 +If you run an older moin version than 1.9, please first upgrade to a recent
11.11 +moin 1.9.x version (preferably >= 1.9.7) before upgrading to moin2.
11.12 +You may want to run that for a while to be sure everything is working as expected.
11.13
11.14 Note: Both moin 1.9.x and moin2 are WSGI applications.
11.15 Upgrading to 1.9 first also makes sense concerning the WSGI / server side.
11.16 @@ -29,6 +29,14 @@
11.17
11.18 From moin 1.9.x
11.19 ===============
11.20 +
11.21 +If you want to keep your user's password hashes and migrate them to moin2,
11.22 +make sure you use moin >= 1.9.7 WITH enabled passlib support and that all
11.23 +password hashes stored in user profiles are {PASSLIB} hashes. Other hashes
11.24 +will get removed in the migration process and users will need to do password
11.25 +recovery via email (or with admin help, if that does not work).
11.26 +
11.27 +
11.28 Backup
11.29 ------
11.30 Have a backup of everything, so you can go back in case it doesn't do what
11.31 @@ -56,6 +64,8 @@
11.32 sitename = u'...' # same as in 1.9
11.33 item_root = u'...' # see page_front_page in 1.9
11.34
11.35 + # if you had a custom passlib_crypt_context in 1.9, put it here
11.36 +
11.37 # configure backend and ACLs to use in future
11.38 # TODO
11.39
12.1 --- a/setup.py Sat Jan 19 04:23:42 2013 +0100
12.2 +++ b/setup.py Sun Jan 20 16:12:13 2013 +0100
12.3 @@ -101,6 +101,7 @@
12.4 'whoosh>=2.4.0', # needed for indexed search
12.5 'sphinx>=1.1', # needed to build the docs
12.6 'pdfminer', # pdf -> text/plain conversion
12.7 + 'passlib>=1.6.0', # strong password hashing (1.6 needed for consteq)
12.8 'XStatic>=0.0.2', # support for static file pypi packages
12.9 'XStatic-CKEditor>=3.6.1.2',
12.10 'XStatic-jQuery>=1.8.2',