Hello, this patch adds tests for ChangeProfileRule and ChangeProfileRuleset.
As usual, those classes have 100% test coverage. [ 02-add-tests-for-ChangeProfileRule.diff ] === modified file utils/test/test-change_profile.py --- utils/test/test-change_profile.py 2015-05-09 22:07:32.450220141 +0200 +++ utils/test/test-change_profile.py 2015-05-09 20:23:18.967205925 +0200 @@ -0,0 +1,443 @@ +#!/usr/bin/env python +# ---------------------------------------------------------------------- +# Copyright (C) 2015 Christian Boltz <appar...@cboltz.de> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program 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. +# +# ---------------------------------------------------------------------- + +import unittest +from collections import namedtuple +from common_test import AATest, setup_all_loops + +from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset +from apparmor.rule import BaseRule +from apparmor.common import AppArmorException, AppArmorBug +from apparmor.logparser import ReadLog + +exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', + 'execcond', 'all_execconds', 'targetprofile', 'all_targetprofiles']) + +# --- tests for single ChangeProfileRule --- # + +class ChangeProfileTest(AATest): + def _compare_obj(self, obj, expected): + self.assertEqual(expected.allow_keyword, obj.allow_keyword) + self.assertEqual(expected.audit, obj.audit) + self.assertEqual(expected.execcond, obj.execcond) + self.assertEqual(expected.targetprofile, obj.targetprofile) + self.assertEqual(expected.all_execconds, obj.all_execconds) + self.assertEqual(expected.all_targetprofiles, obj.all_targetprofiles) + self.assertEqual(expected.deny, obj.deny) + self.assertEqual(expected.comment, obj.comment) + +class ChangeProfileTestParse(ChangeProfileTest): + tests = [ + # rawrule audit allow deny comment execcond all? targetprof all? + ('change_profile,' , exp(False, False, False, '' , None , True , None , True )), + ('change_profile /foo,' , exp(False, False, False, '' , '/foo', False, None , True )), + ('change_profile /foo -> /bar,' , exp(False, False, False, '' , '/foo', False, '/bar' , False)), + ('deny change_profile /foo -> /bar, # comment' , exp(False, False, True , ' # comment' , '/foo', False, '/bar' , False)), + ('audit allow change_profile /foo,' , exp(True , True , False, '' , '/foo', False, None , True )), + ('change_profile -> /bar,' , exp(False, False, False, '' , None , True , '/bar' , False)), + ('audit allow change_profile -> /bar,' , exp(True , True , False, '' , None , True , '/bar' , False)), + # quoted versions + ('change_profile "/foo",' , exp(False, False, False, '' , '/foo', False, None , True )), + ('change_profile "/foo" -> "/bar",' , exp(False, False, False, '' , '/foo', False, '/bar' , False)), + ('deny change_profile "/foo" -> "/bar", # cmt' , exp(False, False, True, ' # cmt' , '/foo', False, '/bar' , False)), + ('audit allow change_profile "/foo",' , exp(True , True , False, '' , '/foo', False, None , True )), + ('change_profile -> "/bar",' , exp(False, False, False, '' , None , True , '/bar' , False)), + ('audit allow change_profile -> "/bar",' , exp(True , True , False, '' , None , True , '/bar' , False)), + # with globbing and/or named profiles + ('change_profile,' , exp(False, False, False, '' , None , True , None , True )), + ('change_profile /*,' , exp(False, False, False, '' , '/*' , False, None , True )), + ('change_profile /* -> bar,' , exp(False, False, False, '' , '/*' , False, 'bar' , False)), + ('deny change_profile /** -> bar, # comment' , exp(False, False, True , ' # comment' , '/**' , False, 'bar' , False)), + ('audit allow change_profile /**,' , exp(True , True , False, '' , '/**' , False, None , True )), + ('change_profile -> "ba r",' , exp(False, False, False, '' , None , True , 'ba r' , False)), + ('audit allow change_profile -> "ba r",' , exp(True , True , False, '' , None , True , 'ba r' , False)), + ] + + def _run_test(self, rawrule, expected): + self.assertTrue(ChangeProfileRule.match(rawrule)) + obj = ChangeProfileRule.parse(rawrule) + self.assertEqual(rawrule.strip(), obj.raw_rule) + self._compare_obj(obj, expected) + +class ChangeProfileTestParseInvalid(ChangeProfileTest): + tests = [ + ('change_profile -> ,' , AppArmorException), + ('change_profile foo -> ,' , AppArmorException), + ] + + def _run_test(self, rawrule, expected): + self.assertFalse(ChangeProfileRule.match(rawrule)) + with self.assertRaises(expected): + ChangeProfileRule.parse(rawrule) + +class ChangeProfileTestParseFromLog(ChangeProfileTest): + def test_net_from_log(self): + parser = ReadLog('', '', '', '', '') + + event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' + + # libapparmor doesn't understand this log format (from JJ) + # event = '[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' + + parsed_event = parser.parse_event(event) + + self.assertEqual(parsed_event, { + 'request_mask': None, + 'denied_mask': None, + 'error_code': 0, + #'family': 'inet', + 'magic_token': 0, + 'parent': 0, + 'profile': '/foo/changeprofile', + 'operation': 'change_profile', + 'resource': None, + 'info': None, + 'aamode': 'REJECTING', + 'time': 1428699242, + 'active_hat': None, + 'pid': 3459, + 'task': 0, + 'attr': None, + 'name2': '/foo/rename', # target + 'name': None, + }) + + obj = ChangeProfileRule(ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event) + + # audit allow deny comment execcond all? targetprof all? + expected = exp(False, False, False, '' , None, True, '/foo/rename', False) + + self._compare_obj(obj, expected) + + self.assertEqual(obj.get_raw(1), ' change_profile -> /foo/rename,') + + +class ChangeProfileFromInit(ChangeProfileTest): + tests = [ + # ChangeProfileRule object audit allow deny comment execcond all? targetprof all? + (ChangeProfileRule('/foo', '/bar', deny=True) , exp(False, False, True , '' , '/foo', False, '/bar' , False)), + (ChangeProfileRule('/foo', '/bar') , exp(False, False, False, '' , '/foo', False, '/bar' , False)), + (ChangeProfileRule('/foo', ChangeProfileRule.ALL) , exp(False, False, False, '' , '/foo', False, None , True )), + (ChangeProfileRule(ChangeProfileRule.ALL, '/bar') , exp(False, False, False, '' , None , True , '/bar' , False)), + (ChangeProfileRule(ChangeProfileRule.ALL, + ChangeProfileRule.ALL) , exp(False, False, False, '' , None , True , None , True )), + ] + + def _run_test(self, obj, expected): + self._compare_obj(obj, expected) + + +class InvalidChangeProfileInit(AATest): + tests = [ + # init params expected exception + (['/foo', '' ] , AppArmorBug), # empty targetprofile + (['' , '/bar' ] , AppArmorBug), # empty execcond + ([' ', '/bar' ] , AppArmorBug), # whitespace execcond + (['/foo', ' ' ] , AppArmorBug), # whitespace targetprofile + (['xyxy', '/bar' ] , AppArmorException), # invalid execcond + ([dict(), '/bar' ] , AppArmorBug), # wrong type for execcond + ([None , '/bar' ] , AppArmorBug), # wrong type for execcond + (['/foo', dict() ] , AppArmorBug), # wrong type for targetprofile + (['/foo', None ] , AppArmorBug), # wrong type for targetprofile + ] + + def _run_test(self, params, expected): + with self.assertRaises(expected): + ChangeProfileRule(params[0], params[1]) + + def test_missing_params_1(self): + with self.assertRaises(TypeError): + ChangeProfileRule() + + def test_missing_params_2(self): + with self.assertRaises(TypeError): + ChangeProfileRule('inet') + + +class InvalidChangeProfileTest(AATest): + def _check_invalid_rawrule(self, rawrule): + obj = None + self.assertFalse(ChangeProfileRule.match(rawrule)) + with self.assertRaises(AppArmorException): + obj = ChangeProfileRule(ChangeProfileRule.parse(rawrule)) + + self.assertIsNone(obj, 'ChangeProfileRule handed back an object unexpectedly') + + def test_invalid_net_missing_comma(self): + self._check_invalid_rawrule('change_profile') # missing comma + + def test_invalid_net_non_ChangeProfileRule(self): + self._check_invalid_rawrule('dbus,') # not a change_profile rule + + def test_empty_net_data_1(self): + obj = ChangeProfileRule('/foo', '/bar') + obj.execcond = '' + # no execcond set, and ALL not set + with self.assertRaises(AppArmorBug): + obj.get_clean(1) + + def test_empty_net_data_2(self): + obj = ChangeProfileRule('/foo', '/bar') + obj.targetprofile = '' + # no targetprofile set, and ALL not set + with self.assertRaises(AppArmorBug): + obj.get_clean(1) + + +class WriteChangeProfileTestAATest(AATest): + tests = [ + # raw rule clean rule + (' change_profile , # foo ' , 'change_profile, # foo'), + (' audit change_profile /foo,' , 'audit change_profile /foo,'), + (' deny change_profile /foo -> bar,# foo bar' , 'deny change_profile /foo -> bar, # foo bar'), + (' deny change_profile /foo ,# foo bar' , 'deny change_profile /foo, # foo bar'), + (' allow change_profile -> /bar ,# foo bar' , 'allow change_profile -> /bar, # foo bar'), + (' allow change_profile /** -> /bar ,# foo bar' , 'allow change_profile /** -> /bar, # foo bar'), + (' allow change_profile "/fo o" -> "/b ar",' , 'allow change_profile "/fo o" -> "/b ar",'), + ] + + def _run_test(self, rawrule, expected): + self.assertTrue(ChangeProfileRule.match(rawrule)) + obj = ChangeProfileRule.parse(rawrule) + clean = obj.get_clean() + raw = obj.get_raw() + + self.assertEqual(expected.strip(), clean, 'unexpected clean rule') + self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') + + def test_write_manually(self): + obj = ChangeProfileRule('/foo', 'bar', allow_keyword=True) + + expected = ' allow change_profile /foo -> bar,' + + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + + +class ChangeProfileCoveredTest(AATest): + def _run_test(self, param, expected): + obj = ChangeProfileRule.parse(self.rule) + check_obj = ChangeProfileRule.parse(param) + + self.assertTrue(ChangeProfileRule.match(param)) + + self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0]) + self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1]) + + self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) + self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + +class ChangeProfileCoveredTest_01(ChangeProfileCoveredTest): + rule = 'change_profile /foo,' + + tests = [ + # rule equal strict equal covered covered exact + (' change_profile,' , [ False , False , False , False ]), + (' change_profile /foo,' , [ True , True , True , True ]), + (' change_profile /foo, # comment', [ True , False , True , True ]), + (' allow change_profile /foo,' , [ True , False , True , True ]), + (' change_profile /foo,' , [ True , False , True , True ]), + (' change_profile /foo -> /bar,' , [ False , False , True , True ]), + (' change_profile /foo -> bar,' , [ False , False , True , True ]), + ('audit change_profile /foo,' , [ False , False , False , False ]), + ('audit change_profile,' , [ False , False , False , False ]), + (' change_profile /asdf,' , [ False , False , False , False ]), + (' change_profile -> /bar,' , [ False , False , False , False ]), + ('audit deny change_profile /foo,' , [ False , False , False , False ]), + (' deny change_profile /foo,' , [ False , False , False , False ]), + ] + +class ChangeProfileCoveredTest_02(ChangeProfileCoveredTest): + rule = 'audit change_profile /foo,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'change_profile /foo,' , [ False , False , True , False ]), + ('audit change_profile /foo,' , [ True , True , True , True ]), + ( 'change_profile /foo -> /bar,' , [ False , False , True , False ]), + ('audit change_profile /foo -> /bar,' , [ False , False , True , True ]), # XXX is "covered exact" correct here? + ( 'change_profile,' , [ False , False , False , False ]), + ('audit change_profile,' , [ False , False , False , False ]), + (' change_profile -> /bar,' , [ False , False , False , False ]), + ] + + +class ChangeProfileCoveredTest_03(ChangeProfileCoveredTest): + rule = 'change_profile /foo -> /bar,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'change_profile /foo -> /bar,' , [ True , True , True , True ]), + ('allow change_profile /foo -> /bar,' , [ True , False , True , True ]), + ( 'change_profile /foo,' , [ False , False , False , False ]), + ( 'change_profile,' , [ False , False , False , False ]), + ( 'change_profile /foo -> /xyz,' , [ False , False , False , False ]), + ('audit change_profile,' , [ False , False , False , False ]), + ('audit change_profile /foo -> /bar,' , [ False , False , False , False ]), + ( 'change_profile -> /bar,' , [ False , False , False , False ]), + ( 'change_profile,' , [ False , False , False , False ]), + ] + +class ChangeProfileCoveredTest_04(ChangeProfileCoveredTest): + rule = 'change_profile,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'change_profile,' , [ True , True , True , True ]), + ('allow change_profile,' , [ True , False , True , True ]), + ( 'change_profile /foo,' , [ False , False , True , True ]), + ( 'change_profile /xyz -> bar,' , [ False , False , True , True ]), + ( 'change_profile -> /bar,' , [ False , False , True , True ]), + ( 'change_profile /foo -> /bar,' , [ False , False , True , True ]), + ('audit change_profile,' , [ False , False , False , False ]), + ('deny change_profile,' , [ False , False , False , False ]), + ] + +class ChangeProfileCoveredTest_05(ChangeProfileCoveredTest): + rule = 'deny change_profile /foo,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'deny change_profile /foo,' , [ True , True , True , True ]), + ('audit deny change_profile /foo,' , [ False , False , False , False ]), + ( 'change_profile /foo,' , [ False , False , False , False ]), # XXX should covered be true here? + ( 'deny change_profile /bar,' , [ False , False , False , False ]), + ( 'deny change_profile,' , [ False , False , False , False ]), + ] + + +class ChangeProfileCoveredTest_Invalid(AATest): + def test_borked_obj_is_covered_1(self): + obj = ChangeProfileRule.parse('change_profile /foo,') + + testobj = ChangeProfileRule('/foo', '/bar') + testobj.execcond = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_borked_obj_is_covered_2(self): + obj = ChangeProfileRule.parse('change_profile /foo,') + + testobj = ChangeProfileRule('/foo', '/bar') + testobj.targetprofile = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_covered(self): + obj = ChangeProfileRule.parse('change_profile /foo,') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_equal(self): + obj = ChangeProfileRule.parse('change_profile -> /bar,') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_equal(testobj) + +# --- tests for ChangeProfileRuleset --- # + +class ChangeProfileRulesTest(AATest): + def test_empty_ruleset(self): + ruleset = ChangeProfileRuleset() + ruleset_2 = ChangeProfileRuleset() + self.assertEqual([], ruleset.get_raw(2)) + self.assertEqual([], ruleset.get_clean(2)) + self.assertEqual([], ruleset_2.get_raw(2)) + self.assertEqual([], ruleset_2.get_clean(2)) + + def test_ruleset_1(self): + ruleset = ChangeProfileRuleset() + rules = [ + 'change_profile -> /bar,', + 'change_profile /foo,', + ] + + expected_raw = [ + 'change_profile -> /bar,', + 'change_profile /foo,', + '', + ] + + expected_clean = [ + 'change_profile -> /bar,', + 'change_profile /foo,', + '', + ] + + for rule in rules: + ruleset.add(ChangeProfileRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw()) + self.assertEqual(expected_clean, ruleset.get_clean()) + + def test_ruleset_2(self): + ruleset = ChangeProfileRuleset() + rules = [ + 'change_profile /foo -> /bar,', + 'allow change_profile /asdf,', + 'deny change_profile -> xy, # example comment', + ] + + expected_raw = [ + ' change_profile /foo -> /bar,', + ' allow change_profile /asdf,', + ' deny change_profile -> xy, # example comment', + '', + ] + + expected_clean = [ + ' deny change_profile -> xy, # example comment', + '', + ' allow change_profile /asdf,', + ' change_profile /foo -> /bar,', + '', + ] + + for rule in rules: + ruleset.add(ChangeProfileRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw(1)) + self.assertEqual(expected_clean, ruleset.get_clean(1)) + + +class ChangeProfileGlobTestAATest(AATest): + def setUp(self): + self.ruleset = ChangeProfileRuleset() + + def test_glob_1(self): + self.assertEqual(self.ruleset.get_glob('change_profile /foo,'), 'change_profile,') + + # not supported or used yet, glob behaviour not decided yet + # def test_glob_2(self): + # self.assertEqual(self.ruleset.get_glob('change_profile /foo -> /bar,'), 'change_profile -> /bar,') + + def test_glob_ext(self): + with self.assertRaises(AppArmorBug): + # get_glob_ext is not available for change_profile rules + self.ruleset.get_glob_ext('change_profile /foo -> /bar,') + +class ChangeProfileDeleteTestAATest(AATest): + pass + +setup_all_loops(__name__) +if __name__ == '__main__': + unittest.main(verbosity=2) Regards, Christian Boltz -- Fsck, I'm remembering back to the pre-archived-by-Google era of Usenet; some local newbie was asking how to compile RM COBOL programs in the Unix environment, and my anti-COBOL bias might have shown through as I explained that the RM COBOL compiler on Unix was named "rm". [AdB] -- AppArmor mailing list AppArmor@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor