| # Copyright 2015-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/>. |
| # |
| import logging |
| from collections import namedtuple |
| |
| modified_item_record = namedtuple("modified_item", ["left", "right"]) |
| |
| |
| class Difference(object): |
| |
| """Base class for all policy differences.""" |
| |
| def __init__(self, left_policy, right_policy): |
| self.log = logging.getLogger(self.__class__.__name__) |
| self.left_policy = left_policy |
| self.right_policy = right_policy |
| |
| # |
| # Policies to compare |
| # |
| @property |
| def left_policy(self): |
| return self._left_policy |
| |
| @left_policy.setter |
| def left_policy(self, policy): |
| self.log.info("Policy diff left policy set to {0}".format(policy)) |
| self._left_policy = policy |
| self._reset_diff() |
| |
| @property |
| def right_policy(self): |
| return self._right_policy |
| |
| @right_policy.setter |
| def right_policy(self, policy): |
| self.log.info("Policy diff right policy set to {0}".format(policy)) |
| self._right_policy = policy |
| self._reset_diff() |
| |
| # |
| # Internal functions |
| # |
| def _reset_diff(self): |
| """Reset diff results on policy changes.""" |
| raise NotImplementedError |
| |
| @staticmethod |
| def _expand_generator(rule_list, Wrapper): |
| """Generator that yields a wrapped, expanded rule list.""" |
| # this is to delay creating any containers |
| # as long as possible, since rule lists |
| # are typically massive. |
| for unexpanded_rule in rule_list: |
| for expanded_rule in unexpanded_rule.expand(): |
| yield Wrapper(expanded_rule) |
| |
| @staticmethod |
| def _set_diff(left, right): |
| """ |
| Standard diff of two sets. |
| |
| Parameters: |
| left An iterable |
| right An iterable |
| |
| Return: |
| tuple (added, removed, matched) |
| |
| added Set of items in right but not left |
| removed Set of items in left but not right |
| matched Set of items in both left and right. This is |
| in the form of tuples with the matching item |
| from left and right |
| """ |
| |
| left_items = set(left) |
| right_items = set(right) |
| added_items = right_items - left_items |
| removed_items = left_items - right_items |
| |
| # The problem here is the symbol from both policies are |
| # needed to build each tuple in the matched items set. |
| # Using the standard Python set intersection code will only result |
| # in one object. |
| # |
| # This tuple-generating code creates lists from the sets, to sort them. |
| # This should result in all of the symbols lining up. If they don't, |
| # this will break the caller. This should work since there is no remapping. |
| # |
| # This has extra checking to make sure this assertion holds, to fail |
| # instead of giving wrong results. If there is a better way to, |
| # ensure the items match up, please let me know how or submit a patch. |
| matched_items = set() |
| left_matched_items = sorted((left_items - removed_items)) |
| right_matched_items = sorted((right_items - added_items)) |
| assert len(left_matched_items) == len(right_matched_items), \ |
| "Matched items assertion failure (this is an SETools bug), {0} != {1}". \ |
| format(len(left_matched_items), len(right_matched_items)) |
| |
| for l, r in zip(left_matched_items, right_matched_items): |
| assert l == r, \ |
| "Matched items assertion failure (this is an SETools bug), {0} != {1}".format(l, r) |
| |
| matched_items.add((l, r)) |
| |
| try: |
| # unwrap the objects |
| return set(i.origin for i in added_items), \ |
| set(i.origin for i in removed_items), \ |
| set((l.origin, r.origin) for (l, r) in matched_items) |
| except AttributeError: |
| return added_items, removed_items, matched_items |
| |
| |
| class Wrapper(object): |
| |
| """Base class for policy object wrappers.""" |
| |
| origin = None |
| |
| def __repr__(self): |
| return "<{0.__class__.__name__}(Wrapping {1})>".format(self, repr(self.origin)) |
| |
| def __hash__(self): |
| raise NotImplementedError |
| |
| def __eq__(self, other): |
| raise NotImplementedError |
| |
| def __lt__(self, other): |
| raise NotImplementedError |
| |
| def __ne__(self, other): |
| return not self == other |
| |
| |
| class SymbolWrapper(Wrapper): |
| |
| """ |
| General wrapper for policy symbols, e.g. types, roles |
| to provide a diff-specific equality operation based |
| on its name. |
| """ |
| |
| def __init__(self, symbol): |
| self.origin = symbol |
| self.name = str(symbol) |
| |
| def __hash__(self): |
| return hash(self.name) |
| |
| def __lt__(self, other): |
| return self.name < other.name |
| |
| def __eq__(self, other): |
| return self.name == other.name |