view MoinMoin/security/_tests/test_security.py @ 5870:7b9f39289e16

security fix: fix virtual group bug in ACL evaluation, add a test for it affected moin releases: all 1.9 releases up to and including 1.9.4 moin releases < 1.9 are NOT affected. You can find out the moin version by looking at SystemInfo page or at the output of <<SystemInfo>> macro. Issue description: We have code that checks whether a group has special members "All" or "Known" or "Trusted", but there was a bug that checked whether these are present in the group NAME (not, as intended, in the group MEMBERS). a) If you have group MEMBERS like "All" or "Known" or "Trusted", they did not work until now, but will start working with this changeset. E.g. SomeGroup: * JoeDoe * Trusted SomeGroup will now (correctly) include JoeDoe and also all trusted users. It (erroneously) contained only "JoeDoe" and "Trusted" (as a username, not as a virtual group) before. b) If you have group NAMES containing "All" or "Known" or "Trusted", they behaved wrong until now (they erroneously included All/Known/Trusted users even if you did not list them as members), but will start working correctly with this changeset. E.g. AllFriendsGroup: * JoeDoe AllFriendsGroup will now (correctly) include only JoeDoe. It (erroneously) contained all users (including JoeDoe) before. E.g. MyTrustedFriendsGroup: * JoeDoe MyTrustedFriendsGroup will now (correctly) include only JoeDoe. It (erroneously) contained all trusted users and JoeDoe before.
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 03 Sep 2012 15:30:35 +0200
parents 53d0b91736c4
children
line wrap: on
line source
# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - MoinMoin.security Tests

    TODO: when refactoring this, do not use "iter" (is a builtin)

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

import py

from MoinMoin import security
acliter = security.ACLStringIterator
AccessControlList = security.AccessControlList

from MoinMoin.datastruct import ConfigGroups
from MoinMoin.PageEditor import PageEditor
from MoinMoin.user import User

from MoinMoin._tests import wikiconfig, become_trusted, create_page, nuke_page

