~ubuntu-branches/ubuntu/wily/apparmor/wily-proposed

« back to all changes in this revision

Viewing changes to utils/test/test-aa.py

  • Committer: Package Import Robot
  • Author(s): Steve Beattie
  • Date: 2015-05-11 22:03:04 UTC
  • mfrom: (1.1.31)
  • Revision ID: package-import@ubuntu.com-20150511220304-gcgk9ix2ui7wvyt0
Tags: 2.9.2-0ubuntu1
* Update to apparmor 2.9.2
  - Fix minitools to work with multiple profiles at once (LP: #1378095)
  - Parse mounts that have non-ascii UTF-8 chars (LP: #1310598)
  - Update dovecot profiles (LP: #1296667)
  - Allow ubuntu-helpers to build texlive fonts (LP: #1010909)
* dropped patches incorporated upstream:
  add-mir-abstraction-lp1422521.patch, systemd-dev-log-lp1413232.patch
  parser-fix_modifier_compilation_+_tests.patch,
  tests-fix_systemd_breakage_in_pivot_root-lp1436109.patch,
  GDM_X_authority-lp1432126.patch, and
  debian/patches/easyprof-framework-policy.patch
* Partial merge with debian apparmor package:
  - debian/rules: enable the bindnow hardening flag during build.
  - debian/upstream/signing-key.asc: add new upstream public
    signing key
  - debian/watch: fix watch file, add gpg signature checking
  - install libapparmor.so dev symlink under /usr not /lib
  - debian/patches/reproducible-pdf.patch: make techdoc.pdf
    reproducible even in face of timezone variations.
  - debian/control: sync fields
  - debian/debhelper/postrm-apparmor: remove
    /etc/apparmor.d/{disable,} on package purge
  - debian/libapache2-mod-apparmor.postrm: on package purge, delete
    /etc/apparmor.d/{,disable} if empty
  - debian/libapparmor1.symbols: Use Build-Depends-Package in the
    symbols file.
  - debian/copyright: sync

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#! /usr/bin/env python
2
2
# ------------------------------------------------------------------
3
3
#
4
 
#    Copyright (C) 2014 Christian Boltz
 
4
#    Copyright (C) 2014-2015 Christian Boltz
5
5
#
6
6
#    This program is free software; you can redistribute it and/or
7
7
#    modify it under the terms of version 2 of the GNU General Public
10
10
# ------------------------------------------------------------------
11
11
 
12
12
import unittest
 
13
from common_test import AATest, setup_all_loops
13
14
import os
14
15
import shutil
15
16
import tempfile
16
 
from common_test import write_file
17
 
 
18
 
from apparmor.aa import check_for_apparmor
19
 
 
20
 
class AaTest_check_for_apparmor(unittest.TestCase):
 
17
from common_test import read_file, write_file
 
18
 
 
19
from apparmor.aa import check_for_apparmor, get_profile_flags, set_profile_flags, is_skippable_file, is_skippable_dir, parse_profile_start, separate_vars, store_list_var, write_header, serialize_parse_profile_start
 
20
from apparmor.common import AppArmorException, AppArmorBug
 
21
 
 
22
class AaTestWithTempdir(AATest):
 
23
    def setUp(self):
 
24
        self.tmpdir = tempfile.mkdtemp(prefix='aa-py-')
 
25
 
 
26
    def tearDown(self):
 
27
        if os.path.exists(self.tmpdir):
 
28
            shutil.rmtree(self.tmpdir)
 
29
 
 
30
 
 
31
class AaTest_check_for_apparmor(AaTestWithTempdir):
21
32
    FILESYSTEMS_WITH_SECURITYFS = 'nodev\tdevtmpfs\nnodev\tsecurityfs\nnodev\tsockfs\n\text3\n\text2\n\text4'
22
33
    FILESYSTEMS_WITHOUT_SECURITYFS = 'nodev\tdevtmpfs\nnodev\tsockfs\n\text3\n\text2\n\text4'
23
34
 
28
39
    MOUNTS_WITHOUT_SECURITYFS = ( 'proc /proc proc rw,relatime 0 0\n'
29
40
        '/dev/sda1 / ext3 rw,noatime,data=ordered 0 0' )
30
41
 
31
 
    def setUp(self):
32
 
        self.tmpdir = tempfile.mkdtemp(prefix='aa-py-')
33
 
 
34
 
    def tearDown(self):
35
 
        if os.path.exists(self.tmpdir):
36
 
            shutil.rmtree(self.tmpdir)
37
 
 
38
42
    def test_check_for_apparmor_None_1(self):
39
43
        filesystems = write_file(self.tmpdir, 'filesystems', self.FILESYSTEMS_WITHOUT_SECURITYFS)
40
44
        mounts = write_file(self.tmpdir, 'mounts', self.MOUNTS_WITH_SECURITYFS)
70
74
        mounts = write_file(self.tmpdir, 'mounts', self.MOUNTS_WITH_SECURITYFS % self.tmpdir)
71
75
        self.assertEqual('%s/security/apparmor' % self.tmpdir, check_for_apparmor(filesystems, mounts))
72
76
 
73
 
 
 
77
class AaTest_get_profile_flags(AaTestWithTempdir):
 
78
    def _test_get_flags(self, profile_header, expected_flags):
 
79
        file = write_file(self.tmpdir, 'profile', '%s {\n}\n' % profile_header)
 
80
        flags = get_profile_flags(file, '/foo')
 
81
        self.assertEqual(flags, expected_flags)
 
82
 
 
83
    def test_get_flags_01(self):
 
84
        self._test_get_flags('/foo', None)
 
85
    def test_get_flags_02(self):
 
86
        self._test_get_flags('/foo (  complain  )', '  complain  ')
 
87
    def test_get_flags_04(self):
 
88
        self._test_get_flags('/foo (complain)', 'complain')
 
89
    def test_get_flags_05(self):
 
90
        self._test_get_flags('/foo flags=(complain)', 'complain')
 
91
    def test_get_flags_06(self):
 
92
        self._test_get_flags('/foo flags=(complain,  audit)', 'complain,  audit')
 
93
 
 
94
    def test_get_flags_invalid_01(self):
 
95
        with self.assertRaises(AppArmorException):
 
96
            self._test_get_flags('/foo ()', None)
 
97
    def test_get_flags_invalid_02(self):
 
98
        with self.assertRaises(AppArmorException):
 
99
            self._test_get_flags('/foo flags=()', None)
 
100
    def test_get_flags_invalid_03(self):
 
101
        with self.assertRaises(AppArmorException):
 
102
            self._test_get_flags('/foo (  )', '  ')
 
103
 
 
104
    def test_get_flags_other_profile(self):
 
105
        with self.assertRaises(AppArmorException):
 
106
            self._test_get_flags('/no-such-profile flags=(complain)', 'complain')
 
107
 
 
108
class AaTest_set_profile_flags(AaTestWithTempdir):
 
109
    def _test_set_flags(self, profile, old_flags, new_flags, whitespace='', comment='', more_rules='',
 
110
                        expected_flags='@-@-@', check_new_flags=True, profile_name='/foo'):
 
111
        if old_flags:
 
112
            old_flags = ' %s' % old_flags
 
113
 
 
114
        if expected_flags == '@-@-@':
 
115
            expected_flags = new_flags
 
116
 
 
117
        if expected_flags:
 
118
            expected_flags = ' flags=(%s)' % (expected_flags)
 
119
        else:
 
120
            expected_flags = ''
 
121
 
 
122
        if comment:
 
123
            comment = ' %s' % comment
 
124
 
 
125
        dummy_profile_content = '  #include <abstractions/base>\n  capability chown,\n  /bar r,'
 
126
        prof_template = '%s%s%s {%s\n%s\n%s\n}\n'
 
127
        old_prof = prof_template % (whitespace, profile, old_flags,      comment, more_rules, dummy_profile_content)
 
128
        new_prof = prof_template % (whitespace, profile, expected_flags, comment, more_rules, dummy_profile_content)
 
129
 
 
130
        self.file = write_file(self.tmpdir, 'profile', old_prof)
 
131
        set_profile_flags(self.file, profile_name, new_flags)
 
132
        if check_new_flags:
 
133
            real_new_prof = read_file(self.file)
 
134
            self.assertEqual(new_prof, real_new_prof)
 
135
 
 
136
    # tests that actually don't change the flags
 
137
    def test_set_flags_nochange_01(self):
 
138
        self._test_set_flags('/foo', '', '')
 
139
    def test_set_flags_nochange_02(self):
 
140
        self._test_set_flags('/foo', '(  complain  )', '  complain  ', whitespace='   ')
 
141
    def test_set_flags_nochange_03(self):
 
142
        self._test_set_flags('/foo', '(complain)', 'complain')
 
143
    def test_set_flags_nochange_04(self):
 
144
        self._test_set_flags('/foo', 'flags=(complain)', 'complain')
 
145
    def test_set_flags_nochange_05(self):
 
146
        self._test_set_flags('/foo', 'flags=(complain,  audit)', 'complain,  audit', whitespace='     ')
 
147
    def test_set_flags_nochange_06(self):
 
148
        self._test_set_flags('/foo', 'flags=(complain,  audit)', 'complain,  audit', whitespace='     ', comment='# a comment')
 
149
    def test_set_flags_nochange_07(self):
 
150
        self._test_set_flags('/foo', 'flags=(complain,  audit)', 'complain,  audit', whitespace='     ', more_rules='  # a comment\n#another  comment')
 
151
    def test_set_flags_nochange_08(self):
 
152
        self._test_set_flags('profile /foo', 'flags=(complain)', 'complain')
 
153
    def test_set_flags_nochange_09(self):
 
154
        self._test_set_flags('profile xy /foo', 'flags=(complain)', 'complain', profile_name='xy')
 
155
    def test_set_flags_nochange_10(self):
 
156
        self._test_set_flags('profile "/foo bar"', 'flags=(complain)', 'complain', profile_name='/foo bar')
 
157
    def test_set_flags_nochange_11(self):
 
158
        self._test_set_flags('/foo', '(complain)', 'complain', profile_name=None)
 
159
    #def test_set_flags_nochange_12(self):
 
160
    # XXX changes the flags for the child profile (which happens to have the same profile name) to 'complain'
 
161
    #    self._test_set_flags('/foo', 'flags=(complain)', 'complain', more_rules='  profile /foo {\n}')
 
162
 
 
163
    # tests that change the flags
 
164
    def test_set_flags_01(self):
 
165
        self._test_set_flags('/foo', '', 'audit')
 
166
    def test_set_flags_02(self):
 
167
        self._test_set_flags('/foo', '(  complain  )', 'audit ', whitespace='  ')
 
168
    def test_set_flags_04(self):
 
169
        self._test_set_flags('/foo', '(complain)', 'audit')
 
170
    def test_set_flags_05(self):
 
171
        self._test_set_flags('/foo', 'flags=(complain)', 'audit')
 
172
    def test_set_flags_06(self):
 
173
        self._test_set_flags('/foo', 'flags=(complain,  audit)', None, whitespace='    ')
 
174
    def test_set_flags_07(self):
 
175
        self._test_set_flags('/foo', 'flags=(complain,  audit)', '', expected_flags=None)
 
176
    def test_set_flags_08(self):
 
177
        self._test_set_flags('/foo', '(  complain  )', 'audit ', whitespace='  ', profile_name=None)
 
178
    def test_set_flags_09(self):
 
179
        self._test_set_flags('profile /foo', 'flags=(complain)', 'audit')
 
180
    def test_set_flags_10(self):
 
181
        self._test_set_flags('profile xy /foo', 'flags=(complain)', 'audit', profile_name='xy')
 
182
    def test_set_flags_11(self):
 
183
        self._test_set_flags('profile "/foo bar"', 'flags=(complain)', 'audit', profile_name='/foo bar')
 
184
    def test_set_flags_12(self):
 
185
        self._test_set_flags('profile xy "/foo bar"', 'flags=(complain)', 'audit', profile_name='xy')
 
186
 
 
187
 
 
188
    # XXX regex_hat_flag in set_profile_flags() is totally broken - it matches for '   ' and '  X ', but doesn't match for ' ^foo {'
 
189
    # oh, it matches on a line like 'dbus' and changes it to 'dbus flags=(...)' if there's no leading whitespace (and no comma)
 
190
    #def test_set_flags_hat_01(self):
 
191
    #    self._test_set_flags('  ^hat', '', 'audit')
 
192
 
 
193
 
 
194
    def test_set_flags_invalid_01(self):
 
195
        with self.assertRaises(AppArmorBug):
 
196
            self._test_set_flags('/foo', '()', None, check_new_flags=False)
 
197
    def test_set_flags_invalid_02(self):
 
198
        with self.assertRaises(AppArmorBug):
 
199
            self._test_set_flags('/foo', 'flags=()', None, check_new_flags=False)
 
200
    def test_set_flags_invalid_03(self):
 
201
        with self.assertRaises(AppArmorException):
 
202
            self._test_set_flags('/foo', '(  )', '', check_new_flags=False)
 
203
    def test_set_flags_invalid_04(self):
 
204
        with self.assertRaises(AppArmorBug):
 
205
            self._test_set_flags('/foo', 'flags=(complain,  audit)', '  ', check_new_flags=False) # whitespace-only newflags
 
206
 
 
207
    def test_set_flags_other_profile(self):
 
208
        # test behaviour if the file doesn't contain the specified /foo profile
 
209
        orig_prof = '/no-such-profile flags=(complain) {\n}'
 
210
        self.file = write_file(self.tmpdir, 'profile', orig_prof)
 
211
 
 
212
        with self.assertRaises(AppArmorBug):
 
213
            set_profile_flags(self.file, '/foo', 'audit')
 
214
 
 
215
        # the file should not be changed
 
216
        real_new_prof = read_file(self.file)
 
217
        self.assertEqual(orig_prof, real_new_prof)
 
218
 
 
219
    def test_set_flags_no_profile_found(self):
 
220
        # test behaviour if the file doesn't contain any profile
 
221
        orig_prof = '# /comment flags=(complain) {\n# }'
 
222
        self.file = write_file(self.tmpdir, 'profile', orig_prof)
 
223
 
 
224
        with self.assertRaises(AppArmorBug):
 
225
            set_profile_flags(self.file, None, 'audit')
 
226
 
 
227
        # the file should not be changed
 
228
        real_new_prof = read_file(self.file)
 
229
        self.assertEqual(orig_prof, real_new_prof)
 
230
 
 
231
    def test_set_flags_file_not_found(self):
 
232
        with self.assertRaises(IOError):
 
233
            set_profile_flags('%s/file-not-found' % self.tmpdir, '/foo', 'audit')
 
234
 
 
235
 
 
236
class AaTest_is_skippable_file(AATest):
 
237
    def test_not_skippable_01(self):
 
238
        self.assertFalse(is_skippable_file('bin.ping'))
 
239
    def test_not_skippable_02(self):
 
240
        self.assertFalse(is_skippable_file('usr.lib.dovecot.anvil'))
 
241
    def test_not_skippable_03(self):
 
242
        self.assertFalse(is_skippable_file('bin.~ping'))
 
243
    def test_not_skippable_04(self):
 
244
        self.assertFalse(is_skippable_file('bin.rpmsave.ping'))
 
245
    def test_not_skippable_05(self):
 
246
        # normally is_skippable_file should be called without directory, but it shouldn't hurt too much
 
247
        self.assertFalse(is_skippable_file('/etc/apparmor.d/bin.ping'))
 
248
    def test_not_skippable_06(self):
 
249
        self.assertFalse(is_skippable_file('bin.pingrej'))
 
250
 
 
251
    def test_skippable_01(self):
 
252
        self.assertTrue(is_skippable_file('bin.ping.dpkg-new'))
 
253
    def test_skippable_02(self):
 
254
        self.assertTrue(is_skippable_file('bin.ping.dpkg-old'))
 
255
    def test_skippable_03(self):
 
256
        self.assertTrue(is_skippable_file('bin.ping..dpkg-dist'))
 
257
    def test_skippable_04(self):
 
258
        self.assertTrue(is_skippable_file('bin.ping..dpkg-bak'))
 
259
    def test_skippable_05(self):
 
260
        self.assertTrue(is_skippable_file('bin.ping.rpmnew'))
 
261
    def test_skippable_06(self):
 
262
        self.assertTrue(is_skippable_file('bin.ping.rpmsave'))
 
263
    def test_skippable_07(self):
 
264
        self.assertTrue(is_skippable_file('bin.ping.orig'))
 
265
    def test_skippable_08(self):
 
266
        self.assertTrue(is_skippable_file('bin.ping.rej'))
 
267
    def test_skippable_09(self):
 
268
        self.assertTrue(is_skippable_file('bin.ping~'))
 
269
    def test_skippable_10(self):
 
270
        self.assertTrue(is_skippable_file('.bin.ping'))
 
271
    def test_skippable_11(self):
 
272
        self.assertTrue(is_skippable_file(''))  # empty filename
 
273
    def test_skippable_12(self):
 
274
        self.assertTrue(is_skippable_file('/etc/apparmor.d/'))  # directory without filename
 
275
    def test_skippable_13(self):
 
276
        self.assertTrue(is_skippable_file('README'))
 
277
 
 
278
 
 
279
class AaTest_is_skippable_dir(AATest):
 
280
    tests = [
 
281
        ('disable',                     True),
 
282
        ('cache',                       True),
 
283
        ('lxc',                         True),
 
284
        ('force-complain',              True),
 
285
        ('/etc/apparmor.d/cache',       True),
 
286
        ('/etc/apparmor.d/lxc/',        True),
 
287
 
 
288
        ('dont_disable',                False),
 
289
        ('/etc/apparmor.d/cache_foo',   False),
 
290
        ('abstractions',                False),
 
291
        ('apache2.d',                   False),
 
292
        ('/etc/apparmor.d/apache2.d',   False),
 
293
        ('local',                       False),
 
294
        ('/etc/apparmor.d/local/',      False),
 
295
        ('tunables',                    False),
 
296
        ('/etc/apparmor.d/tunables',    False),
 
297
        ('/etc/apparmor.d/tunables/multiarch.d',            False),
 
298
        ('/etc/apparmor.d/tunables/xdg-user-dirs.d',        False),
 
299
        ('/etc/apparmor.d/tunables/home.d',                 False),
 
300
        ('/etc/apparmor.d/abstractions',                    False),
 
301
        ('/etc/apparmor.d/abstractions/ubuntu-browsers.d',  False),
 
302
        ('/etc/apparmor.d/abstractions/apparmor_api',       False),
 
303
    ]
 
304
 
 
305
    def _run_test(self, params, expected):
 
306
        self.assertEqual(is_skippable_dir(params), expected)
 
307
 
 
308
class AaTest_parse_profile_start(AATest):
 
309
    def _parse(self, line, profile, hat):
 
310
        return parse_profile_start(line, 'somefile', 1, profile, hat)
 
311
        # (profile, hat, flags, in_contained_hat, pps_set_profile, pps_set_hat_external)
 
312
 
 
313
    def test_parse_profile_start_01(self):
 
314
        result = self._parse('/foo {', None, None)
 
315
        expected = ('/foo', '/foo', None, None, False, False, False)
 
316
        self.assertEqual(result, expected)
 
317
 
 
318
    def test_parse_profile_start_02(self):
 
319
        result = self._parse('/foo (complain) {', None, None)
 
320
        expected = ('/foo', '/foo', None, 'complain', False, False, False)
 
321
        self.assertEqual(result, expected)
 
322
 
 
323
    def test_parse_profile_start_03(self):
 
324
        result = self._parse('profile foo /foo {', None, None) # named profile
 
325
        expected = ('foo', 'foo', '/foo', None, False, False, False)
 
326
        self.assertEqual(result, expected)
 
327
 
 
328
    def test_parse_profile_start_04(self):
 
329
        result = self._parse('profile /foo {', '/bar', '/bar') # child profile
 
330
        expected = ('/bar', '/foo', None, None, True, True, False)
 
331
        self.assertEqual(result, expected)
 
332
 
 
333
    def test_parse_profile_start_05(self):
 
334
        result = self._parse('/foo//bar {', None, None) # external hat
 
335
        expected = ('/foo', 'bar', None, None, False, False, True)
 
336
        self.assertEqual(result, expected)
 
337
 
 
338
    def test_parse_profile_start_06(self):
 
339
        result = self._parse('profile "/foo" (complain) {', None, None)
 
340
        expected = ('/foo', '/foo', None, 'complain', False, False, False)
 
341
        self.assertEqual(result, expected)
 
342
 
 
343
 
 
344
    def test_parse_profile_start_invalid_01(self):
 
345
        with self.assertRaises(AppArmorException):
 
346
            self._parse('/foo {', '/bar', '/bar') # child profile without profile keyword
 
347
 
 
348
    def test_parse_profile_start_invalid_02(self):
 
349
        with self.assertRaises(AppArmorBug):
 
350
            self._parse('xy', '/bar', '/bar') # not a profile start
 
351
 
 
352
class AaTest_separate_vars(AATest):
 
353
    tests = [
 
354
        (''                             , set()                      ),
 
355
        ('       '                      , set()                      ),
 
356
        ('  foo bar'                    , {'foo', 'bar'             }),
 
357
        ('foo "  '                      , {'foo'                    }), # XXX " is ignored
 
358
        (' " foo '                      , {' "', 'foo'              }), # XXX really?
 
359
        ('  foo bar   '                 , {'foo', 'bar'             }),
 
360
        ('  foo bar   # comment'        , {'foo', 'bar', 'comment'  }), # XXX should comments be stripped?
 
361
        ('foo'                          , {'foo'                    }),
 
362
        ('"foo" "bar baz"'              , {'foo', 'bar baz'         }),
 
363
        ('foo "bar baz" xy'             , {'foo', 'bar baz', 'xy'   }),
 
364
        ('foo "bar baz '                , {'foo', 'bar', 'baz'      }), # half-quoted
 
365
        ('  " foo" bar'                 , {' foo', 'bar'            }),
 
366
    ]
 
367
 
 
368
    def _run_test(self, params, expected):
 
369
        result = separate_vars(params)
 
370
        self.assertEqual(result, expected)
 
371
 
 
372
 
 
373
class AaTest_store_list_var(AATest):
 
374
    tests = [
 
375
        #  old var                        value        operation   expected (False for exception)
 
376
        ([ {}                           , 'foo'         , '='   ], {'foo'}                      ), # set
 
377
        ([ {}                           , 'foo bar'     , '='   ], {'foo', 'bar'}               ), # set multi
 
378
        ([ {'@{var}': {'foo'}}          , 'bar'         , '='   ], False                        ), # redefine var
 
379
        ([ {}                           , 'bar'         , '+='  ], False                        ), # add to undefined var
 
380
        ([ {'@{var}': {'foo'}}          , 'bar'         , '+='  ], {'foo', 'bar'}               ), # add
 
381
        ([ {'@{var}': {'foo'}}          , 'bar baz'     , '+='  ], {'foo', 'bar', 'baz'}        ), # add multi
 
382
        ([ {'@{var}': {'foo', 'xy'}}    , 'bar baz'     , '+='  ], {'foo', 'xy', 'bar', 'baz'}  ), # add multi to multi
 
383
        ([ {}                           , 'foo'         , '-='  ], False                        ), # unknown operation
 
384
    ]
 
385
 
 
386
    def _run_test(self, params, expected):
 
387
        var         = params[0]
 
388
        value       = params[1]
 
389
        operation   = params[2]
 
390
 
 
391
        if not expected:
 
392
            with self.assertRaises(AppArmorException):
 
393
                store_list_var(var, '@{var}', value, operation, 'somefile')
 
394
            return
 
395
 
 
396
        # dumy value that must not be changed
 
397
        var['@{foo}'] = {'one', 'two'}
 
398
 
 
399
        exp_var = {
 
400
            '@{foo}':   {'one', 'two'},
 
401
            '@{var}':   expected,
 
402
        }
 
403
 
 
404
        store_list_var(var, '@{var}', value, operation, 'somefile')
 
405
 
 
406
        self.assertEqual(var.keys(), exp_var.keys())
 
407
 
 
408
        for key in exp_var:
 
409
            self.assertEqual(var[key], exp_var[key])
 
410
 
 
411
 
 
412
class AaTest_write_header(AATest):
 
413
    tests = [
 
414
        # name       embedded_hat    write_flags    depth   flags           attachment  prof.keyw.  comment    expected
 
415
        (['/foo',    False,          True,          1,      'complain',     None,       None,       None    ],  '  /foo flags=(complain) {'),
 
416
        (['/foo',    True,           True,          1,      'complain',     None,       None,       None    ],  '  profile /foo flags=(complain) {'),
 
417
        (['/foo sp', False,          False,         2,      'complain',     None,       None,       None    ],  '    "/foo sp" {'),
 
418
        (['/foo'    ,False,          False,         2,      'complain',     None,       None,       None    ],  '    /foo {'),
 
419
        (['/foo',    True,           False,         2,      'complain',     None,       None,       None    ],  '    profile /foo {'),
 
420
        (['/foo',    False,          True,          0,      None,           None,       None,       None    ],  '/foo {'),
 
421
        (['/foo',    True,           True,          0,      None,           None,       None,       None    ],  'profile /foo {'),
 
422
        (['/foo',    False,          False,         0,      None,           None,       None,       None    ],  '/foo {'),
 
423
        (['/foo',    True,           False,         0,      None,           None,       None,       None    ],  'profile /foo {'),
 
424
        (['bar',     False,          True,          1,      'complain',     None,       None,       None    ],  '  profile bar flags=(complain) {'),
 
425
        (['bar',     False,          True,          1,      'complain',     '/foo',     None,       None    ],  '  profile bar /foo flags=(complain) {'),
 
426
        (['bar',     True,           True,          1,      'complain',     '/foo',     None,       None    ],  '  profile bar /foo flags=(complain) {'),
 
427
        (['bar baz', False,          True,          1,      None,           '/foo',     None,       None    ],  '  profile "bar baz" /foo {'),
 
428
        (['bar',     True,           True,          1,      None,           '/foo',     None,       None    ],  '  profile bar /foo {'),
 
429
        (['bar baz', False,          True,          1,      'complain',     '/foo sp',  None,       None    ],  '  profile "bar baz" "/foo sp" flags=(complain) {'),
 
430
        (['^foo',    False,          True,          1,      'complain',     None,       None,       None    ],  '  profile ^foo flags=(complain) {'),
 
431
        (['^foo',    True,           True,          1,      'complain',     None,       None,       None    ],  '  ^foo flags=(complain) {'),
 
432
        (['^foo',    True,           True,          1.5,    'complain',     None,       None,       None    ],  '   ^foo flags=(complain) {'),
 
433
        (['^foo',    True,           True,          1.3,    'complain',     None,       None,       None    ],  '  ^foo flags=(complain) {'),
 
434
        (['/foo',    False,          True,          1,      'complain',     None,       'profile',  None    ],  '  profile /foo flags=(complain) {'),
 
435
        (['/foo',    True,           True,          1,      'complain',     None,       'profile',  None    ],  '  profile /foo flags=(complain) {'),
 
436
        (['/foo',    False,          True,          1,      'complain',     None,       None,       '# x'   ],  '  /foo flags=(complain) { # x'),
 
437
        (['/foo',    True,           True,          1,      None,           None,       None,       '# x'   ],  '  profile /foo { # x'),
 
438
        (['/foo',    False,          True,          1,      None,           None,       'profile',  '# x'   ],  '  profile /foo { # x'),
 
439
        (['/foo',    True,           True,          1,      'complain',     None,       'profile',  '# x'   ],  '  profile /foo flags=(complain) { # x'),
 
440
     ]
 
441
 
 
442
    def _run_test(self, params, expected):
 
443
        name = params[0]
 
444
        embedded_hat = params[1]
 
445
        write_flags = params[2]
 
446
        depth = params[3]
 
447
        prof_data = { 'flags': params[4], 'attachment': params[5], 'profile_keyword': params[6], 'header_comment': params[7] }
 
448
 
 
449
        result = write_header(prof_data, depth, name, embedded_hat, write_flags)
 
450
        self.assertEqual(result, [expected])
 
451
 
 
452
class AaTest_serialize_parse_profile_start(AATest):
 
453
    def _parse(self, line, profile, hat, prof_data_profile, prof_data_external):
 
454
        # 'correct' is always True in the code that uses serialize_parse_profile_start() (set some lines above the function call)
 
455
        return serialize_parse_profile_start(line, 'somefile', 1, profile, hat, prof_data_profile, prof_data_external, True)
 
456
 
 
457
    def test_serialize_parse_profile_start_01(self):
 
458
        result = self._parse('/foo {', None, None, False, False)
 
459
        expected = ('/foo', '/foo', None, None, False, True)
 
460
        self.assertEqual(result, expected)
 
461
 
 
462
    def test_serialize_parse_profile_start_02(self):
 
463
        result = self._parse('/foo (complain) {', None, None, False, False)
 
464
        expected = ('/foo', '/foo', None, 'complain', False, True)
 
465
        self.assertEqual(result, expected)
 
466
 
 
467
    def test_serialize_parse_profile_start_03(self):
 
468
        result = self._parse('profile foo /foo {', None, None, False, False) # named profile
 
469
        expected = ('foo', 'foo', '/foo', None, False, True)
 
470
        self.assertEqual(result, expected)
 
471
 
 
472
    def test_serialize_parse_profile_start_04(self):
 
473
        result = self._parse('profile /foo {', '/bar', '/bar', False, False) # child profile
 
474
        expected = ('/bar', '/foo', None, None, True, True)
 
475
        self.assertEqual(result, expected)
 
476
 
 
477
    def test_serialize_parse_profile_start_05(self):
 
478
        result = self._parse('/foo//bar {', None, None, False, False) # external hat
 
479
        expected = ('/foo', 'bar', None, None, False, False) # note correct == False here
 
480
        self.assertEqual(result, expected)
 
481
 
 
482
    def test_serialize_parse_profile_start_06(self):
 
483
        result = self._parse('profile "/foo" (complain) {', None, None, False, False)
 
484
        expected = ('/foo', '/foo', None, 'complain', False, True)
 
485
        self.assertEqual(result, expected)
 
486
 
 
487
    def test_serialize_parse_profile_start_07(self):
 
488
        result = self._parse('/foo {', None, None, True, False)
 
489
        expected = ('/foo', '/foo', None, None, False, True)
 
490
        self.assertEqual(result, expected)
 
491
 
 
492
    def test_serialize_parse_profile_start_08(self):
 
493
        result = self._parse('/foo {', None, None, False, True)
 
494
        expected = ('/foo', '/foo', None, None, False, True)
 
495
        self.assertEqual(result, expected)
 
496
 
 
497
    def test_serialize_parse_profile_start_09(self):
 
498
        result = self._parse('/foo {', None, None, True, True)
 
499
        expected = ('/foo', '/foo', None, None, False, True)
 
500
        self.assertEqual(result, expected)
 
501
 
 
502
    def test_serialize_parse_profile_start_10(self):
 
503
        result = self._parse('profile /foo {', '/bar', '/bar', True, False) # child profile
 
504
        expected = ('/bar', '/foo', None, None, True, True)
 
505
        self.assertEqual(result, expected)
 
506
 
 
507
    def test_serialize_parse_profile_start_11(self):
 
508
        result = self._parse('profile /foo {', '/bar', '/bar', False, True) # child profile
 
509
        expected = ('/bar', '/foo', None, None, True, True)
 
510
        self.assertEqual(result, expected)
 
511
 
 
512
    def test_serialize_parse_profile_start_12(self):
 
513
        result = self._parse('profile /foo {', '/bar', '/bar', True, True) # child profile
 
514
        expected = ('/bar', '/foo', None, None, True, True)
 
515
        self.assertEqual(result, expected)
 
516
 
 
517
class AaTestInvalid_serialize_parse_profile_start(AATest):
 
518
    tests = [
 
519
        # line              profile     hat     p_d_profile p_d_external   expected
 
520
        (['/foo {',         '/bar',     '/bar', False,      False       ], AppArmorException), # child profile without 'profile' keyword
 
521
        (['profile /foo {', '/bar',     '/xy',  False,      False       ], AppArmorException), # already inside a child profile - nesting level reached
 
522
        (['/ext//hat {',    '/bar',     '/bar', True,       True        ], AppArmorException), # external hat inside a profile
 
523
        (['/ext//hat {',    '/bar',     '/bar', True,       False       ], AppArmorException), # external hat inside a profile
 
524
        (['xy',             '/bar',     '/bar', False,      False       ], AppArmorBug      ), # not a profile start
 
525
    ]
 
526
 
 
527
    def _run_test(self, params, expected):
 
528
        line = params[0]
 
529
        profile = params[1]
 
530
        hat = params[2]
 
531
        prof_data_profile = params[3]
 
532
        prof_data_external = params[4]
 
533
 
 
534
        with self.assertRaises(expected):
 
535
            # 'correct' is always True in the code that uses serialize_parse_profile_start() (set some lines above the function call)
 
536
            serialize_parse_profile_start(line, 'somefile', 1, profile, hat, prof_data_profile, prof_data_external, True)
 
537
 
 
538
setup_all_loops(__name__)
74
539
if __name__ == '__main__':
75
540
    unittest.main(verbosity=2)