view MoinMoin/_tests/test_user.py @ 6028:1893da1d5213

userid lookup caches: use 1 on-disk cache file, update cache rather than rebuild Before this, we maintained one cache file per attribute (e.g. name2id, openid2id, ...) - the related code did multiple passes over all user profiles to rebuild these cache files. Now doing a one-pass rebuild, writing all attribute -> userid mappings into one on-disk cache file called "lookup". Additionally to "name" and "openids", support fast lookup for "email" and "jid" also. On profile save, we use to just kill the cache and let it rebuild. Now the cache is read, updated and written back (which is much less expensive for wikis with more than a few users). Did some refactoring also, reducing duplication, breaking down the code into smaller functions / methods.
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Wed, 12 Feb 2014 18:22:10 +0100
parents 05a6897ee496
children
line wrap: on
line source
# -*- coding: utf-8 -*-
"""
    MoinMoin - MoinMoin.user Tests

    @copyright: 2003-2004 by Juergen Hermann <jh@web.de>
                2009 by ReimarBauer
                2013 by MoinMoin:ThomasWaldmann
    @license: GNU GPL, see COPYING for details.
"""

import os
import py

from MoinMoin import user, caching


class TestEncodePassword(object):
    """user: encode passwords tests"""

    def testAscii(self):
        """user: encode ascii password"""
        # u'MoinMoin' and 'MoinMoin' should be encoded to same result
        cfg = self.request.cfg
        tests = [
            ('{PASSLIB}', '12345', "{PASSLIB}$6$rounds=1001$12345$jrPUCzPJt1yiixDbzIgSBoKED0/DlNDTHZN3lVarCtN6IM/.LoAw5pgUQH112CErU6wS8HXTZNpqb7wVjHLs/0"),
            ('{SSHA}', '12345', "{SSHA}xkDIIx1I7A4gC98Vt/+UelIkTDYxMjM0NQ=="),
        ]
        for scheme, salt, expected in tests:
            result = user.encodePassword(cfg, "MoinMoin", salt=salt, scheme=scheme)
            assert result == expected
            result = user.encodePassword(cfg, u"MoinMoin", salt=salt, scheme=scheme)
            assert result == expected

    def testUnicode(self):
        """ user: encode unicode password """
        cfg = self.request.cfg
        tests = [
            ('{PASSLIB}', '12345', "{PASSLIB}$6$rounds=1001$12345$5srFB66ZCu2JgGwPgdfb1lHRmqkjnKC/RxdsFlWn2WzoQh3btIjH6Ai1LJV9iYLDa9kLP/VQYa4DHLkRnaBw8."),
            ('{SSHA}', '12345', "{SSHA}YiwfeVWdVW9luqyVn8t2JivlzmUxMjM0NQ=="),
            ]
        for scheme, salt, expected in tests:
            result = user.encodePassword(cfg, u'סיסמה סודית בהחלט', salt=salt, scheme=scheme) # Hebrew
            assert result == expected


