#!/usr/bin/python3
# -*- coding: UTF-8 -*-

# ------------------------------------------------------------------
#
#    Copyright (C) 2013-2014 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.
#
# ------------------------------------------------------------------

# not needed for python3, but pyflakes is grumpy without it
from __future__ import print_function, unicode_literals

from apparmor import click
from apparmor.click import AppName
import apparmor.easyprof
import json
import os
import shutil
import sys
import tempfile
import time
import unittest


class ClickState(object):
    def __init__(self, rootdir):
        self.rootdir = rootdir
        # note: this contains a '_' to trip up the name parsing code
        # if not properly filtered off
        self.click_dir = os.path.join(self.rootdir, "click_apparmor")
        os.mkdir(self.click_dir)
        self.packages_tree = os.path.join(self.rootdir, "click.ubuntu.com")
        os.mkdir(self.packages_tree)
        self.profiles_dir = os.path.join(self.rootdir, "apparmor-profiles")
        os.mkdir(self.profiles_dir)

    def _stub_json(self, name, version):
        j = dict()
        j['name'] = name
        j['version'] = version
        j['hooks'] = dict()
        j['title'] = "Some silly demo app called %s" % (name)
        j['maintainer'] = 'Büggy McJerkerson <buggy@bad.soft.warez>'
        return j

    def _add_app_to_json(self, app):
        j = self.manifest_json
        j['hooks'][app] = dict()
        j['hooks'][app]['apparmor'] = "apparmor/%s.json" % (app)
        with open(self.click_manifest, "w", encoding="UTF-8") as f:
            json.dump(j, f, indent=2, separators=(',', ': '), sort_keys=True,
                      ensure_ascii=False)

    def add_package(self, name, version, framework=None):
        self.package = name
        self.version = version
        self.click_pkgdir = os.path.join(self.packages_tree, name, version,
                                         ".click", "info")
        self.pkg_dir = os.path.join(self.packages_tree, name, version)
        os.makedirs(self.click_pkgdir)
        self.manifest_json = self._stub_json(name, version)
        if framework:
            self.manifest_json['framework'] = framework
        self.click_manifest = os.path.join(self.click_pkgdir, "%s.manifest" %
                                           (name))
        with open(self.click_manifest, "w+", encoding="UTF-8") as f:
            json.dump(self.manifest_json, f, indent=2, separators=(',', ': '),
                      sort_keys=True, ensure_ascii=False)

    def add_app(self, appname, manifest=None):
        os.mkdir(os.path.join(self.click_pkgdir, "apparmor"))
        aa_json = os.path.join(self.click_pkgdir, "apparmor", "%s.json" %
                               (appname))
        self._add_app_to_json(appname)

        with open(aa_json, "w+", encoding="UTF-8") as f:
            if manifest:
                f.write(manifest)
        os.symlink(aa_json, os.path.join(self.click_dir,
                   "%s_%s_%s.json" % (self.package, appname, self.version)))