class TestACLStringIterator(object):

    def testEmpty(self):
        """ security: empty acl string raise StopIteration """
        iter = acliter(self.request.cfg.acl_rights_valid, '')
        py.test.raises(StopIteration, iter.next)

    def testWhiteSpace(self):
        """ security: white space acl string raise StopIteration """
        iter = acliter(self.request.cfg.acl_rights_valid, '       ')
        py.test.raises(StopIteration, iter.next)

    def testDefault(self):
        """ security: default meta acl """
        iter = acliter(self.request.cfg.acl_rights_valid, 'Default Default')
        for mod, entries, rights in iter:
            assert entries == ['Default']
            assert rights == []

    def testEmptyRights(self):
        """ security: empty rights """
        iter = acliter(self.request.cfg.acl_rights_valid, 'WikiName:')
        mod, entries, rights = iter.next()
        assert entries == ['WikiName']
        assert rights == []

    def testSingleWikiNameSingleWrite(self):
        """ security: single wiki name, single right """
        iter = acliter(self.request.cfg.acl_rights_valid, 'WikiName:read')
        mod, entries, rights = iter.next()
        assert entries == ['WikiName']
        assert rights == ['read']

    def testMultipleWikiNameAndRights(self):
        """ security: multiple wiki names and rights """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne,UserTwo:read,write')
        mod, entries, rights = iter.next()
        assert entries == ['UserOne', 'UserTwo']
        assert rights == ['read', 'write']

    def testMultipleWikiNameAndRightsSpaces(self):
        """ security: multiple names with spaces """
        iter = acliter(self.request.cfg.acl_rights_valid, 'user one,user two:read')
        mod, entries, rights = iter.next()
        assert entries == ['user one', 'user two']
        assert rights == ['read']

    def testMultipleEntries(self):
        """ security: multiple entries """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read,write UserTwo:read All:')
        mod, entries, rights = iter.next()
        assert entries == ['UserOne']
        assert rights == ['read', 'write']
        mod, entries, rights = iter.next()
        assert entries == ['UserTwo']
        assert rights == ['read']
        mod, entries, rights = iter.next()
        assert entries == ['All']
        assert rights == []

    def testNameWithSpaces(self):
        """ security: single name with spaces """
        iter = acliter(self.request.cfg.acl_rights_valid, 'user one:read')
        mod, entries, rights = iter.next()
        assert entries == ['user one']
        assert rights == ['read']

    def testMultipleEntriesWithSpaces(self):
        """ security: multiple entries with spaces """
        iter = acliter(self.request.cfg.acl_rights_valid, 'user one:read,write user two:read')
        mod, entries, rights = iter.next()
        assert entries == ['user one']
        assert rights == ['read', 'write']
        mod, entries, rights = iter.next()
        assert entries == ['user two']
        assert rights == ['read']

    def testMixedNames(self):
        """ security: mixed wiki names and names with spaces """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne,user two:read,write user three,UserFour:read')
        mod, entries, rights = iter.next()
        assert entries == ['UserOne', 'user two']
        assert rights == ['read', 'write']
        mod, entries, rights = iter.next()
        assert entries == ['user three', 'UserFour']
        assert rights == ['read']

    def testModifier(self):
        """ security: acl modifiers """
        iter = acliter(self.request.cfg.acl_rights_valid, '+UserOne:read -UserTwo:')
        mod, entries, rights = iter.next()
        assert mod == '+'
        assert entries == ['UserOne']
        assert rights == ['read']
        mod, entries, rights = iter.next()
        assert mod == '-'
        assert entries == ['UserTwo']
        assert rights == []

    def testIgnoreInvalidACL(self):
        """ security: ignore invalid acl

        The last part of this acl can not be parsed. If it ends with :
        then it will be parsed as one name with spaces.
        """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read user two is ignored')
        mod, entries, rights = iter.next()
        assert entries == ['UserOne']
        assert rights == ['read']
        py.test.raises(StopIteration, iter.next)

    def testEmptyNamesWithRight(self):
        """ security: empty names with rights

        The documents does not talk about this case, may() should ignore
        the rights because there is no entry.
        """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read :read All:')
        mod, entries, rights = iter.next()
        assert entries == ['UserOne']
        assert rights == ['read']
        mod, entries, rights = iter.next()
        assert entries == []
        assert rights == ['read']
        mod, entries, rights = iter.next()
        assert entries == ['All']
        assert rights == []

    def testIgnodeInvalidRights(self):
        """ security: ignore rights not in acl_rights_valid """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read,sing,write,drink,sleep')
        mod, entries, rights = iter.next()
        assert rights == ['read', 'write']

    def testBadGuy(self):
        """ security: bad guy may not allowed anything

        This test was failing on the apply acl rights test.
        """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read,write BadGuy: All:read')
        mod, entries, rights = iter.next()
        mod, entries, rights = iter.next()
        assert entries == ['BadGuy']
        assert rights == []

    def testAllowExtraWhitespace(self):
        """ security: allow extra white space between entries """
        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne,user two:read,write   user three,UserFour:read  All:')
        mod, entries, rights = iter.next()
        assert  entries == ['UserOne', 'user two']
        assert rights == ['read', 'write']
        mod, entries, rights = iter.next()
        assert entries == ['user three', 'UserFour']
        assert rights == ['read']
        mod, entries, rights = iter.next()
        assert entries == ['All']
        assert rights == []


class TestAcl(object):
    """ security: testing access control list

    TO DO: test unknown user?
    """
    def setup_method(self, method):
        # Backup user
        self.savedUser = self.request.user.name

    def teardown_method(self, method):
        # Restore user
        self.request.user.name = self.savedUser

    def testApplyACLByUser(self):
        """ security: applying acl by user name"""
        # This acl string...
        acl_rights = [
            "-MinusGuy:read "
            "+MinusGuy:read "
            "+PlusGuy:read "
            "-PlusGuy:read "
            "Admin1,Admin2:read,write,delete,revert,admin  "
            "Admin3:read,write,admin  "
            "JoeDoe:read,write  "
            "name with spaces,another one:read,write  "
            "CamelCase,extended name:read,write  "
            "BadGuy:  "
            "All:read  "
            ]
        acl = security.AccessControlList(self.request.cfg, acl_rights)

        # Should apply these rights:
        users = (
            # user,                 rights
            # CamelCase names
            ('Admin1', ('read', 'write', 'admin', 'revert', 'delete')),
            ('Admin2', ('read', 'write', 'admin', 'revert', 'delete')),
            ('Admin3', ('read', 'write', 'admin')),
            ('JoeDoe', ('read', 'write')),
            ('SomeGuy', ('read', )),
            # Extended names or mix of extended and CamelCase
            ('name with spaces', ('read', 'write', )),
            ('another one', ('read', 'write', )),
            ('CamelCase', ('read', 'write', )),
            ('extended name', ('read', 'write', )),
            # Blocking bad guys
            ('BadGuy', ()),
            # All other users - every one not mentioned in the acl lines
            ('All', ('read', )),
            ('Anonymous', ('read', )),
            # we check whether ACL processing stops for a user/right match
            # with ACL modifiers
            ('MinusGuy', ()),
            ('PlusGuy', ('read', )),
            )

        # Check rights
        for user, may in users:
            mayNot = [right for right in self.request.cfg.acl_rights_valid
                      if right not in may]
            # User should have these rights...
            for right in may:
                assert acl.may(self.request, user, right)
            # But NOT these:
            for right in mayNot:
                assert not acl.may(self.request, user, right)


class TestGroupACL(object):

    class Config(wikiconfig.Config):
        def groups(self, request):
            groups = {
                u'PGroup': frozenset([u'Antony', u'Beatrice', ]),
                u'AGroup': frozenset([u'All', ]),
                # note: the next line is a INTENDED misnomer, there is "All" in
                # the group NAME, but not in the group members. This makes
                # sure that a bug that erroneously checked "in groupname" (instead
                # of "in groupmembers") does not reappear.
                u'AllGroup': frozenset([]), # note: intended misnomer
            }
            return ConfigGroups(request, groups)

    def testApplyACLByGroup(self):
        """ security: applying acl by group name"""
        # This acl string...
        acl_rights = [
            "PGroup,AllGroup:read,write,admin "
            "AGroup:read "
            ]
        acl = security.AccessControlList(self.request.cfg, acl_rights)

        # Should apply these rights:
        users = (
            # user, rights
            ('Antony', ('read', 'write', 'admin', )),  # in PGroup
            ('Beatrice', ('read', 'write', 'admin', )),  # in PGroup
            ('Charles', ('read', )),  # virtually in AGroup
            )

        # Check rights
        for user, may in users:
            mayNot = [right for right in self.request.cfg.acl_rights_valid
                      if right not in may]
            # User should have these rights...
            for right in may:
                assert acl.may(self.request, user, right)
            # But NOT these:
            for right in mayNot:
                assert not acl.may(self.request, user, right)