class TestLoginWithPassword(object):
    """user: login tests"""

    def setup_method(self, method):
        # Save original user and cookie
        self.saved_cookie = self.request.cookies
        self.saved_user = self.request.user

        # Create anon user for the tests
        self.request.cookies = {}
        self.request.user = user.User(self.request)

        self.user = None
        self.passlib_support = self.request.cfg.passlib_support
        self.password_scheme = self.request.cfg.password_scheme

    def teardown_method(self, method):
        """ Run after each test

        Remove user and reset user listing cache.
        """
        # Remove user file and user
        if self.user is not None:
            try:
                path = self.user._User__filename()
                os.remove(path)
            except OSError:
                pass
            del self.user

        # Restore original user
        self.request.cookies = self.saved_cookie
        self.request.user = self.saved_user

        # Remove user lookup caches, or next test will fail
        user.clearLookupCaches(self.request)

    def testAsciiPassword(self):
        """ user: login with ascii password """
        # Create test user
        name = u'__Non Existent User Name__'
        password = name
        self.createUser(name, password)

        # Try to "login"
        theUser = user.User(self.request, name=name, password=password)
        assert theUser.valid

    def testUnicodePassword(self):
        """ user: login with non-ascii password """
        # Create test user
        name = u'__שם משתמש לא קיים__' # Hebrew
        password = name
        self.createUser(name, password)

        # Try to "login"
        theUser = user.User(self.request, name=name, password=password)
        assert theUser.valid

    def test_auth_with_apr1_stored_password(self):
        """
        Create user with {APR1} password and check that user can login.
        Also check if auto-upgrade happens and is saved to disk.
        """
        # Create test user
        name = u'Test User'
        password = '12345'
        # generated with "htpasswd -nbm blaze 12345"
        pw_hash = '{APR1}$apr1$NG3VoiU5$PSpHT6tV0ZMKkSZ71E3qg.'
        self.createUser(name, pw_hash, True)

        # Try to "login"
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.valid
        # Check if the stored password was auto-upgraded on login and saved
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.enc_password.startswith(self.password_scheme)

    def test_auth_with_md5_stored_password(self):
        """
        Create user with {MD5} password and check that user can login.
        Also check if auto-upgrade happens and is saved to disk.
        """
        # Create test user
        name = u'Test User'
        password = '12345'
        pw_hash = '{MD5}$1$salt$etVYf53ma13QCiRbQOuRk/'
        self.createUser(name, pw_hash, True)

        # Try to "login"
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.valid
        # Check if the stored password was auto-upgraded on login and saved
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.enc_password.startswith(self.password_scheme)

    def test_auth_with_des_stored_password(self):
        """
        Create user with {DES} password and check that user can login.
        Also check if auto-upgrade happens and is saved to disk.
        """
        # Create test user
        name = u'Test User'
        password = '12345'
        # generated with "htpasswd -nbd blaze 12345"
        pw_hash = '{DES}gArsfn7O5Yqfo'
        self.createUser(name, pw_hash, True)

        try:
            import crypt
            # Try to "login"
            theuser = user.User(self.request, name=name, password=password)
            assert theuser.valid
            # Check if the stored password was auto-upgraded on login and saved
            theuser = user.User(self.request, name=name, password=password)
            assert theuser.enc_password.startswith(self.password_scheme)
        except ImportError:
            py.test.skip("Platform does not provide crypt module!")

    def test_auth_with_sha_stored_password(self):
        """
        Create user with {SHA} password and check that user can login.
        Also check if auto-upgrade happens and is saved to disk.
        """
        # Create test user
        name = u'Test User'
        password = '12345'
        pw_hash = '{SHA}jLIjfQZ5yojbZGTqxg2pY0VROWQ='
        self.createUser(name, pw_hash, True)

        # Try to "login"
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.valid
        # Check if the stored password was auto-upgraded on login and saved
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.enc_password.startswith(self.password_scheme)

    def test_auth_with_ssha_stored_password(self):
        """
        Create user with {SSHA} password and check that user can login.
        Also check if auto-upgrade happens and is saved to disk.
        """
        # Create test user
        name = u'Test User'
        password = '12345'
        pw_hash = '{SSHA}dbeFtH5EGkOI1jgPADlGZgHWq072TIsKqWfHX7zZbUQa85Ze8774Rg=='
        self.createUser(name, pw_hash, True)

        # Try to "login"
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.valid
        # Check if the stored password was auto-upgraded on login and saved
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.enc_password.startswith(self.password_scheme)

    def test_auth_with_passlib_stored_password(self):
        """
        Create user with {PASSLIB} password and check that user can login.
        """
        if not self.passlib_support:
            py.test.skip("test requires passlib, but passlib_support is False")
        # Create test user
        name = u'Test User'
        password = '12345'
        pw_hash = '{PASSLIB}$6$rounds=1001$/AVWSh/RUWpcppfl$8DCRGLaBD3KoV4Ag67sUv6b2QdrUFXk1yWCxqWnBLJ.iHSe4Piv6nqzSQgELeLPIvwTC9APaWv1XCTOHjkLOj/'
        self.createUser(name, pw_hash, True)

        # Try to "login"
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.valid
        # Check if the stored password was auto-upgraded on login and saved
        theuser = user.User(self.request, name=name, password=password)
        assert theuser.enc_password.startswith(self.password_scheme)

    def testSubscriptionSubscribedPage(self):
        """ user: tests isSubscribedTo  """
        pagename = u'HelpMiscellaneous'
        name = u'__Jürgen Herman__'
        password = name
        self.createUser(name, password)
        # Login - this should replace the old password in the user file
        theUser = user.User(self.request, name=name, password=password)
        theUser.subscribe(pagename)
        assert theUser.isSubscribedTo([pagename]) # list(!) of pages to check

    def testSubscriptionSubPage(self):
        """ user: tests isSubscribedTo on a subpage """
        pagename = u'HelpMiscellaneous'
        testPagename = u'HelpMiscellaneous/FrequentlyAskedQuestions'
        name = u'__Jürgen Herman__'
        password = name
        self.createUser(name, password)
        # Login - this should replace the old password in the user file
        theUser = user.User(self.request, name=name, password=password)
        theUser.subscribe(pagename)
        assert not theUser.isSubscribedTo([testPagename]) # list(!) of pages to check

    def testRenameUser(self):
        """ create user and then rename user and check whether
        the old username is removed (and the lookup cache behaves well)
        """
        # Create test user
        name = u'__Some Name__'
        password = name
        self.createUser(name, password)
        # Login - this should replace the old password in the user file
        theUser = user.User(self.request, name=name)
        # Rename user
        theUser.name = u'__SomeName__'
        theUser.save()
        theUser = user.User(self.request, name=name, password=password)

        assert not theUser.exists()

    def test_for_email_attribute_by_name(self):
        """
        checks for no access to the email attribute by getting the user object from name
        """
        name = u"__TestUser__"
        password = u"ekfdweurwerh"
        email = "__TestUser__@moinhost"
        self.createUser(name, password, email=email)
        theuser = user.User(self.request, name=name)
        assert theuser.email == ""

    def test_for_email_attribut_by_uid(self):
        """
        checks access to the email attribute by getting the user object from the uid
        """
        name = u"__TestUser2__"
        password = u"ekERErwerwerh"
        email = "__TestUser2__@moinhost"
        self.createUser(name, password, email=email)
        uid = user.getUserId(self.request, name)
        theuser = user.User(self.request, uid)
        assert theuser.email == email

    # Helpers ---------------------------------------------------------

    def createUser(self, name, password, pwencoded=False, email=None):
        """ helper to create test user
        """
        # Create user
        self.user = user.User(self.request)
        self.user.name = name
        self.user.email = email
        if not pwencoded:
            password = user.encodePassword(self.request.cfg, password)
        self.user.enc_password = password

        # Validate that we are not modifying existing user data file!
        if self.user.exists():
            self.user = None
            py.test.skip("Test user exists, will not override existing user data file!")

        # Save test user
        self.user.save()

        # Validate user creation
        if not self.user.exists():
            self.user = None
            py.test.skip("Can't create test user")


