コード例 #1
0
ファイル: help.py プロジェクト: reanimat0r/viper
    def run(self, *args):
        try:
            args = self.parser.parse_args(args)
        except SystemExit:
            return

        self.log("info", "Commands")

        rows = []
        commands = load_commands()
        for command_name, command_item in commands.items():
            rows.append([command_name, command_item["description"]])

        rows.append(["exit, quit", "Exit Viper"])
        rows = sorted(rows, key=lambda entry: entry[0])

        self.log("table", dict(header=["Command", "Description"], rows=rows))


        if len(__modules__) == 0:
            self.log("info", "No modules installed.")
        else:
            self.log("info", "Modules")
            rows = []
            for module_name, module_item in __modules__.items():
                rows.append([module_name, module_item["description"],
                    ", ".join(c for c in module_item["categories"])])

            rows = sorted(rows, key=lambda entry: entry[0])

            self.log("table", dict(header=["Command", "Description", "Categories"], rows=rows))
コード例 #2
0
    def run(self, *args):
        try:
            args = self.parser.parse_args(args)
        except SystemExit:
            return

        self.log('info', "Commands")

        rows = []
        commands = load_commands()
        for command_name, command_item in commands.items():
            rows.append([command_name, command_item['description']])

        rows.append(["exit, quit", "Exit Viper"])
        rows = sorted(rows, key=lambda entry: entry[0])

        self.log('table', dict(header=['Command', 'Description'], rows=rows))
        self.log('info', "Modules")

        rows = []
        for module_name, module_item in __modules__.items():
            rows.append([module_name, module_item['description']])

        rows = sorted(rows, key=lambda entry: entry[0])

        self.log('table', dict(header=['Command', 'Description'], rows=rows))
コード例 #3
0
 def __init__(self):
     Database().__init__()
     # Map commands to their related functions.
     self.commands = load_commands()
コード例 #4
0
class TestUseCases:

    cmd = load_commands()

    def teardown_method(self):
        __sessions__.close()

    @pytest.mark.usefixtures("cleandir")
    @pytest.mark.parametrize("filename, name", [
        ("chromeinstall-8u31.exe", "chromeinstall-8u31.exe"),
        ("string_handling/with blank.txt", "with blank.txt"),
        ])
    def test_store(self, capsys, filename, name):
        # use cleandir fixture operate on clean ./ local dir
        copyfile(os.path.join(FIXTURE_DIR, filename), os.path.join(".", os.path.basename(filename)))
        self.cmd['open']['obj']('-f', os.path.join(".", os.path.basename(filename)))
        self.cmd['store']['obj']()
        if sys.version_info <= (3, 0):
            in_fct = 'builtins.raw_input'
        else:
            in_fct = 'builtins.input'
        with mock.patch(in_fct, return_value='y'):
            self.cmd['delete']['obj']()
        out, err = capsys.readouterr()
        lines = out.split("\n")

        assert re.search(r".*Session opened on.*", lines[0])
        assert not re.search(r".*Unable to store file.*", out)
        assert re.search(r".*{}.*".format(name), lines[1])
        assert re.search(r".*Running command.*", lines[5])
        assert re.search(r".*Deleted opened file.*", lines[7])

    @pytest.mark.skipif(sys.version_info >= (3, 0), reason="requires python2")
    @pytest.mark.xfail(raises=Python2UnsupportedUnicode)
    @pytest.mark.usefixtures("cleandir")
    @pytest.mark.parametrize("filename, name", [
        ("string_handling/dümmy.txt", "dümmy.txt")
        ])
    def test_store_unicode_py2(self, capsys, filename, name):
        # use cleandir fixture operate on clean ./ local dir
        copyfile(os.path.join(FIXTURE_DIR, filename), os.path.join(".", os.path.basename(filename)))
        self.cmd['open']['obj']('-f', os.path.join(".", os.path.basename(filename)))
        self.cmd['store']['obj']()
        if sys.version_info <= (3, 0):
            in_fct = 'builtins.raw_input'
        else:
            in_fct = 'builtins.input'
        with mock.patch(in_fct, return_value='y'):
            self.cmd['delete']['obj']()
        out, err = capsys.readouterr()
        lines = out.split("\n")

        assert re.search(r".*Session opened on.*", lines[0])
        assert not re.search(r".*Unable to store file.*", out)
        assert re.search(r".*{}.*".format(name), lines[1])
        assert re.search(r".*Running command.*", lines[5])
        assert re.search(r".*Deleted opened file.*", lines[7])

    @pytest.mark.skipif(sys.version_info < (3, 3), reason="requires at least python3.3")
    @pytest.mark.usefixtures("cleandir")
    @pytest.mark.parametrize("filename, name", [
        ("string_handling/dümmy.txt", "dümmy.txt")
        ])
    def test_store_unicode_py3(self, capsys, filename, name):
        # use cleandir fixture operate on clean ./ local dir
        copyfile(os.path.join(FIXTURE_DIR, filename), os.path.join(".", os.path.basename(filename)))
        self.cmd['open']['obj']('-f', os.path.join(".", os.path.basename(filename)))
        self.cmd['store']['obj']()
        if sys.version_info <= (3, 0):
            in_fct = 'builtins.raw_input'
        else:
            in_fct = 'builtins.input'
        with mock.patch(in_fct, return_value='y'):
            self.cmd['delete']['obj']()
        out, err = capsys.readouterr()
        lines = out.split("\n")

        assert re.search(r".*Session opened on.*", lines[0])
        assert not re.search(r".*Unable to store file.*", out)
        assert re.search(r".*{}.*".format(name), lines[1])
        assert re.search(r".*Running command.*", lines[5])
        assert re.search(r".*Deleted opened file.*", lines[7])

    @pytest.mark.skipif(sys.version_info >= (3, 0), reason="requires python2")
    @pytest.mark.xfail(raises=Python2UnsupportedUnicode)
    @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"])
    def test_store_all_py2(self, capsys, filename):
        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, filename))
        self.cmd['store']['obj']()
        self.cmd['store']['obj']('-f', FIXTURE_DIR)
        out, err = capsys.readouterr()
        lines = out.split("\n")

        assert re.search(r".*Session opened on.*", lines[0])
        assert re.search(r".*appears to be already stored.*", out)
        assert re.search(r".*Skip, file \"chromeinstall-8u31.exe\" appears to be already stored.*", out)

    @pytest.mark.skipif(sys.version_info < (3, 3), reason="requires at least python3.3")
    @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"])
    def test_store_all_py3(self, capsys, filename):
        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, filename))
        self.cmd['store']['obj']()
        self.cmd['store']['obj']('-f', FIXTURE_DIR)
        out, err = capsys.readouterr()
        lines = out.split("\n")

        assert re.search(r".*Session opened on.*", lines[0])
        assert re.search(r".*appears to be already stored.*", out)
        assert re.search(r".*Skip, file \"chromeinstall-8u31.exe\" appears to be already stored.*", out)

    @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"])
    def test_open(self, capsys, filename):
        with open(os.path.join(FIXTURE_DIR, filename), 'rb') as f:
            hashfile = sha256(f.read()).hexdigest()
        self.cmd['open']['obj'](hashfile)
        self.cmd['info']['obj']()
        self.cmd['close']['obj']()
        out, err = capsys.readouterr()
        lines = out.split("\n")

        assert re.search(r".*Session opened on.*", lines[0])
        assert re.search(r".*| SHA1     | 56c5b6cd34fa9532b5a873d6bdd4380cfd102218.*", lines[11])

    @pytest.mark.parametrize("filename", ["chromeinstall-8u31.exe"])
    def test_find(self, capsys, filename):
        with open(os.path.join(FIXTURE_DIR, filename), 'rb') as f:
            data = f.read()
            hashfile_sha = sha256(data).hexdigest()
        self.cmd['find']['obj']('all')
        self.cmd['find']['obj']('sha256', hashfile_sha)
        self.cmd['open']['obj']('-l', '1')
        self.cmd['close']['obj']()
        self.cmd['tags']['obj']('-a', 'blah')
        self.cmd['find']['obj']('-t')
        self.cmd['tags']['obj']('-d', 'blah')
        out, err = capsys.readouterr()

        assert re.search(r".*EICAR.com.*", out)
        assert re.search(r".*{0}.*".format(filename), out)
        assert re.search(r".*Tag.*|.*# Entries.*", out)

    def test_stats(self, capsys):
        self.cmd['stats']['obj']()
        out, err = capsys.readouterr()

        assert re.search(r".*Projects.*Name | Count.*", out)
