From d59444ef0e9c0d4f45b7c425371794e8c7d6ebd8 Mon Sep 17 00:00:00 2001
From: Chris PeBenito <cpebenito@tresys.com>
Date: Fri, 8 Jan 2016 15:30:39 -0500
Subject: [PATCH] PolicyDifference: implement Booleans diff

---
 sediff                   | 23 +++++++++++++++
 setools/diff/__init__.py |  4 ++-
 setools/diff/bool.py     | 64 ++++++++++++++++++++++++++++++++++++++++
 tests/diff.py            | 29 ++++++++++++++++++
 tests/diff_left.conf     |  5 ++++
 tests/diff_right.conf    |  5 ++++
 6 files changed, 129 insertions(+), 1 deletion(-)
 create mode 100644 setools/diff/bool.py

diff --git a/sediff b/sediff
index cf5e04c..c9ced6b 100755
--- a/sediff
+++ b/sediff
@@ -148,6 +148,29 @@ try:
                         print("          - {0}".format(p))
             print()
 
+    if all_differences or args.bool_:
+        if diff.added_booleans or diff.removed_booleans or \
+                diff.modified_booleans or args.bool_:
+            print("Booleans ({0} Added, {1} Removed, {2} Modified)".format(
+                len(diff.added_booleans), len(diff.removed_booleans),
+                len(diff.modified_booleans)))
+            if diff.added_booleans and not args.stats:
+                print("   Added Booleans: {0}".format(len(diff.added_booleans)))
+                for a in sorted(diff.added_booleans):
+                    print("      + {0}".format(a))
+            if diff.removed_booleans and not args.stats:
+                print("   Removed Booleans: {0}".format(len(diff.removed_booleans)))
+                for a in sorted(diff.removed_booleans):
+                    print("      - {0}".format(a))
+            if diff.modified_booleans and not args.stats:
+                print("   Modified Booleans: {0}".format(len(diff.modified_booleans)))
+                for name, mod in sorted(diff.modified_booleans.items()):
+                    print("      * {0} (Modified default state)".format(name))
+                    print("          + {0}".format(mod.added_state))
+                    print("          - {0}".format(mod.removed_state))
+
+            print()
+
     if all_differences or args.role:
         if diff.added_roles or diff.removed_roles or diff.modified_roles or args.role:
             print("Roles ({0} Added, {1} Removed, {2} Modified)".format(len(diff.added_roles),
diff --git a/setools/diff/__init__.py b/setools/diff/__init__.py
index 3171f9e..703ac43 100644
--- a/setools/diff/__init__.py
+++ b/setools/diff/__init__.py
@@ -16,6 +16,7 @@
 # License along with SETools.  If not, see
 # <http://www.gnu.org/licenses/>.
 #
+from .bool import BooleansDifference
 from .commons import CommonDifference
 from .mlsrules import MLSRulesDifference
 from .objclass import ObjClassDifference
@@ -29,7 +30,8 @@ from .users import UsersDifference
 __all__ = ['PolicyDifference']
 
 
-class PolicyDifference(CommonDifference,
+class PolicyDifference(BooleansDifference,
+                       CommonDifference,
                        MLSRulesDifference,
                        ObjClassDifference,
                        RBACRulesDifference,
diff --git a/setools/diff/bool.py b/setools/diff/bool.py
new file mode 100644
index 0000000..212a715
--- /dev/null
+++ b/setools/diff/bool.py
@@ -0,0 +1,64 @@
+# Copyright 2016, Tresys Technology, LLC
+#
+# This file is part of SETools.
+#
+# SETools is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 2.1 of
+# the License, or (at your option) any later version.
+#
+# SETools is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with SETools.  If not, see
+# <http://www.gnu.org/licenses/>.
+#
+from collections import namedtuple
+
+from .descriptors import DiffResultDescriptor
+from .difference import Difference, SymbolWrapper
+
+
+modified_bool_record = namedtuple("modified_boolean", ["added_state", "removed_state"])
+
+
+class BooleansDifference(Difference):
+
+    """Determine the difference in type attributes between two policies."""
+
+    added_booleans = DiffResultDescriptor("diff_booleans")
+    removed_booleans = DiffResultDescriptor("diff_booleans")
+    modified_booleans = DiffResultDescriptor("diff_booleans")
+
+    def diff_booleans(self):
+        """Generate the difference in type attributes between the policies."""
+
+        self.log.info("Generating Boolean differences from {0.left_policy} to {0.right_policy}".
+                      format(self))
+
+        self.added_booleans, self.removed_booleans, matched_booleans = \
+            self._set_diff(
+                (SymbolWrapper(b) for b in self.left_policy.bools()),
+                (SymbolWrapper(b) for b in self.right_policy.bools()))
+
+        self.modified_booleans = dict()
+
+        for left_boolean, right_boolean in matched_booleans:
+            # Criteria for modified booleans
+            # 1. change to default state
+            if left_boolean.state != right_boolean.state:
+                self.modified_booleans[left_boolean] = modified_bool_record(right_boolean.state,
+                                                                            left_boolean.state)
+
+    #
+    # Internal functions
+    #
+    def _reset_diff(self):
+        """Reset diff results on policy changes."""
+        self.log.debug("Resetting Boolean differences")
+        self.added_booleans = None
+        self.removed_booleans = None
+        self.modified_booleans = None
diff --git a/tests/diff.py b/tests/diff.py
index 69a65b0..2d94d91 100644
--- a/tests/diff.py
+++ b/tests/diff.py
@@ -910,6 +910,23 @@ class PolicyDifferenceTest(ValidateRule, unittest.TestCase):
         self.assertSetEqual(set(["modified_remove_attr"]),
                             self.diff.modified_type_attributes["an_attr"].removed_types)
 
+    #
+    # Booleans
+    #
+    def test_added_boolean(self):
+        """Diff: added boolean."""
+        self.assertSetEqual(set(["added_bool"]), self.diff.added_booleans)
+
+    def test_removed_boolean(self):
+        """Diff: removed boolean."""
+        self.assertSetEqual(set(["removed_bool"]), self.diff.removed_booleans)
+
+    def test_modified_boolean(self):
+        """Diff: modified boolean."""
+        self.assertEqual(1, len(self.diff.modified_booleans))
+        self.assertTrue(self.diff.modified_booleans["modified_bool"].added_state)
+        self.assertFalse(self.diff.modified_booleans["modified_bool"].removed_state)
+
 
 class PolicyDifferenceTestNoDiff(unittest.TestCase):
 
@@ -1110,3 +1127,15 @@ class PolicyDifferenceTestNoDiff(unittest.TestCase):
     def test_modified_type_attributes(self):
         """NoDiff: no modified type attribute rules."""
         self.assertFalse(self.diff.modified_type_attributes)
+
+    def test_added_booleans(self):
+        """NoDiff: no added booleans."""
+        self.assertFalse(self.diff.added_booleans)
+
+    def test_removed_booleans(self):
+        """NoDiff: no removed booleans."""
+        self.assertFalse(self.diff.removed_booleans)
+
+    def test_modified_booleans(self):
+        """NoDiff: no modified booleans."""
+        self.assertFalse(self.diff.modified_booleans)
diff --git a/tests/diff_left.conf b/tests/diff_left.conf
index d4b9693..8a2ec7d 100644
--- a/tests/diff_left.conf
+++ b/tests/diff_left.conf
@@ -161,6 +161,11 @@ role modified_add_type;
 role modified_remove_type;
 role modified_remove_type types { system };
 
+# booleans
+bool same_bool true;
+bool removed_bool true;
+bool modified_bool false;
+
 # Allow rule differences
 type matched_source;
 type matched_target;
diff --git a/tests/diff_right.conf b/tests/diff_right.conf
index dae33ca..5bf073c 100644
--- a/tests/diff_right.conf
+++ b/tests/diff_right.conf
@@ -162,6 +162,11 @@ role modified_add_type types { system };
 
 role modified_remove_type;
 
+# booleans
+bool same_bool true;
+bool added_bool true;
+bool modified_bool true;
+
 # Allow rule differences
 type matched_source;
 type matched_target;