#! /usr/bin/python3
# ------------------------------------------------------------------
#
#    Copyright (C) 2011-2015 Canonical Ltd.
#
#    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 published by the Free Software Foundation.
#
# ------------------------------------------------------------------

import glob
import json
import optparse
import os
import shutil
import sys
import tempfile
import unittest

import apparmor.easyprof as easyprof
from apparmor.common import AppArmorException

topdir = None
debugging = False


def recursive_rm(dirPath, contents_only=False):
    """recursively remove directory"""
    names = os.listdir(dirPath)
    for name in names:
        path = os.path.join(dirPath, name)
        if os.path.islink(path) or not os.path.isdir(path):
            os.unlink(path)
        else:
            recursive_rm(path)
    if not contents_only:
        os.rmdir(dirPath)


# From Lib/test/test_optparse.py from python 2.7.4
class InterceptedError(Exception):
    def __init__(self,
                 error_message=None,
                 exit_status=None,
                 exit_message=None):
        self.error_message = error_message
        self.exit_status = exit_status
        self.exit_message = exit_message

    def __str__(self):
        return self.error_message or self.exit_message or "intercepted error"


class InterceptingOptionParser(optparse.OptionParser):
    def exit(self, status=0, msg=None):
        raise InterceptedError(exit_status=status, exit_message=msg)

    def error(self, msg):
        raise InterceptedError(error_message=msg)


class Manifest:
    def __init__(self, profile_name):
        self.security = dict()
        self.security['profiles'] = dict()
        self.profile_name = profile_name
        self.security['profiles'][self.profile_name] = dict()

    def add_policygroups(self, policy_list):
        self.security['profiles'][self.profile_name]['policy_groups'] = policy_list.split(",")

    def add_author(self, author):
        self.security['profiles'][self.profile_name]['author'] = author

    def add_copyright(self, copyright):
        self.security['profiles'][self.profile_name]['copyright'] = copyright

    def add_comment(self, comment):
        self.security['profiles'][self.profile_name]['comment'] = comment

    def add_binary(self, binary):
        self.security['profiles'][self.profile_name]['binary'] = binary

    def add_template(self, template):
        self.security['profiles'][self.profile_name]['template'] = template

    def add_template_variable(self, name, value):
        if 'template_variables' not in self.security['profiles'][self.profile_name]:
            self.security['profiles'][self.profile_name]['template_variables'] = dict()

        self.security['profiles'][self.profile_name]['template_variables'][name] = value

    def emit_json(self, use_security_prefix=True):
        manifest = dict()
        manifest['security'] = self.security
        if use_security_prefix:
            dumpee = manifest
        else:
            dumpee = self.security

        return json.dumps(dumpee, indent=2)


#
# Our test class
#
class T(unittest.TestCase):

    # work around UsrMove
    ls = os.path.realpath('/bin/ls')

    def setUp(self):
        """Setup for tests"""
        global topdir

        self.tmpdir = os.path.realpath(tempfile.mkdtemp(prefix='test-aa-easyprof'))

        # Copy everything into place
        for d in ('easyprof/policygroups', 'easyprof/templates'):
            shutil.copytree(os.path.join(topdir, d),
                            os.path.join(self.tmpdir, os.path.basename(d)))

        # Create a test template
        self.test_template = "test-template"
        contents = '''# vim:syntax=apparmor
# %s
# AppArmor policy for ###NAME###
# ###AUTHOR###
# ###COPYRIGHT###
# ###COMMENT###

#include <tunables/global>

###VAR###

###PROFILEATTACH### {
  #include <abstractions/base>

  ###ABSTRACTIONS###

  ###POLICYGROUPS###

  ###READS###

  ###WRITES###
}

''' % (self.test_template,)
        with open(os.path.join(self.tmpdir, 'templates', self.test_template), 'w') as f:
            f.write(contents)

        # Create a test policygroup
        self.test_policygroup = "test-policygroup"
        contents = '''
  # {}
  #include <abstractions/gnome>
  #include <abstractions/nameservice>
'''.format(self.test_policygroup)
        with open(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), 'w') as f:
            f.write(contents)

        # setup our conffile
        self.conffile = os.path.join(self.tmpdir, 'easyprof.conf')
        contents = '''
POLICYGROUPS_DIR="{}/policygroups"
TEMPLATES_DIR="{}/templates"
'''.format(self.tmpdir, self.tmpdir)
        with open(self.conffile, 'w') as f:
            f.write(contents)

        self.binary = "/opt/bin/foo"
        self.full_args = ['-c', self.conffile, self.binary]

        # Check __AA_BASEDIR, which may be set by the Makefile, to see if
        # we should use a non-default base directory path to find
        # abstraction files
        #
        # NOTE: Individual tests can append another --base path to the
        #       args list and override a base path set here
        base = os.getenv('__AA_BASEDIR')
        if base:
            self.full_args.append('--base=' + base)

        # Check __AA_PARSER, which may be set by the Makefile, to see if
        # we should use a non-default apparmor_parser path to verify
        # policy
        parser = os.getenv('__AA_PARSER')
        if parser:
            self.full_args.append('--parser=' + parser)

        if debugging:
            self.full_args.append('-d')

        (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])

        # Now create some differently prefixed files in the include-dir
        self.test_include_dir = os.path.join(self.tmpdir, 'include-dir')
        os.mkdir(self.test_include_dir)
        os.mkdir(os.path.join(self.test_include_dir, "templates"))
        os.mkdir(os.path.join(self.test_include_dir, "policygroups"))
        for d in ('policygroups', 'templates'):
            for f in easyprof.get_directory_contents(os.path.join(
                                                     self.tmpdir, d)):
                shutil.copy(f, os.path.join(self.test_include_dir, d,
                                            "inc_" + os.path.basename(f)))

    def tearDown(self):
        """Teardown for tests"""
        if os.path.exists(self.tmpdir):
            if debugging:
                sys.stdout.write(self.tmpdir + "\n")
            else:
                recursive_rm(self.tmpdir)

#
# config file tests
#
    def test_configuration_file_p_invalid(self):
        """Test config parsing (invalid POLICYGROUPS_DIR)"""
        contents = '''
POLICYGROUPS_DIR=
TEMPLATES_DIR="{}/templates"
'''.format(self.tmpdir)

        with open(self.conffile, 'w') as f:
            f.write(contents)
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("File should have been invalid")

    def test_configuration_file_p_empty(self):
        """Test config parsing (empty POLICYGROUPS_DIR)"""
        contents = '''
POLICYGROUPS_DIR=""
TEMPLATES_DIR="{}/templates"
'''.format(self.tmpdir)

        with open(self.conffile, 'w') as f:
            f.write(contents)
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("File should have been invalid")

    def test_configuration_file_p_nonexistent(self):
        """Test config parsing (nonexistent POLICYGROUPS_DIR)"""
        contents = '''
POLICYGROUPS_DIR="/nonexistent/policygroups"
TEMPLATES_DIR="{}/templates"
'''.format(self.tmpdir)

        with open(self.conffile, 'w') as f:
            f.write(contents)
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("File should have been invalid")

    def test_policygroups_dir_relative(self):
        """Test --policy-groups-dir (relative DIR)"""
        os.chdir(self.tmpdir)
        rel = os.path.join(self.tmpdir, 'relative')
        os.mkdir(rel)
        shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(rel, self.test_policygroup))

        args = self.full_args
        args += ['--policy-groups-dir', './relative', '--show-policy-group', '--policy-groups=' + self.test_policygroup]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        # no fallback
        self.assertTrue(easyp.dirs['policygroups'] == rel,
                        "Not using specified --policy-groups-dir\n"
                        "Specified dir: {}\nActual dir: {}".format(rel, easyp.dirs['policygroups']))
        self.assertFalse(easyp.get_policy_groups() is None, "Could not find policy-groups")

    def test_policygroups_dir_nonexistent(self):
        """Test --policy-groups-dir (nonexistent DIR)"""
        os.chdir(self.tmpdir)
        rel = os.path.join(self.tmpdir, 'nonexistent')

        args = self.full_args
        args += ['--policy-groups-dir', rel, '--show-policy-group', '--policy-groups=' + self.test_policygroup]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        # test if using fallback
        self.assertFalse(easyp.dirs['policygroups'] == rel, "Using nonexistent --policy-groups-dir")

        # test fallback
        self.assertTrue(easyp.get_policy_groups() is not None, "Found policy-groups when shouldn't have")

    def test_policygroups_dir_valid(self):
        """Test --policy-groups-dir (valid DIR)"""
        os.chdir(self.tmpdir)
        valid = os.path.join(self.tmpdir, 'valid')
        os.mkdir(valid)
        shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(valid, self.test_policygroup))

        args = self.full_args
        args += ['--policy-groups-dir', valid, '--show-policy-group', '--policy-groups=' + self.test_policygroup]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        # no fallback
        self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir")
        self.assertFalse(easyp.get_policy_groups() is None, "Could not find policy-groups")

    def test_policygroups_dir_valid_with_vendor(self):
        """Test --policy-groups-dir (valid DIR with vendor)"""
        os.chdir(self.tmpdir)
        valid = os.path.join(self.tmpdir, 'valid')
        os.mkdir(valid)
        shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup),
                    os.path.join(valid, self.test_policygroup))

        vendor = "ubuntu"
        version = "1.0"
        valid_distro = os.path.join(valid, vendor, version)
        os.mkdir(os.path.join(valid, vendor))
        os.mkdir(valid_distro)
        shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), valid_distro)

        args = self.full_args
        args += ['--policy-groups-dir', valid, '--show-policy-group', '--policy-groups=' + self.test_policygroup]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir")
        self.assertFalse(easyp.get_policy_groups() is None, "Could not find policy-groups")
        for f in easyp.get_policy_groups():
            self.assertFalse(os.path.basename(f) == vendor, "Found '{}' in {}".format(vendor, f))

    def test_configuration_file_t_invalid(self):
        """Test config parsing (invalid TEMPLATES_DIR)"""
        contents = '''
TEMPLATES_DIR=
POLICYGROUPS_DIR="{}/templates"
'''.format(self.tmpdir)

        with open(self.conffile, 'w') as f:
            f.write(contents)
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("File should have been invalid")

    def test_configuration_file_t_empty(self):
        """Test config parsing (empty TEMPLATES_DIR)"""
        contents = '''
TEMPLATES_DIR=""
POLICYGROUPS_DIR="{}/templates"
'''.format(self.tmpdir)

        with open(self.conffile, 'w') as f:
            f.write(contents)
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("File should have been invalid")

    def test_configuration_file_t_nonexistent(self):
        """Test config parsing (nonexistent TEMPLATES_DIR)"""
        contents = '''
TEMPLATES_DIR="/nonexistent/policygroups"
POLICYGROUPS_DIR="{}/templates"
'''.format(self.tmpdir)

        with open(self.conffile, 'w') as f:
            f.write(contents)
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("File should have been invalid")

    def test_templates_dir_relative(self):
        """Test --templates-dir (relative DIR)"""
        os.chdir(self.tmpdir)
        rel = os.path.join(self.tmpdir, 'relative')
        os.mkdir(rel)
        shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(rel, self.test_template))

        args = self.full_args
        args += ['--templates-dir', './relative', '--show-template', '--template=' + self.test_template]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        # no fallback
        self.assertTrue(easyp.dirs['templates'] == rel,
                        "Not using specified --template-dir\n"
                        "Specified dir: {}\nActual dir: {}".format(rel, easyp.dirs['templates']))
        self.assertFalse(easyp.get_templates() is None, "Could not find templates")

    def test_templates_dir_nonexistent(self):
        """Test --templates-dir (nonexistent DIR)"""
        os.chdir(self.tmpdir)
        rel = os.path.join(self.tmpdir, 'nonexistent')

        args = self.full_args
        args += ['--templates-dir', rel, '--show-template', '--template=' + self.test_template]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        # test if using fallback
        self.assertFalse(easyp.dirs['templates'] == rel, "Using nonexistent --template-dir")

        # test fallback
        self.assertTrue(easyp.get_templates() is not None, "Found templates when shouldn't have")

    def test_templates_dir_valid(self):
        """Test --templates-dir (valid DIR)"""
        os.chdir(self.tmpdir)
        valid = os.path.join(self.tmpdir, 'valid')
        os.mkdir(valid)
        shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(valid, self.test_template))

        args = self.full_args
        args += ['--templates-dir', valid, '--show-template', '--template=' + self.test_template]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        # no fallback
        self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir")
        self.assertFalse(easyp.get_templates() is None, "Could not find templates")

    def test_templates_dir_valid_with_vendor(self):
        """Test --templates-dir (valid DIR with vendor)"""
        os.chdir(self.tmpdir)
        valid = os.path.join(self.tmpdir, 'valid')
        os.mkdir(valid)
        shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(valid, self.test_template))

        vendor = "ubuntu"
        version = "1.0"
        valid_distro = os.path.join(valid, vendor, version)
        os.mkdir(os.path.join(valid, vendor))
        os.mkdir(valid_distro)
        shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), valid_distro)

        args = self.full_args
        args += ['--templates-dir', valid, '--show-template', '--template=' + self.test_template]
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir")
        self.assertFalse(easyp.get_templates() is None, "Could not find templates")
        for f in easyp.get_templates():
            self.assertFalse(os.path.basename(f) == vendor, "Found '{}' in {}".format(vendor, f))

#
# Binary file tests
#
    def test_binary_without_profile_name(self):
        """Test binary (<binary> { })"""
        easyprof.AppArmorEasyProfile(self.ls, self.options)

    def test_binary_with_profile_name(self):
        """Test binary (profile <name> <binary> { })"""
        args = self.full_args
        args += ['--profile-name=some-profile-name']
        (self.options, self.args) = easyprof.parse_args(args)
        easyprof.AppArmorEasyProfile(self.ls, self.options)

    def test_binary_omitted_with_profile_name(self):
        """Test binary (profile <name> { })"""
        args = self.full_args
        args += ['--profile-name=some-profile-name']
        (self.options, self.args) = easyprof.parse_args(args)
        easyprof.AppArmorEasyProfile(None, self.options)

    def test_binary_nonexistent(self):
        """Test binary (nonexistent)"""
        easyprof.AppArmorEasyProfile(os.path.join(self.tmpdir, 'nonexistent'), self.options)

    def test_binary_relative(self):
        """Test binary (relative)"""
        try:
            easyprof.AppArmorEasyProfile('./foo', self.options)
        except AppArmorException:
            return
        raise Exception("Binary should have been invalid")

    def test_binary_symlink(self):
        """Test binary (symlink)"""
        exe = os.path.join(self.tmpdir, 'exe')
        open(exe, 'a').close()
        symlink = exe + ".lnk"
        os.symlink(exe, symlink)

        try:
            easyprof.AppArmorEasyProfile(symlink, self.options)
        except AppArmorException:
            return
        raise Exception("Binary should have been invalid")

#
# Templates tests
#
    def test_templates_list(self):
        """Test templates (list)"""
        args = self.full_args
        args.append('--list-templates')
        (self.options, self.args) = easyprof.parse_args(args)

        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        for i in easyp.get_templates():
            self.assertTrue(os.path.exists(i), "Could not find '{}'".format(i))

    def test_templates_show(self):
        """Test templates (show)"""
        files = glob.glob(self.tmpdir + "/templates/*")
        for f in files:
            args = self.full_args
            args += ['--show-template', '--template', f]
            (self.options, self.args) = easyprof.parse_args(args)
            easyp = easyprof.AppArmorEasyProfile(None, self.options)

            path = os.path.join(easyp.dirs['templates'], f)
            self.assertTrue(os.path.exists(path), "Could not find '{}'".format(path))
            with open(path) as fd:
                fd.read()

    def test_templates_list_include(self):
        """Test templates (list with --include-templates-dir)"""
        args = self.full_args
        args.append('--list-templates')
        (self.options, self.args) = easyprof.parse_args(args)

        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        orig_templates = easyp.get_templates()

        args = self.full_args
        args.append('--list-templates')
        args.append('--include-templates-dir='
                    + os.path.join(self.test_include_dir, 'templates'))
        (self.options, self.args) = easyprof.parse_args(args)

        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        inc_templates = easyp.get_templates()
        self.assertTrue(len(inc_templates) == len(orig_templates) * 2,
                        "templates missing: {}".format(inc_templates))

        for i in inc_templates:
            self.assertTrue(os.path.exists(i), "Could not find '{}'".format(i))

    def test_templates_show_include(self):
        """Test templates (show with --include-templates-dir)"""
        files = glob.glob(self.test_include_dir + "/templates/*")
        for f in files:
            args = self.full_args
            args += ['--show-template',
                     '--template', f,
                     '--include-templates-dir='
                     + os.path.join(self.test_include_dir, 'templates')]
            (self.options, self.args) = easyprof.parse_args(args)
            easyp = easyprof.AppArmorEasyProfile(None, self.options)

            path = os.path.join(easyp.dirs['templates_include'], f)
            self.assertTrue(os.path.exists(path), "Could not find '{}'".format(path))
            with open(path) as fd:
                fd.read()

            bn = os.path.basename(f)
            # setup() copies everything in the include prefixed with inc_
            self.assertTrue(bn.startswith('inc_'),
                            "'{}' does not start with 'inc_'".format(bn))