コード例 #5
0
ファイル: test_commands.py プロジェクト: MasterScott/viper-1
 def setup_class(cls):
     cmd = load_commands()
     cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
     cmd['store']['obj']()
コード例 #6
0
ファイル: test_commands.py プロジェクト: MasterScott/viper-1
class TestCommands:
    cmd = load_commands()

    def setup_class(cls):
        cmd = load_commands()
        cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
        cmd['store']['obj']()

    def teardown_method(self):
        self.cmd['close']['obj']()

    def test_help(self, capsys):
        self.cmd['help']['obj']()
        self.cmd['clear']['obj']()
        out, err = capsys.readouterr()
        assert re.search(r".* Commands.*", out)
        assert re.search(r".* Modules.*", out)

    def test_notes(self, capsys):
        self.cmd['notes']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: notes \[-h\] .*", out)

        self.cmd['notes']['obj']('-l')
        out, err = capsys.readouterr()
        assert re.search(".*No notes available for this file or.*", out)

    def test_open(self, capsys):
        self.cmd['open']['obj']('-h')
        self.cmd['open']['obj']('-u', 'https://github.com/viper-framework/viper-test-files/raw/master/test_files/cmd.exe')
        out, err = capsys.readouterr()
        assert re.search(r"usage: open \[-h\] .*", out)
        assert re.search(".*Session opened on /tmp/.*", out)

    def test_open_tor(self, capsys):
        self.cmd['open']['obj']('-h')
        self.cmd['open']['obj']('-t', '-u', 'https://github.com/viper-framework/viper-test-files/raw/master/test_files/cmd.exe')
        out, err = capsys.readouterr()
        assert re.search(r"usage: open \[-h\] .*", out)
        assert re.search(".*Session opened on /tmp/.*", out)

    def test_notes_existing(self, capsys):
        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
        Database().add_note(__sessions__.current.file.sha256, 'Note test', 'This is the content')
        self.cmd['notes']['obj']('-l')
        self.cmd['notes']['obj']('-v', '1')
        self.cmd['notes']['obj']('-d', '1')
        out, err = capsys.readouterr()
        assert re.search(".*1  | Note test.*", out)
        assert re.search(".*This is the content.*", out)

    def test_analysis(self, capsys):
        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
        self.cmd['analysis']['obj']('-h')
        self.cmd['analysis']['obj']('-l')
        self.cmd['analysis']['obj']('-v', '1')
        out, err = capsys.readouterr()
        assert re.search(r"usage: analysis \[-h\] .*", out)
        assert re.search(".*Saved On.*", out)
        assert re.search(".*Cmd Line.*", out)

    def test_store(self, capsys):
        self.cmd['store']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: store \[-h\] .*", out)

    def test_delete(self, capsys):
        self.cmd['delete']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: delete \[-h\] .*", out)

    def test_find(self, capsys):
        self.cmd['find']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: find \[-h\] .*", out)

        self.cmd['find']['obj']('all')
        out, err = capsys.readouterr()
        assert re.search(".*chromeinstall-8u31.exe.*", out)

    def test_tags(self, capsys):
        self.cmd['tags']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: tags \[-h\] .*", out)

    def test_tags_use(self, capsys):
        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
        self.cmd['tags']['obj']('-a', 'mytag')
        self.cmd['tags']['obj']('-d', 'mytag')
        out, err = capsys.readouterr()
        lines = out.split('\n')
        assert re.search(".*Tags added to the currently opened file.*", lines[1])
        assert re.search(".*Refreshing session to update attributes....*", lines[2])

    def test_sessions(self, capsys):
        self.cmd['sessions']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: sessions \[-h\] .*", out)

        self.cmd['sessions']['obj']('-l')
        out, err = capsys.readouterr()
        assert re.search(".*Opened Sessions.*", out)

    def test_projects(self, capsys):
        self.cmd['projects']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: projects \[-h\] .*", out)

        p = Project()
        p.open("project_switch_test1")

        self.cmd['projects']['obj']('-l')
        out, err = capsys.readouterr()
        assert re.search(".*Projects Available.*", out)
        assert re.search(".*project_switch_test1.*", out)
        assert not re.search(".*not_there.*", out)

        self.cmd['projects']['obj']('-s', 'project_switch_test1')
        out, err = capsys.readouterr()
        lines = out.split('\n')
        assert re.search(".*Switched to project.*", lines[0])

        # return to default
        p.open("default")

    def test_export(self, capsys):
        self.cmd['export']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: export \[-h\] .*", out)

    def test_stats(self, capsys):
        self.cmd['stats']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: stats \[-h\] .*", out)

    def test_parent(self, capsys):
        self.cmd['parent']['obj']('-h')
        out, err = capsys.readouterr()
        assert re.search(r"usage: parent \[-h\] .*", out)

    def test_rename(self, capsys):
        self.cmd['find']['obj']("all")
        out, err = capsys.readouterr()
        assert out == ""

        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
        self.cmd['store']['obj']()
        _, _ = capsys.readouterr()

        if sys.version_info <= (3, 0):
            in_fct = 'builtins.raw_input'
        else:
            in_fct = 'builtins.input'

        with mock.patch(in_fct, return_value='chromeinstall-8u31.exe.new'):
            self.cmd['rename']['obj']()

        out, err = capsys.readouterr()
        lines = out.split('\n')

        assert re.search(r".*Current name is.*1mchromeinstall-8u31.exe.*", lines[0])
        assert re.search(r".*Refreshing session to update attributes.*", lines[1])

    def test_copy(self, capsys):
        self.cmd['projects']['obj']('-s', 'copy_test_dst')
        out, err = capsys.readouterr()
        lines = out.split('\n')
        assert re.search(r".*Switched to project.*", lines[0])

        self.cmd['find']['obj']('all')
        out, err = capsys.readouterr()
        assert out == ""

        self.cmd['projects']['obj']('-s', 'copy_test_src')
        out, err = capsys.readouterr()
        lines = out.split('\n')
        assert re.search(r".*Switched to project.*", lines[0])

        self.cmd['find']['obj']('all')
        out, err = capsys.readouterr()
        assert out == ""

        self.cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
        self.cmd['store']['obj']()
        out, err = capsys.readouterr()
        lines = out.split('\n')
        assert re.search(r".*Session opened on.*", lines[0])
        assert re.search(r".*Stored file.*", lines[1])

        self.cmd['find']['obj']('all')
        out, err = capsys.readouterr()
        assert re.search(r".*\| 1 \| chromeinstall-8u31.exe.*", out)
        assert not re.search(r".*\| 2 \|.*", out)

        self.cmd['copy']['obj']('-d', 'copy_test_dst')
        out, err = capsys.readouterr()
        lines = out.split('\n')
        assert re.search(r".*Copied:.*", lines[0])
        assert re.search(r".*Deleted:.*", lines[1])
        assert re.search(r".*Successfully copied sample.*", lines[2])

        self.cmd['find']['obj']('all')
        out, err = capsys.readouterr()
        assert out == ""
        assert not re.search(r".*\| 1 \| chromeinstall-8u31.exe.*", out)
        assert not re.search(r".*\| 2 \|.*", out)

        self.cmd['projects']['obj']('-s', 'copy_test_dst')
        out, err = capsys.readouterr()
        assert re.search(r".*Switched to project.*", out)

        self.cmd['find']['obj']('all')
        out, err = capsys.readouterr()
        assert re.search(r".*\| 1 \| chromeinstall-8u31.exe.*", out)
        assert not re.search(r".*\| 2 \|.*", out)
