1.1 --- a/MoinMoin/_tests/test_user.py Thu Jan 17 12:17:13 2013 +0100
1.2 +++ b/MoinMoin/_tests/test_user.py Sat Jan 19 04:23:42 2013 +0100
1.3 @@ -78,105 +78,19 @@
1.4 theUser = user.User(name=name, password=password)
1.5 assert theUser.valid
1.6
1.7 - def test_auth_with_ssha_stored_password(self):
1.8 + def test_login(self):
1.9 """
1.10 - Create user with {SSHA} password and check that user can login.
1.11 + Create user with some password and check that user can login.
1.12 """
1.13 # Create test user
1.14 name = u'Test User'
1.15 - # pass = 12345
1.16 - # salt = salt
1.17 - password = '{SSHA}x4YEGdfI4i0qROaY3NTHCmwSJY5zYWx0'
1.18 - self.createUser(name, password, True)
1.19 + password = '12345'
1.20 + salt = 'salt'
1.21 + pw_hash = ''
1.22 + self.createUser(name, pw_hash, True)
1.23
1.24 # Try to "login"
1.25 - theuser = user.User(name=name, password='12345')
1.26 - assert theuser.valid
1.27 -
1.28 - def test_auth_with_apr1_stored_password(self):
1.29 - """
1.30 - Create user with {APR1} password and check that user can login.
1.31 - """
1.32 - # Create test user
1.33 - name = u'Test User'
1.34 - # generated with "htpasswd -nbm blaze 12345"
1.35 - password = '{APR1}$apr1$NG3VoiU5$PSpHT6tV0ZMKkSZ71E3qg.' # 12345
1.36 - self.createUser(name, password, True)
1.37 -
1.38 - # Try to "login"
1.39 - theuser = user.User(name=name, password='12345')
1.40 - assert theuser.valid
1.41 -
1.42 - def test_auth_with_md5_stored_password(self):
1.43 - """
1.44 - Create user with {MD5} password and check that user can login.
1.45 - """
1.46 - # Create test user
1.47 - name = u'Test User'
1.48 - password = '{MD5}$1$salt$etVYf53ma13QCiRbQOuRk/' # 12345
1.49 - self.createUser(name, password, True)
1.50 -
1.51 - # Try to "login"
1.52 - theuser = user.User(name=name, password='12345')
1.53 - assert theuser.valid
1.54 -
1.55 - def test_auth_with_des_stored_password(self):
1.56 - """
1.57 - Create user with {DES} password and check that user can login.
1.58 - """
1.59 - # Create test user
1.60 - name = u'Test User'
1.61 - # generated with "htpasswd -nbd blaze 12345"
1.62 - password = '{DES}gArsfn7O5Yqfo' # 12345
1.63 - self.createUser(name, password, True)
1.64 -
1.65 - try:
1.66 - import crypt
1.67 - # Try to "login"
1.68 - theuser = user.User(name=name, password='12345')
1.69 - assert theuser.valid
1.70 - except ImportError:
1.71 - pytest.skip("Platform does not provide crypt module!")
1.72 -
1.73 - def test_auth_with_ssha256_stored_password(self):
1.74 - """
1.75 - Create user with {SSHA256} password and check that user can login.
1.76 - """
1.77 - # Create test user
1.78 - name = u'Test User'
1.79 - # generated with online sha256 tool
1.80 - # pass: 12345
1.81 - # salt: salt
1.82 - # base64 encoded
1.83 - password = '{SSHA256}r4ONZUfEyn9MUkcyDQkQ5MBNpdIerM24MasxFpuQBaFzYWx0'
1.84 -
1.85 - self.createUser(name, password, True)
1.86 -
1.87 - # Try to "login"
1.88 - theuser = user.User(name=name, password='12345')
1.89 - assert theuser.valid
1.90 -
1.91 - def test_regression_user_password_started_with_sha(self):
1.92 - # This is regression test for bug in function 'user.create_user'.
1.93 - #
1.94 - # This function does not encode passwords which start with '{SHA}'
1.95 - # It treats them as already encoded SHA hashes.
1.96 - #
1.97 - # If user during registration specifies password starting with '{SHA}'
1.98 - # this password will not get encoded and user object will get saved with empty enc_password
1.99 - # field.
1.100 - #
1.101 - # Such situation leads to "KeyError: 'enc_password'" during
1.102 - # user authentication.
1.103 -
1.104 - # Any Password begins with the {SHA} symbols led to
1.105 - # "KeyError: 'enc_password'" error during user authentication.
1.106 - user_name = u'moin'
1.107 - user_password = u'{SHA}LKM56'
1.108 - user.create_user(user_name, user_password, u'moin@moinmo.in', u'')
1.109 -
1.110 - # Try to "login"
1.111 - theuser = user.User(name=user_name, password=user_password)
1.112 + theuser = user.User(name=name, password=password)
1.113 assert theuser.valid
1.114
1.115 def testSubscriptionSubscribedPage(self):
1.116 @@ -202,72 +116,6 @@
1.117 theUser.subscribe(pagename)
1.118 assert not theUser.is_subscribed_to([testPagename]) # list(!) of pages to check
1.119
1.120 - def test_upgrade_password_from_ssha_to_ssha256(self):
1.121 - """
1.122 - Create user with {SSHA} password and check that logging in
1.123 - upgrades to {SSHA256}.
1.124 - """
1.125 - name = u'/no such user/'
1.126 - # pass = 'MoinMoin', salt = '12345'
1.127 - password = '{SSHA}xkDIIx1I7A4gC98Vt/+UelIkTDYxMjM0NQ=='
1.128 - self.createUser(name, password, True)
1.129 -
1.130 - theuser = user.User(name=name, password='MoinMoin')
1.131 - assert theuser.enc_password[:9] == '{SSHA256}'
1.132 -
1.133 - def test_upgrade_password_from_sha_to_ssha256(self):
1.134 - """
1.135 - Create user with {SHA} password and check that logging in
1.136 - upgrades to {SSHA256}.
1.137 - """
1.138 - name = u'/no such user/'
1.139 - password = '{SHA}jLIjfQZ5yojbZGTqxg2pY0VROWQ=' # 12345
1.140 - self.createUser(name, password, True)
1.141 -
1.142 - theuser = user.User(name=name, password='12345')
1.143 - assert theuser.enc_password[:9] == '{SSHA256}'
1.144 -
1.145 - def test_upgrade_password_from_apr1_to_ssha256(self):
1.146 - """
1.147 - Create user with {APR1} password and check that logging in
1.148 - upgrades to {SSHA256}.
1.149 - """
1.150 - # Create test user
1.151 - name = u'Test User'
1.152 - # generated with "htpasswd -nbm blaze 12345"
1.153 - password = '{APR1}$apr1$NG3VoiU5$PSpHT6tV0ZMKkSZ71E3qg.' # 12345
1.154 - self.createUser(name, password, True)
1.155 -
1.156 - theuser = user.User(name=name, password='12345')
1.157 - assert theuser.enc_password[:9] == '{SSHA256}'
1.158 -
1.159 - def test_upgrade_password_from_md5_to_ssha256(self):
1.160 - """
1.161 - Create user with {MD5} password and check that logging in
1.162 - upgrades to {SSHA}.
1.163 - """
1.164 - # Create test user
1.165 - name = u'Test User'
1.166 - password = '{MD5}$1$salt$etVYf53ma13QCiRbQOuRk/' # 12345
1.167 - self.createUser(name, password, True)
1.168 -
1.169 - theuser = user.User(name=name, password='12345')
1.170 - assert theuser.enc_password[:9] == '{SSHA256}'
1.171 -
1.172 - def test_upgrade_password_from_des_to_ssha256(self):
1.173 - """
1.174 - Create user with {DES} password and check that logging in
1.175 - upgrades to {SSHA}.
1.176 - """
1.177 - # Create test user
1.178 - name = u'Test User'
1.179 - # generated with "htpasswd -nbd blaze 12345"
1.180 - password = '{DES}gArsfn7O5Yqfo' # 12345
1.181 - self.createUser(name, password, True)
1.182 -
1.183 - theuser = user.User(name=name, password='12345')
1.184 - assert theuser.enc_password[:9] == '{SSHA256}'
1.185 -
1.186 # Bookmarks -------------------------------------------------------
1.187
1.188 def test_bookmark(self):
2.1 --- a/MoinMoin/util/_tests/test_crypto.py Thu Jan 17 12:17:13 2013 +0100
2.2 +++ b/MoinMoin/util/_tests/test_crypto.py Sat Jan 19 04:23:42 2013 +0100
2.3 @@ -29,49 +29,6 @@
2.4 assert result == expected, ('Expected length "%(expected)s" but got "%(result)s"') % locals()
2.5
2.6
2.7 -class TestEncodePassword(object):
2.8 - """crypto: encode passwords tests"""
2.9 -
2.10 - def testAscii(self):
2.11 - """user: encode ascii password"""
2.12 - # u'MoinMoin' and 'MoinMoin' should be encoded to same result
2.13 - expected = "{SSHA256}n0JB8FCTQCpQeg0bmdgvTGwPKvxm8fVNjSRD+JGNs50xMjM0NQ=="
2.14 -
2.15 - result = crypto.crypt_password("MoinMoin", salt='12345')
2.16 - assert result == expected
2.17 - result = crypto.crypt_password(u"MoinMoin", salt='12345')
2.18 - assert result == expected
2.19 -
2.20 - def testUnicode(self):
2.21 - """ user: encode unicode password """
2.22 - result = crypto.crypt_password(u'סיסמה סודית בהחלט', salt='12345') # Hebrew
2.23 - expected = "{SSHA256}pdYvYv+4Vph259sv/HAm7zpZTv4sBKX9ITOX/m00HMsxMjM0NQ=="
2.24 - assert result == expected
2.25 -
2.26 - def testupgradepassword(self):
2.27 - """ return new password hash with better hash """
2.28 - result = crypto.upgrade_password(u'MoinMoin', "junk_hash")
2.29 - assert result.startswith('{SSHA256}')
2.30 -
2.31 - def testvalidpassword(self):
2.32 - """ validate user password """
2.33 - hash_val = crypto.crypt_password(u"MoinMoin", salt='12345')
2.34 - result = crypto.valid_password(u'MoinMoin', hash_val)
2.35 - assert result
2.36 - with pytest.raises(ValueError):
2.37 - invalid_result = crypto.valid_password("MoinMoin", '{junk_value}')
2.38 -
2.39 - def testvalidpassword2(self):
2.40 - """ validate user password """
2.41 - hash_val = crypto.crypt_password(u"MoinMoin")
2.42 - result = crypto.valid_password('MoinMoin', hash_val)
2.43 - assert result
2.44 - result = crypto.valid_password('WrongPassword', hash_val)
2.45 - assert not result
2.46 - with pytest.raises(ValueError):
2.47 - invalid_result = crypto.valid_password("MoinMoin", '{junk_value}')
2.48 -
2.49 -
2.50 class TestToken(object):
2.51 """ tests for the generated tokens """
2.52
3.1 --- a/MoinMoin/util/_tests/test_md5crypt.py Thu Jan 17 12:17:13 2013 +0100
3.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
3.3 @@ -1,28 +0,0 @@
3.4 -# -*- coding: utf-8 -*-
3.5 -# Copyright: 2011 Prashant Kumar <contactprashantat AT gmail DOT com>
3.6 -# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
3.7 -
3.8 -"""
3.9 -MoinMoin - MoinMoin.util.md5crypt Tests
3.10 -"""
3.11 -
3.12 -
3.13 -import pytest
3.14 -from MoinMoin.util import md5crypt
3.15 -
3.16 -def test_unix_md5_crypt():
3.17 - # when magic != None
3.18 - result = md5crypt.unix_md5_crypt('test_pass', 'Moin_test', '$test_magic$')
3.19 - expected = '$test_magic$Moin_tes$JRfmeHgnmCVhVYW.bTtiY1'
3.20 - assert result == expected
3.21 -
3.22 - # when magic == None
3.23 - result = md5crypt.unix_md5_crypt('test_pass', 'Moin_test', None)
3.24 - expected = '$1$Moin_tes$hArc67BzmDWtyWWKO5uxQ1'
3.25 - assert result == expected
3.26 -
3.27 -def test_apache_md5_crypt():
3.28 - # Here magic == '$apr1$'
3.29 - result = md5crypt.apache_md5_crypt('test_pass', 'Moin_test')
3.30 - expected = '$apr1$Moin_tes$4/5zV8nADrNv3BJcY1rZX1'
3.31 - assert result == expected
4.1 --- a/MoinMoin/util/crypto.py Thu Jan 17 12:17:13 2013 +0100
4.2 +++ b/MoinMoin/util/crypto.py Sat Jan 19 04:23:42 2013 +0100
4.3 @@ -13,8 +13,6 @@
4.4
4.5 - generate strong, salted cryptographic password hashes for safe pw storage
4.6 - verify cleartext password against any supported crypto (see METHODS)
4.7 -- support old (weak) password crypto so one can import existing password
4.8 - databases
4.9 - supports password hash upgrades to stronger methods if the cleartext
4.10 password is available (usually at login time)
4.11 - generate password recovery tokens
4.12 @@ -26,27 +24,11 @@
4.13
4.14 from __future__ import absolute_import, division
4.15
4.16 -import base64
4.17 import hashlib
4.18 import hmac
4.19 import random
4.20 import time
4.21
4.22 -# Note: have the (strong) method that crypt_password() uses at index 0:
4.23 -METHODS = ['{SSHA256}', '{SSHA}', '{SHA}', ]
4.24 -
4.25 -try:
4.26 - from . import md5crypt
4.27 - METHODS.extend(['{APR1}', '{MD5}', ])
4.28 -except ImportError:
4.29 - pass
4.30 -
4.31 -try:
4.32 - import crypt
4.33 - METHODS.extend(['{DES}', ])
4.34 -except ImportError:
4.35 - pass
4.36 -
4.37 from uuid import uuid4
4.38
4.39 make_uuid = lambda: unicode(uuid4().hex)
4.40 @@ -79,15 +61,9 @@
4.41 :param password: cleartext password [unicode]
4.42 :param salt: salt for the password [str] or None to generate a random salt
4.43 :rtype: str
4.44 - :returns: the SSHA256 password hash
4.45 + :returns: the password hash
4.46 """
4.47 - password = password.encode('utf-8')
4.48 - if salt is None:
4.49 - salt = random_string(32)
4.50 - assert isinstance(salt, str)
4.51 - h = hashlib.new('sha256', password)
4.52 - h.update(salt)
4.53 - return '{SSHA256}' + base64.encodestring(h.digest() + salt).rstrip()
4.54 + return 'foobar' # TODO
4.55
4.56
4.57 def upgrade_password(password, pw_hash):
4.58 @@ -99,10 +75,7 @@
4.59 :rtype: str
4.60 :returns: new password hash (or None, if unchanged)
4.61 """
4.62 - if not pw_hash.startswith('{SSHA256}'):
4.63 - # pw_hash using some old hash method, upgrade to better method
4.64 - return crypt_password(password)
4.65 -
4.66 + # TODO
4.67
4.68 def valid_password(password, pw_hash):
4.69 """
4.70 @@ -113,47 +86,7 @@
4.71 :rtype: bool
4.72 :returns: password is valid
4.73 """
4.74 - # encode password
4.75 - pw_utf8 = password.encode('utf-8')
4.76 -
4.77 - for method in METHODS:
4.78 - if pw_hash.startswith(method):
4.79 - d = pw_hash[len(method):]
4.80 - if method == '{SSHA256}':
4.81 - ph = base64.decodestring(d)
4.82 - # ph is of the form "<hash><salt>"
4.83 - salt = ph[32:]
4.84 - h = hashlib.new('sha256', pw_utf8)
4.85 - h.update(salt)
4.86 - enc = base64.encodestring(h.digest() + salt).rstrip()
4.87 - elif method == '{SSHA}':
4.88 - ph = base64.decodestring(d)
4.89 - # ph is of the form "<hash><salt>"
4.90 - salt = ph[20:]
4.91 - h = hashlib.new('sha1', pw_utf8)
4.92 - h.update(salt)
4.93 - enc = base64.encodestring(h.digest() + salt).rstrip()
4.94 - elif method == '{SHA}':
4.95 - h = hashlib.new('sha1', pw_utf8)
4.96 - enc = base64.encodestring(h.digest()).rstrip()
4.97 - elif method == '{APR1}':
4.98 - # d is of the form "$apr1$<salt>$<hash>"
4.99 - salt = d.split('$')[2]
4.100 - enc = md5crypt.apache_md5_crypt(pw_utf8, salt.encode('ascii'))
4.101 - elif method == '{MD5}':
4.102 - # d is of the form "$1$<salt>$<hash>"
4.103 - salt = d.split('$')[2]
4.104 - enc = md5crypt.unix_md5_crypt(pw_utf8, salt.encode('ascii'))
4.105 - elif method == '{DES}':
4.106 - # d is 2 characters salt + 11 characters hash
4.107 - salt = d[:2]
4.108 - enc = crypt.crypt(pw_utf8, salt.encode('ascii'))
4.109 - else:
4.110 - raise ValueError("missing password hash method {0} handler".format(method))
4.111 - return pw_hash == method + enc
4.112 - else:
4.113 - idx = pw_hash.index('}') + 1
4.114 - raise ValueError("unsupported password hash method {0!r}".format(pw_hash[:idx]))
4.115 + return True # TODO
4.116
4.117
4.118 # password recovery token
5.1 --- a/MoinMoin/util/md5crypt.py Thu Jan 17 12:17:13 2013 +0100
5.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
5.3 @@ -1,172 +0,0 @@
5.4 -#########################################################
5.5 -# md5crypt.py
5.6 -#
5.7 -# 0423.2000 by michal wallace http://www.sabren.com/
5.8 -# based on perl's Crypt::PasswdMD5 by Luis Munoz (lem@cantv.net)
5.9 -# based on /usr/src/libcrypt/crypt.c from FreeBSD 2.2.5-RELEASE
5.10 -#
5.11 -# MANY THANKS TO
5.12 -#
5.13 -# Carey Evans - http://home.clear.net.nz/pages/c.evans/
5.14 -# Dennis Marti - http://users.starpower.net/marti1/
5.15 -#
5.16 -# For the patches that got this thing working!
5.17 -#
5.18 -#########################################################
5.19 -"""md5crypt.py - Provides interoperable MD5-based crypt() function
5.20 -
5.21 -SYNOPSIS
5.22 -
5.23 - import md5crypt.py
5.24 -
5.25 - cryptedpassword = md5crypt.md5crypt(password, salt);
5.26 -
5.27 -DESCRIPTION
5.28 -
5.29 -unix_md5_crypt() provides a crypt()-compatible interface to the
5.30 -rather new MD5-based crypt() function found in modern operating systems.
5.31 -It's based on the implementation found on FreeBSD 2.2.[56]-RELEASE and
5.32 -contains the following license in it:
5.33 -
5.34 - "THE BEER-WARE LICENSE" (Revision 42):
5.35 - <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
5.36 - can do whatever you want with this stuff. If we meet some day, and you think
5.37 - this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
5.38 -
5.39 -apache_md5_crypt() provides a function compatible with Apache's
5.40 -.htpasswd files. This was contributed by Bryan Hart <bryan@eai.com>.
5.41 -
5.42 -"""
5.43 -
5.44 -MAGIC = '$1$' # Magic string
5.45 -ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
5.46 -
5.47 -try:
5.48 - import hashlib
5.49 - hash_md5 = hashlib.md5
5.50 -except ImportError:
5.51 - # maybe we have python < 2.5 (no hashlib)
5.52 - import md5
5.53 - hash_md5 = md5.new
5.54 -
5.55 -
5.56 -def to64(v, n):
5.57 - ret = ''
5.58 - while (n - 1 >= 0):
5.59 - n = n - 1
5.60 - ret = ret + ITOA64[v & 0x3f]
5.61 - v = v >> 6
5.62 - return ret
5.63 -
5.64 -
5.65 -def apache_md5_crypt(pw, salt):
5.66 - # change the Magic string to match the one used by Apache
5.67 - return unix_md5_crypt(pw, salt, '$apr1$')
5.68 -
5.69 -
5.70 -def unix_md5_crypt(pw, salt, magic=None):
5.71 -
5.72 - if magic is None:
5.73 - magic = MAGIC
5.74 -
5.75 - # Take care of the magic string if present
5.76 - if salt[:len(magic)] == magic:
5.77 - salt = salt[len(magic):]
5.78 -
5.79 -
5.80 - # salt can have up to 8 characters:
5.81 - import string
5.82 - salt = string.split(salt, '$', 1)[0]
5.83 - salt = salt[:8]
5.84 -
5.85 - ctx = pw + magic + salt
5.86 -
5.87 - md5 = hash_md5()
5.88 - md5.update(pw + salt + pw)
5.89 - final = md5.digest()
5.90 -
5.91 - for pl in range(len(pw), 0, -16):
5.92 - if pl > 16:
5.93 - ctx = ctx + final[:16]
5.94 - else:
5.95 - ctx = ctx + final[:pl]
5.96 -
5.97 -
5.98 - # Now the 'weird' xform (??)
5.99 -
5.100 - i = len(pw)
5.101 - while i:
5.102 - if i & 1:
5.103 - ctx = ctx + chr(0) #if ($i & 1) { $ctx->add(pack("C", 0)); }
5.104 - else:
5.105 - ctx = ctx + pw[0]
5.106 - i = i >> 1
5.107 -
5.108 - md5 = hash_md5()
5.109 - md5.update(ctx)
5.110 - final = md5.digest()
5.111 -
5.112 - # The following is supposed to make
5.113 - # things run slower.
5.114 -
5.115 - # my question: WTF???
5.116 -
5.117 - for i in range(1000):
5.118 - ctx1 = ''
5.119 - if i & 1:
5.120 - ctx1 = ctx1 + pw
5.121 - else:
5.122 - ctx1 = ctx1 + final[:16]
5.123 -
5.124 - if i % 3:
5.125 - ctx1 = ctx1 + salt
5.126 -
5.127 - if i % 7:
5.128 - ctx1 = ctx1 + pw
5.129 -
5.130 - if i & 1:
5.131 - ctx1 = ctx1 + final[:16]
5.132 - else:
5.133 - ctx1 = ctx1 + pw
5.134 -
5.135 -
5.136 - md5 = hash_md5()
5.137 - md5.update(ctx1)
5.138 - final = md5.digest()
5.139 -
5.140 -
5.141 - # Final xform
5.142 -
5.143 - passwd = ''
5.144 -
5.145 - passwd = passwd + to64((int(ord(final[0])) << 16)
5.146 - |(int(ord(final[6])) << 8)
5.147 - |(int(ord(final[12]))), 4)
5.148 -
5.149 - passwd = passwd + to64((int(ord(final[1])) << 16)
5.150 - |(int(ord(final[7])) << 8)
5.151 - |(int(ord(final[13]))), 4)
5.152 -
5.153 - passwd = passwd + to64((int(ord(final[2])) << 16)
5.154 - |(int(ord(final[8])) << 8)
5.155 - |(int(ord(final[14]))), 4)
5.156 -
5.157 - passwd = passwd + to64((int(ord(final[3])) << 16)
5.158 - |(int(ord(final[9])) << 8)
5.159 - |(int(ord(final[15]))), 4)
5.160 -
5.161 - passwd = passwd + to64((int(ord(final[4])) << 16)
5.162 - |(int(ord(final[10])) << 8)
5.163 - |(int(ord(final[5]))), 4)
5.164 -
5.165 - passwd = passwd + to64((int(ord(final[11]))), 2)
5.166 -
5.167 -
5.168 - return magic + salt + '$' + passwd
5.169 -
5.170 -
5.171 -## assign a wrapper function:
5.172 -md5crypt = unix_md5_crypt
5.173 -
5.174 -if __name__ == "__main__":
5.175 - print unix_md5_crypt("cat", "hat")
6.1 --- a/docs/admin/configure.rst Thu Jan 17 12:17:13 2013 +0100
6.2 +++ b/docs/admin/configure.rst Sat Jan 19 04:23:42 2013 +0100
6.3 @@ -630,8 +630,7 @@
6.4
6.5 Password storage
6.6 ----------------
6.7 -Moin never stores passwords in clear text, but always as cryptographic hash
6.8 -with a random salt. Currently ssha256 is the default.
6.9 +Moin never stores passwords in clear text.
6.10
6.11
6.12 Authorization