#
# Policygroups tests
#
    def test_policygroups_list(self):
        """Test policygroups (list)"""
        args = self.full_args
        args.append('--list-policy-groups')
        (self.options, self.args) = easyprof.parse_args(args)

        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        for i in easyp.get_policy_groups():
            self.assertTrue(os.path.exists(i), "Could not find '{}'".format(i))

    def test_policygroups_show(self):
        """Test policygroups (show)"""
        files = glob.glob(self.tmpdir + "/policygroups/*")
        for f in files:
            args = self.full_args
            args += ['--show-policy-group',
                     '--policy-groups', os.path.basename(f)]
            (self.options, self.args) = easyprof.parse_args(args)
            easyp = easyprof.AppArmorEasyProfile(None, self.options)

            path = os.path.join(easyp.dirs['policygroups'], f)
            self.assertTrue(os.path.exists(path), "Could not find '{}'".format(path))
            with open(path) as fd:
                fd.read()

    def test_policygroups_list_include(self):
        """Test policygroups (list with --include-policy-groups-dir)"""
        args = self.full_args
        args.append('--list-policy-groups')
        (self.options, self.args) = easyprof.parse_args(args)

        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        orig_policy_groups = easyp.get_policy_groups()

        args = self.full_args
        args.append('--list-policy-groups')
        args.append('--include-policy-groups-dir='
                    + os.path.join(self.test_include_dir, 'policygroups'))
        (self.options, self.args) = easyprof.parse_args(args)

        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        inc_policy_groups = easyp.get_policy_groups()
        self.assertTrue(len(inc_policy_groups) == len(orig_policy_groups) * 2,
                        "policy_groups missing: {}".format(inc_policy_groups))

        for i in inc_policy_groups:
            self.assertTrue(os.path.exists(i), "Could not find '{}'".format(i))

    def test_policygroups_show_include(self):
        """Test policygroups (show with --include-policy-groups-dir)"""
        files = glob.glob(self.test_include_dir + "/policygroups/*")
        for f in files:
            args = self.full_args
            args += ['--show-policy-group',
                     '--policy-groups', os.path.basename(f),
                     '--include-policy-groups-dir='
                     + os.path.join(self.test_include_dir, 'policygroups')]
            (self.options, self.args) = easyprof.parse_args(args)
            easyp = easyprof.AppArmorEasyProfile(None, self.options)

            path = os.path.join(easyp.dirs['policygroups_include'], f)
            self.assertTrue(os.path.exists(path), "Could not find '{}'".format(path))
            with open(path) as fd:
                fd.read()

            bn = os.path.basename(f)
            # setup() copies everything in the include prefixed with inc_
            self.assertTrue(bn.startswith('inc_'),
                            "'{}' does not start with 'inc_'".format(bn))

#
# Manifest file argument tests
#
    def test_manifest_argument(self):
        """Test manifest argument"""

        # setup our manifest
        self.manifest = os.path.join(self.tmpdir, 'manifest.json')
        contents = '''
{"security": {"domain.reverse.appname": {"name": "simple-app"}}}
'''
        with open(self.manifest, 'w') as f:
            f.write(contents)

        args = self.full_args
        args.extend(('--manifest', self.manifest))
        easyprof.parse_args(args)

    def _manifest_conflicts(self, opt, value):
        """Helper for conflicts tests"""
        # setup our manifest
        self.manifest = os.path.join(self.tmpdir, 'manifest.json')
        contents = '''
{"security": {"domain.reverse.appname": {"binary": /nonexistent"}}}
'''
        with open(self.manifest, 'w') as f:
            f.write(contents)

        # opt first
        args = self.full_args
        args.extend((opt, value, '--manifest', self.manifest))
        raised = False
        try:
            easyprof.parse_args(args, InterceptingOptionParser())
        except InterceptedError:
            raised = True

        self.assertTrue(raised, msg=opt + " and manifest arguments did not "
                                          "raise a parse error")

        # manifest first
        args = self.full_args
        args.extend(('--manifest', self.manifest, opt, value))
        raised = False
        try:
            easyprof.parse_args(args, InterceptingOptionParser())
        except InterceptedError:
            raised = True

        self.assertTrue(raised, msg=opt + " and manifest arguments did not "
                                          "raise a parse error")

    def test_manifest_conflicts_profilename(self):
        """Test manifest arg conflicts with profile_name arg"""
        self._manifest_conflicts("--profile-name", "simple-app")

    def test_manifest_conflicts_copyright(self):
        """Test manifest arg conflicts with copyright arg"""
        self._manifest_conflicts("--copyright", "2013-01-01")

    def test_manifest_conflicts_author(self):
        """Test manifest arg conflicts with author arg"""
        self._manifest_conflicts("--author", "Foo Bar")

    def test_manifest_conflicts_comment(self):
        """Test manifest arg conflicts with comment arg"""
        self._manifest_conflicts("--comment", "some comment")

    def test_manifest_conflicts_abstractions(self):
        """Test manifest arg conflicts with abstractions arg"""
        self._manifest_conflicts("--abstractions", "base")

    def test_manifest_conflicts_read_path(self):
        """Test manifest arg conflicts with read-path arg"""
        self._manifest_conflicts("--read-path", "/etc/passwd")

    def test_manifest_conflicts_write_path(self):
        """Test manifest arg conflicts with write-path arg"""
        self._manifest_conflicts("--write-path", "/tmp/foo")

    def test_manifest_conflicts_policy_groups(self):
        """Test manifest arg conflicts with policy-groups arg"""
        self._manifest_conflicts("--policy-groups", "opt-application")

    def test_manifest_conflicts_name(self):
        """Test manifest arg conflicts with name arg"""
        self._manifest_conflicts("--name", "foo")

    def test_manifest_conflicts_template_var(self):
        """Test manifest arg conflicts with template-var arg"""
        self._manifest_conflicts("--template-var", "foo")

    def test_manifest_conflicts_policy_version(self):
        """Test manifest arg conflicts with policy-version arg"""
        self._manifest_conflicts("--policy-version", "1.0")

    def test_manifest_conflicts_policy_vendor(self):
        """Test manifest arg conflicts with policy-vendor arg"""
        self._manifest_conflicts("--policy-vendor", "somevendor")

