Home | History | Annotate | Download | only in diff
      1 # Copyright 2015-2016, Tresys Technology, LLC
      2 #
      3 # This file is part of SETools.
      4 #
      5 # SETools is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Lesser General Public License as
      7 # published by the Free Software Foundation, either version 2.1 of
      8 # the License, or (at your option) any later version.
      9 #
     10 # SETools is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Lesser General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Lesser General Public
     16 # License along with SETools.  If not, see
     17 # <http://www.gnu.org/licenses/>.
     18 #
     19 import logging
     20 from collections import namedtuple
     21 
     22 modified_item_record = namedtuple("modified_item", ["left", "right"])
     23 
     24 
     25 class Difference(object):
     26 
     27     """Base class for all policy differences."""
     28 
     29     def __init__(self, left_policy, right_policy):
     30         self.log = logging.getLogger(__name__)
     31         self.left_policy = left_policy
     32         self.right_policy = right_policy
     33 
     34     #
     35     # Policies to compare
     36     #
     37     @property
     38     def left_policy(self):
     39         return self._left_policy
     40 
     41     @left_policy.setter
     42     def left_policy(self, policy):
     43         self.log.info("Policy diff left policy set to {0}".format(policy))
     44         self._left_policy = policy
     45         self._reset_diff()
     46 
     47     @property
     48     def right_policy(self):
     49         return self._right_policy
     50 
     51     @right_policy.setter
     52     def right_policy(self, policy):
     53         self.log.info("Policy diff right policy set to {0}".format(policy))
     54         self._right_policy = policy
     55         self._reset_diff()
     56 
     57     #
     58     # Internal functions
     59     #
     60     def _reset_diff(self):
     61         """Reset diff results on policy changes."""
     62         raise NotImplementedError
     63 
     64     @staticmethod
     65     def _expand_generator(rule_list, Wrapper):
     66         """Generator that yields a wrapped, expanded rule list."""
     67         # this is to delay creating any containers
     68         # as long as possible, since rule lists
     69         # are typically massive.
     70         for unexpanded_rule in rule_list:
     71             for expanded_rule in unexpanded_rule.expand():
     72                 yield Wrapper(expanded_rule)
     73 
     74     @staticmethod
     75     def _set_diff(left, right, key=None):
     76         """
     77         Standard diff of two sets.
     78 
     79         Parameters:
     80         left        An iterable
     81         right       An iterable
     82 
     83         Return:
     84         tuple       (added, removed, matched)
     85 
     86         added       Set of items in right but not left
     87         removed     Set of items in left but not right
     88         matched     Set of items in both left and right.  This is
     89                     in the form of tuples with the matching item
     90                     from left and right
     91         """
     92 
     93         left_items = set(left)
     94         right_items = set(right)
     95         added_items = right_items - left_items
     96         removed_items = left_items - right_items
     97 
     98         # The problem here is the symbol from both policies are
     99         # needed to build each tuple in the matched items set.
    100         # Using the standard Python set intersection code will only result
    101         # in one object.
    102         #
    103         # This tuple-generating code creates lists from the sets, to sort them.
    104         # This should result in all of the symbols lining up.  If they don't,
    105         # this will break the caller.  This should work since there is no remapping.
    106         #
    107         # This has extra checking to make sure this assertion holds, to fail
    108         # instead of giving wrong results.  If there is a better way to,
    109         # ensure the items match up, please let me know how or submit a patch.
    110         matched_items = set()
    111         left_matched_items = sorted((left_items - removed_items), key=key)
    112         right_matched_items = sorted((right_items - added_items), key=key)
    113         assert len(left_matched_items) == len(right_matched_items), \
    114             "Matched items assertion failure (this is an SETools bug), {0} != {1}". \
    115             format(len(left_matched_items), len(right_matched_items))
    116 
    117         for l, r in zip(left_matched_items, right_matched_items):
    118             assert l == r, \
    119                 "Matched items assertion failure (this is an SETools bug), {0} != {1}".format(l, r)
    120 
    121             matched_items.add((l, r))
    122 
    123         try:
    124             # unwrap the objects
    125             return set(i.origin for i in added_items), \
    126                    set(i.origin for i in removed_items), \
    127                    set((l.origin, r.origin) for (l, r) in matched_items)
    128         except AttributeError:
    129             return added_items, removed_items, matched_items
    130 
    131 
    132 class Wrapper(object):
    133 
    134     """Base class for policy object wrappers."""
    135 
    136     origin = None
    137 
    138     def __repr__(self):
    139         return "<{0.__class__.__name__}(Wrapping {1})>".format(self, repr(self.origin))
    140 
    141     def __hash__(self):
    142         raise NotImplementedError
    143 
    144     def __eq__(self, other):
    145         raise NotImplementedError
    146 
    147     def __lt__(self, other):
    148         raise NotImplementedError
    149 
    150     def __ne__(self, other):
    151         return not self == other
    152 
    153 
    154 class SymbolWrapper(Wrapper):
    155 
    156     """
    157     General wrapper for policy symbols, e.g. types, roles
    158     to provide a diff-specific equality operation based
    159     on its name.
    160     """
    161 
    162     def __init__(self, symbol):
    163         self.origin = symbol
    164         self.name = str(symbol)
    165 
    166     def __hash__(self):
    167         return hash(self.name)
    168 
    169     def __lt__(self, other):
    170         return self.name < other.name
    171 
    172     def __eq__(self, other):
    173         return self.name == other.name
    174