class T(unittest.TestCase):
    def setUp(self):
        self.tmpdir = tempfile.mkdtemp(prefix="aa-click-manifest-")
        self.clickstate = ClickState(self.tmpdir)
        self.maxDiff = None

    def tearDown(self):
        if os.path.exists(self.tmpdir):
            shutil.rmtree(self.tmpdir)

    def test_manifest_name_parsing_1(self):
        '''Test simple acceptance for parsing click manifest name'''
        ex_app = "com.ubuntu.developer.username.myapp"
        ex_name = "myapp"
        ex_vers = "0.1"
        fname = "%s_%s_%s.json" % (ex_app, ex_name, ex_vers)

        (app, appname, version) = click.parse_manifest_name(fname)

        self.assertEquals(ex_app, app, "expected app %s, got %s" %
                                       (ex_app, app))
        self.assertEquals(ex_name, appname, "expected appname %s, got %s" %
                                            (ex_name, appname))
        self.assertEquals(ex_vers, version, "expected version %s, got %s" %
                                            (ex_vers, version))

    def test_bad_manifest_name(self):
        '''Test manifest name with not enough elements'''
        ex_app = "com.ubuntu.developer.username.myapp"
        ex_name = "myapp"
        ex_vers = "0.1"
        fname = "%s_%s%s" % (ex_app, ex_name, ex_vers)

        try:
            (app, appname, version) = click.parse_manifest_name(fname)
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("name %s should be invalid" % (fname))

    def test_bad_manifest_name_2(self):
        '''Test manifest name with too many elements'''
        ex_app = "com.ubuntu.developer.username.myapp"
        ex_name = "myapp"
        ex_vers = "0.1"
        fname = "%s_%s_%s_err" % (ex_app, ex_name, ex_vers)

        try:
            (app, appname, version) = click.parse_manifest_name(fname)
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("name %s should be invalid" % (fname))

    def test_click_name_to_profile(self):
        '''Test click name conversion to profile name'''

        profile = "com.ubuntu.developer.username.myapp_myapp_0.3"
        expected = "%s.json" % (profile)
        orig = "click_%s" % (profile)
        app = click.AppName(profile_filename=orig)
        self.assertEquals(expected, app.click_name,
                          "expected click name %s, got %s" % (expected,
                                                              app.click_name))

    def test_click_appname_no_args_to_init(self):
        '''Test AppName has args'''
        try:
            click.AppName()
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("name unset should be invalid")

    def test_click_appname_bad_click_name(self):
        '''Test AppName with bad click_name'''
        n = "bad"
        try:
            click.AppName(click_name="bad")
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("click_name '%s' should be invalid" % n)

    def test_click_appname_bad_profile_filename(self):
        '''Test AppName with bad profile_filename'''
        n = "bad"
        try:
            click.AppName(profile_filename="bad")
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("profile_filename '%s' should be invalid" % n)

    def test_profile_name_to_click(self):
        '''Test profile name conversion to click name'''

        orig = "com.ubuntu.developer.username.myapp_myapp_0.3.json"
        expected = "click_com.ubuntu.developer.username.myapp_myapp_0.3"
        app = click.AppName(orig)
        self.assertEquals(expected, app.profile_filename,
                          "expected profile filename %s, got %s" %
                          (expected, app.profile_filename))

        expected = "com.ubuntu.developer.username.myapp_myapp_0.3"
        self.assertEquals(expected, app.profile_name,
                          "expected profile name %s, got %s" %
                          (expected, app.profile_name))

    def test_find_manifest_file(self):
        '''Test being given a symlink and finding the main package manifest'''
        c = self.clickstate
        c.add_package("package", "version")
        c.add_app("app")

        (result, pkg_dir) = apparmor.click.get_package_manifest(
            os.path.join(c.click_dir, "package_app_version.json"), "package")
        self.assertEquals(c.click_manifest, result,
                          "Expected to get %s, got %s" %
                          (c.click_manifest, result))
        self.assertEquals(c.pkg_dir, pkg_dir, "Expected to get %s, got %s" %
                                              (c.pkg_dir, pkg_dir))

    def test_parse_security_manifest(self):
        '''Test being given a symlink and parsing the manifests'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        nbase = n.strip(".json")
        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path(nbase)

        expected = json.loads('''{
  "profiles": {
    "com.ubuntu.developer.username.myapp_sample-app_0.1": {
      "policy_groups": [ "networking" ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "com.ubuntu.developer.username.myapp",
        "APP_VERSION": "0.1",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (dbus_id, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

        # verify we're testing the json structures deeply
        expected['profiles'][nbase]['template_variables']['BOGUS_VAR'] = \
            'bogus'
        self.assertNotEquals(expected, easyprof_manifest,
                             "Expected %s and %s to differ" %
                             (expected, easyprof_manifest))

    def test_transform_nonexistent_framework(self):
        '''Test transform() with invalid framework'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n))
        cm.framework = "nonexistent"
        try:
            apparmor.click.transform(cm)
        except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e:
            self.assertTrue("Unknown framework" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on nonexistent framework")

    def test_parse_security_manifest_without_required_field(self):
        '''Test click manifest without required field'''
        j = dict()
        j['version'] = "0.1"
        j['hooks'] = dict()
        j['title'] = "Some silly demo app"
        j['maintainer'] = 'Büggy McJerkerson <buggy@bad.soft.warez>'

        self.click_manifest = os.path.join(self.tmpdir, "test-app.manifest")
        with open(self.click_manifest, "w+", encoding="UTF-8") as f:
            json.dump(j, f, indent=2, separators=(',', ': '), sort_keys=True,
                      ensure_ascii=False)
        try:
            apparmor.click.read_click_manifest(self.click_manifest)
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("Should have errored on missing name")

    # the top level of the security manifest is not *supposed* to
    # be a single dict entry of the app, but apparently this happens.
    # Accept it and move on
    def test_parse_security_manifest_lenient(self):
        '''Test being given a symlink and parsing the manifests, leniently'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.3")
        security_json = '''{
  "sample-app": {
    "policy_groups": [ "networking" ],
    "policy_version": 1.0
  }
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.3.json"
        nbase = n.strip(".json")
        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path(nbase)

        expected = json.loads('''{
  "profiles": {
    "%s": {
      "policy_groups": [ "networking" ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "com.ubuntu.developer.username.myapp",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (nbase, dbus_id, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_with_framework(self):
        '''Test being given a symlink and parsing the manifests w/framework
           setting'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1",
                      framework='ubuntu-sdk-13.10')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        nbase = n.strip(".json")
        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path(nbase)

        expected = json.loads('''{
  "profiles": {
    "%s": {
      "policy_groups": [ "networking" ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "com.ubuntu.developer.username.myapp",
        "APP_VERSION": "0.1",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (nbase, dbus_id, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_unknown_framework(self):
        '''Test unknown framework raises exception'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1",
                      framework='unknown-sdk-13.10')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        try:
            cm = apparmor.click.ClickManifest(os.path.join(c.click_dir, n))
            apparmor.click.transform(cm)
        except click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("Framework should be invalid")

    # the security policy is supposed to have a policy_groups entry even
    # if the list inside it is empty. However, I'm seeing click apps
    # without it so we'll accept it and move_on
    def test_parse_security_manifest_no_policy_groups(self):
        '''Test being given a symlink and parsing the manifests, leniently'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3")
        security_json = '{ "policy_version": 1.0 }'
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [ ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_no_entries(self):
        '''Test being given a symlink and parsing the manifests, no policy
           whatsoever'''
        c = self.clickstate
        package = "com.ubuntu.newbie.username.yourapp"
        c.add_package(package, "0.3")
        security_json = '{ }'
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [ ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" % (expected,
                                                          easyprof_manifest))

    def test_parse_security_manifest_unconfined(self):
        '''Test being given a symlink and parsing the manifests for an
           unconfined app'''

        c = self.clickstate
        appname = "com.ubuntu.developer.trusteddev.terminal"
        c.add_package(appname, "0.99.9~123")
        security_json = '''{
  "template": "unconfined",
  "policy_groups": [ ],
  "policy_version": 1.0
}'''
        c.add_app('0wnzered', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_0wnzered_0.99.9~123.json" %
                                          (appname)))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname)

        expected = json.loads('''{
  "profiles": {
    "%s_0wnzered_0.99.9~123": {
      "policy_groups": [ ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "unconfined",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.99.9~123",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (appname, dbus_id, appname, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" % (expected,
                                                          easyprof_manifest))

    def test_parse_security_manifest_alternative_policy_vendor(self):
        '''Test that an alternative vendor is ok'''

        c = self.clickstate
        appname = "com.ubuntu.developer.trusteddev.terminal"
        c.add_package(appname, "0.99.9~123")
        security_json = '''{
  "template": "unconfined",
  "policy_groups": [ ],
  "policy_version": 1.0,
  "policy_vendor": "somevendor"
}'''
        c.add_app('0wnzered', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_0wnzered_0.99.9~123.json" %
                                          (appname)))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname)

        expected = json.loads('''{
  "profiles": {
    "%s_0wnzered_0.99.9~123": {
      "policy_groups": [ ],
      "policy_vendor": "somevendor",
      "policy_version": 1.0,
      "template": "unconfined",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.99.9~123",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (appname, dbus_id, appname, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" % (expected,
                                                          easyprof_manifest))

    def test_parse_security_manifest_policy_version(self):
        '''Test numeric policy_version'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_policy_version_invalid(self):
        '''Test numeric policy_version'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 0.1
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        try:
            apparmor.click.transform(cm)
        except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e:
            self.assertTrue("Invalid policy version" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on invalid policy_version")

    def test_parse_security_manifest_policy_version_high(self):
        '''Test policy_version high'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='ubuntu-sdk-13.10')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.1
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        try:
            apparmor.click.transform(cm)
        except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e:
            self.assertTrue("Invalid policy version" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on invalid policy_version")

    def test_parse_security_manifest_policy_version_low(self):
        '''Test policy_version low'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='ubuntu-sdk-14.04')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        try:
            apparmor.click.transform(cm)
        except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e:
            self.assertTrue("Invalid policy version" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on invalid policy_version")

    def test_parse_security_manifest_policy_version_without_decimal(self):
        '''Test numeric policy_version without decimal (LP: # 1214618)'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    # The security policy is supposed to have a JSON Number. The click hook
    # currently will convert "1.0" to 1.0 for easyprof. Test for that.
    def test_parse_security_manifest_policy_version_as_string(self):
        '''Test numeric policy_version as string'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": "1.0"
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    # The security policy is supposed to have a JSON Number
    def test_parse_security_manifest_policy_version_is_number(self):
        '''Test numeric policy_version requires a number'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3")
        v = "abc"
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": "%s"
}''' % v
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        try:
            apparmor.click.transform(cm)
        except ValueError:
            return
        except Exception:
            raise
        raise Exception("version %s should be invalid" % (v))

    def test_parse_security_manifest_framework_default(self):
        '''Test manifest framework (default)'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='default')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_framework_1310(self):
        '''Test manifest framework (13.10)'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='ubuntu-sdk-13.10')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_framework_1404(self):
        '''Test manifest framework (14.04)'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='ubuntu-sdk-14.04')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.1
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

        expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.1,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" %
                          (expected, easyprof_manifest))

    def test_parse_security_manifest_framework_1404_subframework(self):
        '''Test manifest framework 14.04 (subframeworks)'''
        for sub in ['-dev', '-dev1',
                    '-html-dev1', '-html',
                    '-papi-dev2', '-papi',
                    '-qml-dev3', 'qml']:
            c = self.clickstate
            package = "com.ubuntu.developer.username.yourapp%s" % sub
            c.add_package(package, "0.3",
                          framework='ubuntu-sdk-14.04%s' % sub)
            security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.1
}'''
            c.add_app('sample-app', manifest=security_json)

            cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                              "%s_sample-app_0.3.json" %
                                              package))
            easyprof_manifest = apparmor.click.transform(cm)

            dbus_id = apparmor.click.dbus_path("%s_sample-app_0.3" % package)

            expected = json.loads('''{
  "profiles": {
    "%s_sample-app_0.3": {
      "policy_groups": [
        "networking"
      ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.1,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.3",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (package, dbus_id, package, c.packages_tree))
            self.assertEquals(expected, easyprof_manifest,
                              "Expected to get %s, got %s" %
                              (expected, easyprof_manifest))

    def test_parse_security_manifest_framework_1404_subframework_low(self):
        '''Test manifest framework 14.04 (subframework policy version low)'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='ubuntu-sdk-14.04-html')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_sample-app_0.3.json" % package))
        try:
            apparmor.click.transform(cm)
        except apparmor.click.AppArmorExceptionClickInvalidPolicyVersion as e:
            self.assertTrue("Invalid policy version" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on invalid policy_version")

    def test_parse_security_manifest_framework_nonexistent(self):
        '''Test framework (nonexistent)'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        c.add_package(package, "0.3", framework='nonexistent')
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        try:
            cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                              "%s_sample-app_0.3.json" %
                                              package))
            apparmor.click.transform(cm)
        except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e:
            self.assertTrue("Unknown framework" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on nonexistent framework")

    def test_parse_security_manifest_abstractions(self):
        '''Test we pass along abstractions to easyprof'''

        c = self.clickstate
        appname = "com.ubuntu.developer.trusteddev.terminal"
        c.add_package(appname, "0.99.9~123")
        security_json = '''{
  "template": "ubuntu-sdk",
  "policy_groups": [ ],
  "policy_version": 1.0,
  "abstractions": [
    "gnupg",
    "ssl_keys"
  ]
}'''
        c.add_app('0wnzered', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_0wnzered_0.99.9~123.json" %
                                          (appname)))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname)

        expected = json.loads('''{
  "profiles": {
    "%s_0wnzered_0.99.9~123": {
      "abstractions": [ "gnupg", "ssl_keys" ],
      "policy_groups": [ ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.99.9~123",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (appname, dbus_id, appname, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" % (expected,
                                                          easyprof_manifest))

    def test_parse_security_manifest_read_paths(self):
        '''Test we pass along read_paths to easyprof'''

        c = self.clickstate
        appname = "com.ubuntu.developer.trusteddev.terminal"
        c.add_package(appname, "0.99.9~123")
        security_json = '''{
  "template": "ubuntu-sdk",
  "policy_groups": [ ],
  "policy_version": 1.0,
  "read_path": [
    "/tmp/foo_r",
    "/tmp/bar_r/"
  ]
}'''
        c.add_app('0wnzered', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_0wnzered_0.99.9~123.json" %
                                          (appname)))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname)

        expected = json.loads('''{
  "profiles": {
    "%s_0wnzered_0.99.9~123": {
      "policy_groups": [ ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "read_path": [
        "/tmp/foo_r",
        "/tmp/bar_r/"
      ],
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.99.9~123",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (appname, dbus_id, appname, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" % (expected,
                                                          easyprof_manifest))

    def test_parse_security_manifest_write_paths(self):
        '''Test we pass along write_paths to easyprof'''

        c = self.clickstate
        appname = "com.ubuntu.developer.trusteddev.terminal"
        c.add_package(appname, "0.99.9~123")
        security_json = '''{
  "template": "ubuntu-sdk",
  "policy_groups": [ ],
  "policy_version": 1.0,
  "write_path": [
    "/tmp/foo_w",
    "/tmp/bar_w/"
  ]
}'''
        c.add_app('0wnzered', manifest=security_json)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s_0wnzered_0.99.9~123.json" %
                                          (appname)))
        easyprof_manifest = apparmor.click.transform(cm)

        dbus_id = apparmor.click.dbus_path("%s_0wnzered_0.99.9~123" % appname)

        expected = json.loads('''{
  "profiles": {
    "%s_0wnzered_0.99.9~123": {
      "policy_groups": [ ],
      "policy_vendor": "ubuntu",
      "policy_version": 1.0,
      "write_path": [
        "/tmp/foo_w",
        "/tmp/bar_w/"
      ],
      "template": "ubuntu-sdk",
      "template_variables": {
        "APP_ID_DBUS": "%s",
        "APP_PKGNAME": "%s",
        "APP_VERSION": "0.99.9~123",
        "CLICK_DIR": "%s"
      }
    }
  }
}''' % (appname, dbus_id, appname, c.packages_tree))
        self.assertEquals(expected, easyprof_manifest,
                          "Expected to get %s, got %s" % (expected,
                                                          easyprof_manifest))

    def test_parse_security_manifest_bad_symlink(self):
        '''Test being given a symlink and parsing the manifests'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        fn = os.path.join(c.click_dir, n)
        shutil.move(os.path.dirname(c.click_pkgdir),
                    os.path.dirname(c.click_pkgdir) + '.new')
        try:
            apparmor.click.ClickManifest(fn)
        except apparmor.click.AppArmorException as e:
            self.assertTrue("from symlink" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have not found click manifest from symlink")

    def test_get_click_dir(self):
        '''Test get_click_dir()'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        fn = os.path.join(c.click_dir, n)
        (package, app, version) = apparmor.click.parse_manifest_name(n)
        (m, pkg_dir) = apparmor.click.get_package_manifest(fn, package)
        click_dir = apparmor.click.get_click_dir(pkg_dir, package, version)
        self.assertTrue(os.path.basename(click_dir) ==
                        os.path.basename(c.packages_tree),
                        "'%s' != " % os.path.basename(click_dir) +
                        "'%s'" % os.path.basename(c.packages_tree))

    def test_get_click_dir_bad_version(self):
        '''Test get_click_dir() with bad version'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        fn = os.path.join(c.click_dir, n)
        (package, app, version) = apparmor.click.parse_manifest_name(n)
        (m, pkg_dir) = apparmor.click.get_package_manifest(fn, package)
        click_dir = apparmor.click.get_click_dir(pkg_dir, package, version)
        self.assertTrue(os.path.basename(click_dir) ==
                        os.path.basename(c.packages_tree),
                        "'%s' != " % os.path.basename(click_dir) +
                        "'%s'" % os.path.basename(c.packages_tree))

        try:
            apparmor.click.get_click_dir(pkg_dir, package, "0.2")
        except apparmor.click.AppArmorException as e:
            self.assertTrue("package version dir" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should not have found bad version '0.2'")

    def test_get_click_dir_bad_package(self):
        '''Test get_click_dir() with bad package'''
        c = self.clickstate
        c.add_package("com.ubuntu.developer.username.myapp", "0.1")
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app('sample-app', manifest=security_json)

        n = "com.ubuntu.developer.username.myapp_sample-app_0.1.json"
        fn = os.path.join(c.click_dir, n)
        (package, app, version) = apparmor.click.parse_manifest_name(n)
        (m, pkg_dir) = apparmor.click.get_package_manifest(fn, package)
        click_dir = apparmor.click.get_click_dir(pkg_dir, package, version)
        self.assertTrue(os.path.basename(click_dir) ==
                        os.path.basename(c.packages_tree),
                        "'%s' != " % os.path.basename(click_dir) +
                        "'%s'" % os.path.basename(c.packages_tree))

        try:
            apparmor.click.get_click_dir(pkg_dir, package + "-bad", version)
        except apparmor.click.AppArmorException as e:
            self.assertTrue("package dir" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should not have found bad version '%s'" %
                        package + "-bad")

    def test_to_profiles(self):
        '''Test to_profiles()'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        c.add_package(package, version)
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app(appname, manifest=security_json)
        full_name = "%s_%s_%s" % (package, appname, version)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        files = click.to_profiles(easyprof_manifest, self.tmpdir)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" %
                                                  files[0])
        expected_fn = "click_%s" % (full_name)
        fn = os.path.basename(files[0])
        self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn,
                                                             expected_fn))

    def test_to_profiles_existing_profile_same(self):
        '''Test to_profiles() with existing profile that is the same'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        c.add_package(package, version)
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app(appname, manifest=security_json)
        full_name = "%s_%s_%s" % (package, appname, version)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        files = click.to_profiles(easyprof_manifest, self.tmpdir)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" %
                                                  files[0])

        # Test that we don't change the time stamp of the original file if
        # there are no changes
        mtime = os.stat(files[0]).st_mtime
        time.sleep(1)

        files = click.to_profiles(easyprof_manifest, self.tmpdir)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" %
                                                  files[0])

        cur_mtime = os.stat(files[0]).st_mtime
        self.assertTrue(mtime == cur_mtime, 'mtime is different')

    def test_to_profiles_existing_profile_different(self):
        '''Test to_profiles() with existing profile that is different'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        c.add_package(package, version)
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app(appname, manifest=security_json)
        full_name = "%s_%s_%s" % (package, appname, version)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        files = click.to_profiles(easyprof_manifest, self.tmpdir)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" %
                                                  files[0])

        # Test that we do change the time stamp of the original file if
        # there are changes
        mtime = os.stat(files[0]).st_mtime
        time.sleep(1)

        c2 = self.clickstate
        shutil.rmtree(c2.click_pkgdir)
        os.unlink(os.path.join(c2.click_dir, "%s.json" % full_name))

        c2.add_package(package, version)
        security_json = '''{
  "policy_groups": [ ],
  "policy_version": 1.0
}'''
        c2.add_app(appname, manifest=security_json)
        cm = apparmor.click.ClickManifest(os.path.join(c2.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        files = click.to_profiles(easyprof_manifest, self.tmpdir)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        expected_fn = "click_%s" % (full_name)
        fn = os.path.basename(files[0])
        self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn,
                                                             expected_fn))

        cur_mtime = os.stat(files[0]).st_mtime
        self.assertTrue(mtime != cur_mtime, 'mtime are the same')

    def test_to_profiles_nonexistent_dir(self):
        '''Test to_profiles() with non-existent directory'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        c.add_package(package, version)
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app(appname, manifest=security_json)
        full_name = "%s_%s_%s" % (package, appname, version)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        didnt_exist_dir = os.path.join(self.tmpdir, "didnt_exist")
        files = []
        files = click.to_profiles(easyprof_manifest, didnt_exist_dir)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.isdir(didnt_exist_dir), "Could not find " +
                        "%s" % didnt_exist_dir)

    def test_to_profiles_with_include(self):
        '''Test to_profiles() with include file'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        c.add_package(package, version)
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app(appname, manifest=security_json)
        full_name = "%s_%s_%s" % (package, appname, version)

        include = os.path.join(self.tmpdir, "test-inject.include")
        with open(include, "w+") as f:
            f.write('''  / r,\n''')
            f.close()

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        files = click.to_profiles(easyprof_manifest, self.tmpdir, include)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" %
                                                  files[0])
        expected_fn = "click_%s" % (full_name)
        fn = os.path.basename(files[0])
        self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn,
                                                             expected_fn))

        with open(files[0], 'r') as f:
            contents = f.read()
            f.close()
        for s in ['# injected via click hook', 'test-inject.include']:
            self.assertTrue(s in contents, "Could not find '%s'" % s +
                            "in:\n%s" % contents)

    def test_to_profiles_without_include(self):
        '''Test to_profiles() without include file'''
        c = self.clickstate
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        c.add_package(package, version)
        security_json = '''{
  "policy_groups": [ "networking" ],
  "policy_version": 1.0
}'''
        c.add_app(appname, manifest=security_json)
        full_name = "%s_%s_%s" % (package, appname, version)

        cm = apparmor.click.ClickManifest(os.path.join(c.click_dir,
                                          "%s.json" % full_name))
        easyprof_manifest = apparmor.click.transform(cm)

        files = click.to_profiles(easyprof_manifest, self.tmpdir,
                                  include=None)
        self.assertTrue(len(files) == 1,
                        "to_profiles didn't return one profile:\n%s" % files)
        self.assertTrue(os.path.exists(files[0]), "'%s' does not exist" %
                                                  files[0])
        expected_fn = "click_%s" % (full_name)
        fn = os.path.basename(files[0])
        self.assertTrue(fn == expected_fn, "'%s' != '%s'" % (fn,
                                                             expected_fn))

        with open(files[0], 'r') as f:
            contents = f.read()
            f.close()
        for s in ['# injected via click hook', 'test-inject.include']:
            self.assertFalse(s in contents, "Found '%s' in:\n%s" % (
                             s, contents))

    def test_easyprof_profile_methods(self):
        '''Test EasyprofProfile methods'''
        newp = apparmor.click.EasyprofProfile("foo")

        s = "test-string"
        self.assertTrue(newp.template is None)
        newp.template = s
        self.assertTrue(newp.template == s)

        self.assertTrue(newp.policyvendor is None)
        newp.policyvendor = s
        self.assertTrue(newp.policyvendor == s)

        self.assertTrue(newp.policyversion is None)
        newp.policyversion = 1.0
        self.assertTrue(newp.policyversion == 1.0)

        k = "test-key"
        self.assertTrue(k not in newp.profile['template_variables'])
        newp.add_variable(k, s)
        self.assertTrue(k in newp.profile['template_variables'])
        self.assertTrue(newp.profile['template_variables'][k] == s)

        e = "test-entry"
        self.assertTrue(e not in newp.profile['policy_groups'])
        newp.add_policygroup(e)
        self.assertTrue(e in newp.profile['policy_groups'])

        self.assertTrue('abstractions' not in newp.profile)
        newp.add_abstraction(e)
        self.assertTrue(e in newp.profile['abstractions'])

        self.assertTrue('read_path' not in newp.profile)
        newp.add_read_path(e)
        self.assertTrue(e in newp.profile['read_path'])

        self.assertTrue('write_path' not in newp.profile)
        newp.add_write_path(e)
        self.assertTrue(e in newp.profile['write_path'])

    def test_parse_manifest_name(self):
        '''Test parse_manifest_name()'''
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        manifest_name = "%s_%s_%s.json" % (package, appname, version)

        (p, a, v) = apparmor.click.parse_manifest_name(manifest_name)
        self.assertTrue(p == package, "'%s' != '%s'" % (p, package))
        self.assertTrue(a == appname, "'%s' != '%s'" % (a, appname))
        self.assertTrue(v == version, "'%s' != '%s'" % (v, version))

    def test_parse_manifest_name_bad_no_json(self):
        '''Test parse_manifest_name() without .json'''
        package = "com.ubuntu.developer.username.yourapp"
        appname = "sample-app"
        version = "0.3"
        manifest_name = "%s_%s_%s" % (package, appname, version)

        try:
            apparmor.click.parse_manifest_name(manifest_name)
        except apparmor.click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("'%s' should be invalid" % (manifest_name))

    def test_parse_manifest_name_bad_name(self):
        '''Test parse_manifest_name() bad name'''
        package = "com.ubuntu.developer.username.yourapp"
        manifest_name = "%s.json" % package

        try:
            apparmor.click.parse_manifest_name(manifest_name)
        except apparmor.click.AppArmorException:
            return
        except Exception:
            raise
        raise Exception("'%s' should be invalid" % (manifest_name))

    def test_get_framework_base_version_subframework(self):
        '''Test get_framework_base_version() (subframework)'''
        for k in click.framework_transforms.keys():
            if k == 'ubuntu-sdk-13.10':  # ubuntu-sdk-13.10 doesn't have
                                         # subframeworks
                continue
            for sub in ['-dev', '-dev1',
                        '-html-dev1', '-html',
                        '-papi-dev2', '-papi',
                        '-qml-dev3', 'qml']:
                framework = "%s%s" % (k, sub)
                framework_base = click.get_framework_base_version(framework)
                self.assertTrue(framework_base == k,
                                "%s != %s" % (framework_base, k))

    def test_get_framework_base_version_1310(self):
        '''Test get_framework_base_version() (13.10)'''
        framework = 'ubuntu-sdk-13.10'
        framework_base = click.get_framework_base_version(framework)
        self.assertTrue(framework_base == framework,
                        "%s != %s" % (framework_base, framework))

    def test_get_framework_base_version_nonexistent(self):
        '''Test get_framework_base_version() (nonexistent)'''
        framework = 'nonexistsent'
        try:
            click.get_framework_base_version(framework)
        except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e:
            self.assertTrue("Unknown framework" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on nonexistent framework")

    def test_get_policy_version_for_framework(self):
        '''Test get_policy_version_for_framework() (subframework)'''
        for k in click.framework_transforms.keys():
            if k == 'ubuntu-sdk-13.10':  # ubuntu-sdk-13.10 doesn't have
                                         # subframeworks
                continue
            for sub in ['-dev', '-dev1',
                        '-html-dev1', '-html',
                        '-papi-dev2', '-papi',
                        '-qml-dev3', 'qml']:
                framework = "%s%s" % (k, sub)
                policy_version = click.get_policy_version_for_framework(
                    framework)
                recommended = click.framework_transforms[k][1]
                self.assertTrue(policy_version == recommended,
                                "%s != %s" % (policy_version, recommended))

    def test_get_policy_version_for_framework_1310(self):
        '''Test get_policy_version_for_framework() (13.10)'''
        framework = 'ubuntu-sdk-13.10'
        policy_version = click.get_policy_version_for_framework(framework)
        recommended = click.framework_transforms[framework][1]
        self.assertTrue(policy_version == recommended,
                        "%s != %s" % (policy_version, recommended))

    def test_get_policy_version_for_framework_nonexistent(self):
        '''Test get_policy_version_for_framework() (nonexistent)'''
        framework = 'nonexistsent'
        try:
            click.get_policy_version_for_framework(framework)
        except apparmor.click.AppArmorExceptionClickFrameworkNotFound as e:
            self.assertTrue("Unknown framework" in str(e))
            return
        except Exception:
            raise
        raise Exception("Should have failed on nonexistent framework")


class AppArmorManifestSynchronization(unittest.TestCase):
    def setUp(self):
        self.tmpdir = tempfile.mkdtemp(prefix="aa-manifest-consistency-")
        self.clickstate = ClickState(self.tmpdir)
        self.maxDiff = None

    def tearDown(self):
        if os.path.exists(self.tmpdir):
            shutil.rmtree(self.tmpdir)

    def test_two_equal_directories(self):
        '''Test two equal directories'''
        c = self.clickstate
        clicks = ["alpha_beta_gamma", "click_click_version",
                  "wat_no-really_wat"]
        for cname in clicks:
            with open(os.path.join(c.click_dir, '%s.json' %
                      (cname)), 'w+') as f:
                f.write('invalid json here')
            with open(os.path.join(c.profiles_dir, 'click_%s' %
                                   (cname)), 'w+') as f:
                f.write('profile %s { }' % (cname))

        expected = []
        result = click.get_missing_profiles(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no profiles, got %s" % (result))
        result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no click hooks, got %s" % (result))

    def test_two_empty_directories_for_profiles(self):
        '''Test two empty directories returns no new needed profiles'''
        c = self.clickstate

        expected = []
        result = click.get_missing_profiles(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no profiles, got %s" % (result))

    def test_two_empty_directories_for_clicks(self):
        '''Test two empty directories returns no removed click hooks'''
        c = self.clickstate

        expected = []
        result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no clickhooks, got %s" % (result))

    def test_versus_empty_profiles_directory(self):
        '''Test against empty profiles directory'''
        c = self.clickstate
        clicks = ["alpha_beta_gamma.json", "click_click_version.json",
                  "wat_no-really_wat.json"]
        for cname in clicks:
            with open(os.path.join(c.click_dir, cname), 'w+') as f:
                f.write('invalid json here')

        expected = set(clicks)
        result = set(click.get_missing_profiles(c.click_dir, c.profiles_dir))
        self.assertEquals(expected, result,
                          "Expected to get %s profiles, got %s" % (expected,
                                                                   result))

    def test_versus_empty_clicks_directory(self):
        '''Test against empty clicks directory'''
        c = self.clickstate
        clicks = ["alpha_beta_gamma", "click_click_version",
                  "wat_no-really_wat"]
        for cname in clicks:
            with open(os.path.join(c.profiles_dir, 'click_%s' %
                                   (cname)), 'w+') as f:
                f.write('profile %s { }' % (cname))

        expected = set(["%s%s" % (AppName._CLICK_PREFIX, x) for x in clicks])
        result = set(click.get_missing_clickhooks(c.click_dir, c.profiles_dir))
        self.assertEquals(expected, result,
                          "Expected to get %s hooks, got %s" % (expected,
                                                                result))

    def test_two_unequal_directories(self):
        '''Test two equal directories'''
        c = self.clickstate
        expected_clicks = ['missing_click_profile.json',
                           'another-missing_click_profile.json']
        expected_profiles = ['%sremoved_click_package' %
                             (AppName._CLICK_PREFIX)]
        clicks = ["alpha_beta_gamma.json", "click_click_version.json",
                  "wat_no-really_wat.json"]
        profiles = ["%s%s" % (AppName._CLICK_PREFIX,
                              x[:-len('.json')]) for x in clicks]
        clicks.extend(expected_clicks)
        profiles.extend(expected_profiles)
        for cname in clicks:
            with open(os.path.join(c.click_dir, cname), 'w+') as f:
                f.write('invalid json here')
        for pname in profiles:
            with open(os.path.join(c.profiles_dir, pname), 'w+') as f:
                f.write('profile %s { }' % (pname))

        result = set(click.get_missing_profiles(c.click_dir, c.profiles_dir))
        self.assertEquals(set(expected_clicks), result,
                          "Expected to get '%s' missing profiles, got %s" %
                          (set(expected_clicks), result))
        result = set(click.get_missing_clickhooks(c.click_dir, c.profiles_dir))
        self.assertEquals(set(expected_profiles), result,
                          "Expected to get '%s' missing click hooks, got %s" %
                          (set(expected_profiles), result))

    def test_two_unequal_directories_bad_profile_filename(self):
        '''Test two unequal directories (bad profile_filename)'''
        c = self.clickstate
        clicks = ["alpha_beta_gamma", "click_click_version",
                  "wat_no-really_wat"]
        for cname in clicks:
            with open(os.path.join(c.click_dir, '%s.json' %
                      (cname)), 'w+') as f:
                f.write('invalid json here')
            with open(os.path.join(c.profiles_dir, 'badformat_%s' %
                                   (cname)), 'w+') as f:
                f.write('profile %s { }' % (cname))

        expected = []
        result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no click hooks, got %s" % (result))

    def test_two_equal_directories_new_symlink(self):
        '''Test two equal directories with new symlink (LP: #1291549)'''
        c = self.clickstate
        clicks = ["alpha_beta_gamma", "click_click_version",
                  "wat_no-really_wat"]
        for cname in clicks:
            with open(os.path.join(c.click_dir, '%s.json' %
                      (cname)), 'w+') as f:
                f.write('invalid json here')
            with open(os.path.join(c.profiles_dir, 'click_%s' %
                                   (cname)), 'w+') as f:
                f.write('profile %s { }' % (cname))

        # Click hooks not updated yet, so everything should be the same
        expected = []
        result = click.get_missing_profiles(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no profiles, got %s" % (result))
        result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no click hooks, got %s" % (result))

        time.sleep(1)
        clicks = ["alpha_beta_gamma", "click_click_version"]
        for cname in clicks:
            with open(os.path.join(c.click_dir, '%s.json' %
                      (cname)), 'w+') as f:
                f.write('invalid json here')

        expected = []
        result = click.get_missing_clickhooks(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, result,
                          "Expected to get no click hooks, got %s" % (result))
        expected = len(clicks)
        result = click.get_missing_profiles(c.click_dir, c.profiles_dir)
        self.assertEquals(expected, len(result),
                          "Expected to get %d profiles, got %s:\n" %
                          (expected, len(result)))


class AppArmorPolicyModificationTests(unittest.TestCase):

    def setUp(self):
        self.remove_profiles = []

    def tearDown(self):
        if "ADTTMP" in os.environ:  # skip unload if running under autopkgtest
            return
        apparmor_remove = "/sys/kernel/security/apparmor/.remove"
        for p in self.remove_profiles:
            with open(apparmor_remove, 'w') as f:
                f.write(p)

    def test_load_simple_policy(self):
        '''Test basic load policy function'''
        policy = "profile invalid_policy_dont_use { }"
        with tempfile.NamedTemporaryFile(prefix="aa-clicktest-",
                                         delete=False) as profile:
            profile.write(bytes(policy, 'utf-8'))
            name = profile.name

        try:
            rc, output = click.load_profile(name)
        except:
            raise

        self.remove_profiles.append('invalid_policy_dont_use')
        os.remove(name)

    def test_load_simple_policies(self):
        '''Test basic load policies function'''
        policy = "profile invalid_policy_dont_use { }"
        with tempfile.NamedTemporaryFile(prefix="aa-clicktest-",
                                         delete=False) as profile:
            profile.write(bytes(policy, 'utf-8'))
            name = profile.name

        try:
            rc, output = click.load_profiles([name])
        except:
            raise

        self.remove_profiles.append('invalid_policy_dont_use')
        os.remove(name)

    def test_load_no_policies(self):
        '''Test load zero policies function'''
        try:
            rc, output = click.load_profiles([])
        except:
            raise

    def test_unload_simple_policy(self):
        '''Test unload simple policy'''
        policy = "profile invalid_policy_dont_use { }"
        with tempfile.NamedTemporaryFile(prefix="aa-clicktest-",
                                         delete=False) as profile:
            profile.write(bytes(policy, 'utf-8'))
            name = profile.name

        try:
            rc, output = click.load_profile(name)
        except:
            raise

        click.unload_profile('invalid_policy_dont_use')
        os.remove(name)

    def test_unload_simple_policies(self):
        '''Test unload simple policies'''
        policies = ['more_invalid_policy_%s' % (suffix)
                    for suffix in ['blart', 'blort', 'blat']]
        names = []
        for p in policies:
            policy = "profile %s { }" % (p)
            with tempfile.NamedTemporaryFile(prefix="aa-clicktest-",
                                             delete=False) as profile:
                profile.write(bytes(policy, 'utf-8'))
                names.append(profile.name)

        try:
            rc, output = click.load_profiles(names)
        except:
            raise

        click.unload_profiles(policies)
        for name in names:
            os.remove(name)

    def test_unload_nonexistent_policy(self):
        '''Test unload nonexistent policy'''
        profile = 'invalid_policy_does_not_exist_dont_use'
        click.unload_profile(profile)

    def test_apparmor_available(self):
        '''Test apparmor_available()'''
        try:
            click.apparmor_available(parser="./aa-clickhook",
                                     apparmor_dirs=['./'])
        except click.AppArmorException:
            raise Exception("apparmor_available() should have passed")

    def test_apparmor_not_available(self):
        '''Test apparmor not available'''
        try:
            click.apparmor_available(parser="/nonexistent",
                                     apparmor_dirs=['/nonexistent.dir'])
        except click.AppArmorException as e:
            self.assertTrue("Could not find '/nonexistent.dir'" in str(e))
            return
        except Exception:
            raise
        raise Exception("apparmor_available should have failed")


if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(T))
    if os.geteuid() == 0:
        suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
                      AppArmorPolicyModificationTests))
    else:
        print('Not running as root, skipping tests that load/unload ' +
              'AppArmor policy', file=sys.stderr)
    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
                  AppArmorManifestSynchronization))

    rc = unittest.TextTestRunner(verbosity=2).run(suite)

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