#
# Test genpolicy
#
    def _gen_policy(self, name=None, template=None, extra_args=None):
        """Generate a policy"""
        # Build up our args
        args = self.full_args

        if template is None:
            args.append('--template=' + self.test_template)
        else:
            args.append('--template=' + template)

        if name is not None:
            args.append('--name=' + name)

        if extra_args:
            args += extra_args

        args.append(self.binary)

        # Now parse our args
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
        params = easyprof.gen_policy_params(self.binary, self.options)
        p = easyp.gen_policy(**params)

        # We always need to check for these
        search_terms = [self.binary]
        if name is not None:
            search_terms.append(name)

        if template is None:
            search_terms.append(self.test_template)

        for s in search_terms:
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))

        # ###NAME### should be replaced with self.binary or 'name'. Check for that
        inv_s = '###NAME###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

        if debugging:
            sys.stdout.write(p + "\n")

        return p

    def _gen_manifest_policy(self, manifest, use_security_prefix=True):
        # Build up our args
        args = self.full_args
        args.append("--manifest=/dev/null")

        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(manifest.emit_json(use_security_prefix), self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, self.options)
        params = easyprof.gen_policy_params(binary, self.options)
        p = easyp.gen_policy(**params)

        # ###NAME### should be replaced with self.binary or 'name'. Check for that
        inv_s = '###NAME###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

        if debugging:
            sys.stdout.write(p + "\n")

        return p

    def test__is_safe(self):
        """Test _is_safe()"""
        bad = (
            "/../../../../etc/passwd",
            "abstraction with spaces",
            "semicolon;bad",
            "bad\x00baz",
            "foo/bar",
            "foo'bar",
            'foo"bar',
        )
        for s in bad:
            self.assertFalse(easyprof._is_safe(s), "'{}' should be bad".format(s))

    def test_genpolicy_templates_abspath(self):
        """Test genpolicy (abspath to template)"""
        # create a new template
        template = os.path.join(self.tmpdir, "test-abspath-template")
        shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template)
        with open(template) as f:
            contents = f.read()
        test_string = "#teststring"
        with open(template, 'w') as f:
            f.write(contents + "\n{}\n".format(test_string))

        p = self._gen_policy(template=template)

        for s in (self.test_template, test_string):
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))

    def test_genpolicy_templates_system(self):
        """Test genpolicy (system template)"""
        self._gen_policy()

    def test_genpolicy_templates_nonexistent(self):
        """Test genpolicy (nonexistent template)"""
        try:
            self._gen_policy(template=os.path.join(self.tmpdir, "/nonexistent"))
        except AppArmorException:
            return
        raise Exception("template should be invalid")

    def test_genpolicy_name(self):
        """Test genpolicy (name)"""
        self._gen_policy(name='test-foo')

    def test_genpolicy_comment(self):
        """Test genpolicy (comment)"""
        s = "test comment"
        p = self._gen_policy(extra_args=['--comment=' + s])
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###COMMENT###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_author(self):
        """Test genpolicy (author)"""
        s = "Archibald Poindexter"
        p = self._gen_policy(extra_args=['--author=' + s])
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###AUTHOR###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_copyright(self):
        """Test genpolicy (copyright)"""
        s = "2112/01/01"
        p = self._gen_policy(extra_args=['--copyright=' + s])
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###COPYRIGHT###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_abstractions(self):
        """Test genpolicy (single abstraction)"""
        s = "nameservice"
        p = self._gen_policy(extra_args=['--abstractions=' + s])
        search = "#include <abstractions/{}>".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###ABSTRACTIONS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_abstractions_multiple(self):
        """Test genpolicy (multiple abstractions)"""
        abstractions = "authentication,X,user-tmp"
        p = self._gen_policy(extra_args=['--abstractions=' + abstractions])
        for s in abstractions.split(','):
            search = "#include <abstractions/{}>".format(s)
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###ABSTRACTIONS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_abstractions_bad(self):
        """Test genpolicy (abstractions - bad values)"""
        bad = (
               "nonexistent",
               "/../../../../etc/passwd",
               "abstraction with spaces",
              )
        for s in bad:
            try:
                self._gen_policy(extra_args=['--abstractions=' + s])
            except AppArmorException:
                continue
            raise Exception("abstraction '{}' should be invalid".format(s))

    def _create_tmp_base_dir(self, prefix='', abstractions=(), tunables=()):
        """Create a temporary base dir layout"""
        base_name = 'apparmor.d'
        if prefix:
            base_name = '{}-{}'.format(prefix, base_name)
        base_dir = os.path.join(self.tmpdir, base_name)
        abstractions_dir = os.path.join(base_dir, 'abstractions')
        tunables_dir = os.path.join(base_dir, 'tunables')

        os.mkdir(base_dir)
        os.mkdir(abstractions_dir)
        os.mkdir(tunables_dir)

        for f in abstractions:
            contents = '''
  # Abstraction file for testing
  /{} r,
'''.format(f)
            with open(os.path.join(abstractions_dir, f), 'w') as fd:
                fd.write(contents)

        for f in tunables:
            contents = '''
# Tunable file for testing
@{AA_TEST_%s}=foo
''' % (f,)
            with open(os.path.join(tunables_dir, f), 'w') as fd:
                fd.write(contents)

        return base_dir

    def test_genpolicy_abstractions_custom_base(self):
        """Test genpolicy (custom base dir)"""
        abstraction = "custom-base-dir-test-abstraction"
        # The default template #includes the base abstraction and global
        # tunable so we need to create placeholders
        base = self._create_tmp_base_dir(abstractions=['base', abstraction], tunables=['global'])
        args = ['--abstractions=' + abstraction, '--base=' + base]

        p = self._gen_policy(extra_args=args)
        search = "#include <abstractions/{}>".format(abstraction)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###ABSTRACTIONS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_abstractions_custom_base_bad(self):
        """Test genpolicy (custom base dir - bad base dirs)"""
        abstraction = "custom-base-dir-test-abstraction"
        bad = [None, '/etc/apparmor.d', '/']
        for base in bad:
            try:
                args = ['--abstractions=' + abstraction]
                if base:
                    args.append('--base={}'.format(base))
                self._gen_policy(extra_args=args)
            except AppArmorException:
                continue
            raise Exception("abstraction '{}' should be invalid".format(abstraction))

    def test_genpolicy_abstractions_custom_include(self):
        """Test genpolicy (custom include dir)"""
        abstraction = "custom-include-dir-test-abstraction"
        # No need to create placeholders for the base abstraction or global
        # tunable since we're not adjusting the base directory
        include = self._create_tmp_base_dir(abstractions=[abstraction])
        args = ['--abstractions=' + abstraction, '--Include=' + include]
        p = self._gen_policy(extra_args=args)
        search = "#include <abstractions/{}>".format(abstraction)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###ABSTRACTIONS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_abstractions_custom_include_bad(self):
        """Test genpolicy (custom include dir - bad include dirs)"""
        abstraction = "custom-include-dir-test-abstraction"
        bad = [None, '/etc/apparmor.d', '/']
        for include in bad:
            try:
                args = ['--abstractions=' + abstraction]
                if include:
                    args.append('--Include={}'.format(include))
                self._gen_policy(extra_args=args)
            except AppArmorException:
                continue
            raise Exception("abstraction '{}' should be invalid".format(abstraction))

    def test_genpolicy_profile_name_bad(self):
        """Test genpolicy (profile name - bad values)"""
        bad = [
            "/../../../../etc/passwd",
            "../../../../etc/passwd",
            "profile name with spaces",
        ]
        for s in bad:
            try:
                self._gen_policy(extra_args=['--profile-name=' + s])
            except AppArmorException:
                continue
            raise Exception("profile_name '{}' should be invalid".format(s))

    def test_genpolicy_policy_group_bad(self):
        """Test genpolicy (policy group - bad values)"""
        bad = [
            "/../../../../etc/passwd",
            "../../../../etc/passwd",
            "profile name with spaces",
        ]
        for s in bad:
            try:
                self._gen_policy(extra_args=['--policy-groups=' + s])
            except AppArmorException:
                continue
            raise Exception("policy group '{}' should be invalid".format(s))

    def test_genpolicy_policygroups(self):
        """Test genpolicy (single policygroup)"""
        groups = self.test_policygroup
        p = self._gen_policy(extra_args=['--policy-groups=' + groups])

        for s in ('#include <abstractions/nameservice>', '#include <abstractions/gnome>'):
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###POLICYGROUPS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_policygroups_multiple(self):
        """Test genpolicy (multiple policygroups)"""
        test_policygroup2 = "test-policygroup2"
        contents = '''
  # {}
  #include <abstractions/kde>
  #include <abstractions/openssl>
'''.format(self.test_policygroup)
        with open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w') as f:
            f.write(contents)

        groups = "{},{}".format(self.test_policygroup, test_policygroup2)
        p = self._gen_policy(extra_args=['--policy-groups=' + groups])

        for s in ('#include <abstractions/nameservice>',
                  '#include <abstractions/gnome>',
                  '#include <abstractions/kde>',
                  '#include <abstractions/openssl>'):
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###POLICYGROUPS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_policygroups_nonexistent(self):
        """Test genpolicy (nonexistent policygroup)"""
        try:
            self._gen_policy(extra_args=['--policy-groups=nonexistent'])
        except AppArmorException:
            return
        raise Exception("policygroup should be invalid")

    def test_genpolicy_readpath_file(self):
        """Test genpolicy (read-path file)"""
        s = "/opt/test-foo"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search = "{} rk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_home_file(self):
        """Test genpolicy (read-path file in /home)"""
        s = "/home/*/test-foo"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search = "owner {} rk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_homevar_file(self):
        """Test genpolicy (read-path file in @{HOME})"""
        s = "@{HOME}/test-foo"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search = "owner {} rk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_homedirs_file(self):
        """Test genpolicy (read-path file in @{HOMEDIRS})"""
        s = "@{HOMEDIRS}/test-foo"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search = "owner {} rk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_dir(self):
        """Test genpolicy (read-path directory/)"""
        s = "/opt/test-foo-dir/"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search_terms = ["{} rk,".format(s), "{}** rk,".format(s)]
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_dir_glob(self):
        """Test genpolicy (read-path directory/*)"""
        s = "/opt/test-foo-dir/*"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search_terms = ["{} rk,".format(os.path.dirname(s)), "{} rk,".format(s)]
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_dir_glob_all(self):
        """Test genpolicy (read-path directory/**)"""
        s = "/opt/test-foo-dir/**"
        p = self._gen_policy(extra_args=['--read-path=' + s])
        search_terms = ["{} rk,".format(os.path.dirname(s)), "{} rk,".format(s)]
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_multiple(self):
        """Test genpolicy (read-path multiple)"""
        paths = [
            "/opt/test-foo",
            "/home/*/test-foo",
            "@{HOME}/test-foo",
            "@{HOMEDIRS}/test-foo",
            "/opt/test-foo-dir/",
            "/opt/test-foo-dir/*",
            "/opt/test-foo-dir/**",
        ]
        args = []
        search_terms = []
        for s in paths:
            args.append('--read-path=' + s)
            # This mimics easyprof.gen_path_rule()
            owner = ""
            if s.startswith('/home/') or s.startswith("@{HOME"):
                owner = "owner "
            if s.endswith('/'):
                search_terms.append("{} rk,".format(s))
                search_terms.append("{}{}** rk,".format(owner, s))
            elif s.endswith('/**') or s.endswith('/*'):
                search_terms.append("{} rk,".format(os.path.dirname(s)))
                search_terms.append("{}{} rk,".format(owner, s))
            else:
                search_terms.append("{}{} rk,".format(owner, s))

        p = self._gen_policy(extra_args=args)
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_readpath_bad(self):
        """Test genpolicy (read-path bad)"""
        s = "bar"
        try:
            self._gen_policy(extra_args=['--read-path=' + s])
        except AppArmorException:
            return
        raise Exception("read-path should be invalid")

    def test_genpolicy_writepath_file(self):
        """Test genpolicy (write-path file)"""
        s = "/opt/test-foo"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search = "{} rwk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_home_file(self):
        """Test genpolicy (write-path file in /home)"""
        s = "/home/*/test-foo"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search = "owner {} rwk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_homevar_file(self):
        """Test genpolicy (write-path file in @{HOME})"""
        s = "@{HOME}/test-foo"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search = "owner {} rwk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_homedirs_file(self):
        """Test genpolicy (write-path file in @{HOMEDIRS})"""
        s = "@{HOMEDIRS}/test-foo"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search = "owner {} rwk,".format(s)
        self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_dir(self):
        """Test genpolicy (write-path directory/)"""
        s = "/opt/test-foo-dir/"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search_terms = ["{} rwk,".format(s), "{}** rwk,".format(s)]
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_dir_glob(self):
        """Test genpolicy (write-path directory/*)"""
        s = "/opt/test-foo-dir/*"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search_terms = ["{} rwk,".format(os.path.dirname(s)), "{} rwk,".format(s)]
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_dir_glob_all(self):
        """Test genpolicy (write-path directory/**)"""
        s = "/opt/test-foo-dir/**"
        p = self._gen_policy(extra_args=['--write-path=' + s])
        search_terms = ["{} rwk,".format(os.path.dirname(s)), "{} rwk,".format(s)]
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_multiple(self):
        """Test genpolicy (write-path multiple)"""
        paths = [
            "/opt/test-foo",
            "/home/*/test-foo",
            "@{HOME}/test-foo",
            "@{HOMEDIRS}/test-foo",
            "/opt/test-foo-dir/",
            "/opt/test-foo-dir/*",
            "/opt/test-foo-dir/**",
        ]
        args = []
        search_terms = []
        for s in paths:
            args.append('--write-path=' + s)
            # This mimics easyprof.gen_path_rule()
            owner = ""
            if s.startswith('/home/') or s.startswith("@{HOME"):
                owner = "owner "
            if s.endswith('/'):
                search_terms.append("{} rwk,".format(s))
                search_terms.append("{}{}** rwk,".format(owner, s))
            elif s.endswith('/**') or s.endswith('/*'):
                search_terms.append("{} rwk,".format(os.path.dirname(s)))
                search_terms.append("{}{} rwk,".format(owner, s))
            else:
                search_terms.append("{}{} rwk,".format(owner, s))

        p = self._gen_policy(extra_args=args)
        for search in search_terms:
            self.assertTrue(search in p, "Could not find '{}' in:\n{}".format(search, p))
        inv_s = '###READPATH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_writepath_bad(self):
        """Test genpolicy (write-path bad)"""
        s = "bar"
        try:
            self._gen_policy(extra_args=['--write-path=' + s])
        except AppArmorException:
            return
        raise Exception("write-path should be invalid")

    def test_genpolicy_templatevar(self):
        """Test genpolicy (template-var single)"""
        s = "@{FOO}=bar"
        p = self._gen_policy(extra_args=['--template-var=' + s])
        k, v = s.split('=')
        s = '{}="{}"'.format(k, v)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###TEMPLATEVAR###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_templatevar_multiple(self):
        """Test genpolicy (template-var multiple)"""
        variables = ['@{FOO}=bar', '@{BAR}=baz']
        args = []
        for s in variables:
            args.append('--template-var=' + s)

        p = self._gen_policy(extra_args=args)
        for s in variables:
            k, v = s.split('=')
            s = '{}="{}"'.format(k, v)
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
            inv_s = '###TEMPLATEVAR###'
            self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_templatevar_bad(self):
        """Test genpolicy (template-var - bad values)"""
        bad = [
               "{FOO}=bar",
               "@FOO}=bar",
               "@{FOO=bar",
               "FOO=bar",
               "@FOO=bar",
               "@{FOO}=/../../../etc/passwd",
               "@{FOO}=bar=foo",
               "@{FOO;BAZ}=bar",
               '@{FOO}=bar"baz',
              ]
        for s in bad:
            try:
                self._gen_policy(extra_args=['--template-var=' + s])
            except AppArmorException:
                continue
            raise Exception("template-var should be invalid")

    def test_genpolicy_invalid_template_policy(self):
        """Test genpolicy (invalid template policy)"""
        # create a new template
        template = os.path.join(self.tmpdir, "test-invalid-template")
        shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template)
        with open(template) as f:
            contents = f.read()
        bad_pol = ""
        bad_string = "bzzzt"
        for line in contents.splitlines():
            if '}' in line:
                bad_pol += bad_string
            else:
                bad_pol += line
            bad_pol += "\n"
        with open(template, 'w') as f:
            f.write(bad_pol)
        try:
            self._gen_policy(template=template)
        except AppArmorException:
            return
        raise Exception("policy should be invalid")

    def test_genpolicy_no_binary_without_profile_name(self):
        """Test genpolicy (no binary with no profile name)"""
        try:
            easyprof.gen_policy_params(None, self.options)
        except AppArmorException:
            return
        raise Exception("No binary or profile name should have been invalid")

    def test_genpolicy_with_binary_with_profile_name(self):
        """Test genpolicy (binary with profile name)"""
        profile_name = "some-profile-name"
        p = self._gen_policy(extra_args=['--profile-name=' + profile_name])
        s = 'profile "%s" "%s" {' % (profile_name, self.binary)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###PROFILEATTACH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_with_binary_without_profile_name(self):
        """Test genpolicy (binary without profile name)"""
        p = self._gen_policy()
        s = '"%s" {' % (self.binary,)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###PROFILEATTACH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_genpolicy_without_binary_with_profile_name(self):
        """Test genpolicy (no binary with profile name)"""
        profile_name = "some-profile-name"
        args = self.full_args
        args.append('--profile-name=' + profile_name)
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(None, self.options)
        params = easyprof.gen_policy_params(None, self.options)
        p = easyp.gen_policy(**params)
        s = 'profile "%s" {' % (profile_name,)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###PROFILEATTACH###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