class TestGroupName(object):

    def testGroupNames(self):
        """ user: isValidName: reject group names """
        test = u'AdminGroup'
        assert not user.isValidName(self.request, test)


class TestIsValidName(object):

    def testNonAlnumCharacters(self):
        """ user: isValidName: reject unicode non alpha numeric characters

        : and , used in acl rules, we might add more characters to the syntax.
        """
        invalid = u'! # $ % ^ & * ( ) = + , : ; " | ~ / \\ \u0000 \u202a'.split()
        base = u'User%sName'
        for c in invalid:
            name = base % c
            assert not user.isValidName(self.request, name)

    def testWhitespace(self):
        """ user: isValidName: reject leading, trailing or multiple whitespace """
        cases = (
            u' User Name',
            u'User Name ',
            u'User   Name',
            )
        for test in cases:
            assert not user.isValidName(self.request, test)

    def testValid(self):
        """ user: isValidName: accept names in any language, with spaces """
        cases = (
            u'Jürgen Hermann', # German
            u'ניר סופר', # Hebrew
            u'CamelCase', # Good old camel case
            u'가각간갇갈 갉갊감 갬갯걀갼' # Hangul (gibberish)
            )
        for test in cases:
            assert user.isValidName(self.request, test)


coverage_modules = ['MoinMoin.user']