コード例 #7
0
ファイル: test_commands.py プロジェクト: cvandeplas/viper
 def setup_class(cls):
     cmd = load_commands()
     cmd['open']['obj']('-f', os.path.join(FIXTURE_DIR, "chromeinstall-8u31.exe"))
     cmd['store']['obj']()
コード例 #8
0
ファイル: test_viperapi.py プロジェクト: MasterScott/viper-1
class ViperAPIv3Test(TestCase):
    cmd = load_commands()

    def setUp(self):
        self.factory = APIRequestFactory()
        self.client = APIClient()
        self.user = User.objects.create_user('testuser',
                                             email='*****@*****.**',
                                             password='******')
        self.user.save()
        token = Token.objects.create(user=self.user)
        token.save()

    def setup_method(self, method):
        """setUp by adding clean file"""
        # create api_test
        self.cmd['projects']['obj']('-s', 'api_test')
        self.cmd['open']['obj']('-f',
                                os.path.join(FIXTURE_DIR,
                                             "chromeinstall-8u31.exe"))
        self.cmd['store']['obj']()

    def teardown_method(self, method):
        """clean all files"""
        self.cmd['projects']['obj']('-s', 'api_test')
        self.cmd['close']['obj']()

        if sys.version_info <= (3, 0):
            in_fct = 'builtins.raw_input'
        else:
            in_fct = 'builtins.input'
        with mock.patch(in_fct, return_value='y'):
            self.cmd['delete']['obj']('-a')

    def _require_login(self):
        self.client.login(username='******', password='******')

    def test_api_root(self):
        # Issue a GET request and check response code
        response = self.client.get("/api/v3/")
        self.assertEqual(response.status_code, 200)

        # Check that the response content
        self.assertEqual(response.json()['project'],
                         "http://testserver/api/v3/project/")
        self.assertJSONEqual(force_text(response.content),
                             {"project": "http://testserver/api/v3/project/"})

    # TODO 2017-08-25 (frennkie) can't get this to run on Travis
    # def test_project_list_add(self):
    #     self._require_login()
    #
    #     self.maxDiff = None
    #
    #     # check list
    #     response = self.client.get("/api/v3/project/")
    #     self.assertEqual(response.status_code, 200)
    #
    #     # Check that the response content
    #     self.assertIsInstance(response.json()['results'], list)
    #     self.assertEqual(len(response.json()['results']), 5)
    #
    #     expected_json = {
    #         "count": 5,
    #         "next": None,
    #         "previous": None,
    #         "results": [
    #             {
    #                 "url": "http://testserver/api/v3/project/api_test/",
    #                 "data":
    #                     {"name": "api_test"}
    #             },
    #             {
    #                 'url': 'http://testserver/api/v3/project/project_switch_test1/',
    #                 'data':
    #                     {'name': 'project_switch_test1'},
    #             },
    #             {
    #                 'url': 'http://testserver/api/v3/project/copy_test_src/',
    #                 'data':
    #                     {'name': 'copy_test_src'},
    #             },
    #             {
    #                 'url': 'http://testserver/api/v3/project/copy_test_dst/',
    #                 'data':
    #                     {'name': 'copy_test_dst'},
    #             },
    #             {
    #                 "url": "http://testserver/api/v3/project/default/",
    #                 "data":
    #                     {"name": "default"}
    #             }
    #         ]
    #     }
    #
    #     self.assertJSONEqual(force_text(response.content), expected_json)
    #
    #     # now add
    #     response = self.client.post("/api/v3/project/", {"name": "api_test2"})
    #     self.assertEqual(response.status_code, 201)
    #
    #     # Check that the response content
    #     self.assertIsInstance(response.json()['data'], dict)
    #
    #     expected_json = {
    #         "data": {
    #             "name": "api_test2"
    #         },
    #         "url": "http://testserver/api/v3/project/api_test2/",
    #         "links": {
    #             "tags": "http://testserver/api/v3/project/api_test2/tag/",
    #             "malware": "http://testserver/api/v3/project/api_test2/malware/",
    #             "analysis": "http://testserver/api/v3/project/api_test2/analysis/",
    #             "notes": "http://testserver/api/v3/project/api_test2/note/"
    #         }
    #     }
    #
    #     self.assertJSONEqual(force_text(response.content), expected_json)
    #
    #     response = self.client.get("/api/v3/project/")
    #     self.assertEqual(len(response.json()['results']), 6)

    def test_default_project_detail(self):
        self._require_login()

        response = self.client.get("/api/v3/project/default/")
        self.assertEqual(response.status_code, 200)

        # Check that the response content
        self.assertIsInstance(response.json()['data'], dict)

        expected_json = {
            "data": {
                "name": "default"
            },
            "url": "http://testserver/api/v3/project/default/",
            "links": {
                "tags": "http://testserver/api/v3/project/default/tag/",
                "malware": "http://testserver/api/v3/project/default/malware/",
                "analysis":
                "http://testserver/api/v3/project/default/analysis/",
                "notes": "http://testserver/api/v3/project/default/note/",
                "web": "http://testserver/project/default/"
            }
        }

        self.assertJSONEqual(force_text(response.content), expected_json)

    def test_project_detail(self):
        self._require_login()

        response = self.client.get("/api/v3/project/api_test/")
        self.assertEqual(response.status_code, 200)

        expected_json = {
            "data": {
                "name": "api_test"
            },
            "url": "http://testserver/api/v3/project/api_test/",
            "links": {
                "tags": "http://testserver/api/v3/project/api_test/tag/",
                "malware":
                "http://testserver/api/v3/project/api_test/malware/",
                "analysis":
                "http://testserver/api/v3/project/api_test/analysis/",
                "notes": "http://testserver/api/v3/project/api_test/note/",
                "web": "http://testserver/project/api_test/"
            }
        }

        self.assertJSONEqual(force_text(response.content), expected_json)

    # Analysis: List all Analysis instances
    def test_analysis_list(self):
        self._require_login()

        response = self.client.get("/api/v3/project/api_test/analysis/")
        self.assertEqual(response.status_code, 200)
        self.assertIsInstance(response.json()['results'], list)
        self.assertEqual(len(response.json()['results']), 0)

        # self.assertEqual(response.json()['results'][0]['data']['cmd_line'], 'yara scan -t')
        # self.assertEqual(response.json()['results'][1]['data']['cmd_line'], 'triage')

    # Analysis: Retrieve an Analysis instance
    # def test_analysis_detail(self):
    #     self._require_login()
    #
    #     response = self.client.get("/api/v3/project/api_test/analysis/1/")
    #     self.assertEqual(response.status_code, 200)
    #     # self.assertEqual(response.json()['results']['data']['cmd_line'], 'yara scan -t')

    # Malware: List all Malware instances
    def test_malware_list(self):
        self._require_login()

        response = self.client.get("/api/v3/project/api_test/malware/")
        self.assertEqual(response.status_code, 200)
        self.assertIsInstance(response.json()['results'], list)
        self.assertEqual(len(response.json()['results']), 1)

        self.assertEqual(
            response.json()['results'][0]['data']['sha256'],
            '583a2d05ff0d4864f525a6cdd3bfbd549616d9e1d84e96fe145794ba0519d752')

    # Note: List all Note instances
    def test_note_list(self):
        self._require_login()

        response = self.client.get("/api/v3/project/api_test/note/")
        self.assertEqual(response.status_code, 200)
        self.assertIsInstance(response.json()['results'], list)
        self.assertEqual(len(response.json()['results']), 0)

    # Tag: List all Tag instances in project
    def test_tag_list(self):
        self._require_login()

        self.maxDiff = None

        response = self.client.get("/api/v3/project/api_test/tag/")
        self.assertEqual(response.status_code, 200)
        self.assertIsInstance(response.json()['results'], list)
        self.assertEqual(len(response.json()['results']), 0)

    # Tag: Add a new Tag to a Malware instance
    def test_malware_tag_add_remove(self):
        self._require_login()

        self.maxDiff = None

        response = self.client.get(
            "/api/v3/project/api_test/malware/583a2d05ff0d4864f525a6cdd3bfbd549616d9e1d84e96fe145794ba0519d752/tag/"
        )
        self.assertEqual(response.status_code, 200)
        self.assertIsInstance(response.json()['results'], list)
        self.assertEqual(len(response.json()['results']), 0)

        response = self.client.post(
            "/api/v3/project/api_test/malware/583a2d05ff0d4864f525a6cdd3bfbd549616d9e1d84e96fe145794ba0519d752/tag/",
            {"tag": "foobar"})
        self.assertEqual(response.status_code, 201)

        expected_json = {
            "data": {
                "id": 1,
                "tag": "foobar"
            },
            "url": "http://testserver/api/v3/project/api_test/tag/1/"
        }

        self.assertJSONEqual(force_text(response.content), expected_json)

        response = self.client.delete(
            "/api/v3/project/api_test/malware/583a2d05ff0d4864f525a6cdd3bfbd549616d9e1d84e96fe145794ba0519d752/tag/1/"
        )
        self.assertEqual(response.status_code, 204)