class TestPageAcls(object):
    """ security: real-life access control list on pages testing
    """
    mainpage_name = u'AclTestMainPage'
    subpage_name = u'AclTestMainPage/SubPage'
    item_rwforall = u'EveryoneMayReadWriteMe'
    subitem_4boss = u'EveryoneMayReadWriteMe/OnlyTheBossMayWMe'
    pages = [
        # pagename, content
        (mainpage_name, u"#acl JoeDoe:\n#acl JaneDoe:read,write\nFoo!"),
        (subpage_name, u"FooFoo!"),
        (item_rwforall, u"#acl All:read,write\nMay be read from and written to by anyone"),
        (subitem_4boss, u"#acl JoeDoe:read,write\nOnly JoeDoe (the boss) may write"),
    ]

    from MoinMoin._tests import wikiconfig
    class Config(wikiconfig.Config):
        acl_rights_before = u"WikiAdmin:admin,read,write,delete,revert"
        acl_rights_default = u"All:read,write"
        acl_rights_after = u"All:read"
        acl_hierarchic = False

    def setup_class(self):
        # Backup user
        self.savedUser = self.request.user.name
        self.request.user = User(self.request, auth_username=u'WikiAdmin')
        self.request.user.valid = True

        for page_name, page_content in self.pages:
            create_page(self.request, page_name, page_content)

    def teardown_class(self):
        # Restore user
        self.request.user.name = self.savedUser

        for page_name, dummy in self.pages:
            nuke_page(self.request, page_name)

    def testPageACLs(self):
        """ security: test page acls """
        tests = [
            # hierarchic, pagename, username, expected_rights
            (False, self.mainpage_name, u'WikiAdmin', ['read', 'write', 'admin', 'revert', 'delete']),
            (True,  self.mainpage_name, u'WikiAdmin', ['read', 'write', 'admin', 'revert', 'delete']),
            (False, self.mainpage_name, u'AnyUser', ['read']), # by after acl
            (True,  self.mainpage_name, u'AnyUser', ['read']), # by after acl
            (False, self.mainpage_name, u'JaneDoe', ['read', 'write']), # by page acl
            (True,  self.mainpage_name, u'JaneDoe', ['read', 'write']), # by page acl
            (False, self.mainpage_name, u'JoeDoe', []), # by page acl
            (True,  self.mainpage_name, u'JoeDoe', []), # by page acl
            (False, self.subpage_name, u'WikiAdmin', ['read', 'write', 'admin', 'revert', 'delete']),
            (True,  self.subpage_name, u'WikiAdmin', ['read', 'write', 'admin', 'revert', 'delete']),
            (False, self.subpage_name, u'AnyUser', ['read', 'write']), # by default acl
            (True,  self.subpage_name, u'AnyUser', ['read']), # by after acl
            (False, self.subpage_name, u'JoeDoe', ['read', 'write']), # by default acl
            (True,  self.subpage_name, u'JoeDoe', []), # by inherited acl from main page
            (False, self.subpage_name, u'JaneDoe', ['read', 'write']), # by default acl
            (True,  self.subpage_name, u'JaneDoe', ['read', 'write']), # by inherited acl from main page
            (True,  self.subitem_4boss, u'AnyUser', ['read']), # by after acl
            (True,  self.subitem_4boss, u'JoeDoe', ['read', 'write']), # by item acl
        ]

        for hierarchic, pagename, username, may in tests:
            u = User(self.request, auth_username=username)
            u.valid = True

            def _have_right(u, right, pagename, hierarchic):
                self.request.cfg.acl_hierarchic = hierarchic
                can_access = u.may.__getattr__(right)(pagename)
                if can_access:
                    print "page %s: %s test if %s may %s (success)" % (
                        pagename, ['normal', 'hierarchic'][hierarchic], username, right)
                else:
                    print "page %s: %s test if %s may %s (failure)" % (
                        pagename, ['normal', 'hierarchic'][hierarchic], username, right)
                assert can_access

            # User should have these rights...
            for right in may:
                yield _have_right, u, right, pagename, hierarchic

            def _not_have_right(u, right, pagename, hierarchic):
                self.request.cfg.acl_hierarchic = hierarchic
                can_access = u.may.__getattr__(right)(pagename)
                if can_access:
                    print "page %s: %s test if %s may not %s (failure)" % (
                        pagename, ['normal', 'hierarchic'][hierarchic], username, right)
                else:
                    print "page %s: %s test if %s may not %s (success)" % (
                        pagename, ['normal', 'hierarchic'][hierarchic], username, right)
                assert not can_access

            # User should NOT have these rights:
            mayNot = [right for right in self.request.cfg.acl_rights_valid
                      if right not in may]
            for right in mayNot:
                yield _not_have_right, u, right, pagename, hierarchic

coverage_modules = ['MoinMoin.security']