# manifest tests

    def test_gen_manifest_policy_with_binary_with_profile_name(self):
        """Test gen_manifest_policy (binary with profile name)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_binary(self.ls)
        self._gen_manifest_policy(m)

    def test_gen_manifest_policy_without_binary_with_profile_name(self):
        """Test gen_manifest_policy (no binary with profile name)"""
        m = Manifest("test_gen_manifest_policy")
        self._gen_manifest_policy(m)

    def test_gen_manifest_policy_templates_system(self):
        """Test gen_manifest_policy (system template)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_template(self.test_template)
        self._gen_manifest_policy(m)

    def test_gen_manifest_policy_templates_system_noprefix(self):
        """Test gen_manifest_policy (system template, no security prefix)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_template(self.test_template)
        self._gen_manifest_policy(m, use_security_prefix=False)

    def test_gen_manifest_abs_path_template(self):
        """Test gen_manifest_policy (abs path template)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_template("/etc/shadow")
        try:
            self._gen_manifest_policy(m)
        except AppArmorException:
            return
        raise Exception("abs path template name should be invalid")

    def test_gen_manifest_escape_path_templates(self):
        """Test gen_manifest_policy (esc path template)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_template("../../../../../../../../etc/shadow")
        try:
            self._gen_manifest_policy(m)
        except AppArmorException:
            return
        raise Exception("../ template name should be invalid")

    def test_gen_manifest_policy_templates_nonexistent(self):
        """Test gen manifest policy (nonexistent template)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_template("nonexistent")
        try:
            self._gen_manifest_policy(m)
        except AppArmorException:
            return
        raise Exception("template should be invalid")

    def test_gen_manifest_policy_comment(self):
        """Test gen manifest policy (comment)"""
        s = "test comment"
        m = Manifest("test_gen_manifest_policy")
        m.add_comment(s)
        p = self._gen_manifest_policy(m)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###COMMENT###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_author(self):
        """Test gen manifest policy (author)"""
        s = "Archibald Poindexter"
        m = Manifest("test_gen_manifest_policy")
        m.add_author(s)
        p = self._gen_manifest_policy(m)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###AUTHOR###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_copyright(self):
        """Test genpolicy (copyright)"""
        s = "2112/01/01"
        m = Manifest("test_gen_manifest_policy")
        m.add_copyright(s)
        p = self._gen_manifest_policy(m)
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###COPYRIGHT###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_policygroups(self):
        """Test gen manifest policy (single policygroup)"""
        groups = self.test_policygroup
        m = Manifest("test_gen_manifest_policy")
        m.add_policygroups(groups)
        p = self._gen_manifest_policy(m)

        for s in ('#include <abstractions/nameservice>', '#include <abstractions/gnome>'):
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###POLICYGROUPS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_policygroups_multiple(self):
        """Test genpolicy (multiple policygroups)"""
        test_policygroup2 = "test-policygroup2"
        contents = '''
  # {}
  #include <abstractions/kde>
  #include <abstractions/openssl>
'''.format(self.test_policygroup)
        with open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w') as f:
            f.write(contents)

        groups = "{},{}".format(self.test_policygroup, test_policygroup2)
        m = Manifest("test_gen_manifest_policy")
        m.add_policygroups(groups)
        p = self._gen_manifest_policy(m)

        for s in ('#include <abstractions/nameservice>',
                  '#include <abstractions/gnome>',
                  '#include <abstractions/kde>',
                  '#include <abstractions/openssl>'):
            self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###POLICYGROUPS###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_policygroups_nonexistent(self):
        """Test gen manifest policy (nonexistent policygroup)"""
        groups = "nonexistent"
        m = Manifest("test_gen_manifest_policy")
        m.add_policygroups(groups)
        try:
            self._gen_manifest_policy(m)
        except AppArmorException:
            return
        raise Exception("policygroup should be invalid")

    def test_gen_manifest_policy_templatevar(self):
        """Test gen manifest policy (template-var single)"""
        m = Manifest("test_gen_manifest_policy")
        m.add_template_variable("FOO", "bar")
        p = self._gen_manifest_policy(m)
        s = '@{FOO}="bar"'
        self.assertTrue(s in p, "Could not find '{}' in:\n{}".format(s, p))
        inv_s = '###TEMPLATEVAR###'
        self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_templatevar_multiple(self):
        """Test gen manifest policy (template-var multiple)"""
        variables = [["FOO", "bar"], ["BAR", "baz"]]
        m = Manifest("test_gen_manifest_policy")
        for s in variables:
            m.add_template_variable(s[0], s[1])

        p = self._gen_manifest_policy(m)
        for s in variables:
            str_s = '@{%s}="%s"' % (s[0], s[1])
            self.assertTrue(str_s in p, "Could not find '{}' in:\n{}".format(str_s, p))
            inv_s = '###TEMPLATEVAR###'
            self.assertFalse(inv_s in p, "Found '{}' in :\n{}".format(inv_s, p))

    def test_gen_manifest_policy_invalid_keys(self):
        """Test gen manifest policy (invalid keys)"""
        keys = [
            'config_file',
            'debug',
            'help',
            'list-templates',
            'list_templates',
            'show-template',
            'show_template',
            'list-policy-groups',
            'list_policy_groups',
            'show-policy-group',
            'show_policy_group',
            'templates-dir',
            'templates_dir',
            'policy-groups-dir',
            'policy_groups_dir',
            'nonexistent',
            'no_verify',
        ]

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        for k in keys:
            security = dict()
            security["profile_name"] = "test-app"
            security[k] = "bad"
            j = json.dumps(security, indent=2)
            try:
                easyprof.parse_manifest(j, self.options)
            except AppArmorException:
                continue
            raise Exception("'{}' should be invalid".format(k))

    def test_gen_manifest(self):
        """Test gen_manifest"""
        #  this should come from manpage
        m = '''{
  "security": {
    "profiles": {
      "com.example.foo": {
        "abstractions": [
          "audio",
          "gnome"
        ],
        "author": "Your Name",
        "binary": "/opt/foo/**",
        "comment": "Unstructured single-line comment",
        "copyright": "Unstructured single-line copyright statement",
        "name": "My Foo App",
        "policy_groups": [
          "opt-application",
          "user-application"
        ],
        "policy_vendor": "somevendor",
        "policy_version": 1.0,
        "read_path": [
          "/tmp/foo_r",
          "/tmp/bar_r/"
        ],
        "template": "user-application",
        "template_variables": {
          "APPNAME": "foo",
          "VAR1": "bar",
          "VAR2": "baz"
        },
        "write_path": [
          "/tmp/foo_w",
          "/tmp/bar_w/"
        ]
      }
    }
  }
}'''

        for d in ('policygroups', 'templates'):
            shutil.copytree(os.path.join(self.tmpdir, d),
                            os.path.join(self.tmpdir, d, "somevendor/1.0"))

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(m, self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, self.options)
        params = easyprof.gen_policy_params(binary, self.options)

        # verify we get the same manifest back
        man_new = easyp.gen_manifest(params)
        self.assertEqual(m, man_new)

    def test_gen_manifest_ubuntu(self):
        """Test gen_manifest (ubuntu)"""
        # this should be based on the manpage (but use existing policy_groups
        # and template
        m = '''{
  "security": {
    "profiles": {
      "com.ubuntu.developer.myusername.MyCoolApp": {
        "name": "MyCoolApp",
        "policy_groups": [
          "opt-application",
          "user-application"
        ],
        "policy_vendor": "ubuntu",
        "policy_version": 1.0,
        "template": "user-application",
        "template_variables": {
          "APPNAME": "MyCoolApp",
          "APPVERSION": "0.1.2"
        }
      }
    }
  }
}'''

        for d in ('policygroups', 'templates'):
            shutil.copytree(os.path.join(self.tmpdir, d),
                            os.path.join(self.tmpdir, d, "ubuntu/1.0"))

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(m, self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, self.options)
        params = easyprof.gen_policy_params(binary, self.options)

        # verify we get the same manifest back
        man_new = easyp.gen_manifest(params)
        self.assertEqual(m, man_new)

    def test_parse_manifest_no_version(self):
        """Test parse_manifest (vendor with no version)"""
        #  this should come from manpage
        m = '''{
  "security": {
    "profiles": {
      "com.ubuntu.developer.myusername.MyCoolApp": {
        "policy_groups": [
          "opt-application",
          "user-application"
        ],
        "policy_vendor": "ubuntu",
        "template": "user-application",
        "template_variables": {
          "APPNAME": "MyCoolApp",
          "APPVERSION": "0.1.2"
        }
      }
    }
  }
}'''

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(m, self.options)[0]
        try:
            easyprof.AppArmorEasyProfile(binary, self.options)
        except AppArmorException:
            return
        raise Exception("Should have failed on missing version")

    def test_parse_manifest_no_vendor(self):
        """Test parse_manifest (version with no vendor)"""
        #  this should come from manpage
        m = '''{
  "security": {
    "profiles": {
      "com.ubuntu.developer.myusername.MyCoolApp": {
        "policy_groups": [
          "opt-application",
          "user-application"
        ],
        "policy_version": 1.0,
        "template": "user-application",
        "template_variables": {
          "APPNAME": "MyCoolApp",
          "APPVERSION": "0.1.2"
        }
      }
    }
  }
}'''

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(m, self.options)[0]
        try:
            easyprof.AppArmorEasyProfile(binary, self.options)
        except AppArmorException:
            return
        raise Exception("Should have failed on missing vendor")

    def test_parse_manifest_multiple(self):
        """Test parse_manifest_multiple"""
        m = '''{
  "security": {
    "profiles": {
      "com.example.foo": {
        "abstractions": [
          "audio",
          "gnome"
        ],
        "author": "Your Name",
        "binary": "/opt/foo/**",
        "comment": "Unstructured single-line comment",
        "copyright": "Unstructured single-line copyright statement",
        "name": "My Foo App",
        "policy_groups": [
          "opt-application",
          "user-application"
        ],
        "read_path": [
          "/tmp/foo_r",
          "/tmp/bar_r/"
        ],
        "template": "user-application",
        "template_variables": {
          "APPNAME": "foo",
          "VAR1": "bar",
          "VAR2": "baz"
        },
        "write_path": [
          "/tmp/foo_w",
          "/tmp/bar_w/"
        ]
      },
      "com.ubuntu.developer.myusername.MyCoolApp": {
        "policy_groups": [
          "opt-application"
        ],
        "policy_vendor": "ubuntu",
        "policy_version": 1.0,
        "template": "user-application",
        "template_variables": {
          "APPNAME": "MyCoolApp",
          "APPVERSION": "0.1.2"
        }
      }
    }
  }
}'''

        for d in ('policygroups', 'templates'):
            shutil.copytree(os.path.join(self.tmpdir, d),
                            os.path.join(self.tmpdir, d, "ubuntu/1.0"))

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        profiles = easyprof.parse_manifest(m, self.options)
        for (binary, options) in profiles:
            easyp = easyprof.AppArmorEasyProfile(binary, options)
            params = easyprof.gen_policy_params(binary, options)
            easyp.gen_manifest(params)
            easyp.gen_policy(**params)

# verify manifest tests
    def _verify_manifest(self, m, expected, invalid=False):
        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        try:
            (binary, options) = easyprof.parse_manifest(m, self.options)[0]
        except AppArmorException:
            if invalid:
                return
            raise
        params = easyprof.gen_policy_params(binary, options)
        if expected:
            self.assertTrue(easyprof.verify_manifest(params, args), "params={}\nmanifest={}".format(params, m))
        else:
            self.assertFalse(easyprof.verify_manifest(params, args), "params={}\nmanifest={}".format(params, m))

    def test_verify_manifest_full(self):
        """Test verify_manifest (full)"""
        m = '''{
  "security": {
    "profiles": {
      "com.example.foo": {
        "abstractions": [
          "base"
        ],
        "author": "Your Name",
        "binary": "/opt/com.example/foo/**",
        "comment": "some free-form single-line comment",
        "copyright": "Unstructured single-line copyright statement",
        "name": "foo",
        "policy_groups": [
          "user-application",
          "opt-application"
        ],
        "template": "user-application",
        "template_variables": {
          "OK1": "foo",
          "OK2": "com.example.foo"
        }
      }
    }
  }
}'''
        self._verify_manifest(m, expected=True)

    def test_verify_manifest_full_bad(self):
        """Test verify_manifest (full bad)"""
        m = '''{
  "security": {
    "profiles": {
      "/com.example.foo": {
         "abstractions": [
          "audio",
          "gnome"
        ],
        "author": "Your Name",
        "binary": "/usr/foo/**",
        "comment": "some free-form single-line comment",
        "copyright": "Unstructured single-line copyright statement",
        "name": "foo",
        "policy_groups": [
          "user-application",
          "opt-application"
        ],
        "read_path": [
          "/tmp/foo_r",
          "/tmp/bar_r/"
        ],
        "template": "user-application",
        "template_variables": {
          "VAR1": "f*o",
          "VAR2": "*foo",
          "VAR3": "fo*",
          "VAR4": "b{ar",
          "VAR5": "b{a,r}",
          "VAR6": "b}ar",
          "VAR7": "bar[0-9]",
          "VAR8": "b{ar",
          "VAR9": "/tmp/../etc/passwd"
        },
        "write_path": [
          "/tmp/foo_w",
          "/tmp/bar_w/"
        ]
      }
    }
  }
}'''

        self._verify_manifest(m, expected=False, invalid=True)

    def test_verify_manifest_binary(self):
        """Test verify_manifest (binary in /usr)"""
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/usr/foo/**",
     "template": "user-application"
    }
   }
  }
}'''
        self._verify_manifest(m, expected=True)

    def test_verify_manifest_profile_profile_name_bad(self):
        """Test verify_manifest (bad profile_name)"""
        m = '''{
  "security": {
   "profiles": {
    "/foo": {
     "binary": "/opt/com.example/foo/**",
     "template": "user-application"
    }
   }
  }
}'''
        self._verify_manifest(m, expected=False, invalid=True)

        m = '''{
  "security": {
   "profiles": {
    "bin/*": {
     "binary": "/opt/com.example/foo/**",
     "template": "user-application"
    }
   }
  }
}'''
        self._verify_manifest(m, expected=False)

    def test_verify_manifest_profile_profile_name(self):
        """Test verify_manifest (profile_name)"""
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/opt/com.example/foo/**",
     "template": "user-application"
    }
   }
  }
}'''
        self._verify_manifest(m, expected=True)

    def test_verify_manifest_profile_abstractions(self):
        """Test verify_manifest (abstractions)"""
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/opt/com.example/foo/**",
     "template": "user-application",
     "abstractions": [
       "base"
     ]
    }
   }
  }
}'''
        self._verify_manifest(m, expected=True)

    def test_verify_manifest_profile_abstractions_bad(self):
        """Test verify_manifest (bad abstractions)"""
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/opt/com.example/foo/**",
     "template": "user-application",
     "abstractions": [
       "user-tmp"
     ]
    }
   }
  }
}'''
        self._verify_manifest(m, expected=False)

    def test_verify_manifest_profile_template_var(self):
        """Test verify_manifest (good template_var)"""
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/opt/com.example/something with spaces/**",
     "template": "user-application",
     "template_variables": {
       "OK1": "foo",
       "OK2": "com.example.foo",
       "OK3": "something with spaces"
     }
    }
   }
  }
}'''
        self._verify_manifest(m, expected=True)

    def test_verify_manifest_profile_template_var_bad(self):
        """Test verify_manifest (bad template_var)"""
        for v in ('"VAR1": "f*o"',
                  '"VAR2": "*foo"',
                  '"VAR3": "fo*"',
                  '"VAR4": "b{ar"',
                  '"VAR5": "b{a,r}"',
                  '"VAR6": "b}ar"',
                  '"VAR7": "bar[0-9]"',
                  '"VAR8": "b{ar"',
                  '"VAR9": "foo/bar"'  # this is valid, but potentially unsafe
                  ):
            m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/opt/com.example/foo/**",
    "template": "user-application",
     "template_variables": {
       %s
     }
    }
   }
  }
}''' % (v,)
            self._verify_manifest(m, expected=False)

    def test_manifest_invalid(self):
        """Test invalid manifest (parse error)"""
        m = '''{
  "security": {
   "com.example.foo": {
    "binary": "/opt/com.example/foo/**",
    "template": "user-application",
    "abstractions": [
      "base"
    ]
}'''
        self._verify_manifest(m, expected=False, invalid=True)

    def test_manifest_invalid2(self):
        """Test invalid manifest (profile_name is not key)"""
        m = '''{
  "security": {
    "binary": "/opt/com.example/foo/**",
    "template": "user-application",
    "abstractions": [
      "base"
    ]
  }
}'''
        self._verify_manifest(m, expected=False, invalid=True)

    def test_manifest_invalid3(self):
        """Test invalid manifest (profile_name in dict)"""
        m = '''{
  "security": {
    "binary": "/opt/com.example/foo/**",
    "template": "user-application",
    "abstractions": [
      "base"
    ],
    "profile_name": "com.example.foo"
  }
}'''
        self._verify_manifest(m, expected=False, invalid=True)

    def test_manifest_invalid4(self):
        """Test invalid manifest (bad path in template var)"""
        for v in ('"VAR1": "/tmp/../etc/passwd"',
                  '"VAR2": "./"',
                  '"VAR3": "foo\"bar"',
                  '"VAR4": "foo//bar"',
                  ):
            m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "binary": "/opt/com.example/foo/**",
     "template": "user-application",
     "template_variables": {
       %s
     }
    }
   }
  }
}''' % (v,)
            args = self.full_args
            args.append("--manifest=/dev/null")
            (self.options, self.args) = easyprof.parse_args(args)
            (binary, options) = easyprof.parse_manifest(m, self.options)[0]
            params = easyprof.gen_policy_params(binary, options)
            try:
                easyprof.verify_manifest(params)
            except AppArmorException:
                return

            raise Exception("Should have failed with invalid variable declaration")

# policy version tests
    def test_policy_vendor_manifest_nonexistent(self):
        """Test policy vendor via manifest (nonexistent)"""
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "policy_vendor": "nonexistent",
     "policy_version": 1.0,
     "binary": "/opt/com.example/foo/**",
     "template": "user-application"
    }
   }
  }
}'''
        # Build up our args
        args = self.full_args
        args.append("--manifest=/dev/null")

        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(m, self.options)[0]
        try:
            easyprof.AppArmorEasyProfile(binary, self.options)
        except AppArmorException:
            return

        raise Exception("Should have failed with non-existent directory")

    def test_policy_version_manifest(self):
        """Test policy version via manifest (good)"""
        policy_vendor = "somevendor"
        policy_version = "1.0"
        policy_subdir = "{}/{}".format(policy_vendor, policy_version)
        m = '''{
  "security": {
   "profiles": {
    "com.example.foo": {
     "policy_vendor": "%s",
     "policy_version": %s,
     "binary": "/opt/com.example/foo/**",
     "template": "user-application"
    }
   }
  }
}''' % (policy_vendor, policy_version)
        for d in ('policygroups', 'templates'):
            shutil.copytree(os.path.join(self.tmpdir, d),
                            os.path.join(self.tmpdir, d, policy_subdir))

        # Build up our args
        args = self.full_args
        args.append("--manifest=/dev/null")

        (self.options, self.args) = easyprof.parse_args(args)
        (binary, self.options) = easyprof.parse_manifest(m, self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, self.options)

        tdir = os.path.join(self.tmpdir, 'templates', policy_subdir)
        for t in easyp.get_templates():
            self.assertTrue(t.startswith(tdir))

        pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir)
        for p in easyp.get_policy_groups():
            self.assertTrue(p.startswith(pdir))

        params = easyprof.gen_policy_params(binary, self.options)
        easyp.gen_policy(**params)

    def test_policy_vendor_version_args(self):
        """Test policy vendor and version via command line args (good)"""
        policy_version = "1.0"
        policy_vendor = "somevendor"
        policy_subdir = "{}/{}".format(policy_vendor, policy_version)

        # Create the directories
        for d in ('policygroups', 'templates'):
            shutil.copytree(os.path.join(self.tmpdir, d),
                            os.path.join(self.tmpdir, d, policy_subdir))

        # Build up our args
        args = self.full_args
        args.append("--policy-version=" + policy_version)
        args.append("--policy-vendor=" + policy_vendor)

        (self.options, self.args) = easyprof.parse_args(args)
        (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])
        easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)

        tdir = os.path.join(self.tmpdir, 'templates', policy_subdir)
        for t in easyp.get_templates():
            self.assertTrue(t.startswith(tdir),
                            "'{}' does not start with '{}'".format(t, tdir))

        pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir)
        for p in easyp.get_policy_groups():
            self.assertTrue(p.startswith(pdir),
                            "'{}' does not start with '{}'".format(p, pdir))

        params = easyprof.gen_policy_params(self.binary, self.options)
        easyp.gen_policy(**params)

    def test_policy_vendor_args_nonexistent(self):
        """Test policy vendor via command line args (nonexistent)"""
        policy_vendor = "nonexistent"
        policy_version = "1.0"
        args = self.full_args
        args.append("--policy-version=" + policy_version)
        args.append("--policy-vendor=" + policy_vendor)

        (self.options, self.args) = easyprof.parse_args(args)
        (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])
        try:
            easyprof.AppArmorEasyProfile(self.binary, self.options)
        except AppArmorException:
            return

        raise Exception("Should have failed with non-existent directory")

    def test_policy_version_args_bad(self):
        """Test policy version via command line args (bad)"""
        bad = [
            "../../../../../../etc",
            "notanumber",
            "v1.0a",
            "-1",
        ]
        for policy_version in bad:
            args = self.full_args
            args.append("--policy-version=" + policy_version)
            args.append("--policy-vendor=somevendor")

            (self.options, self.args) = easyprof.parse_args(args)
            (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])
            try:
                easyprof.AppArmorEasyProfile(self.binary, self.options)
            except AppArmorException:
                continue

            raise Exception("Should have failed with bad version")

    def test_policy_vendor_args_bad(self):
        """Test policy vendor via command line args (bad)"""
        bad = [
            "../../../../../../etc",
            "vendor with space",
            "semicolon;isbad",
        ]
        for policy_vendor in bad:
            args = self.full_args
            args.append("--policy-vendor=" + policy_vendor)
            args.append("--policy-version=1.0")

            (self.options, self.args) = easyprof.parse_args(args)
            (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])
            try:
                easyprof.AppArmorEasyProfile(self.binary, self.options)
            except AppArmorException:
                continue

            raise Exception("Should have failed with bad vendor")

# output_directory tests
    def test_output_directory_multiple(self):
        """Test output_directory (multiple)"""
        files = dict()
        files["com.example.foo"] = "com.example.foo"
        files["com.ubuntu.developer.myusername.MyCoolApp"] = "com.ubuntu.developer.myusername.MyCoolApp"
        files["usr.bin.baz"] = "/usr/bin/baz"
        m = '''{
  "security": {
   "profiles": {
    "%s": {
      "abstractions": [
        "audio",
        "gnome"
      ],
      "author": "Your Name",
      "binary": "/opt/foo/**",
      "comment": "Unstructured single-line comment",
      "copyright": "Unstructured single-line copyright statement",
      "name": "My Foo App",
      "policy_groups": [
        "opt-application",
        "user-application"
      ],
      "read_path": [
        "/tmp/foo_r",
        "/tmp/bar_r/"
      ],
      "template": "user-application",
      "template_variables": {
        "APPNAME": "foo",
        "VAR1": "bar",
        "VAR2": "baz"
      },
      "write_path": [
        "/tmp/foo_w",
        "/tmp/bar_w/"
      ]
    },
    "%s": {
      "policy_groups": [
        "opt-application",
        "user-application"
      ],
      "template": "user-application",
      "template_variables": {
        "APPNAME": "MyCoolApp",
        "APPVERSION": "0.1.2"
      }
    },
    "%s": {
      "abstractions": [
        "gnome"
      ],
      "policy_groups": [
        "user-application"
      ],
      "template_variables": {
        "APPNAME": "baz"
      }
    }
   }
  }
}''' % (files["com.example.foo"],
        files["com.ubuntu.developer.myusername.MyCoolApp"],
        files["usr.bin.baz"])

        out_dir = os.path.join(self.tmpdir, "output")

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        profiles = easyprof.parse_manifest(m, self.options)
        for (binary, options) in profiles:
            easyp = easyprof.AppArmorEasyProfile(binary, options)
            params = easyprof.gen_policy_params(binary, options)
            easyp.output_policy(params, dir=out_dir)

        for fn in files:
            f = os.path.join(out_dir, fn)
            self.assertTrue(os.path.exists(f), "Could not find '{}'".format(f))

    def test_output_directory_single(self):
        """Test output_directory (single)"""
        files = dict()
        files["com.example.foo"] = "com.example.foo"
        m = '''{
  "security": {
   "profiles": {
    "%s": {
      "abstractions": [
        "audio",
        "gnome"
      ],
      "author": "Your Name",
      "binary": "/opt/foo/**",
      "comment": "Unstructured single-line comment",
      "copyright": "Unstructured single-line copyright statement",
      "name": "My Foo App",
      "policy_groups": [
        "opt-application",
        "user-application"
      ],
      "read_path": [
        "/tmp/foo_r",
        "/tmp/bar_r/"
      ],
      "template": "user-application",
      "template_variables": {
        "APPNAME": "foo",
        "VAR1": "bar",
        "VAR2": "baz"
      },
      "write_path": [
        "/tmp/foo_w",
        "/tmp/bar_w/"
      ]
    }
   }
  }
}''' % (files["com.example.foo"],)

        out_dir = os.path.join(self.tmpdir, "output")

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        profiles = easyprof.parse_manifest(m, self.options)
        for (binary, options) in profiles:
            easyp = easyprof.AppArmorEasyProfile(binary, options)
            params = easyprof.gen_policy_params(binary, options)
            easyp.output_policy(params, dir=out_dir)

        for fn in files:
            f = os.path.join(out_dir, fn)
            self.assertTrue(os.path.exists(f), "Could not find '{}'".format(f))

    def test_output_directory_invalid(self):
        """Test output_directory (output directory exists as file)"""
        files = dict()
        files["usr.bin.baz"] = "/usr/bin/baz"
        m = '''{
  "security": {
    "profiles": {
      "%s": {
        "abstractions": [
          "gnome"
        ],
        "policy_groups": [
          "user-application"
        ],
        "template_variables": {
          "APPNAME": "baz"
        }
      }
    }
  }
}''' % (files["usr.bin.baz"],)

        out_dir = os.path.join(self.tmpdir, "output")
        open(out_dir, 'w').close()

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, options) = easyprof.parse_manifest(m, self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, options)
        params = easyprof.gen_policy_params(binary, options)
        try:
            easyp.output_policy(params, dir=out_dir)
        except AppArmorException:
            return
        raise Exception("Should have failed with 'is not a directory'")

    def test_output_directory_invalid_params(self):
        """Test output_directory (no binary or profile_name)"""
        files = dict()
        files["usr.bin.baz"] = "/usr/bin/baz"
        m = '''{
  "security": {
    "profiles": {
      "%s": {
        "abstractions": [
          "gnome"
        ],
        "policy_groups": [
          "user-application"
        ],
        "template_variables": {
          "APPNAME": "baz"
        }
      }
    }
  }
}''' % (files["usr.bin.baz"],)

        out_dir = os.path.join(self.tmpdir, "output")

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, options) = easyprof.parse_manifest(m, self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, options)
        params = easyprof.gen_policy_params(binary, options)
        del params['binary']
        try:
            easyp.output_policy(params, dir=out_dir)
        except AppArmorException:
            return
        raise Exception("Should have failed with 'Must specify binary and/or profile name'")

    def test_output_directory_invalid2(self):
        """Test output_directory (profile exists)"""
        files = dict()
        files["usr.bin.baz"] = "/usr/bin/baz"
        m = '''{
  "security": {
    "profiles": {
      "%s": {
        "abstractions": [
          "gnome"
        ],
        "policy_groups": [
          "user-application"
        ],
        "template_variables": {
          "APPNAME": "baz"
        }
      }
    }
  }
}''' % (files["usr.bin.baz"],)

        out_dir = os.path.join(self.tmpdir, "output")
        os.mkdir(out_dir)
        open(os.path.join(out_dir, "usr.bin.baz"), 'w').close()

        args = self.full_args
        args.append("--manifest=/dev/null")
        (self.options, self.args) = easyprof.parse_args(args)
        (binary, options) = easyprof.parse_manifest(m, self.options)[0]
        easyp = easyprof.AppArmorEasyProfile(binary, options)
        params = easyprof.gen_policy_params(binary, options)
        try:
            easyp.output_policy(params, dir=out_dir)
        except AppArmorException:
            return
        raise Exception("Should have failed with 'already exists'")

    def test_output_directory_args(self):
        """Test output_directory (args)"""
        files = dict()
        files["usr.bin.baz"] = "/usr/bin/baz"

        # Build up our args
        args = self.full_args
        args.append('--template=' + self.test_template)
        args.append('--name=foo')
        args.append(files["usr.bin.baz"])

        out_dir = os.path.join(self.tmpdir, "output")

        # Now parse our args
        (self.options, self.args) = easyprof.parse_args(args)
        easyp = easyprof.AppArmorEasyProfile(files["usr.bin.baz"], self.options)
        params = easyprof.gen_policy_params(files["usr.bin.baz"], self.options)
        easyp.output_policy(params, dir=out_dir)

        for fn in files:
            f = os.path.join(out_dir, fn)
            self.assertTrue(os.path.exists(f), "Could not find '{}'".format(f))

#
# utility classes
#
    def test_valid_profile_name(self):
        """Test valid_profile_name"""
        names = [
            'foo',
            'com.example.foo',
            '/usr/bin/foo',
            'com.example.app_myapp_1:2.3+ab12~foo',
        ]
        for n in names:
            self.assertTrue(easyprof.valid_profile_name(n), "'{}' should be valid".format(n))

    def test_valid_profile_name_invalid(self):
        """Test valid_profile_name (invalid)"""
        names = [
            'fo/o',
            '/../../etc/passwd',
            '../../etc/passwd',
            './../etc/passwd',
            './etc/passwd',
            '/usr/bin//foo',
            '/usr/bin/./foo',
            'foo`',
            'foo!',
            'foo@',
            'foo$',
            'foo#',
            'foo%',
            'foo^',
            'foo&',
            'foo*',
            'foo(',
            'foo)',
            'foo=',
            'foo{',
            'foo}',
            'foo[',
            'foo]',
            'foo|',
            'foo/',
            'foo\\',
            'foo;',
            "foo'",
            'foo"',
            'foo<',
            'foo>',
            'foo?',
            r'foo\/',
            'foo,',
            '_foo',
        ]
        for n in names:
            self.assertFalse(easyprof.valid_profile_name(n), "'{}' should be invalid".format(n))

    def test_valid_path(self):
        """Test valid_path"""
        names = [
            '/bin/bar',
            '/etc/apparmor.d/com.example.app_myapp_1:2.3+ab12~foo',
        ]
        names_rel = [
            'bin/bar',
            'apparmor.d/com.example.app_myapp_1:2.3+ab12~foo',
            'com.example.app_myapp_1:2.3+ab12~foo',
        ]
        for n in names:
            self.assertTrue(easyprof.valid_path(n), "'{}' should be valid".format(n))
        for n in names_rel:
            self.assertTrue(easyprof.valid_path(n, relative_ok=True), "'{}' should be valid".format(n))

    def test_zz_valid_path_invalid(self):
        """Test valid_path (invalid)"""
        names = [
            '/bin//bar',
            'bin/bar',
            '/../etc/passwd',
            './bin/bar',
            './',
        ]
        names_rel = [
            'bin/../bar',
            'apparmor.d/../passwd',
            'com.example.app_"myapp_1:2.3+ab12~foo',
        ]
        for n in names:
            self.assertFalse(easyprof.valid_path(n, relative_ok=False), "'{}' should be invalid".format(n))
        for n in names_rel:
            self.assertFalse(easyprof.valid_path(n, relative_ok=True), "'{}' should be invalid".format(n))


#
# End test class
#

#
# Main
#
if __name__ == '__main__':
    absfn = os.path.abspath(sys.argv[0])
    topdir = os.path.dirname(os.path.dirname(absfn))

    if len(sys.argv) > 1 and (sys.argv[1] == '-d' or sys.argv[1] == '--debug'):
        debugging = True

    # run the tests
    suite = unittest.TestSuite()
    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(T))
    rc = unittest.TextTestRunner(verbosity=1).run(suite)

    if not rc.wasSuccessful():
        sys.exit(1)
